cc-claw 0.18.1 → 0.18.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +226 -29
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -33,7 +33,7 @@ var VERSION;
33
33
  var init_version = __esm({
34
34
  "src/version.ts"() {
35
35
  "use strict";
36
- VERSION = true ? "0.18.1" : (() => {
36
+ VERSION = true ? "0.18.2" : (() => {
37
37
  try {
38
38
  return JSON.parse(readFileSync(join(process.cwd(), "package.json"), "utf-8")).version ?? "unknown";
39
39
  } catch {
@@ -1342,7 +1342,8 @@ function initSchema(db3) {
1342
1342
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
1343
1343
  last_run_at TEXT,
1344
1344
  next_run_at TEXT,
1345
- consecutive_failures INTEGER NOT NULL DEFAULT 0
1345
+ consecutive_failures INTEGER NOT NULL DEFAULT 0,
1346
+ allow_paid_slots INTEGER NOT NULL DEFAULT 0
1346
1347
  );
1347
1348
  `);
1348
1349
  try {
@@ -1498,6 +1499,10 @@ function initSchema(db3) {
1498
1499
  db3.exec("ALTER TABLE jobs ADD COLUMN job_type TEXT DEFAULT 'normal'");
1499
1500
  } catch {
1500
1501
  }
1502
+ try {
1503
+ db3.exec("ALTER TABLE jobs ADD COLUMN allow_paid_slots INTEGER NOT NULL DEFAULT 0");
1504
+ } catch {
1505
+ }
1501
1506
  try {
1502
1507
  db3.exec("UPDATE jobs SET job_type = 'reflection' WHERE description LIKE '%reflection analysis%' AND job_type = 'normal'");
1503
1508
  } catch {
@@ -1781,6 +1786,13 @@ function initSchema(db3) {
1781
1786
  PRIMARY KEY (chat_id, backend)
1782
1787
  );
1783
1788
  `);
1789
+ db3.exec(`
1790
+ CREATE TABLE IF NOT EXISTS chat_allow_paid_slots (
1791
+ chat_id TEXT NOT NULL,
1792
+ backend TEXT NOT NULL,
1793
+ PRIMARY KEY (chat_id, backend)
1794
+ );
1795
+ `);
1784
1796
  try {
1785
1797
  const deleted = db3.prepare(
1786
1798
  "DELETE FROM usage_log WHERE created_at < datetime('now', '-90 days')"
@@ -2445,6 +2457,8 @@ var chat_settings_exports = {};
2445
2457
  __export(chat_settings_exports, {
2446
2458
  ALL_TOOLS: () => ALL_TOOLS,
2447
2459
  clearAgentMode: () => clearAgentMode,
2460
+ clearAllPaidSlots: () => clearAllPaidSlots,
2461
+ clearChatPaidSlots: () => clearChatPaidSlots,
2448
2462
  clearCwd: () => clearCwd,
2449
2463
  clearExecMode: () => clearExecMode,
2450
2464
  clearModel: () => clearModel,
@@ -2455,6 +2469,7 @@ __export(chat_settings_exports, {
2455
2469
  findBookmarksByPrefix: () => findBookmarksByPrefix,
2456
2470
  getAgentMode: () => getAgentMode,
2457
2471
  getAllBookmarks: () => getAllBookmarks,
2472
+ getAllowPaidSlots: () => getAllowPaidSlots,
2458
2473
  getBackend: () => getBackend,
2459
2474
  getBookmark: () => getBookmark,
2460
2475
  getCwd: () => getCwd,
@@ -2473,6 +2488,7 @@ __export(chat_settings_exports, {
2473
2488
  removePendingEscalation: () => removePendingEscalation,
2474
2489
  resetTools: () => resetTools,
2475
2490
  setAgentMode: () => setAgentMode,
2491
+ setAllowPaidSlots: () => setAllowPaidSlots,
2476
2492
  setBackend: () => setBackend,
2477
2493
  setCwd: () => setCwd,
2478
2494
  setExecMode: () => setExecMode,
@@ -2774,6 +2790,23 @@ function toggleSessionLogEnabled(chatId) {
2774
2790
  setSessionLogEnabled(chatId, next);
2775
2791
  return next;
2776
2792
  }
2793
+ function getAllowPaidSlots(chatId, backend2) {
2794
+ const row = getDb().prepare(
2795
+ "SELECT 1 FROM chat_allow_paid_slots WHERE chat_id = ? AND backend = ?"
2796
+ ).get(chatId, backend2);
2797
+ return !!row;
2798
+ }
2799
+ function setAllowPaidSlots(chatId, backend2) {
2800
+ getDb().prepare(`
2801
+ INSERT OR IGNORE INTO chat_allow_paid_slots (chat_id, backend) VALUES (?, ?)
2802
+ `).run(chatId, backend2);
2803
+ }
2804
+ function clearChatPaidSlots(chatId) {
2805
+ getDb().prepare("DELETE FROM chat_allow_paid_slots WHERE chat_id = ?").run(chatId);
2806
+ }
2807
+ function clearAllPaidSlots() {
2808
+ getDb().prepare("DELETE FROM chat_allow_paid_slots").run();
2809
+ }
2777
2810
  var pendingEscalations, ESCALATION_TTL_MS, ALL_TOOLS;
2778
2811
  var init_chat_settings = __esm({
2779
2812
  "src/memory/chat-settings.ts"() {
@@ -3038,8 +3071,8 @@ function insertJob(params) {
3038
3071
  const db3 = getDb();
3039
3072
  const result = db3.prepare(`
3040
3073
  INSERT INTO jobs (schedule_type, cron, at_time, every_ms, title, description, chat_id,
3041
- backend, model, thinking, timeout, fallbacks, session_type, channel, target, delivery_mode, timezone, job_type)
3042
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
3074
+ backend, model, thinking, timeout, fallbacks, session_type, channel, target, delivery_mode, timezone, job_type, allow_paid_slots)
3075
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
3043
3076
  `).run(
3044
3077
  params.scheduleType,
3045
3078
  params.cron ?? null,
@@ -3058,13 +3091,15 @@ function insertJob(params) {
3058
3091
  params.target ?? null,
3059
3092
  params.deliveryMode ?? "announce",
3060
3093
  params.timezone ?? "UTC",
3061
- params.jobType ?? "normal"
3094
+ params.jobType ?? "normal",
3095
+ params.allowPaidSlots ? 1 : 0
3062
3096
  );
3063
3097
  return getJobById(Number(result.lastInsertRowid));
3064
3098
  }
3065
3099
  function mapJobRow(row) {
3066
3100
  if (!row) return void 0;
3067
3101
  row.fallbacks = row.fallbacks ? JSON.parse(row.fallbacks) : [];
3102
+ row.allowPaidSlots = !!row.allowPaidSlots;
3068
3103
  return row;
3069
3104
  }
3070
3105
  function getJobById(id) {
@@ -3118,7 +3153,8 @@ function updateJob(id, fields) {
3118
3153
  target: "target",
3119
3154
  deliveryMode: "delivery_mode",
3120
3155
  timezone: "timezone",
3121
- jobType: "job_type"
3156
+ jobType: "job_type",
3157
+ allowPaidSlots: "allow_paid_slots"
3122
3158
  };
3123
3159
  for (const [key, val] of Object.entries(fields)) {
3124
3160
  const col = fieldMap[key];
@@ -3190,7 +3226,8 @@ var init_jobs = __esm({
3190
3226
  title, description, chat_id as chatId, backend, model, thinking, timeout, fallbacks,
3191
3227
  session_type as sessionType, channel, target, delivery_mode as deliveryMode,
3192
3228
  timezone, job_type as jobType, enabled, active, created_at as createdAt, last_run_at as lastRunAt,
3193
- next_run_at as nextRunAt, consecutive_failures as consecutiveFailures
3229
+ next_run_at as nextRunAt, consecutive_failures as consecutiveFailures,
3230
+ allow_paid_slots as allowPaidSlots
3194
3231
  FROM jobs
3195
3232
  `;
3196
3233
  }
@@ -3370,9 +3407,13 @@ function getGeminiSlots() {
3370
3407
  FROM gemini_credentials ORDER BY priority ASC, id ASC
3371
3408
  `).all();
3372
3409
  }
3373
- function getEligibleGeminiSlots(mode) {
3410
+ function getEligibleGeminiSlots(mode, allowPaid) {
3374
3411
  if (mode === "off") return [];
3375
- const slotTypeFilter = mode === "accounts" ? "AND slot_type = 'oauth'" : mode === "keys" ? "AND slot_type = 'api_key'" : "";
3412
+ const effectiveAllowPaid = mode === "keys" ? true : allowPaid ?? false;
3413
+ const slotTypeFilter = mode === "accounts" ? "AND slot_type = 'oauth'" : mode === "keys" ? "AND slot_type = 'api_key'" : (
3414
+ // "all" or undefined: exclude paid slots unless allowed
3415
+ !effectiveAllowPaid ? "AND slot_type != 'api_key'" : ""
3416
+ );
3376
3417
  return getDb().prepare(`
3377
3418
  SELECT id, slot_type AS slotType, label, api_key AS apiKey, config_home AS configHome,
3378
3419
  priority, enabled, cooldown_until AS cooldownUntil, last_used AS lastUsed,
@@ -3380,7 +3421,8 @@ function getEligibleGeminiSlots(mode) {
3380
3421
  FROM gemini_credentials
3381
3422
  WHERE enabled = 1 AND (cooldown_until IS NULL OR cooldown_until <= datetime('now'))
3382
3423
  ${slotTypeFilter}
3383
- ORDER BY priority ASC, last_used ASC NULLS FIRST
3424
+ ORDER BY CASE WHEN slot_type = 'api_key' THEN 1 ELSE 0 END ASC,
3425
+ priority ASC, last_used ASC NULLS FIRST
3384
3426
  `).all();
3385
3427
  }
3386
3428
  function getChatGeminiSlotId(chatId) {
@@ -3473,13 +3515,20 @@ function getBackendSlots(backend2) {
3473
3515
  FROM backend_credentials bc WHERE bc.backend = ? ORDER BY bc.priority ASC, bc.id ASC
3474
3516
  `).all(backend2);
3475
3517
  }
3476
- function getEligibleBackendSlots(backend2) {
3518
+ function getEligibleBackendSlots(backend2, mode, allowPaid) {
3519
+ if (mode === "off") return [];
3520
+ const slotTypeFilter = mode === "accounts" ? "AND bc.slot_type = 'oauth'" : mode === "keys" ? "AND bc.slot_type = 'api_key'" : (
3521
+ // "all" or undefined: exclude paid slots unless allowed
3522
+ !allowPaid ? "AND bc.slot_type != 'api_key'" : ""
3523
+ );
3477
3524
  return getDb().prepare(`
3478
3525
  SELECT ${SLOT_COLUMNS}
3479
3526
  FROM backend_credentials bc
3480
3527
  WHERE bc.backend = ? AND bc.enabled = 1
3481
3528
  AND (bc.cooldown_until IS NULL OR bc.cooldown_until <= datetime('now'))
3482
- ORDER BY bc.priority ASC, bc.last_used ASC NULLS FIRST
3529
+ ${slotTypeFilter}
3530
+ ORDER BY CASE WHEN bc.slot_type = 'api_key' THEN 1 ELSE 0 END ASC,
3531
+ bc.priority ASC, bc.last_used ASC NULLS FIRST
3483
3532
  `).all(backend2);
3484
3533
  }
3485
3534
  function getChatBackendSlotId(chatId, backend2) {
@@ -3550,6 +3599,13 @@ function reenableBackendSlot(slotId) {
3550
3599
  WHERE id = ?
3551
3600
  `).run(slotId);
3552
3601
  }
3602
+ function getBackendRotationMode(backend2) {
3603
+ const row = getDb().prepare("SELECT value FROM meta WHERE key = ?").get(`${backend2}_rotation_mode`);
3604
+ return row?.value ?? "all";
3605
+ }
3606
+ function setBackendRotationMode(backend2, mode) {
3607
+ getDb().prepare("INSERT OR REPLACE INTO meta (key, value) VALUES (?, ?)").run(`${backend2}_rotation_mode`, mode);
3608
+ }
3553
3609
  var SLOT_COLUMNS;
3554
3610
  var init_backend_slots = __esm({
3555
3611
  "src/memory/backend-slots.ts"() {
@@ -3576,10 +3632,12 @@ __export(store_exports5, {
3576
3632
  checkBackendLimits: () => checkBackendLimits,
3577
3633
  cleanExpiredWatches: () => cleanExpiredWatches,
3578
3634
  clearAgentMode: () => clearAgentMode,
3635
+ clearAllPaidSlots: () => clearAllPaidSlots,
3579
3636
  clearAllSessions: () => clearAllSessions,
3580
3637
  clearBackendLimit: () => clearBackendLimit,
3581
3638
  clearChatBackendSlot: () => clearChatBackendSlot,
3582
3639
  clearChatGeminiSlot: () => clearChatGeminiSlot,
3640
+ clearChatPaidSlots: () => clearChatPaidSlots,
3583
3641
  clearCwd: () => clearCwd,
3584
3642
  clearExecMode: () => clearExecMode,
3585
3643
  clearMessageLog: () => clearMessageLog,
@@ -3605,8 +3663,10 @@ __export(store_exports5, {
3605
3663
  getAllMemoriesWithEmbeddings: () => getAllMemoriesWithEmbeddings,
3606
3664
  getAllSessionSummariesWithEmbeddings: () => getAllSessionSummariesWithEmbeddings,
3607
3665
  getAllTimeUsage: () => getAllTimeUsage,
3666
+ getAllowPaidSlots: () => getAllowPaidSlots,
3608
3667
  getBackend: () => getBackend,
3609
3668
  getBackendLimit: () => getBackendLimit,
3669
+ getBackendRotationMode: () => getBackendRotationMode,
3610
3670
  getBackendSlots: () => getBackendSlots,
3611
3671
  getBackendUsageInWindow: () => getBackendUsageInWindow,
3612
3672
  getBookmark: () => getBookmark,
@@ -3686,8 +3746,10 @@ __export(store_exports5, {
3686
3746
  searchMessageLog: () => searchMessageLog,
3687
3747
  searchSessionSummaries: () => searchSessionSummaries,
3688
3748
  setAgentMode: () => setAgentMode,
3749
+ setAllowPaidSlots: () => setAllowPaidSlots,
3689
3750
  setBackend: () => setBackend,
3690
3751
  setBackendLimit: () => setBackendLimit,
3752
+ setBackendRotationMode: () => setBackendRotationMode,
3691
3753
  setBackendSlotEnabled: () => setBackendSlotEnabled,
3692
3754
  setBootTime: () => setBootTime,
3693
3755
  setChatAlias: () => setChatAlias,
@@ -10886,6 +10948,7 @@ var init_detect = __esm({
10886
10948
  var agent_exports = {};
10887
10949
  __export(agent_exports, {
10888
10950
  FIRST_RESPONSE_TIMEOUT_ERROR: () => FIRST_RESPONSE_TIMEOUT_ERROR,
10951
+ FREE_SLOTS_EXHAUSTED: () => FREE_SLOTS_EXHAUSTED,
10889
10952
  askAgent: () => askAgent,
10890
10953
  getInFlightMessage: () => getInFlightMessage,
10891
10954
  isChatBusy: () => isChatBusy,
@@ -11200,9 +11263,15 @@ Partial output: ${accumulatedText.slice(-500)}`;
11200
11263
  });
11201
11264
  });
11202
11265
  }
11203
- async function spawnWithSlotRotation(chatId, adapter, baseConfig, configWithSession, model2, cancelState, thinkingLevel, timeoutMs, maxTurns, opts, onSlotRotation) {
11204
- const slots = getEligibleBackendSlots(adapter.id);
11266
+ async function spawnWithSlotRotation(chatId, adapter, baseConfig, configWithSession, model2, cancelState, thinkingLevel, timeoutMs, maxTurns, rotationMode, allowPaid, opts, onSlotRotation) {
11267
+ const effectiveAllowPaid = rotationMode === "keys" ? true : allowPaid;
11268
+ const slots = getEligibleBackendSlots(adapter.id, rotationMode, effectiveAllowPaid);
11205
11269
  if (slots.length === 0) {
11270
+ const allSlots = getBackendSlots(adapter.id).filter((s) => s.enabled);
11271
+ const paidSlots = allSlots.filter((s) => s.slotType === "api_key");
11272
+ if (!effectiveAllowPaid && paidSlots.length > 0) {
11273
+ throw new Error(`${FREE_SLOTS_EXHAUSTED}|${adapter.id}|${paidSlots.length}`);
11274
+ }
11206
11275
  return spawnQuery(adapter, configWithSession, model2, cancelState, thinkingLevel, timeoutMs, maxTurns, opts);
11207
11276
  }
11208
11277
  const maxAttempts = slots.length;
@@ -11234,7 +11303,7 @@ async function spawnWithSlotRotation(chatId, adapter, baseConfig, configWithSess
11234
11303
  clearSession(chatId);
11235
11304
  } catch {
11236
11305
  }
11237
- const nextSlot = getEligibleBackendSlots(adapter.id)[0];
11306
+ const nextSlot = getEligibleBackendSlots(adapter.id, rotationMode, effectiveAllowPaid)[0];
11238
11307
  if (nextSlot && onSlotRotation) {
11239
11308
  const nextLabel = nextSlot.label || `slot-${nextSlot.id}`;
11240
11309
  onSlotRotation(slotLabel, nextLabel);
@@ -11245,11 +11314,18 @@ async function spawnWithSlotRotation(chatId, adapter, baseConfig, configWithSess
11245
11314
  throw err;
11246
11315
  }
11247
11316
  }
11317
+ if (!effectiveAllowPaid) {
11318
+ const paidSlots = getBackendSlots(adapter.id).filter((s) => s.enabled && s.slotType === "api_key");
11319
+ if (paidSlots.length > 0) {
11320
+ throw new Error(`${FREE_SLOTS_EXHAUSTED}|${adapter.id}|${paidSlots.length}`);
11321
+ }
11322
+ }
11248
11323
  throw lastError ?? new Error(`All ${adapter.id} credential slots exhausted`);
11249
11324
  }
11250
11325
  async function spawnGeminiWithRotation(chatId, adapter, baseConfig, configWithSession, model2, cancelState, thinkingLevel, timeoutMs, maxTurns, rotationMode, opts, onSlotRotation, parentChatId, onModelDowngrade) {
11251
11326
  const geminiAdapter = adapter;
11252
- const slots = getEligibleGeminiSlots(rotationMode);
11327
+ const effectiveAllowPaid = rotationMode === "keys" ? true : getAllowPaidSlots(chatId, "gemini");
11328
+ const slots = getEligibleGeminiSlots(rotationMode, effectiveAllowPaid);
11253
11329
  if (slots.length === 0) {
11254
11330
  return spawnQuery(adapter, configWithSession, model2, cancelState, thinkingLevel, timeoutMs, maxTurns, opts);
11255
11331
  }
@@ -11328,7 +11404,7 @@ async function spawnGeminiWithRotation(chatId, adapter, baseConfig, configWithSe
11328
11404
  clearSession(chatId);
11329
11405
  } catch {
11330
11406
  }
11331
- const nextSlot = getEligibleGeminiSlots(rotationMode)[0];
11407
+ const nextSlot = getEligibleGeminiSlots(rotationMode, effectiveAllowPaid)[0];
11332
11408
  if (nextSlot && onSlotRotation) {
11333
11409
  const nextLabel = nextSlot.label || `slot-${nextSlot.id}`;
11334
11410
  onSlotRotation(slotLabel, nextLabel);
@@ -11413,17 +11489,19 @@ async function askAgentImpl(chatId, userMessage, opts) {
11413
11489
  };
11414
11490
  const resolvedModel = model2 ?? adapter.defaultModel;
11415
11491
  let result = { resultText: "", sessionId: void 0, input: 0, output: 0, cacheRead: 0, sawToolEvents: false, sawResultEvent: false };
11416
- const rotationMode = adapter.id === "gemini" ? getGeminiRotationMode() : "off";
11417
- const useGeminiRotation = rotationMode !== "off" && getEligibleGeminiSlots(rotationMode).length > 0;
11418
- const useBackendRotation = adapter.id !== "gemini" && getEligibleBackendSlots(adapter.id).length > 0;
11492
+ const geminiRotationMode = adapter.id === "gemini" ? getGeminiRotationMode() : "off";
11493
+ const backendRotationMode = adapter.id !== "gemini" ? getBackendRotationMode(adapter.id) : "off";
11494
+ const allowPaid = getAllowPaidSlots(chatId, adapter.id);
11495
+ const useGeminiRotation = geminiRotationMode !== "off" && getEligibleGeminiSlots(geminiRotationMode, allowPaid).length > 0;
11496
+ const useBackendRotation = adapter.id !== "gemini" && (backendRotationMode !== "off" || getBackendSlots(adapter.id).filter((s) => s.enabled).length > 0);
11419
11497
  try {
11420
11498
  if (useGeminiRotation) {
11421
11499
  const rotationCb = onSlotRotation ? (from, to) => onSlotRotation(chatId, from, to) : void 0;
11422
11500
  const downgradeCb = onModelDowngrade ? (from, to, reason) => onModelDowngrade(chatId, from, to, reason) : void 0;
11423
- result = await spawnGeminiWithRotation(chatId, adapter, baseConfig, configWithSession, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, rotationMode, spawnOpts, rotationCb, settingsSourceChatId, downgradeCb);
11501
+ result = await spawnGeminiWithRotation(chatId, adapter, baseConfig, configWithSession, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, geminiRotationMode, spawnOpts, rotationCb, settingsSourceChatId, downgradeCb);
11424
11502
  } else if (useBackendRotation) {
11425
11503
  const rotationCb = onSlotRotation ? (from, to) => onSlotRotation(chatId, from, to) : void 0;
11426
- result = await spawnWithSlotRotation(chatId, adapter, baseConfig, configWithSession, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, spawnOpts, rotationCb);
11504
+ result = await spawnWithSlotRotation(chatId, adapter, baseConfig, configWithSession, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, backendRotationMode, allowPaid, spawnOpts, rotationCb);
11427
11505
  } else {
11428
11506
  const slotSpawnOpts = (() => {
11429
11507
  if (adapter.id !== "gemini") return spawnOpts;
@@ -11474,10 +11552,10 @@ async function askAgentImpl(chatId, userMessage, opts) {
11474
11552
  if (useGeminiRotation) {
11475
11553
  const rotationCb = onSlotRotation ? (from, to) => onSlotRotation(chatId, from, to) : void 0;
11476
11554
  const downgradeCb = onModelDowngrade ? (from, to, reason) => onModelDowngrade(chatId, from, to, reason) : void 0;
11477
- result = await spawnGeminiWithRotation(chatId, adapter, baseConfig, baseConfig, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, rotationMode, spawnOpts, rotationCb, settingsSourceChatId, downgradeCb);
11555
+ result = await spawnGeminiWithRotation(chatId, adapter, baseConfig, baseConfig, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, geminiRotationMode, spawnOpts, rotationCb, settingsSourceChatId, downgradeCb);
11478
11556
  } else if (useBackendRotation) {
11479
11557
  const rotationCb = onSlotRotation ? (from, to) => onSlotRotation(chatId, from, to) : void 0;
11480
- result = await spawnWithSlotRotation(chatId, adapter, baseConfig, baseConfig, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, spawnOpts, rotationCb);
11558
+ result = await spawnWithSlotRotation(chatId, adapter, baseConfig, baseConfig, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, backendRotationMode, allowPaid, spawnOpts, rotationCb);
11481
11559
  } else {
11482
11560
  const retryOpts = (() => {
11483
11561
  if (adapter.id !== "gemini") return spawnOpts;
@@ -11497,10 +11575,10 @@ async function askAgentImpl(chatId, userMessage, opts) {
11497
11575
  if (useGeminiRotation) {
11498
11576
  const rotationCb = onSlotRotation ? (from, to) => onSlotRotation(chatId, from, to) : void 0;
11499
11577
  const downgradeCb = onModelDowngrade ? (from, to, reason) => onModelDowngrade(chatId, from, to, reason) : void 0;
11500
- result = await spawnGeminiWithRotation(chatId, adapter, baseConfig, baseConfig, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, rotationMode, spawnOpts, rotationCb, settingsSourceChatId, downgradeCb);
11578
+ result = await spawnGeminiWithRotation(chatId, adapter, baseConfig, baseConfig, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, geminiRotationMode, spawnOpts, rotationCb, settingsSourceChatId, downgradeCb);
11501
11579
  } else if (useBackendRotation) {
11502
11580
  const rotationCb = onSlotRotation ? (from, to) => onSlotRotation(chatId, from, to) : void 0;
11503
- result = await spawnWithSlotRotation(chatId, adapter, baseConfig, baseConfig, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, spawnOpts, rotationCb);
11581
+ result = await spawnWithSlotRotation(chatId, adapter, baseConfig, baseConfig, resolvedModel, cancelState, thinkingLevel, timeoutMs, maxTurns, backendRotationMode, allowPaid, spawnOpts, rotationCb);
11504
11582
  } else {
11505
11583
  const retryOpts = (() => {
11506
11584
  if (adapter.id !== "gemini") return spawnOpts;
@@ -11615,7 +11693,7 @@ function injectMcpConfig(adapterId, args, mcpConfigPath) {
11615
11693
  if (!flag) return args;
11616
11694
  return [...args, ...flag, mcpConfigPath];
11617
11695
  }
11618
- var activeChats, chatLocks, SPAWN_TIMEOUT_MS, FIRST_RESPONSE_TIMEOUT_MS, FIRST_RESPONSE_TIMEOUT_ERROR, GEMINI_FALLBACK_CHAIN, GEMINI_DOWNGRADE_MODELS, MCP_CONFIG_FLAG;
11696
+ var activeChats, chatLocks, SPAWN_TIMEOUT_MS, FIRST_RESPONSE_TIMEOUT_MS, FIRST_RESPONSE_TIMEOUT_ERROR, FREE_SLOTS_EXHAUSTED, GEMINI_FALLBACK_CHAIN, GEMINI_DOWNGRADE_MODELS, MCP_CONFIG_FLAG;
11619
11697
  var init_agent = __esm({
11620
11698
  "src/agent.ts"() {
11621
11699
  "use strict";
@@ -11641,6 +11719,7 @@ var init_agent = __esm({
11641
11719
  SPAWN_TIMEOUT_MS = 10 * 60 * 1e3;
11642
11720
  FIRST_RESPONSE_TIMEOUT_MS = parseInt(process.env.GEMINI_FIRST_RESPONSE_TIMEOUT_MS ?? "30000", 10);
11643
11721
  FIRST_RESPONSE_TIMEOUT_ERROR = "FIRST_RESPONSE_TIMEOUT";
11722
+ FREE_SLOTS_EXHAUSTED = "FREE_SLOTS_EXHAUSTED";
11644
11723
  GEMINI_FALLBACK_CHAIN = {
11645
11724
  "gemini-3.1-pro-preview": "gemini-2.5-pro",
11646
11725
  "gemini-2.5-pro": "gemini-3.1-flash-preview"
@@ -14004,6 +14083,17 @@ var init_image_gen = __esm({
14004
14083
  });
14005
14084
 
14006
14085
  // src/router/response.ts
14086
+ var response_exports = {};
14087
+ __export(response_exports, {
14088
+ ensureReaction: () => ensureReaction,
14089
+ handleResponseExhaustion: () => handleResponseExhaustion,
14090
+ makeToolActionCallback: () => makeToolActionCallback,
14091
+ pendingFallbackMessages: () => pendingFallbackMessages,
14092
+ processFileSends: () => processFileSends2,
14093
+ processImageGenerations: () => processImageGenerations,
14094
+ processReaction: () => processReaction,
14095
+ sendResponse: () => sendResponse
14096
+ });
14007
14097
  import { readFile as readFile3 } from "fs/promises";
14008
14098
  function makeToolActionCallback(chatId, channel, level) {
14009
14099
  return async (toolName, input, result) => {
@@ -17921,6 +18011,7 @@ Tap to toggle:`,
17921
18011
  const exchangeCount = getMessagePairCount(chatId);
17922
18012
  const summarized = await summarizeSession(chatId);
17923
18013
  clearSession(chatId);
18014
+ clearChatPaidSlots(chatId);
17924
18015
  setSessionStartedAt(chatId);
17925
18016
  logActivity(getDb(), { chatId, source: "telegram", eventType: "config_changed", summary: "New session started", detail: { field: "session", action: "reset", summarized } });
17926
18017
  if (typeof channel.sendKeyboard === "function" && oldSessionId) {
@@ -18241,6 +18332,7 @@ Add with: <code>cc-claw ${slotBackend} add-key</code>`, { parseMode: "html" });
18241
18332
  break;
18242
18333
  }
18243
18334
  if (typeof channel.sendKeyboard === "function") {
18335
+ const currentMode = getBackendRotationMode(slotBackend);
18244
18336
  const pinnedSlotId = getChatBackendSlotId(chatId, slotBackend);
18245
18337
  const slotButtons = slots.filter((s) => s.enabled).map((s) => {
18246
18338
  const label2 = s.label || `slot-${s.id}`;
@@ -18253,8 +18345,15 @@ Add with: <code>cc-claw ${slotBackend} add-key</code>`, { parseMode: "html" });
18253
18345
  rows.push(slotButtons.slice(i, i + 2));
18254
18346
  }
18255
18347
  rows.push([{ label: "\u{1F504} Auto (rotation)", data: `bslot:${slotBackend}:auto` }]);
18256
- await channel.sendKeyboard(chatId, `${slotDisplayName} Accounts:`, rows);
18348
+ const modeLabels = { off: "Off", all: "All", accounts: "\u{1F468}\u{1F3FD}\u200D\u{1F4BB} Only", keys: "\u{1F511} Only" };
18349
+ const modeButtons = ["off", "all", "accounts", "keys"].map((m) => ({
18350
+ label: `${m === currentMode ? "\u2713 " : ""}${modeLabels[m]}`,
18351
+ data: `brotation:${slotBackend}:${m}`
18352
+ }));
18353
+ rows.push(modeButtons);
18354
+ await channel.sendKeyboard(chatId, `${slotDisplayName} Accounts & Rotation:`, rows);
18257
18355
  } else {
18356
+ const currentMode = getBackendRotationMode(slotBackend);
18258
18357
  const list = slots.filter((s) => s.enabled).map((s) => {
18259
18358
  const icon = s.slotType === "oauth" ? "\u{1F468}\u{1F3FD}\u200D\u{1F4BB}" : "\u{1F511}";
18260
18359
  return `${icon} ${s.label || `slot-${s.id}`} (#${s.id})`;
@@ -18262,6 +18361,7 @@ Add with: <code>cc-claw ${slotBackend} add-key</code>`, { parseMode: "html" });
18262
18361
  await channel.sendText(chatId, `${slotDisplayName} Slots:
18263
18362
  ${list}
18264
18363
 
18364
+ Rotation mode: ${currentMode}
18265
18365
  Use: /${command} <name> to pin`, { parseMode: "plain" });
18266
18366
  }
18267
18367
  break;
@@ -19952,6 +20052,32 @@ Result: ${task.result.slice(0, 500)}` : ""
19952
20052
  const modeLabels = { off: "Off", all: "All", accounts: "\u{1F468}\u{1F3FD}\u200D\u{1F4BB} Accounts only", keys: "\u{1F511} Keys only" };
19953
20053
  await channel.sendText(chatId, `Rotation mode set to <b>${modeLabels[mode]}</b>.`, { parseMode: "html" });
19954
20054
  return;
20055
+ } else if (data.startsWith("brotation:")) {
20056
+ const parts = data.split(":");
20057
+ const backend2 = parts[1];
20058
+ const mode = parts[2];
20059
+ const validModes = ["off", "all", "accounts", "keys"];
20060
+ if (!validModes.includes(mode)) return;
20061
+ const displayName = backend2.charAt(0).toUpperCase() + backend2.slice(1);
20062
+ const slots = getBackendSlots(backend2);
20063
+ if (mode === "accounts") {
20064
+ const oauthSlots = slots.filter((s) => s.enabled && s.slotType === "oauth");
20065
+ if (oauthSlots.length === 0) {
20066
+ await channel.sendText(chatId, `\u26A0\uFE0F No ${displayName} OAuth accounts configured. Add one with <code>cc-claw ${backend2} add-account</code> or choose a different mode.`, { parseMode: "html" });
20067
+ return;
20068
+ }
20069
+ } else if (mode === "keys") {
20070
+ const keySlots = slots.filter((s) => s.enabled && s.slotType === "api_key");
20071
+ if (keySlots.length === 0) {
20072
+ await channel.sendText(chatId, `\u26A0\uFE0F No ${displayName} API keys configured. Add one with <code>cc-claw ${backend2} add-key</code> or choose a different mode.`, { parseMode: "html" });
20073
+ return;
20074
+ }
20075
+ }
20076
+ setBackendRotationMode(backend2, mode);
20077
+ clearSession(chatId);
20078
+ const modeLabelMap = { off: "Off", all: "All", accounts: "\u{1F468}\u{1F3FD}\u200D\u{1F4BB} Accounts only", keys: "\u{1F511} Keys only" };
20079
+ await channel.sendText(chatId, `${displayName} rotation mode set to <b>${modeLabelMap[mode]}</b>.`, { parseMode: "html" });
20080
+ return;
19955
20081
  } else if (data.startsWith("gslot:")) {
19956
20082
  const val = data.split(":")[1];
19957
20083
  if (val === "auto") {
@@ -20026,6 +20152,37 @@ ${rotationNote}`, { parseMode: "html" });
20026
20152
  }
20027
20153
  }
20028
20154
  return;
20155
+ } else if (data.startsWith("paidslot:")) {
20156
+ const parts = data.split(":");
20157
+ const action = parts[1];
20158
+ const backend2 = parts[2];
20159
+ const displayName = backend2.charAt(0).toUpperCase() + backend2.slice(1);
20160
+ if (action === "approve") {
20161
+ setAllowPaidSlots(chatId, backend2);
20162
+ await channel.sendText(chatId, `\u2705 Paid API key usage approved for ${displayName} (this session). Retrying\u2026`, { parseMode: "html" });
20163
+ const { pendingFallbackMessages: pendingFallbackMessages3 } = await Promise.resolve().then(() => (init_response(), response_exports));
20164
+ const pending = pendingFallbackMessages3.get(chatId);
20165
+ if (pending) {
20166
+ pendingFallbackMessages3.delete(chatId);
20167
+ const { handleMessage: handleMessage2 } = await Promise.resolve().then(() => (init_router(), router_exports));
20168
+ await handleMessage2(pending.msg, pending.channel);
20169
+ }
20170
+ } else if (action === "deny") {
20171
+ const { pendingFallbackMessages: pendingFallbackMessages3 } = await Promise.resolve().then(() => (init_response(), response_exports));
20172
+ pendingFallbackMessages3.delete(chatId);
20173
+ const otherBackends = (await Promise.resolve().then(() => (init_backends(), backends_exports))).getAvailableAdapters().filter((a) => a.id !== backend2);
20174
+ if (typeof channel.sendKeyboard === "function" && otherBackends.length > 0) {
20175
+ const buttons = otherBackends.map((a) => ({
20176
+ label: `\u{1F504} ${a.displayName}`,
20177
+ data: `backend:${a.id}`,
20178
+ style: "primary"
20179
+ }));
20180
+ await channel.sendKeyboard(chatId, `Switch to another backend?`, [buttons]);
20181
+ } else {
20182
+ await channel.sendText(chatId, `Paid usage denied. Try again later or switch backend manually.`, { parseMode: "plain" });
20183
+ }
20184
+ }
20185
+ return;
20029
20186
  } else if (data.startsWith("shell:")) {
20030
20187
  const parts = data.split(":");
20031
20188
  const action = parts[1];
@@ -21399,6 +21556,33 @@ You're still in discussion mode \u2014 try again or click a button to exit.`, {
21399
21556
  }
21400
21557
  return;
21401
21558
  }
21559
+ if (errMsg.startsWith(FREE_SLOTS_EXHAUSTED)) {
21560
+ const parts = errMsg.split("|");
21561
+ const backend2 = parts[1] ?? "unknown";
21562
+ const paidCount = parts[2] ?? "?";
21563
+ const displayName = backend2.charAt(0).toUpperCase() + backend2.slice(1);
21564
+ const { pendingFallbackMessages: pendingFallbackMessages3 } = await Promise.resolve().then(() => (init_response(), response_exports));
21565
+ pendingFallbackMessages3.set(chatId, { msg, channel, agentMode: effectiveAgentMode });
21566
+ if (typeof channel.sendKeyboard === "function") {
21567
+ await channel.sendKeyboard(
21568
+ chatId,
21569
+ `\u26A0\uFE0F All free ${displayName} accounts are exhausted.
21570
+
21571
+ ${paidCount} paid API key slot(s) available. Using them will incur API costs.
21572
+
21573
+ Approve paid usage for this session?`,
21574
+ [
21575
+ [
21576
+ { label: "\u2705 Approve (this session)", data: `paidslot:approve:${backend2}`, style: "success" },
21577
+ { label: "\u274C No, switch backend", data: `paidslot:deny:${backend2}`, style: "danger" }
21578
+ ]
21579
+ ]
21580
+ );
21581
+ } else {
21582
+ await channel.sendText(chatId, `\u26A0\uFE0F All free ${displayName} accounts exhausted. ${paidCount} paid API key slot(s) available but not approved. Switch backend or approve paid usage.`, { parseMode: "plain" });
21583
+ }
21584
+ return;
21585
+ }
21402
21586
  const errorClass = classifyError(err);
21403
21587
  if (errorClass === "exhausted") {
21404
21588
  if (await handleResponseExhaustion(errMsg, chatId, msg, channel)) return;
@@ -21742,6 +21926,10 @@ async function runWithRetry(job, model2, runId, t0) {
21742
21926
  onToolAction = makeToolActionCallback2(chatId, channel, vLevel);
21743
21927
  }
21744
21928
  }
21929
+ if (job.allowPaidSlots) {
21930
+ const cronBackend = currentBackend;
21931
+ setAllowPaidSlots(chatId, cronBackend);
21932
+ }
21745
21933
  const response = await askAgent(chatId, job.description, {
21746
21934
  model: currentModel,
21747
21935
  backend: currentBackend,
@@ -21750,6 +21938,9 @@ async function runWithRetry(job, model2, runId, t0) {
21750
21938
  permMode: job.sessionType === "main" ? getMode(job.chatId) : "yolo",
21751
21939
  onToolAction
21752
21940
  });
21941
+ if (job.allowPaidSlots) {
21942
+ clearChatPaidSlots(chatId);
21943
+ }
21753
21944
  if (isFallback) {
21754
21945
  response.text = `[Fallback: ran on ${currentBackend}:${currentModel}]
21755
21946
 
@@ -21759,8 +21950,13 @@ ${response.text}`;
21759
21950
  } catch (err) {
21760
21951
  lastError = err;
21761
21952
  const errorClass = classifyError(err);
21953
+ const errMsg = errorMessage(err);
21954
+ if (typeof errMsg === "string" && errMsg.startsWith(FREE_SLOTS_EXHAUSTED)) {
21955
+ log(`[scheduler] Job #${job.id} backend ${currentBackend} free slots exhausted (paid not approved): ${errMsg}`);
21956
+ break;
21957
+ }
21762
21958
  if (errorClass === "exhausted") {
21763
- log(`[scheduler] Job #${job.id} backend ${currentBackend} exhausted: ${errorMessage(err)}`);
21959
+ log(`[scheduler] Job #${job.id} backend ${currentBackend} exhausted: ${errMsg}`);
21764
21960
  break;
21765
21961
  }
21766
21962
  if (errorClass === "permanent" || attempt >= MAX_RETRIES) {
@@ -23814,6 +24010,7 @@ async function main() {
23814
24010
  }
23815
24011
  log(`[cc-claw] Starting v${version}`);
23816
24012
  initDatabase();
24013
+ clearAllPaidSlots();
23817
24014
  pruneMessageLog(30, 2e3);
23818
24015
  bootstrapBuiltinMcps(getDb());
23819
24016
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-claw",
3
- "version": "0.18.1",
3
+ "version": "0.18.2",
4
4
  "description": "CC-Claw: Personal AI assistant on Telegram — multi-backend (Claude, Gemini, Codex, Cursor), sub-agent orchestration, MCP management",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",