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/agents/mcp-server.js +582 -306
- package/dist/cli.js +293 -100
- package/package.json +1 -1
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.
|
|
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
|
|
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(
|
|
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
|
-
|
|
8761
|
-
|
|
8762
|
-
|
|
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 ${
|
|
9957
|
-
onModelDowngrade?.(model2,
|
|
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:
|
|
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,
|
|
9978
|
-
fallbackResult.resolvedModel =
|
|
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 ${
|
|
10093
|
-
onModelDowngrade?.(chatId, resolvedModel,
|
|
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:
|
|
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,
|
|
10107
|
-
result.resolvedModel =
|
|
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,
|
|
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
|
-
|
|
10219
|
-
|
|
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