cc-claw 0.13.2 → 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 +270 -87
- 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
|
}
|
|
@@ -10495,83 +10755,6 @@ var init_retry = __esm({
|
|
|
10495
10755
|
}
|
|
10496
10756
|
});
|
|
10497
10757
|
|
|
10498
|
-
// src/scheduler/health.ts
|
|
10499
|
-
import * as os from "os";
|
|
10500
|
-
function startHealthMonitor2() {
|
|
10501
|
-
lastHeartbeat = /* @__PURE__ */ new Date();
|
|
10502
|
-
heartbeatTimer = setInterval(() => {
|
|
10503
|
-
lastHeartbeat = /* @__PURE__ */ new Date();
|
|
10504
|
-
log(`[health] Scheduler heartbeat at ${lastHeartbeat.toISOString()}`);
|
|
10505
|
-
}, HEARTBEAT_INTERVAL_MS);
|
|
10506
|
-
heartbeatTimer.unref();
|
|
10507
|
-
}
|
|
10508
|
-
function stopHealthMonitor2() {
|
|
10509
|
-
if (heartbeatTimer) {
|
|
10510
|
-
clearInterval(heartbeatTimer);
|
|
10511
|
-
heartbeatTimer = null;
|
|
10512
|
-
}
|
|
10513
|
-
}
|
|
10514
|
-
function getHealthReport() {
|
|
10515
|
-
const allJobs = getAllJobs();
|
|
10516
|
-
const activeJobs = allJobs.filter((j) => j.enabled && j.active);
|
|
10517
|
-
const pausedJobs = allJobs.filter((j) => !j.enabled && j.active);
|
|
10518
|
-
const failingJobs = activeJobs.filter((j) => j.consecutiveFailures > 0).map((j) => ({ id: j.id, description: j.description, failures: j.consecutiveFailures }));
|
|
10519
|
-
let status = "healthy";
|
|
10520
|
-
if (failingJobs.length > 0) status = "degraded";
|
|
10521
|
-
if (!lastHeartbeat) status = "unhealthy";
|
|
10522
|
-
return {
|
|
10523
|
-
status,
|
|
10524
|
-
lastHeartbeat: lastHeartbeat?.toISOString() ?? null,
|
|
10525
|
-
activeJobs: activeJobs.length,
|
|
10526
|
-
pausedJobs: pausedJobs.length,
|
|
10527
|
-
failingJobs,
|
|
10528
|
-
totalJobs: allJobs.length,
|
|
10529
|
-
// Audit O39: Include system metrics for operator visibility
|
|
10530
|
-
memoryMB: Math.round(process.memoryUsage().heapUsed / 1024 / 1024),
|
|
10531
|
-
loadAvg: os.loadavg()[0]
|
|
10532
|
-
};
|
|
10533
|
-
}
|
|
10534
|
-
function formatHealthReport(report) {
|
|
10535
|
-
const statusEmoji = report.status === "healthy" ? "OK" : report.status === "degraded" ? "DEGRADED" : "UNHEALTHY";
|
|
10536
|
-
const lines = [
|
|
10537
|
-
`Scheduler: ${statusEmoji}`,
|
|
10538
|
-
`Last heartbeat: ${report.lastHeartbeat ?? "never"}`,
|
|
10539
|
-
`Active jobs: ${report.activeJobs}`,
|
|
10540
|
-
`Paused jobs: ${report.pausedJobs}`,
|
|
10541
|
-
`Total jobs: ${report.totalJobs}`
|
|
10542
|
-
];
|
|
10543
|
-
if (report.failingJobs.length > 0) {
|
|
10544
|
-
lines.push("", "Jobs with failures:");
|
|
10545
|
-
for (const j of report.failingJobs) {
|
|
10546
|
-
lines.push(` #${j.id}: ${j.description} (${j.failures} consecutive failures)`);
|
|
10547
|
-
}
|
|
10548
|
-
}
|
|
10549
|
-
return lines.join("\n");
|
|
10550
|
-
}
|
|
10551
|
-
function computeStaggerMs(jobId, cronExpr) {
|
|
10552
|
-
const parts = cronExpr.split(/\s+/);
|
|
10553
|
-
if (parts.length < 5) return 0;
|
|
10554
|
-
if (parts[0] !== "0") return 0;
|
|
10555
|
-
if (!/[*/]/.test(parts[1])) return 0;
|
|
10556
|
-
const maxStagger = 12e4;
|
|
10557
|
-
let h = jobId * 2654435761;
|
|
10558
|
-
h = (h >>> 16 ^ h) * 73244475;
|
|
10559
|
-
h = (h >>> 16 ^ h) * 73244475;
|
|
10560
|
-
h = h >>> 16 ^ h;
|
|
10561
|
-
return Math.abs(h) % maxStagger;
|
|
10562
|
-
}
|
|
10563
|
-
var lastHeartbeat, heartbeatTimer, HEARTBEAT_INTERVAL_MS;
|
|
10564
|
-
var init_health2 = __esm({
|
|
10565
|
-
"src/scheduler/health.ts"() {
|
|
10566
|
-
"use strict";
|
|
10567
|
-
init_store5();
|
|
10568
|
-
init_log();
|
|
10569
|
-
lastHeartbeat = null;
|
|
10570
|
-
heartbeatTimer = null;
|
|
10571
|
-
HEARTBEAT_INTERVAL_MS = 5 * 60 * 1e3;
|
|
10572
|
-
}
|
|
10573
|
-
});
|
|
10574
|
-
|
|
10575
10758
|
// src/reflection/propose.ts
|
|
10576
10759
|
var propose_exports = {};
|
|
10577
10760
|
__export(propose_exports, {
|
package/package.json
CHANGED