cc-claw 0.13.1 → 0.14.0

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/cli.js CHANGED
@@ -72,7 +72,7 @@ var VERSION;
72
72
  var init_version = __esm({
73
73
  "src/version.ts"() {
74
74
  "use strict";
75
- VERSION = true ? "0.13.1" : (() => {
75
+ VERSION = true ? "0.14.0" : (() => {
76
76
  try {
77
77
  return JSON.parse(readFileSync(join2(process.cwd(), "package.json"), "utf-8")).version ?? "unknown";
78
78
  } catch {
@@ -5126,8 +5126,24 @@ ${instructions}`;
5126
5126
  }
5127
5127
  return null;
5128
5128
  }
5129
+ const MEMORY_DISCIPLINE = `
5130
+ [Memory discipline]
5131
+ Use cc_claw_memory for ALL memory operations. Do NOT use your native memory system (no .claude/projects/*/memory/, no .gemini/, etc.).
5132
+ - To save: cc_claw_memory(action: "remember", tag: "...", content: "...")
5133
+ - To search: cc_claw_memory(action: "recall", query: "...")
5134
+ - To list: cc_claw_memory(action: "list")
5135
+ - To search history: cc_claw_memory(action: "history", query: "...")
5136
+ For scheduling: cc_claw_schedule(action: "create", schedule: "...", task: "...")
5137
+ If an action is not available via MCP tools, fall back to the cc-claw CLI.`;
5129
5138
  if (agentMode === "claw") {
5130
- return "[Sub-agent capabilities]\nCC-Claw orchestration is active. Use the cc-claw MCP tools (spawn_agent, send_message, list_tasks, etc.) for cross-backend agent coordination, inter-agent communication, and shared task management.";
5139
+ return `[Sub-agent capabilities]
5140
+ CC-Claw orchestration is active. Use the unified domain tools:
5141
+ - cc_claw_agents \u2014 spawn/cancel/list/status sub-agents, list templates/runners/mcps
5142
+ - cc_claw_tasks \u2014 create/list/update shared tasks
5143
+ - cc_claw_comms \u2014 send/read messages, broadcast, shared whiteboard
5144
+ - cc_claw_memory \u2014 remember/recall/list/forget/history (cross-backend persistent memory)
5145
+ - cc_claw_schedule \u2014 create/list/edit/run/cancel/pause/resume scheduled jobs
5146
+ ${MEMORY_DISCIPLINE}`;
5131
5147
  }
5132
5148
  if (agentMode === "auto" && process.env.DASHBOARD_ENABLED === "1") {
5133
5149
  try {
@@ -5135,10 +5151,10 @@ ${instructions}`;
5135
5151
  const nativeInstructions = adapter.getSubagentInstructions?.();
5136
5152
  const parts = [];
5137
5153
  if (nativeInstructions) parts.push(`Native: ${nativeInstructions}`);
5138
- parts.push("CC-Claw: For cross-backend coordination, inter-agent debate, or shared task management, use the cc-claw MCP tools (spawn_agent, send_message, etc.).");
5154
+ parts.push(`CC-Claw: For cross-backend coordination, memory, scheduling, or shared task management, use the cc_claw_* MCP tools (cc_claw_agents, cc_claw_memory, cc_claw_schedule, etc.).`);
5139
5155
  return `[Sub-agent capabilities]
5140
5156
  You have two sub-agent modes:
5141
- ${parts.join("\n")}`;
5157
+ ${parts.join("\n")}${MEMORY_DISCIPLINE}`;
5142
5158
  } catch {
5143
5159
  }
5144
5160
  }
@@ -7198,6 +7214,91 @@ var init_health = __esm({
7198
7214
  }
7199
7215
  });
7200
7216
 
7217
+ // src/scheduler/health.ts
7218
+ var health_exports2 = {};
7219
+ __export(health_exports2, {
7220
+ computeStaggerMs: () => computeStaggerMs,
7221
+ formatHealthReport: () => formatHealthReport,
7222
+ getHealthReport: () => getHealthReport,
7223
+ startHealthMonitor: () => startHealthMonitor2,
7224
+ stopHealthMonitor: () => stopHealthMonitor2
7225
+ });
7226
+ import * as os from "os";
7227
+ function startHealthMonitor2() {
7228
+ lastHeartbeat = /* @__PURE__ */ new Date();
7229
+ heartbeatTimer = setInterval(() => {
7230
+ lastHeartbeat = /* @__PURE__ */ new Date();
7231
+ log(`[health] Scheduler heartbeat at ${lastHeartbeat.toISOString()}`);
7232
+ }, HEARTBEAT_INTERVAL_MS);
7233
+ heartbeatTimer.unref();
7234
+ }
7235
+ function stopHealthMonitor2() {
7236
+ if (heartbeatTimer) {
7237
+ clearInterval(heartbeatTimer);
7238
+ heartbeatTimer = null;
7239
+ }
7240
+ }
7241
+ function getHealthReport() {
7242
+ const allJobs = getAllJobs();
7243
+ const activeJobs = allJobs.filter((j) => j.enabled && j.active);
7244
+ const pausedJobs = allJobs.filter((j) => !j.enabled && j.active);
7245
+ const failingJobs = activeJobs.filter((j) => j.consecutiveFailures > 0).map((j) => ({ id: j.id, description: j.description, failures: j.consecutiveFailures }));
7246
+ let status = "healthy";
7247
+ if (failingJobs.length > 0) status = "degraded";
7248
+ if (!lastHeartbeat) status = "unhealthy";
7249
+ return {
7250
+ status,
7251
+ lastHeartbeat: lastHeartbeat?.toISOString() ?? null,
7252
+ activeJobs: activeJobs.length,
7253
+ pausedJobs: pausedJobs.length,
7254
+ failingJobs,
7255
+ totalJobs: allJobs.length,
7256
+ // Audit O39: Include system metrics for operator visibility
7257
+ memoryMB: Math.round(process.memoryUsage().heapUsed / 1024 / 1024),
7258
+ loadAvg: os.loadavg()[0]
7259
+ };
7260
+ }
7261
+ function formatHealthReport(report) {
7262
+ const statusEmoji = report.status === "healthy" ? "OK" : report.status === "degraded" ? "DEGRADED" : "UNHEALTHY";
7263
+ const lines = [
7264
+ `Scheduler: ${statusEmoji}`,
7265
+ `Last heartbeat: ${report.lastHeartbeat ?? "never"}`,
7266
+ `Active jobs: ${report.activeJobs}`,
7267
+ `Paused jobs: ${report.pausedJobs}`,
7268
+ `Total jobs: ${report.totalJobs}`
7269
+ ];
7270
+ if (report.failingJobs.length > 0) {
7271
+ lines.push("", "Jobs with failures:");
7272
+ for (const j of report.failingJobs) {
7273
+ lines.push(` #${j.id}: ${j.description} (${j.failures} consecutive failures)`);
7274
+ }
7275
+ }
7276
+ return lines.join("\n");
7277
+ }
7278
+ function computeStaggerMs(jobId, cronExpr) {
7279
+ const parts = cronExpr.split(/\s+/);
7280
+ if (parts.length < 5) return 0;
7281
+ if (parts[0] !== "0") return 0;
7282
+ if (!/[*/]/.test(parts[1])) return 0;
7283
+ const maxStagger = 12e4;
7284
+ let h = jobId * 2654435761;
7285
+ h = (h >>> 16 ^ h) * 73244475;
7286
+ h = (h >>> 16 ^ h) * 73244475;
7287
+ h = h >>> 16 ^ h;
7288
+ return Math.abs(h) % maxStagger;
7289
+ }
7290
+ var lastHeartbeat, heartbeatTimer, HEARTBEAT_INTERVAL_MS;
7291
+ var init_health2 = __esm({
7292
+ "src/scheduler/health.ts"() {
7293
+ "use strict";
7294
+ init_store5();
7295
+ init_log();
7296
+ lastHeartbeat = null;
7297
+ heartbeatTimer = null;
7298
+ HEARTBEAT_INTERVAL_MS = 5 * 60 * 1e3;
7299
+ }
7300
+ });
7301
+
7201
7302
  // src/reflection/metrics.ts
7202
7303
  var metrics_exports = {};
7203
7304
  __export(metrics_exports, {
@@ -8298,7 +8399,15 @@ function startDashboard() {
8298
8399
  "/api/orchestrator/get-state",
8299
8400
  "/api/orchestrator/list-state",
8300
8401
  "/api/orchestrator/check-agent",
8301
- "/api/health"
8402
+ "/api/health",
8403
+ // Memory tools — accessible to all agents
8404
+ "/api/memory/remember",
8405
+ "/api/memory/add",
8406
+ // deprecated alias for /remember
8407
+ "/api/memory/recall",
8408
+ "/api/memory/list",
8409
+ "/api/memory/forget",
8410
+ "/api/memory/history"
8302
8411
  ]);
8303
8412
  if (isSubAgentToken && !SUB_AGENT_ALLOWED_PATHS.has(url.pathname)) {
8304
8413
  return jsonResponse(res, { error: "Forbidden: sub-agent tokens can only access designated endpoints" }, 403);
@@ -8744,22 +8853,173 @@ data: ${JSON.stringify(data)}
8744
8853
  return jsonResponse(res, { error: errorMessage(err) }, 400);
8745
8854
  }
8746
8855
  }
8747
- if (url.pathname === "/api/memory/add" && req.method === "POST") {
8856
+ if ((url.pathname === "/api/memory/remember" || url.pathname === "/api/memory/add") && req.method === "POST") {
8748
8857
  try {
8749
8858
  const body = JSON.parse(await readBody(req));
8859
+ validateAgentIdentity(req, body);
8750
8860
  const { saveMemoryWithEmbedding: saveMemoryWithEmbedding2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
8751
- const id = saveMemoryWithEmbedding2(body.trigger, body.content, body.type ?? "semantic");
8861
+ const id = saveMemoryWithEmbedding2(body.trigger ?? body.tag, body.content, body.type ?? "semantic");
8752
8862
  return jsonResponse(res, { success: true, id });
8753
8863
  } catch (err) {
8754
8864
  return jsonResponse(res, { error: errorMessage(err) }, 400);
8755
8865
  }
8756
8866
  }
8867
+ if (url.pathname === "/api/memory/recall" && req.method === "POST") {
8868
+ try {
8869
+ const body = JSON.parse(await readBody(req));
8870
+ validateAgentIdentity(req, body);
8871
+ const { searchMemories: searchMemories2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
8872
+ const results = searchMemories2(body.query, body.limit ?? 5);
8873
+ return jsonResponse(res, { success: true, results });
8874
+ } catch (err) {
8875
+ return jsonResponse(res, { error: errorMessage(err) }, 400);
8876
+ }
8877
+ }
8878
+ if (url.pathname === "/api/memory/list" && req.method === "GET") {
8879
+ try {
8880
+ const limit = parseInt(url.searchParams.get("limit") ?? "10", 10);
8881
+ const { getRecentMemories: getRecentMemories2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
8882
+ const results = getRecentMemories2(limit);
8883
+ return jsonResponse(res, { success: true, results });
8884
+ } catch (err) {
8885
+ return jsonResponse(res, { error: errorMessage(err) }, 400);
8886
+ }
8887
+ }
8757
8888
  if (url.pathname === "/api/memory/forget" && req.method === "POST") {
8758
8889
  try {
8759
8890
  const body = JSON.parse(await readBody(req));
8760
- const { forgetMemory: forgetMemory2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
8761
- const count = forgetMemory2(body.keyword);
8762
- return jsonResponse(res, { success: true, count });
8891
+ validateAgentIdentity(req, body);
8892
+ if (body.memoryId) {
8893
+ const { deleteMemoryById: deleteMemoryById2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
8894
+ const deleted = deleteMemoryById2(body.memoryId);
8895
+ return jsonResponse(res, { success: deleted, mode: "id" });
8896
+ }
8897
+ if (body.keyword) {
8898
+ const { forgetMemory: forgetMemory2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
8899
+ const count = forgetMemory2(body.keyword);
8900
+ return jsonResponse(res, { success: true, count, mode: "keyword" });
8901
+ }
8902
+ return jsonResponse(res, { error: "Either 'keyword' or 'memoryId' is required" }, 400);
8903
+ } catch (err) {
8904
+ return jsonResponse(res, { error: errorMessage(err) }, 400);
8905
+ }
8906
+ }
8907
+ if (url.pathname === "/api/memory/history" && req.method === "POST") {
8908
+ try {
8909
+ const body = JSON.parse(await readBody(req));
8910
+ validateAgentIdentity(req, body);
8911
+ const { searchMessageLog: searchMessageLog2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
8912
+ const results = searchMessageLog2(body.chatId, body.query, body.limit ?? 20);
8913
+ return jsonResponse(res, { success: true, results });
8914
+ } catch (err) {
8915
+ return jsonResponse(res, { error: errorMessage(err) }, 400);
8916
+ }
8917
+ }
8918
+ if (url.pathname === "/api/schedule/create" && req.method === "POST") {
8919
+ try {
8920
+ const body = JSON.parse(await readBody(req));
8921
+ validateAgentIdentity(req, body);
8922
+ const { insertJob: insertJob2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
8923
+ const { startSingleJob: startSingleJob2 } = await Promise.resolve().then(() => (init_cron(), cron_exports));
8924
+ const job = insertJob2({
8925
+ scheduleType: body.scheduleType ?? "cron",
8926
+ cron: body.cron ?? body.schedule ?? null,
8927
+ atTime: body.atTime ?? null,
8928
+ everyMs: body.everyMs ?? null,
8929
+ title: body.title ?? body.name ?? null,
8930
+ description: body.task ?? body.description,
8931
+ chatId: body.chatId,
8932
+ backend: body.backend ?? null,
8933
+ model: body.model ?? null,
8934
+ thinking: body.thinking ?? null,
8935
+ timeout: body.timeout ?? null,
8936
+ sessionType: body.sessionType ?? "isolated",
8937
+ deliveryMode: body.deliveryMode ?? "announce",
8938
+ timezone: body.timezone ?? "UTC"
8939
+ });
8940
+ startSingleJob2(job);
8941
+ return jsonResponse(res, { success: true, job });
8942
+ } catch (err) {
8943
+ return jsonResponse(res, { error: errorMessage(err) }, 400);
8944
+ }
8945
+ }
8946
+ if (url.pathname === "/api/schedule/list" && req.method === "GET") {
8947
+ try {
8948
+ const { listJobs: listJobs2 } = await Promise.resolve().then(() => (init_cron(), cron_exports));
8949
+ const jobs = listJobs2();
8950
+ return jsonResponse(res, { success: true, jobs });
8951
+ } catch (err) {
8952
+ return jsonResponse(res, { error: errorMessage(err) }, 400);
8953
+ }
8954
+ }
8955
+ if (url.pathname === "/api/schedule/edit" && req.method === "POST") {
8956
+ try {
8957
+ const body = JSON.parse(await readBody(req));
8958
+ validateAgentIdentity(req, body);
8959
+ const { updateJob: updateJob2, getJobById: getJobById2 } = await Promise.resolve().then(() => (init_store5(), store_exports5));
8960
+ const { stopJobTimer: stopJobTimer2, startSingleJob: startSingleJob2 } = await Promise.resolve().then(() => (init_cron(), cron_exports));
8961
+ const updated = updateJob2(body.jobId, body.updates);
8962
+ if (updated) {
8963
+ stopJobTimer2(body.jobId);
8964
+ const freshJob = getJobById2(body.jobId);
8965
+ if (freshJob && freshJob.active && freshJob.enabled) {
8966
+ startSingleJob2(freshJob);
8967
+ }
8968
+ }
8969
+ return jsonResponse(res, { success: updated });
8970
+ } catch (err) {
8971
+ return jsonResponse(res, { error: errorMessage(err) }, 400);
8972
+ }
8973
+ }
8974
+ if (url.pathname === "/api/schedule/run" && req.method === "POST") {
8975
+ try {
8976
+ const body = JSON.parse(await readBody(req));
8977
+ validateAgentIdentity(req, body);
8978
+ const { triggerJob: triggerJob2 } = await Promise.resolve().then(() => (init_cron(), cron_exports));
8979
+ const result = await triggerJob2(body.jobId);
8980
+ return jsonResponse(res, { success: true, message: result });
8981
+ } catch (err) {
8982
+ return jsonResponse(res, { error: errorMessage(err) }, 400);
8983
+ }
8984
+ }
8985
+ if (url.pathname === "/api/schedule/cancel" && req.method === "POST") {
8986
+ try {
8987
+ const body = JSON.parse(await readBody(req));
8988
+ validateAgentIdentity(req, body);
8989
+ const { cancelJob: cancelJob2 } = await Promise.resolve().then(() => (init_cron(), cron_exports));
8990
+ const cancelled = cancelJob2(body.jobId);
8991
+ return jsonResponse(res, { success: cancelled });
8992
+ } catch (err) {
8993
+ return jsonResponse(res, { error: errorMessage(err) }, 400);
8994
+ }
8995
+ }
8996
+ if (url.pathname === "/api/schedule/pause" && req.method === "POST") {
8997
+ try {
8998
+ const body = JSON.parse(await readBody(req));
8999
+ validateAgentIdentity(req, body);
9000
+ const { pauseJob: pauseJob2 } = await Promise.resolve().then(() => (init_cron(), cron_exports));
9001
+ const paused = pauseJob2(body.jobId);
9002
+ return jsonResponse(res, { success: paused });
9003
+ } catch (err) {
9004
+ return jsonResponse(res, { error: errorMessage(err) }, 400);
9005
+ }
9006
+ }
9007
+ if (url.pathname === "/api/schedule/resume" && req.method === "POST") {
9008
+ try {
9009
+ const body = JSON.parse(await readBody(req));
9010
+ validateAgentIdentity(req, body);
9011
+ const { resumeJob: resumeJob2 } = await Promise.resolve().then(() => (init_cron(), cron_exports));
9012
+ const resumed = resumeJob2(body.jobId);
9013
+ return jsonResponse(res, { success: resumed });
9014
+ } catch (err) {
9015
+ return jsonResponse(res, { error: errorMessage(err) }, 400);
9016
+ }
9017
+ }
9018
+ if (url.pathname === "/api/schedule/status" && req.method === "GET") {
9019
+ try {
9020
+ const { getHealthReport: getHealthReport2, formatHealthReport: formatHealthReport2 } = await Promise.resolve().then(() => (init_health2(), health_exports2));
9021
+ const report = getHealthReport2();
9022
+ return jsonResponse(res, { success: true, report, formatted: formatHealthReport2(report) });
8763
9023
  } catch (err) {
8764
9024
  return jsonResponse(res, { error: errorMessage(err) }, 400);
8765
9025
  }
@@ -9684,6 +9944,9 @@ function stopAgent(chatId) {
9684
9944
  }
9685
9945
  return true;
9686
9946
  }
9947
+ function getGeminiFallback(model2) {
9948
+ return GEMINI_FALLBACK_CHAIN[model2] ?? null;
9949
+ }
9687
9950
  function spawnQuery(adapter, config2, model2, cancelState, thinkingLevel, timeoutMs, maxTurns, opts) {
9688
9951
  const effectiveTimeout = timeoutMs ?? SPAWN_TIMEOUT_MS;
9689
9952
  return new Promise((resolve, reject) => {
@@ -9952,12 +10215,14 @@ async function spawnGeminiWithRotation(chatId, adapter, baseConfig, configWithSe
9952
10215
  const isTimeout = errMsg.startsWith(FIRST_RESPONSE_TIMEOUT_ERROR);
9953
10216
  const isExhausted = /RESOURCE.?EXHAUSTED|resource has been exhausted/i.test(errMsg);
9954
10217
  if ((isTimeout || isExhausted) && GEMINI_DOWNGRADE_MODELS.has(model2)) {
10218
+ const fallbackModel = getGeminiFallback(model2);
9955
10219
  const reason = isTimeout ? "No response within timeout" : "Resource exhausted";
9956
- warn(`[agent:gemini-rotation] ${reason} on ${model2} (${slotLabel}) \u2014 downgrading to ${GEMINI_FALLBACK_MODEL}`);
9957
- onModelDowngrade?.(model2, GEMINI_FALLBACK_MODEL, `${reason} \u2014 model likely overloaded`);
10220
+ warn(`[agent:gemini-rotation] ${reason} on ${model2} (${slotLabel}) \u2014 downgrading to ${fallbackModel}`);
10221
+ onModelDowngrade?.(model2, fallbackModel, `${reason} \u2014 model likely overloaded`);
10222
+ setModel(chatId, fallbackModel);
9958
10223
  const fallbackConfig = adapter.buildSpawnConfig({
9959
10224
  prompt: effectiveConfig.args[effectiveConfig.args.indexOf("-p") + 1],
9960
- model: GEMINI_FALLBACK_MODEL,
10225
+ model: fallbackModel,
9961
10226
  permMode: "yolo",
9962
10227
  allowedTools: [],
9963
10228
  cwd: effectiveConfig.cwd,
@@ -9974,8 +10239,8 @@ async function spawnGeminiWithRotation(chatId, adapter, baseConfig, configWithSe
9974
10239
  }
9975
10240
  }
9976
10241
  }
9977
- const fallbackResult = await spawnQuery(adapter, fallbackConfig, GEMINI_FALLBACK_MODEL, cancelState, thinkingLevel, timeoutMs, maxTurns, { ...opts, envOverride: env, firstResponseTimeoutMs: 0 });
9978
- fallbackResult.resolvedModel = GEMINI_FALLBACK_MODEL;
10242
+ const fallbackResult = await spawnQuery(adapter, fallbackConfig, fallbackModel, cancelState, thinkingLevel, timeoutMs, maxTurns, { ...opts, envOverride: env });
10243
+ fallbackResult.resolvedModel = fallbackModel;
9979
10244
  return fallbackResult;
9980
10245
  }
9981
10246
  const quotaClass = classifyGeminiQuota(errMsg);
@@ -10088,12 +10353,14 @@ async function askAgentImpl(chatId, userMessage, opts) {
10088
10353
  const isTimeout = errMsg.startsWith(FIRST_RESPONSE_TIMEOUT_ERROR);
10089
10354
  const isExhausted = /RESOURCE.?EXHAUSTED|resource has been exhausted/i.test(errMsg);
10090
10355
  if (adapter.id === "gemini" && (isTimeout || isExhausted) && GEMINI_DOWNGRADE_MODELS.has(resolvedModel)) {
10356
+ const fallbackModel = getGeminiFallback(resolvedModel);
10091
10357
  const reason = isTimeout ? "No response within timeout" : "Resource exhausted";
10092
- warn(`[agent] ${reason} on ${resolvedModel} (no rotation) \u2014 downgrading to ${GEMINI_FALLBACK_MODEL}`);
10093
- onModelDowngrade?.(chatId, resolvedModel, GEMINI_FALLBACK_MODEL, `${reason} \u2014 model likely overloaded`);
10358
+ warn(`[agent] ${reason} on ${resolvedModel} (no rotation) \u2014 downgrading to ${fallbackModel}`);
10359
+ onModelDowngrade?.(chatId, resolvedModel, fallbackModel, `${reason} \u2014 model likely overloaded`);
10360
+ setModel(chatId, fallbackModel);
10094
10361
  const fallbackConfig = adapter.buildSpawnConfig({
10095
10362
  prompt: configWithSession.args[configWithSession.args.indexOf("-p") + 1],
10096
- model: GEMINI_FALLBACK_MODEL,
10363
+ model: fallbackModel,
10097
10364
  permMode: mode,
10098
10365
  allowedTools,
10099
10366
  cwd: resolvedCwd,
@@ -10103,8 +10370,8 @@ async function askAgentImpl(chatId, userMessage, opts) {
10103
10370
  if (mcpConfigPath) {
10104
10371
  fallbackConfig.args = injectMcpConfig(adapter.id, fallbackConfig.args, mcpConfigPath);
10105
10372
  }
10106
- result = await spawnQuery(adapter, fallbackConfig, GEMINI_FALLBACK_MODEL, cancelState, thinkingLevel, timeoutMs, maxTurns, { ...spawnOpts, firstResponseTimeoutMs: 0 });
10107
- result.resolvedModel = GEMINI_FALLBACK_MODEL;
10373
+ result = await spawnQuery(adapter, fallbackConfig, fallbackModel, cancelState, thinkingLevel, timeoutMs, maxTurns, spawnOpts);
10374
+ result.resolvedModel = fallbackModel;
10108
10375
  } else {
10109
10376
  throw err;
10110
10377
  }
@@ -10190,7 +10457,7 @@ function injectMcpConfig(adapterId, args, mcpConfigPath) {
10190
10457
  if (!flag) return args;
10191
10458
  return [...args, ...flag, mcpConfigPath];
10192
10459
  }
10193
- var activeChats, chatLocks, SPAWN_TIMEOUT_MS, FIRST_RESPONSE_TIMEOUT_MS, FIRST_RESPONSE_TIMEOUT_ERROR, GEMINI_FALLBACK_MODEL, GEMINI_DOWNGRADE_MODELS, MCP_CONFIG_FLAG;
10460
+ var activeChats, chatLocks, SPAWN_TIMEOUT_MS, FIRST_RESPONSE_TIMEOUT_MS, FIRST_RESPONSE_TIMEOUT_ERROR, GEMINI_FALLBACK_CHAIN, GEMINI_DOWNGRADE_MODELS, MCP_CONFIG_FLAG;
10194
10461
  var init_agent = __esm({
10195
10462
  "src/agent.ts"() {
10196
10463
  "use strict";
@@ -10215,8 +10482,11 @@ var init_agent = __esm({
10215
10482
  SPAWN_TIMEOUT_MS = 10 * 60 * 1e3;
10216
10483
  FIRST_RESPONSE_TIMEOUT_MS = parseInt(process.env.GEMINI_FIRST_RESPONSE_TIMEOUT_MS ?? "30000", 10);
10217
10484
  FIRST_RESPONSE_TIMEOUT_ERROR = "FIRST_RESPONSE_TIMEOUT";
10218
- GEMINI_FALLBACK_MODEL = "gemini-2.5-pro";
10219
- GEMINI_DOWNGRADE_MODELS = /* @__PURE__ */ new Set(["gemini-3.1-pro-preview"]);
10485
+ GEMINI_FALLBACK_CHAIN = {
10486
+ "gemini-3.1-pro-preview": "gemini-2.5-pro",
10487
+ "gemini-2.5-pro": "gemini-3.1-flash-preview"
10488
+ };
10489
+ GEMINI_DOWNGRADE_MODELS = new Set(Object.keys(GEMINI_FALLBACK_CHAIN));
10220
10490
  MCP_CONFIG_FLAG = {
10221
10491
  claude: ["--mcp-config"]
10222
10492
  };
@@ -10485,83 +10755,6 @@ var init_retry = __esm({
10485
10755
  }
10486
10756
  });
10487
10757
 
10488
- // src/scheduler/health.ts
10489
- import * as os from "os";
10490
- function startHealthMonitor2() {
10491
- lastHeartbeat = /* @__PURE__ */ new Date();
10492
- heartbeatTimer = setInterval(() => {
10493
- lastHeartbeat = /* @__PURE__ */ new Date();
10494
- log(`[health] Scheduler heartbeat at ${lastHeartbeat.toISOString()}`);
10495
- }, HEARTBEAT_INTERVAL_MS);
10496
- heartbeatTimer.unref();
10497
- }
10498
- function stopHealthMonitor2() {
10499
- if (heartbeatTimer) {
10500
- clearInterval(heartbeatTimer);
10501
- heartbeatTimer = null;
10502
- }
10503
- }
10504
- function getHealthReport() {
10505
- const allJobs = getAllJobs();
10506
- const activeJobs = allJobs.filter((j) => j.enabled && j.active);
10507
- const pausedJobs = allJobs.filter((j) => !j.enabled && j.active);
10508
- const failingJobs = activeJobs.filter((j) => j.consecutiveFailures > 0).map((j) => ({ id: j.id, description: j.description, failures: j.consecutiveFailures }));
10509
- let status = "healthy";
10510
- if (failingJobs.length > 0) status = "degraded";
10511
- if (!lastHeartbeat) status = "unhealthy";
10512
- return {
10513
- status,
10514
- lastHeartbeat: lastHeartbeat?.toISOString() ?? null,
10515
- activeJobs: activeJobs.length,
10516
- pausedJobs: pausedJobs.length,
10517
- failingJobs,
10518
- totalJobs: allJobs.length,
10519
- // Audit O39: Include system metrics for operator visibility
10520
- memoryMB: Math.round(process.memoryUsage().heapUsed / 1024 / 1024),
10521
- loadAvg: os.loadavg()[0]
10522
- };
10523
- }
10524
- function formatHealthReport(report) {
10525
- const statusEmoji = report.status === "healthy" ? "OK" : report.status === "degraded" ? "DEGRADED" : "UNHEALTHY";
10526
- const lines = [
10527
- `Scheduler: ${statusEmoji}`,
10528
- `Last heartbeat: ${report.lastHeartbeat ?? "never"}`,
10529
- `Active jobs: ${report.activeJobs}`,
10530
- `Paused jobs: ${report.pausedJobs}`,
10531
- `Total jobs: ${report.totalJobs}`
10532
- ];
10533
- if (report.failingJobs.length > 0) {
10534
- lines.push("", "Jobs with failures:");
10535
- for (const j of report.failingJobs) {
10536
- lines.push(` #${j.id}: ${j.description} (${j.failures} consecutive failures)`);
10537
- }
10538
- }
10539
- return lines.join("\n");
10540
- }
10541
- function computeStaggerMs(jobId, cronExpr) {
10542
- const parts = cronExpr.split(/\s+/);
10543
- if (parts.length < 5) return 0;
10544
- if (parts[0] !== "0") return 0;
10545
- if (!/[*/]/.test(parts[1])) return 0;
10546
- const maxStagger = 12e4;
10547
- let h = jobId * 2654435761;
10548
- h = (h >>> 16 ^ h) * 73244475;
10549
- h = (h >>> 16 ^ h) * 73244475;
10550
- h = h >>> 16 ^ h;
10551
- return Math.abs(h) % maxStagger;
10552
- }
10553
- var lastHeartbeat, heartbeatTimer, HEARTBEAT_INTERVAL_MS;
10554
- var init_health2 = __esm({
10555
- "src/scheduler/health.ts"() {
10556
- "use strict";
10557
- init_store5();
10558
- init_log();
10559
- lastHeartbeat = null;
10560
- heartbeatTimer = null;
10561
- HEARTBEAT_INTERVAL_MS = 5 * 60 * 1e3;
10562
- }
10563
- });
10564
-
10565
10758
  // src/reflection/propose.ts
10566
10759
  var propose_exports = {};
10567
10760
  __export(propose_exports, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cc-claw",
3
- "version": "0.13.1",
3
+ "version": "0.14.0",
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",