@gonzih/cc-agent 0.15.11 → 0.15.17
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/LICENSE +203 -0
- package/README.md +3 -1
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +47 -44
- package/dist/agent.js.map +1 -1
- package/dist/cron.d.ts.map +1 -1
- package/dist/cron.js +18 -12
- package/dist/cron.js.map +1 -1
- package/dist/index.js +355 -39
- package/dist/index.js.map +1 -1
- package/dist/preamble.d.ts +1 -1
- package/dist/preamble.d.ts.map +1 -1
- package/dist/preamble.js +9 -3
- package/dist/preamble.js.map +1 -1
- package/dist/swarm.d.ts +81 -0
- package/dist/swarm.d.ts.map +1 -0
- package/dist/swarm.js +409 -0
- package/dist/swarm.js.map +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -22,6 +22,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
22
22
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
23
23
|
import { JobManager, normalizeRepoUrl } from "./agent.js";
|
|
24
24
|
import { listDrivers, getDriverStatus } from "./drivers/index.js";
|
|
25
|
+
import { runSwarm, getSwarmStatus, SWARM_MAX_AGENTS_HARD_CAP } from "./swarm.js";
|
|
25
26
|
import { MetaAgentManager } from "./meta-agent.js";
|
|
26
27
|
import { buildEvaluatorTask } from "./evaluator.js";
|
|
27
28
|
import { loadProfiles, upsertProfile, deleteProfile, getProfile, interpolate } from "./profiles.js";
|
|
@@ -687,6 +688,95 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
|
687
688
|
description: "List all available agent drivers and their status (binary found / API key configured). Use this to check which drivers are ready to use before calling spawn_agent with agent_driver.",
|
|
688
689
|
inputSchema: { type: "object", properties: {} },
|
|
689
690
|
},
|
|
691
|
+
{
|
|
692
|
+
name: "export_jobs",
|
|
693
|
+
description: "Export all job records as JSONL or JSON for statistical analysis. Each record includes id, status, repo_url, task (truncated to 500 chars), started_at, finished_at, exit_code, output_lines count, score, and duration_seconds. Use this to pull job traces, compute success rates, and study failure modes.",
|
|
694
|
+
inputSchema: {
|
|
695
|
+
type: "object",
|
|
696
|
+
properties: {
|
|
697
|
+
days: { type: "number", description: "How many days back to export (default: 7)" },
|
|
698
|
+
format: { type: "string", description: "Output format: 'jsonl' (one record per line) or 'json' (array). Default: 'jsonl'" },
|
|
699
|
+
status: { type: "string", description: "Filter by status: 'done' | 'failed' | 'cancelled' | 'running' (optional)" },
|
|
700
|
+
},
|
|
701
|
+
},
|
|
702
|
+
},
|
|
703
|
+
{
|
|
704
|
+
name: "get_cost_report",
|
|
705
|
+
description: "Longitudinal cost breakdown for research budget tracking. Returns grouped cost summary with total USD spent, job count, avg cost per job, and avg score. Useful for tracking spending by repo, day, or outcome.",
|
|
706
|
+
inputSchema: {
|
|
707
|
+
type: "object",
|
|
708
|
+
properties: {
|
|
709
|
+
days: { type: "number", description: "How many days back to include (default: 30)" },
|
|
710
|
+
group_by: { type: "string", description: "Group by 'repo' | 'day' | 'status' (default: 'repo')" },
|
|
711
|
+
},
|
|
712
|
+
},
|
|
713
|
+
},
|
|
714
|
+
{
|
|
715
|
+
name: "search_jobs",
|
|
716
|
+
description: "Find jobs by content of task prompt. Returns matching jobs with a task snippet showing match context. Useful for finding all jobs that involved a specific tool, repo, or task type.",
|
|
717
|
+
inputSchema: {
|
|
718
|
+
type: "object",
|
|
719
|
+
properties: {
|
|
720
|
+
query: { type: "string", description: "Search term to look for in task prompts (case-insensitive)" },
|
|
721
|
+
days: { type: "number", description: "How many days back to search (default: 30)" },
|
|
722
|
+
status: { type: "string", description: "Filter by status: 'done' | 'failed' | 'cancelled' | 'running' (optional)" },
|
|
723
|
+
},
|
|
724
|
+
required: ["query"],
|
|
725
|
+
},
|
|
726
|
+
},
|
|
727
|
+
{
|
|
728
|
+
name: "swarm_task",
|
|
729
|
+
description: "Auto-decompose a high-level goal into N parallel sub-tasks, fan out agents across all of them, then run a synthesis agent that produces one unified deliverable. Returns immediately with swarm_id and sub_job_ids. Use get_swarm_status to poll progress.",
|
|
730
|
+
inputSchema: {
|
|
731
|
+
type: "object",
|
|
732
|
+
properties: {
|
|
733
|
+
repo_url: {
|
|
734
|
+
type: "string",
|
|
735
|
+
description: "GitHub repo URL for all sub-agents and the synthesis agent",
|
|
736
|
+
},
|
|
737
|
+
goal: {
|
|
738
|
+
type: "string",
|
|
739
|
+
description: "High-level goal to decompose and execute across parallel agents",
|
|
740
|
+
},
|
|
741
|
+
max_agents: {
|
|
742
|
+
type: "number",
|
|
743
|
+
description: `Maximum number of parallel sub-agents (default 10, hard cap ${SWARM_MAX_AGENTS_HARD_CAP})`,
|
|
744
|
+
},
|
|
745
|
+
synthesis_output: {
|
|
746
|
+
type: "string",
|
|
747
|
+
description: "Path in the repo where the synthesis agent writes its deliverable (default: swarm-synthesis.md)",
|
|
748
|
+
},
|
|
749
|
+
synthesis_prompt: {
|
|
750
|
+
type: "string",
|
|
751
|
+
description: "Custom instruction for the synthesis agent. Defaults to: review all outputs and write a unified deliverable.",
|
|
752
|
+
},
|
|
753
|
+
max_budget_per_agent: {
|
|
754
|
+
type: "number",
|
|
755
|
+
description: "Max USD budget per agent (sub-agents and synthesis). Default 5.",
|
|
756
|
+
},
|
|
757
|
+
agent_model: {
|
|
758
|
+
type: "string",
|
|
759
|
+
description: "Model override for all spawned agents (optional)",
|
|
760
|
+
},
|
|
761
|
+
agent_driver: {
|
|
762
|
+
type: "string",
|
|
763
|
+
description: "Driver for all spawned agents (optional, default: claude)",
|
|
764
|
+
},
|
|
765
|
+
},
|
|
766
|
+
required: ["repo_url", "goal"],
|
|
767
|
+
},
|
|
768
|
+
},
|
|
769
|
+
{
|
|
770
|
+
name: "get_swarm_status",
|
|
771
|
+
description: "Poll the status of a swarm created by swarm_task. Returns goal, status (running_subs | synthesizing | done | failed), sub_job counts, and synthesis_job_id once spawned.",
|
|
772
|
+
inputSchema: {
|
|
773
|
+
type: "object",
|
|
774
|
+
properties: {
|
|
775
|
+
swarm_id: { type: "string", description: "Swarm ID returned by swarm_task" },
|
|
776
|
+
},
|
|
777
|
+
required: ["swarm_id"],
|
|
778
|
+
},
|
|
779
|
+
},
|
|
690
780
|
],
|
|
691
781
|
}));
|
|
692
782
|
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
@@ -695,7 +785,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
695
785
|
switch (name) {
|
|
696
786
|
case "spawn_agent": {
|
|
697
787
|
const repoUrl = normalizeRepoUrl(a.repo_url);
|
|
698
|
-
logger.info("
|
|
788
|
+
logger.info("[mcp] spawn_agent", { repo_url: repoUrl, task: a.task?.slice(0, 80) });
|
|
699
789
|
const owner = extractGithubOwner(repoUrl);
|
|
700
790
|
const isTrusted = !owner || TRUSTED_OWNERS.includes(owner);
|
|
701
791
|
const jobId = await manager.spawn({
|
|
@@ -786,7 +876,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
786
876
|
};
|
|
787
877
|
}
|
|
788
878
|
case "get_job_status": {
|
|
789
|
-
logger.info("
|
|
879
|
+
logger.info("[mcp] get_job_status", { job_id: a.job_id });
|
|
790
880
|
const job = manager.getJob(a.job_id);
|
|
791
881
|
if (!job) {
|
|
792
882
|
return { content: [{ type: "text", text: JSON.stringify({ error: "Job not found" }) }] };
|
|
@@ -822,7 +912,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
822
912
|
case "wait_for_job": {
|
|
823
913
|
const waitJobId = a.job_id;
|
|
824
914
|
const timeoutSeconds = typeof a.timeout_seconds === "number" ? a.timeout_seconds : 300;
|
|
825
|
-
logger.info("
|
|
915
|
+
logger.info("[mcp] wait_for_job", { job_id: waitJobId, timeout_seconds: timeoutSeconds });
|
|
826
916
|
const TERMINAL_STATUSES = new Set(["done", "failed", "cancelled", "rejected", "interrupted"]);
|
|
827
917
|
const deadlineMs = Date.now() + timeoutSeconds * 1000;
|
|
828
918
|
let waitRecord = await jobStore.getJob(waitJobId);
|
|
@@ -858,7 +948,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
858
948
|
};
|
|
859
949
|
}
|
|
860
950
|
case "get_job_output": {
|
|
861
|
-
logger.info("
|
|
951
|
+
logger.info("[mcp] get_job_output", { job_id: a.job_id, offset: a.offset });
|
|
862
952
|
const offset = typeof a.offset === "number" ? a.offset : 0;
|
|
863
953
|
const { lines, done, toolCalls } = await manager.getOutput(a.job_id, offset);
|
|
864
954
|
return {
|
|
@@ -878,7 +968,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
878
968
|
};
|
|
879
969
|
}
|
|
880
970
|
case "list_jobs": {
|
|
881
|
-
logger.info("
|
|
971
|
+
logger.info("[mcp] list_jobs");
|
|
882
972
|
const minScore = typeof a.min_score === "number" ? a.min_score : undefined;
|
|
883
973
|
let jobs = (await jobStore.listJobs()) ?? [];
|
|
884
974
|
if (minScore !== undefined) {
|
|
@@ -896,7 +986,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
896
986
|
};
|
|
897
987
|
}
|
|
898
988
|
case "cancel_job": {
|
|
899
|
-
logger.info("
|
|
989
|
+
logger.info("[mcp] cancel_job", { job_id: a.job_id });
|
|
900
990
|
const cancelled = manager.cancel(a.job_id);
|
|
901
991
|
return {
|
|
902
992
|
content: [
|
|
@@ -908,7 +998,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
908
998
|
};
|
|
909
999
|
}
|
|
910
1000
|
case "send_message": {
|
|
911
|
-
logger.info("
|
|
1001
|
+
logger.info("[mcp] send_message", { job_id: a.job_id });
|
|
912
1002
|
const result = await manager.sendMessage(a.job_id, a.message);
|
|
913
1003
|
return {
|
|
914
1004
|
content: [
|
|
@@ -922,7 +1012,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
922
1012
|
};
|
|
923
1013
|
}
|
|
924
1014
|
case "cost_summary": {
|
|
925
|
-
logger.info("
|
|
1015
|
+
logger.info("[mcp] cost_summary");
|
|
926
1016
|
// Use jobStore (Redis/disk) to include all persisted jobs, not just in-memory ones
|
|
927
1017
|
const allRecords = await jobStore.listJobs();
|
|
928
1018
|
let totalCostUsd = 0;
|
|
@@ -955,12 +1045,12 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
955
1045
|
};
|
|
956
1046
|
}
|
|
957
1047
|
case "get_version":
|
|
958
|
-
logger.info("
|
|
1048
|
+
logger.info("[mcp] get_version");
|
|
959
1049
|
return {
|
|
960
1050
|
content: [{ type: "text", text: JSON.stringify({ version: PKG_VERSION }) }],
|
|
961
1051
|
};
|
|
962
1052
|
case "create_profile": {
|
|
963
|
-
logger.info("
|
|
1053
|
+
logger.info("[mcp] create_profile", { name: a.name });
|
|
964
1054
|
const profileName = a.name;
|
|
965
1055
|
if (!/^[\w-]+$/.test(profileName)) {
|
|
966
1056
|
return {
|
|
@@ -982,7 +1072,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
982
1072
|
};
|
|
983
1073
|
}
|
|
984
1074
|
case "list_profiles": {
|
|
985
|
-
logger.info("
|
|
1075
|
+
logger.info("[mcp] list_profiles");
|
|
986
1076
|
const profiles = (await loadProfiles()).map(({ name, repoUrl, description, defaultBudgetUsd, builtin }) => ({
|
|
987
1077
|
name,
|
|
988
1078
|
repoUrl,
|
|
@@ -996,7 +1086,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
996
1086
|
};
|
|
997
1087
|
}
|
|
998
1088
|
case "delete_profile": {
|
|
999
|
-
logger.info("
|
|
1089
|
+
logger.info("[mcp] delete_profile", { name: a.name });
|
|
1000
1090
|
const deleted = await deleteProfile(a.name);
|
|
1001
1091
|
return {
|
|
1002
1092
|
content: [
|
|
@@ -1010,7 +1100,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
1010
1100
|
};
|
|
1011
1101
|
}
|
|
1012
1102
|
case "spawn_from_profile": {
|
|
1013
|
-
logger.info("
|
|
1103
|
+
logger.info("[mcp] spawn_from_profile", { profile_name: a.profile_name });
|
|
1014
1104
|
const profile = await getProfile(a.profile_name);
|
|
1015
1105
|
if (!profile) {
|
|
1016
1106
|
return {
|
|
@@ -1038,7 +1128,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
1038
1128
|
};
|
|
1039
1129
|
}
|
|
1040
1130
|
case "create_plan": {
|
|
1041
|
-
logger.info("
|
|
1131
|
+
logger.info("[mcp] create_plan", { goal: a.goal?.slice(0, 80) });
|
|
1042
1132
|
const goal = a.goal;
|
|
1043
1133
|
const steps = a.steps;
|
|
1044
1134
|
const stepIdToJobId = new Map();
|
|
@@ -1134,14 +1224,14 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
1134
1224
|
};
|
|
1135
1225
|
}
|
|
1136
1226
|
case "wake_job": {
|
|
1137
|
-
logger.info("
|
|
1227
|
+
logger.info("[mcp] wake_job", { job_id: a.job_id });
|
|
1138
1228
|
const result = await manager.wakeJob(a.job_id);
|
|
1139
1229
|
return {
|
|
1140
1230
|
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
1141
1231
|
};
|
|
1142
1232
|
}
|
|
1143
1233
|
case "list_model_ratings": {
|
|
1144
|
-
logger.info("
|
|
1234
|
+
logger.info("[mcp] list_model_ratings");
|
|
1145
1235
|
const ratingsFile = join(homedir(), ".cc-agent", "model-ratings.jsonl");
|
|
1146
1236
|
let ratings = [];
|
|
1147
1237
|
try {
|
|
@@ -1157,7 +1247,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
1157
1247
|
};
|
|
1158
1248
|
}
|
|
1159
1249
|
case "get_logs": {
|
|
1160
|
-
logger.info("
|
|
1250
|
+
logger.info("[mcp] get_logs", { lines: a.lines });
|
|
1161
1251
|
const n = Math.min(typeof a.lines === "number" ? a.lines : 100, 500);
|
|
1162
1252
|
const logFile = join(homedir(), ".cc-agent", "logs", "cc-agent.log");
|
|
1163
1253
|
let lines = [];
|
|
@@ -1174,7 +1264,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
1174
1264
|
};
|
|
1175
1265
|
}
|
|
1176
1266
|
case "list_project_issues": {
|
|
1177
|
-
logger.info("
|
|
1267
|
+
logger.info("[mcp] list_project_issues", { repo: a.repo });
|
|
1178
1268
|
const repo = a.repo;
|
|
1179
1269
|
const state = a.state ?? "open";
|
|
1180
1270
|
const labels = a.labels;
|
|
@@ -1196,7 +1286,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
1196
1286
|
}
|
|
1197
1287
|
}
|
|
1198
1288
|
case "work_on_issue": {
|
|
1199
|
-
logger.info("
|
|
1289
|
+
logger.info("[mcp] work_on_issue", { repo: a.repo, issue_number: a.issue_number });
|
|
1200
1290
|
const repo = a.repo;
|
|
1201
1291
|
const issueNumber = a.issue_number;
|
|
1202
1292
|
const extraContext = a.extra_context;
|
|
@@ -1240,7 +1330,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
1240
1330
|
};
|
|
1241
1331
|
}
|
|
1242
1332
|
case "comment_on_issue": {
|
|
1243
|
-
logger.info("
|
|
1333
|
+
logger.info("[mcp] comment_on_issue", { repo: a.repo, issue_number: a.issue_number });
|
|
1244
1334
|
const repo = a.repo;
|
|
1245
1335
|
const issueNumber = a.issue_number;
|
|
1246
1336
|
const body = a.body;
|
|
@@ -1253,7 +1343,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
1253
1343
|
}
|
|
1254
1344
|
}
|
|
1255
1345
|
case "close_issue": {
|
|
1256
|
-
logger.info("
|
|
1346
|
+
logger.info("[mcp] close_issue", { repo: a.repo, issue_number: a.issue_number });
|
|
1257
1347
|
const repo = a.repo;
|
|
1258
1348
|
const issueNumber = a.issue_number;
|
|
1259
1349
|
const comment = a.comment;
|
|
@@ -1269,14 +1359,14 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
1269
1359
|
}
|
|
1270
1360
|
}
|
|
1271
1361
|
case "approve_job": {
|
|
1272
|
-
logger.info("
|
|
1362
|
+
logger.info("[mcp] approve_job", { job_id: a.job_id });
|
|
1273
1363
|
const result = await manager.approveJob(a.job_id);
|
|
1274
1364
|
return {
|
|
1275
1365
|
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
1276
1366
|
};
|
|
1277
1367
|
}
|
|
1278
1368
|
case "set_job_score": {
|
|
1279
|
-
logger.info("
|
|
1369
|
+
logger.info("[mcp] set_job_score", { job_id: a.job_id, score: a.score });
|
|
1280
1370
|
const result = manager.setJobScore(a.job_id, a.score, a.reason);
|
|
1281
1371
|
return {
|
|
1282
1372
|
content: [{ type: "text", text: JSON.stringify(result) }],
|
|
@@ -1286,7 +1376,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
1286
1376
|
const repoParam = a.repo;
|
|
1287
1377
|
const ns = repoParam ?? a.namespace ?? getNamespace();
|
|
1288
1378
|
const limit = typeof a.limit === "number" ? a.limit : 10;
|
|
1289
|
-
logger.info("
|
|
1379
|
+
logger.info("[mcp] get_learnings", { key: ns, limit });
|
|
1290
1380
|
const learnings = await learningsStore.getLearnings(ns, limit);
|
|
1291
1381
|
return {
|
|
1292
1382
|
content: [{
|
|
@@ -1297,7 +1387,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
1297
1387
|
}
|
|
1298
1388
|
case "clear_learnings": {
|
|
1299
1389
|
const ns = a.namespace ?? getNamespace();
|
|
1300
|
-
logger.info("
|
|
1390
|
+
logger.info("[mcp] clear_learnings", { namespace: ns });
|
|
1301
1391
|
await learningsStore.clearLearnings(ns);
|
|
1302
1392
|
return {
|
|
1303
1393
|
content: [{
|
|
@@ -1307,7 +1397,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
1307
1397
|
};
|
|
1308
1398
|
}
|
|
1309
1399
|
case "docker_ps": {
|
|
1310
|
-
logger.info("
|
|
1400
|
+
logger.info("[mcp] docker_ps");
|
|
1311
1401
|
const containers = await listCcAgentContainers();
|
|
1312
1402
|
return {
|
|
1313
1403
|
content: [{
|
|
@@ -1317,7 +1407,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
1317
1407
|
};
|
|
1318
1408
|
}
|
|
1319
1409
|
case "list_token_status": {
|
|
1320
|
-
logger.info("
|
|
1410
|
+
logger.info("[mcp] list_token_status");
|
|
1321
1411
|
const tokens = loadTokens();
|
|
1322
1412
|
const status = await getTokenStatus();
|
|
1323
1413
|
const tokenList = tokens.map((t, i) => ({
|
|
@@ -1338,12 +1428,12 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
1338
1428
|
};
|
|
1339
1429
|
}
|
|
1340
1430
|
case "list_crons": {
|
|
1341
|
-
logger.info("
|
|
1431
|
+
logger.info("[mcp] list_crons");
|
|
1342
1432
|
const crons = await cronEngine.listCrons();
|
|
1343
1433
|
return { content: [{ type: "text", text: JSON.stringify({ crons, total: crons.length }) }] };
|
|
1344
1434
|
}
|
|
1345
1435
|
case "create_cron": {
|
|
1346
|
-
logger.info("
|
|
1436
|
+
logger.info("[mcp] create_cron", { schedule: a.schedule });
|
|
1347
1437
|
const cron = await cronEngine.addCron({
|
|
1348
1438
|
chatId: typeof a.chat_id === "number" ? a.chat_id : 0,
|
|
1349
1439
|
intervalMs: a.interval_ms,
|
|
@@ -1355,12 +1445,12 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
1355
1445
|
return { content: [{ type: "text", text: JSON.stringify(cron) }] };
|
|
1356
1446
|
}
|
|
1357
1447
|
case "delete_cron": {
|
|
1358
|
-
logger.info("
|
|
1448
|
+
logger.info("[mcp] delete_cron", { cron_id: a.cron_id });
|
|
1359
1449
|
const deleted = await cronEngine.deleteCron(a.cron_id);
|
|
1360
1450
|
return { content: [{ type: "text", text: JSON.stringify({ deleted, cron_id: a.cron_id }) }] };
|
|
1361
1451
|
}
|
|
1362
1452
|
case "update_cron": {
|
|
1363
|
-
logger.info("
|
|
1453
|
+
logger.info("[mcp] update_cron", { cron_id: a.cron_id });
|
|
1364
1454
|
const updates = {};
|
|
1365
1455
|
if (typeof a.interval_ms === "number")
|
|
1366
1456
|
updates.intervalMs = a.interval_ms;
|
|
@@ -1374,7 +1464,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
1374
1464
|
return { content: [{ type: "text", text: JSON.stringify(updated ?? { error: "cron not found" }) }] };
|
|
1375
1465
|
}
|
|
1376
1466
|
case "list_notifications": {
|
|
1377
|
-
logger.info("
|
|
1467
|
+
logger.info("[mcp] list_notifications");
|
|
1378
1468
|
const ns = getNamespace();
|
|
1379
1469
|
const redis = getRedis();
|
|
1380
1470
|
let messages = [];
|
|
@@ -1384,7 +1474,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
1384
1474
|
return { content: [{ type: "text", text: JSON.stringify({ messages, total: messages.length, namespace: ns }) }] };
|
|
1385
1475
|
}
|
|
1386
1476
|
case "list_active_repos": {
|
|
1387
|
-
logger.info("
|
|
1477
|
+
logger.info("[mcp] list_active_repos");
|
|
1388
1478
|
const redis = getRedis();
|
|
1389
1479
|
if (!redis)
|
|
1390
1480
|
return { content: [{ type: "text", text: "Redis unavailable" }] };
|
|
@@ -1431,7 +1521,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
1431
1521
|
};
|
|
1432
1522
|
}
|
|
1433
1523
|
case "get_pubsub_status": {
|
|
1434
|
-
logger.info("
|
|
1524
|
+
logger.info("[mcp] get_pubsub_status");
|
|
1435
1525
|
const redis = getRedis();
|
|
1436
1526
|
if (!redis)
|
|
1437
1527
|
return { content: [{ type: "text", text: "Redis unavailable" }] };
|
|
@@ -1459,7 +1549,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
1459
1549
|
};
|
|
1460
1550
|
}
|
|
1461
1551
|
case "start_meta_agent": {
|
|
1462
|
-
logger.info("
|
|
1552
|
+
logger.info("[mcp] start_meta_agent", { namespace: a.namespace });
|
|
1463
1553
|
const ns = a.namespace;
|
|
1464
1554
|
const repoUrl = a.repo_url;
|
|
1465
1555
|
try {
|
|
@@ -1481,7 +1571,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
1481
1571
|
}
|
|
1482
1572
|
}
|
|
1483
1573
|
case "message_meta_agent": {
|
|
1484
|
-
logger.info("
|
|
1574
|
+
logger.info("[mcp] message_meta_agent", { namespace: a.namespace });
|
|
1485
1575
|
const ns = a.namespace;
|
|
1486
1576
|
const message = a.message;
|
|
1487
1577
|
try {
|
|
@@ -1503,7 +1593,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
1503
1593
|
}
|
|
1504
1594
|
}
|
|
1505
1595
|
case "list_meta_agents": {
|
|
1506
|
-
logger.info("
|
|
1596
|
+
logger.info("[mcp] list_meta_agents");
|
|
1507
1597
|
const agents = await metaAgentManager.listMetaAgents();
|
|
1508
1598
|
return {
|
|
1509
1599
|
content: [{
|
|
@@ -1513,7 +1603,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
1513
1603
|
};
|
|
1514
1604
|
}
|
|
1515
1605
|
case "stop_meta_agent": {
|
|
1516
|
-
logger.info("
|
|
1606
|
+
logger.info("[mcp] stop_meta_agent", { namespace: a.namespace });
|
|
1517
1607
|
const ns = a.namespace;
|
|
1518
1608
|
try {
|
|
1519
1609
|
await metaAgentManager.stopMetaAgent(ns);
|
|
@@ -1534,7 +1624,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
1534
1624
|
}
|
|
1535
1625
|
}
|
|
1536
1626
|
case "list_drivers": {
|
|
1537
|
-
logger.info("
|
|
1627
|
+
logger.info("[mcp] list_drivers");
|
|
1538
1628
|
const drivers = getDriverStatus();
|
|
1539
1629
|
return {
|
|
1540
1630
|
content: [{
|
|
@@ -1547,6 +1637,220 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
1547
1637
|
}],
|
|
1548
1638
|
};
|
|
1549
1639
|
}
|
|
1640
|
+
case "export_jobs": {
|
|
1641
|
+
logger.info("[mcp] export_jobs");
|
|
1642
|
+
const days = typeof a.days === "number" ? a.days : 7;
|
|
1643
|
+
const format = a.format === "json" ? "json" : "jsonl";
|
|
1644
|
+
const statusFilter = typeof a.status === "string" ? a.status : undefined;
|
|
1645
|
+
const cutoff = Date.now() - days * 24 * 60 * 60 * 1000;
|
|
1646
|
+
const allRecords = await jobStore.listJobs();
|
|
1647
|
+
const filtered = allRecords.filter((r) => {
|
|
1648
|
+
if (r.startedAt && new Date(r.startedAt).getTime() < cutoff)
|
|
1649
|
+
return false;
|
|
1650
|
+
if (!r.startedAt)
|
|
1651
|
+
return false;
|
|
1652
|
+
if (statusFilter && r.status !== statusFilter)
|
|
1653
|
+
return false;
|
|
1654
|
+
return true;
|
|
1655
|
+
});
|
|
1656
|
+
const records = filtered.map((r) => {
|
|
1657
|
+
let durationSeconds = null;
|
|
1658
|
+
if (r.startedAt && r.finishedAt) {
|
|
1659
|
+
durationSeconds = Math.round((new Date(r.finishedAt).getTime() - new Date(r.startedAt).getTime()) / 1000);
|
|
1660
|
+
}
|
|
1661
|
+
return {
|
|
1662
|
+
id: r.id,
|
|
1663
|
+
status: r.status,
|
|
1664
|
+
repo_url: r.repoUrl,
|
|
1665
|
+
task: (r.task ?? "").slice(0, 500),
|
|
1666
|
+
started_at: r.startedAt ?? null,
|
|
1667
|
+
finished_at: r.finishedAt ?? null,
|
|
1668
|
+
exit_code: r.exitCode ?? null,
|
|
1669
|
+
output_lines: r.outputLineCount ?? 0,
|
|
1670
|
+
score: r.score ?? null,
|
|
1671
|
+
duration_seconds: durationSeconds,
|
|
1672
|
+
};
|
|
1673
|
+
});
|
|
1674
|
+
const text = format === "json"
|
|
1675
|
+
? JSON.stringify(records)
|
|
1676
|
+
: records.map((r) => JSON.stringify(r)).join("\n");
|
|
1677
|
+
return {
|
|
1678
|
+
content: [{ type: "text", text }],
|
|
1679
|
+
};
|
|
1680
|
+
}
|
|
1681
|
+
case "get_cost_report": {
|
|
1682
|
+
logger.info("[mcp] get_cost_report");
|
|
1683
|
+
const days = typeof a.days === "number" ? a.days : 30;
|
|
1684
|
+
const groupBy = a.group_by === "day" ? "day" : a.group_by === "status" ? "status" : "repo";
|
|
1685
|
+
const cutoff = Date.now() - days * 24 * 60 * 60 * 1000;
|
|
1686
|
+
const allRecords = await jobStore.listJobs();
|
|
1687
|
+
const filtered = allRecords.filter((r) => {
|
|
1688
|
+
if (!r.startedAt)
|
|
1689
|
+
return false;
|
|
1690
|
+
return new Date(r.startedAt).getTime() >= cutoff;
|
|
1691
|
+
});
|
|
1692
|
+
const groups = new Map();
|
|
1693
|
+
for (const r of filtered) {
|
|
1694
|
+
let key;
|
|
1695
|
+
if (groupBy === "day") {
|
|
1696
|
+
key = r.startedAt ? r.startedAt.slice(0, 10) : "unknown";
|
|
1697
|
+
}
|
|
1698
|
+
else if (groupBy === "status") {
|
|
1699
|
+
key = r.status;
|
|
1700
|
+
}
|
|
1701
|
+
else {
|
|
1702
|
+
key = r.repoUrl;
|
|
1703
|
+
}
|
|
1704
|
+
const g = groups.get(key) ?? { total_usd: 0, job_count: 0, scores: [] };
|
|
1705
|
+
g.total_usd += r.costUsd ?? 0;
|
|
1706
|
+
g.job_count += 1;
|
|
1707
|
+
if (r.score != null)
|
|
1708
|
+
g.scores.push(r.score);
|
|
1709
|
+
groups.set(key, g);
|
|
1710
|
+
}
|
|
1711
|
+
const summary = Array.from(groups.entries())
|
|
1712
|
+
.map(([key, g]) => ({
|
|
1713
|
+
group: key,
|
|
1714
|
+
total_usd: Math.round(g.total_usd * 10000) / 10000,
|
|
1715
|
+
job_count: g.job_count,
|
|
1716
|
+
avg_cost_usd: g.job_count > 0 ? Math.round((g.total_usd / g.job_count) * 10000) / 10000 : 0,
|
|
1717
|
+
avg_score: g.scores.length > 0 ? Math.round((g.scores.reduce((s, v) => s + v, 0) / g.scores.length) * 1000) / 1000 : null,
|
|
1718
|
+
}))
|
|
1719
|
+
.sort((a, b) => b.total_usd - a.total_usd);
|
|
1720
|
+
return {
|
|
1721
|
+
content: [{
|
|
1722
|
+
type: "text",
|
|
1723
|
+
text: JSON.stringify({ group_by: groupBy, days, total_groups: summary.length, summary }),
|
|
1724
|
+
}],
|
|
1725
|
+
};
|
|
1726
|
+
}
|
|
1727
|
+
case "search_jobs": {
|
|
1728
|
+
logger.info("[mcp] search_jobs", { query: a.query });
|
|
1729
|
+
const query = (a.query ?? "").toLowerCase();
|
|
1730
|
+
const days = typeof a.days === "number" ? a.days : 30;
|
|
1731
|
+
const statusFilter = typeof a.status === "string" ? a.status : undefined;
|
|
1732
|
+
const cutoff = Date.now() - days * 24 * 60 * 60 * 1000;
|
|
1733
|
+
if (!query) {
|
|
1734
|
+
return {
|
|
1735
|
+
content: [{ type: "text", text: JSON.stringify({ error: "query is required" }) }],
|
|
1736
|
+
};
|
|
1737
|
+
}
|
|
1738
|
+
const allRecords = await jobStore.listJobs();
|
|
1739
|
+
const matches = allRecords
|
|
1740
|
+
.filter((r) => {
|
|
1741
|
+
if (!r.startedAt)
|
|
1742
|
+
return false;
|
|
1743
|
+
if (new Date(r.startedAt).getTime() < cutoff)
|
|
1744
|
+
return false;
|
|
1745
|
+
if (statusFilter && r.status !== statusFilter)
|
|
1746
|
+
return false;
|
|
1747
|
+
return (r.task ?? "").toLowerCase().includes(query);
|
|
1748
|
+
})
|
|
1749
|
+
.map((r) => {
|
|
1750
|
+
const task = r.task ?? "";
|
|
1751
|
+
const idx = task.toLowerCase().indexOf(query);
|
|
1752
|
+
const start = Math.max(0, idx - 50);
|
|
1753
|
+
const end = Math.min(task.length, idx + query.length + 50);
|
|
1754
|
+
const snippet = (start > 0 ? "…" : "") + task.slice(start, end) + (end < task.length ? "…" : "");
|
|
1755
|
+
return {
|
|
1756
|
+
id: r.id,
|
|
1757
|
+
status: r.status,
|
|
1758
|
+
repo_url: r.repoUrl,
|
|
1759
|
+
started_at: r.startedAt ?? null,
|
|
1760
|
+
score: r.score ?? null,
|
|
1761
|
+
task_snippet: snippet,
|
|
1762
|
+
};
|
|
1763
|
+
});
|
|
1764
|
+
return {
|
|
1765
|
+
content: [{
|
|
1766
|
+
type: "text",
|
|
1767
|
+
text: JSON.stringify({ query: a.query, days, total: matches.length, matches }),
|
|
1768
|
+
}],
|
|
1769
|
+
};
|
|
1770
|
+
}
|
|
1771
|
+
case "swarm_task": {
|
|
1772
|
+
const repoUrl = normalizeRepoUrl(a.repo_url);
|
|
1773
|
+
const goal = a.goal;
|
|
1774
|
+
const maxAgents = typeof a.max_agents === "number" ? a.max_agents : 10;
|
|
1775
|
+
if (maxAgents > SWARM_MAX_AGENTS_HARD_CAP) {
|
|
1776
|
+
return {
|
|
1777
|
+
content: [{
|
|
1778
|
+
type: "text",
|
|
1779
|
+
text: JSON.stringify({ error: `max_agents ${maxAgents} exceeds hard cap of ${SWARM_MAX_AGENTS_HARD_CAP}` }),
|
|
1780
|
+
}],
|
|
1781
|
+
};
|
|
1782
|
+
}
|
|
1783
|
+
logger.info("[mcp] swarm_task", { repo_url: repoUrl, goal: goal.slice(0, 80), max_agents: maxAgents });
|
|
1784
|
+
try {
|
|
1785
|
+
const result = await runSwarm({
|
|
1786
|
+
repoUrl,
|
|
1787
|
+
goal,
|
|
1788
|
+
maxAgents,
|
|
1789
|
+
synthesisOutput: a.synthesis_output,
|
|
1790
|
+
synthesisPrompt: a.synthesis_prompt,
|
|
1791
|
+
maxBudgetPerAgent: typeof a.max_budget_per_agent === "number" ? a.max_budget_per_agent : 5,
|
|
1792
|
+
agentModel: a.agent_model,
|
|
1793
|
+
agentDriver: a.agent_driver,
|
|
1794
|
+
manager,
|
|
1795
|
+
redis: getRedis(),
|
|
1796
|
+
getJobOutput: (id, offset) => jobStore.getOutput(id, offset),
|
|
1797
|
+
});
|
|
1798
|
+
return {
|
|
1799
|
+
content: [{
|
|
1800
|
+
type: "text",
|
|
1801
|
+
text: JSON.stringify({
|
|
1802
|
+
swarm_id: result.swarm_id,
|
|
1803
|
+
sub_job_ids: result.sub_job_ids,
|
|
1804
|
+
decomposed_tasks: result.decomposed_tasks,
|
|
1805
|
+
status: result.status,
|
|
1806
|
+
message: `Swarm started with ${result.sub_job_ids.length} sub-agents. Use get_swarm_status to poll progress.`,
|
|
1807
|
+
}),
|
|
1808
|
+
}],
|
|
1809
|
+
};
|
|
1810
|
+
}
|
|
1811
|
+
catch (err) {
|
|
1812
|
+
logger.error("[mcp] swarm_task failed", { err: String(err) });
|
|
1813
|
+
return {
|
|
1814
|
+
content: [{
|
|
1815
|
+
type: "text",
|
|
1816
|
+
text: JSON.stringify({ error: String(err) }),
|
|
1817
|
+
}],
|
|
1818
|
+
};
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
case "get_swarm_status": {
|
|
1822
|
+
const swarmId = a.swarm_id;
|
|
1823
|
+
logger.info("[mcp] get_swarm_status", { swarm_id: swarmId });
|
|
1824
|
+
const record = await getSwarmStatus(swarmId, getRedis());
|
|
1825
|
+
if (!record) {
|
|
1826
|
+
return {
|
|
1827
|
+
content: [{
|
|
1828
|
+
type: "text",
|
|
1829
|
+
text: JSON.stringify({ error: `Swarm '${swarmId}' not found` }),
|
|
1830
|
+
}],
|
|
1831
|
+
};
|
|
1832
|
+
}
|
|
1833
|
+
const subJobsDone = record.sub_job_ids.length;
|
|
1834
|
+
const subJobsFailed = 0; // detailed counts require Redis lookup; status field covers this
|
|
1835
|
+
return {
|
|
1836
|
+
content: [{
|
|
1837
|
+
type: "text",
|
|
1838
|
+
text: JSON.stringify({
|
|
1839
|
+
swarm_id: record.swarm_id,
|
|
1840
|
+
goal: record.goal,
|
|
1841
|
+
status: record.status,
|
|
1842
|
+
sub_job_ids: record.sub_job_ids,
|
|
1843
|
+
sub_jobs_total: record.sub_job_ids.length,
|
|
1844
|
+
synthesis_job_id: record.synthesis_job_id ?? null,
|
|
1845
|
+
synthesis_output: record.synthesis_output,
|
|
1846
|
+
decomposed_tasks: record.decomposed_tasks,
|
|
1847
|
+
created_at: record.created_at,
|
|
1848
|
+
completed_at: record.completed_at ?? null,
|
|
1849
|
+
error: record.error ?? null,
|
|
1850
|
+
}),
|
|
1851
|
+
}],
|
|
1852
|
+
};
|
|
1853
|
+
}
|
|
1550
1854
|
default:
|
|
1551
1855
|
throw new Error(`Unknown tool: ${name}`);
|
|
1552
1856
|
}
|
|
@@ -1569,6 +1873,18 @@ await manager.init();
|
|
|
1569
1873
|
await seedBuiltinProfiles(profileStore);
|
|
1570
1874
|
await coordinator.start();
|
|
1571
1875
|
await cronEngine.start();
|
|
1876
|
+
// Startup summary — logged after all engines are initialized
|
|
1877
|
+
logger.info("[cc-agent] started", { version: PKG_VERSION, namespace });
|
|
1878
|
+
const _startupJobs = manager.list();
|
|
1879
|
+
const _jobCounts = {};
|
|
1880
|
+
for (const j of _startupJobs)
|
|
1881
|
+
_jobCounts[j.status] = (_jobCounts[j.status] ?? 0) + 1;
|
|
1882
|
+
logger.info("[cc-agent] startup", { jobs_total: _startupJobs.length, ..._jobCounts });
|
|
1883
|
+
const _startupCrons = await cronEngine.listCrons();
|
|
1884
|
+
logger.info("[cc-agent] startup", { crons_loaded: _startupCrons.length });
|
|
1885
|
+
for (const _cron of _startupCrons) {
|
|
1886
|
+
logger.info("[cron] registered", { id: _cron.id, schedule: _cron.schedule, intervalMs: _cron.intervalMs });
|
|
1887
|
+
}
|
|
1572
1888
|
metaAgentManager.startPoller();
|
|
1573
1889
|
const transport = new StdioServerTransport();
|
|
1574
1890
|
await server.connect(transport);
|