gossipcat 0.1.2 → 0.2.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-dashboard/assets/index-Dx4sBGgb.js +65 -0
- package/dist-dashboard/assets/index-rAO4S2u5.css +1 -0
- package/dist-dashboard/index.html +2 -2
- package/dist-mcp/mcp-server.js +639 -287
- package/package.json +1 -1
- package/dist-dashboard/assets/index-BvqFkH-m.css +0 -1
- package/dist-dashboard/assets/index-Dsv-K6u_.js +0 -65
package/dist-mcp/mcp-server.js
CHANGED
|
@@ -55,6 +55,9 @@ var init_mcp_context = __esm({
|
|
|
55
55
|
pendingConsensusRounds: /* @__PURE__ */ new Map(),
|
|
56
56
|
nativeUtilityConfig: null,
|
|
57
57
|
mainProvider: "google",
|
|
58
|
+
httpMcpPort: null,
|
|
59
|
+
relayPortSource: null,
|
|
60
|
+
httpMcpPortSource: null,
|
|
58
61
|
booted: false,
|
|
59
62
|
boot: async () => {
|
|
60
63
|
throw new Error("boot not initialized");
|
|
@@ -9219,6 +9222,17 @@ function loadSkills(agentId, skills, projectRoot, index, task) {
|
|
|
9219
9222
|
for (const skill of effectiveSkills) {
|
|
9220
9223
|
const content = resolveSkill(agentId, skill, projectRoot);
|
|
9221
9224
|
if (!content) continue;
|
|
9225
|
+
const frontmatterStatus = parseSkillFrontmatter(content)?.status;
|
|
9226
|
+
if (frontmatterStatus === "failed" || frontmatterStatus === "silent_skill") {
|
|
9227
|
+
process.stderr.write(`[gossipcat] Skipping ${frontmatterStatus} skill ${agentId}/${skill} from injection
|
|
9228
|
+
`);
|
|
9229
|
+
dropped.push(skill);
|
|
9230
|
+
continue;
|
|
9231
|
+
}
|
|
9232
|
+
if (frontmatterStatus === "flagged_for_manual_review") {
|
|
9233
|
+
process.stderr.write(`[gossipcat] Injecting flagged_for_manual_review skill ${agentId}/${skill} \u2014 manual review recommended
|
|
9234
|
+
`);
|
|
9235
|
+
}
|
|
9222
9236
|
const mode = index?.getSkillMode(agentId, skill) ?? "permanent";
|
|
9223
9237
|
if (mode === "permanent") {
|
|
9224
9238
|
permanent.push({ name: skill, content });
|
|
@@ -11310,6 +11324,7 @@ var init_performance_reader = __esm({
|
|
|
11310
11324
|
"use strict";
|
|
11311
11325
|
import_fs15 = require("fs");
|
|
11312
11326
|
import_path17 = require("path");
|
|
11327
|
+
init_skill_name();
|
|
11313
11328
|
CIRCUIT_BREAKER_THRESHOLD = 3;
|
|
11314
11329
|
NEGATIVE_SIGNALS = /* @__PURE__ */ new Set(["hallucination_caught", "disagreement", "unique_unconfirmed"]);
|
|
11315
11330
|
SIGNAL_EXPIRY_DAYS = 30;
|
|
@@ -11393,9 +11408,10 @@ var init_performance_reader = __esm({
|
|
|
11393
11408
|
getCountersSince(agentId, category, sinceMs) {
|
|
11394
11409
|
const allSignals = this.readSignalsRaw();
|
|
11395
11410
|
const counters = { correct: 0, hallucinated: 0 };
|
|
11411
|
+
const normalizedTarget = normalizeSkillName(category);
|
|
11396
11412
|
for (const s of allSignals) {
|
|
11397
11413
|
if (s.agentId !== agentId) continue;
|
|
11398
|
-
if (s.category !==
|
|
11414
|
+
if (normalizeSkillName(s.category ?? "") !== normalizedTarget) continue;
|
|
11399
11415
|
const ts = s.timestamp ? new Date(s.timestamp).getTime() : 0;
|
|
11400
11416
|
if (!isFinite(ts) || ts === 0 || ts < sinceMs) continue;
|
|
11401
11417
|
switch (s.signal) {
|
|
@@ -11670,6 +11686,7 @@ var init_performance_reader = __esm({
|
|
|
11670
11686
|
const timeFreshness = Math.pow(0.5, daysSinceLastSignal / halfLife);
|
|
11671
11687
|
reliability = 0.5 + (reliability - 0.5) * timeFreshness;
|
|
11672
11688
|
}
|
|
11689
|
+
const MIN_CATEGORY_N = 5;
|
|
11673
11690
|
const categoryAccuracy = {};
|
|
11674
11691
|
const allCategories = /* @__PURE__ */ new Set([
|
|
11675
11692
|
...Object.keys(a.categoryCorrect),
|
|
@@ -11678,7 +11695,7 @@ var init_performance_reader = __esm({
|
|
|
11678
11695
|
for (const cat of allCategories) {
|
|
11679
11696
|
const c = a.categoryCorrect[cat] ?? 0;
|
|
11680
11697
|
const h = a.categoryHallucinated[cat] ?? 0;
|
|
11681
|
-
if (c + h
|
|
11698
|
+
if (c + h >= MIN_CATEGORY_N) categoryAccuracy[cat] = c / (c + h);
|
|
11682
11699
|
}
|
|
11683
11700
|
const consec = consecutiveFailures.get(id) || 0;
|
|
11684
11701
|
scores.set(id, {
|
|
@@ -15588,8 +15605,8 @@ Keep it SHORT \u2014 under 30 lines. This is a working document, not a design do
|
|
|
15588
15605
|
]);
|
|
15589
15606
|
const specContent = response.text || "";
|
|
15590
15607
|
try {
|
|
15591
|
-
const { mkdirSync:
|
|
15592
|
-
|
|
15608
|
+
const { mkdirSync: mkdirSync22, writeFileSync: writeFS } = require("fs");
|
|
15609
|
+
mkdirSync22((0, import_path23.join)(this.projectRoot, ".gossip"), { recursive: true });
|
|
15593
15610
|
writeFS(specPath, specContent, "utf-8");
|
|
15594
15611
|
} catch (err) {
|
|
15595
15612
|
return { text: `Spec generated but failed to save: ${err.message}
|
|
@@ -16424,8 +16441,8 @@ message: Your question?
|
|
|
16424
16441
|
}
|
|
16425
16442
|
/** Start all worker agents (connect to relay) */
|
|
16426
16443
|
async start() {
|
|
16427
|
-
const { existsSync:
|
|
16428
|
-
const { join:
|
|
16444
|
+
const { existsSync: existsSync44, readFileSync: readFileSync41 } = await import("fs");
|
|
16445
|
+
const { join: join49 } = await import("path");
|
|
16429
16446
|
for (const config2 of this.registry.getAll()) {
|
|
16430
16447
|
if (config2.native) continue;
|
|
16431
16448
|
if (this.workers.has(config2.id)) continue;
|
|
@@ -16434,8 +16451,8 @@ message: Your question?
|
|
|
16434
16451
|
apiKey = await this.keyProviderFn(config2.provider) ?? void 0;
|
|
16435
16452
|
}
|
|
16436
16453
|
const llm = createProvider(config2.provider, config2.model, apiKey);
|
|
16437
|
-
const instructionsPath =
|
|
16438
|
-
const instructions =
|
|
16454
|
+
const instructionsPath = join49(this.projectRoot, ".gossip", "agents", config2.id, "instructions.md");
|
|
16455
|
+
const instructions = existsSync44(instructionsPath) ? readFileSync41(instructionsPath, "utf-8") : void 0;
|
|
16439
16456
|
const enableWebSearch = config2.preset === "researcher" || config2.skills.includes("research");
|
|
16440
16457
|
const worker = new WorkerAgent(config2.id, llm, this.relayUrl, ALL_TOOLS, instructions, enableWebSearch, this.relayApiKey);
|
|
16441
16458
|
await worker.start();
|
|
@@ -16617,16 +16634,16 @@ message: Your question?
|
|
|
16617
16634
|
this.registry.register(config2);
|
|
16618
16635
|
}
|
|
16619
16636
|
async syncWorkers(keyProvider) {
|
|
16620
|
-
const { existsSync:
|
|
16621
|
-
const { join:
|
|
16637
|
+
const { existsSync: existsSync44, readFileSync: readFileSync41 } = await import("fs");
|
|
16638
|
+
const { join: join49 } = await import("path");
|
|
16622
16639
|
let added = 0;
|
|
16623
16640
|
for (const ac of this.registry.getAll()) {
|
|
16624
16641
|
if (ac.native) continue;
|
|
16625
16642
|
if (this.workers.has(ac.id)) continue;
|
|
16626
16643
|
const key = await keyProvider(ac.provider);
|
|
16627
16644
|
const llm = createProvider(ac.provider, ac.model, key ?? void 0, void 0, ac.base_url);
|
|
16628
|
-
const instructionsPath =
|
|
16629
|
-
const instructions =
|
|
16645
|
+
const instructionsPath = join49(this.projectRoot, ".gossip", "agents", ac.id, "instructions.md");
|
|
16646
|
+
const instructions = existsSync44(instructionsPath) ? readFileSync41(instructionsPath, "utf-8") : void 0;
|
|
16630
16647
|
const enableWebSearch = ac.preset === "researcher" || ac.skills.includes("research");
|
|
16631
16648
|
const worker = new WorkerAgent(ac.id, llm, this.relayUrl, ALL_TOOLS, instructions, enableWebSearch, this.relayApiKey);
|
|
16632
16649
|
await worker.start();
|
|
@@ -18398,7 +18415,13 @@ NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST.
|
|
|
18398
18415
|
this.projectRoot = projectRoot;
|
|
18399
18416
|
}
|
|
18400
18417
|
techStackCache = void 0;
|
|
18401
|
-
|
|
18418
|
+
/**
|
|
18419
|
+
* Build the LLM prompt strings and metadata needed to generate a skill file.
|
|
18420
|
+
* Separated from generate() so callers (e.g. the native-utility re-entry path)
|
|
18421
|
+
* can obtain the prompt, dispatch it externally, and later call saveFromRaw()
|
|
18422
|
+
* with the result — without re-running the expensive data-gathering step.
|
|
18423
|
+
*/
|
|
18424
|
+
async buildPrompt(agentId, category) {
|
|
18402
18425
|
if (!SAFE_NAME.test(agentId)) {
|
|
18403
18426
|
throw new Error(`Invalid agent_id: "${agentId}". Must be lowercase alphanumeric with hyphens/underscores.`);
|
|
18404
18427
|
}
|
|
@@ -18437,10 +18460,13 @@ ${techStack}
|
|
|
18437
18460
|
const totalDispatches = agentScoreData?.totalSignals ?? 0;
|
|
18438
18461
|
const categoryConfirmations = findings.filter((f) => f.agentId === agentId).length;
|
|
18439
18462
|
const baselineRate = totalDispatches > 0 ? categoryConfirmations / totalDispatches : 0;
|
|
18440
|
-
const
|
|
18441
|
-
|
|
18442
|
-
|
|
18443
|
-
|
|
18463
|
+
const lifetime = this.perfReader.getCountersSince(agentId, category, 0);
|
|
18464
|
+
const baseline_accuracy_correct = lifetime.correct;
|
|
18465
|
+
const baseline_accuracy_hallucinated = lifetime.hallucinated;
|
|
18466
|
+
const bound_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
18467
|
+
const skillName = normalizeSkillName(category);
|
|
18468
|
+
const skillPath = (0, import_path31.join)(this.projectRoot, ".gossip", "agents", agentId, "skills", `${skillName}.md`);
|
|
18469
|
+
const system = `You are a senior prompt engineer who builds skill files for AI code review agents. Your skills are injected into agent system prompts at dispatch time \u2014 every word costs tokens and shapes behavior. You write concise, opinionated methodology that changes how an agent thinks about a specific class of problems.
|
|
18444
18470
|
|
|
18445
18471
|
Your output quality is measured by:
|
|
18446
18472
|
1. **Relevance** \u2014 every check must apply to THIS project's tech stack. Generic checklists are waste.
|
|
@@ -18452,11 +18478,8 @@ Study this reference skill \u2014 it represents the quality bar:
|
|
|
18452
18478
|
|
|
18453
18479
|
<reference_skill>
|
|
18454
18480
|
${template}
|
|
18455
|
-
</reference_skill
|
|
18456
|
-
|
|
18457
|
-
{
|
|
18458
|
-
role: "user",
|
|
18459
|
-
content: `Generate a skill file for agent "${agentId}" to improve its "${category}" review performance.
|
|
18481
|
+
</reference_skill>`;
|
|
18482
|
+
const user = `Generate a skill file for agent "${agentId}" to improve its "${category}" review performance.
|
|
18460
18483
|
|
|
18461
18484
|
<project_context>
|
|
18462
18485
|
${projectContext || "No project context available."}
|
|
@@ -18487,27 +18510,42 @@ Requirements:
|
|
|
18487
18510
|
- Keep under 150 lines
|
|
18488
18511
|
- CRITICAL: Tailor ALL content to the project's actual tech stack (see <tech_stack>). Only include checks relevant to technologies the project uses. If the project has no SQL database, do NOT mention SQL injection. If no HTML rendering, do NOT mention XSS. Generic security checklists waste agent prompt tokens.
|
|
18489
18512
|
- Reference actual project file paths and patterns from findings and context
|
|
18490
|
-
- Use bullet lists instead of markdown tables for Anti-Patterns (tables render poorly in agent prompts)
|
|
18491
|
-
|
|
18492
|
-
|
|
18493
|
-
|
|
18494
|
-
|
|
18495
|
-
|
|
18513
|
+
- Use bullet lists instead of markdown tables for Anti-Patterns (tables render poorly in agent prompts)`;
|
|
18514
|
+
return { system, user, skillName, skillPath, baseline_accuracy_correct, baseline_accuracy_hallucinated, bound_at };
|
|
18515
|
+
}
|
|
18516
|
+
/**
|
|
18517
|
+
* Persist a raw LLM-generated skill markdown string to disk.
|
|
18518
|
+
* Mirrors the post-LLM steps from generate(): code-fence stripping,
|
|
18519
|
+
* validation, snapshot-field injection, and atomic file write.
|
|
18520
|
+
*
|
|
18521
|
+
* Called by the native-utility re-entry path after gossip_relay delivers
|
|
18522
|
+
* the agent output back to the orchestrator.
|
|
18523
|
+
*/
|
|
18524
|
+
saveFromRaw(_agentId, _category, rawMarkdown, meta3) {
|
|
18525
|
+
let cleaned = rawMarkdown.trim();
|
|
18496
18526
|
if (cleaned.startsWith("```")) {
|
|
18497
18527
|
cleaned = cleaned.replace(/^```\w*\n/, "").replace(/\n```\s*$/, "").trim();
|
|
18498
18528
|
}
|
|
18499
18529
|
this.validateSkillContent(cleaned);
|
|
18500
|
-
|
|
18501
|
-
|
|
18502
|
-
|
|
18503
|
-
|
|
18504
|
-
|
|
18505
|
-
const
|
|
18506
|
-
const skillDir = (0, import_path31.join)(this.projectRoot, ".gossip", "agents", agentId, "skills");
|
|
18530
|
+
cleaned = this.injectSnapshotFields(cleaned, {
|
|
18531
|
+
baseline_accuracy_correct: meta3.baseline_accuracy_correct,
|
|
18532
|
+
baseline_accuracy_hallucinated: meta3.baseline_accuracy_hallucinated,
|
|
18533
|
+
bound_at: meta3.bound_at
|
|
18534
|
+
});
|
|
18535
|
+
const skillDir = (0, import_path31.join)(this.projectRoot, ".gossip", "agents", _agentId, "skills");
|
|
18507
18536
|
(0, import_fs29.mkdirSync)(skillDir, { recursive: true });
|
|
18508
|
-
|
|
18509
|
-
|
|
18510
|
-
|
|
18537
|
+
(0, import_fs29.writeFileSync)(meta3.skillPath, cleaned);
|
|
18538
|
+
return { path: meta3.skillPath, content: cleaned };
|
|
18539
|
+
}
|
|
18540
|
+
async generate(agentId, category) {
|
|
18541
|
+
const promptData = await this.buildPrompt(agentId, category);
|
|
18542
|
+
const messages = [
|
|
18543
|
+
{ role: "system", content: promptData.system },
|
|
18544
|
+
{ role: "user", content: promptData.user }
|
|
18545
|
+
];
|
|
18546
|
+
const response = await this.llm.generate(messages, { temperature: 0.3 });
|
|
18547
|
+
const content = response.text || "";
|
|
18548
|
+
return this.saveFromRaw(agentId, category, content, promptData);
|
|
18511
18549
|
}
|
|
18512
18550
|
/**
|
|
18513
18551
|
* Post-processes LLM-generated skill content to inject or overwrite snapshot fields
|
|
@@ -19425,11 +19463,11 @@ function startConsensusTimeout(consensusId) {
|
|
|
19425
19463
|
const allEntries = [...snapshot.relayCrossReviewEntries, ...snapshot.nativeCrossReviewEntries];
|
|
19426
19464
|
const report = await engine.synthesizeWithCrossReview(snapshot.allResults, allEntries, consensusId, snapshot.relayCrossReviewSkipped);
|
|
19427
19465
|
try {
|
|
19428
|
-
const { writeFileSync:
|
|
19429
|
-
const { join:
|
|
19430
|
-
const reportsDir =
|
|
19431
|
-
|
|
19432
|
-
|
|
19466
|
+
const { writeFileSync: writeFileSync18, mkdirSync: mkdirSync22 } = require("fs");
|
|
19467
|
+
const { join: join49 } = require("path");
|
|
19468
|
+
const reportsDir = join49(process.cwd(), ".gossip", "consensus-reports");
|
|
19469
|
+
mkdirSync22(reportsDir, { recursive: true });
|
|
19470
|
+
writeFileSync18(join49(reportsDir, `${consensusId}.json`), JSON.stringify({
|
|
19433
19471
|
id: consensusId,
|
|
19434
19472
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
19435
19473
|
agentCount: report.agentCount,
|
|
@@ -19577,12 +19615,12 @@ async function handleRelayCrossReview(consensus_id, agent_id, result) {
|
|
|
19577
19615
|
synthSnapshot.relayCrossReviewSkipped
|
|
19578
19616
|
);
|
|
19579
19617
|
try {
|
|
19580
|
-
const { writeFileSync:
|
|
19581
|
-
const { join:
|
|
19582
|
-
const reportsDir =
|
|
19583
|
-
|
|
19584
|
-
const reportPath =
|
|
19585
|
-
|
|
19618
|
+
const { writeFileSync: writeFileSync18, mkdirSync: mkdirSync22 } = require("fs");
|
|
19619
|
+
const { join: join49 } = require("path");
|
|
19620
|
+
const reportsDir = join49(process.cwd(), ".gossip", "consensus-reports");
|
|
19621
|
+
mkdirSync22(reportsDir, { recursive: true });
|
|
19622
|
+
const reportPath = join49(reportsDir, `${consensus_id}.json`);
|
|
19623
|
+
writeFileSync18(reportPath, JSON.stringify({
|
|
19586
19624
|
id: consensus_id,
|
|
19587
19625
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
19588
19626
|
agentCount: report.agentCount,
|
|
@@ -19622,10 +19660,10 @@ function persistPendingConsensus() {
|
|
|
19622
19660
|
try {
|
|
19623
19661
|
const projectRoot = ctx.mainAgent?.projectRoot;
|
|
19624
19662
|
if (!projectRoot) return;
|
|
19625
|
-
const { writeFileSync:
|
|
19626
|
-
const { join:
|
|
19627
|
-
const dir =
|
|
19628
|
-
|
|
19663
|
+
const { writeFileSync: writeFileSync18, mkdirSync: mkdirSync22 } = require("fs");
|
|
19664
|
+
const { join: join49 } = require("path");
|
|
19665
|
+
const dir = join49(projectRoot, ".gossip");
|
|
19666
|
+
mkdirSync22(dir, { recursive: true });
|
|
19629
19667
|
const rounds = {};
|
|
19630
19668
|
for (const [id, round] of ctx.pendingConsensusRounds) {
|
|
19631
19669
|
rounds[id] = {
|
|
@@ -19647,7 +19685,7 @@ function persistPendingConsensus() {
|
|
|
19647
19685
|
nativePrompts: (round.nativePrompts || []).filter((p) => round.pendingNativeAgents.has(p.agentId))
|
|
19648
19686
|
};
|
|
19649
19687
|
}
|
|
19650
|
-
|
|
19688
|
+
writeFileSync18(join49(dir, CONSENSUS_FILE), JSON.stringify(rounds));
|
|
19651
19689
|
} catch (err) {
|
|
19652
19690
|
process.stderr.write(`[gossipcat] persistPendingConsensus failed: ${err.message}
|
|
19653
19691
|
`);
|
|
@@ -19655,11 +19693,11 @@ function persistPendingConsensus() {
|
|
|
19655
19693
|
}
|
|
19656
19694
|
function restorePendingConsensus(projectRoot) {
|
|
19657
19695
|
try {
|
|
19658
|
-
const { existsSync:
|
|
19659
|
-
const { join:
|
|
19660
|
-
const filePath =
|
|
19661
|
-
if (!
|
|
19662
|
-
const raw = JSON.parse(
|
|
19696
|
+
const { existsSync: existsSync44, readFileSync: readFileSync41, unlinkSync: unlinkSync4 } = require("fs");
|
|
19697
|
+
const { join: join49 } = require("path");
|
|
19698
|
+
const filePath = join49(projectRoot, ".gossip", CONSENSUS_FILE);
|
|
19699
|
+
if (!existsSync44(filePath)) return;
|
|
19700
|
+
const raw = JSON.parse(readFileSync41(filePath, "utf-8"));
|
|
19663
19701
|
const now = Date.now();
|
|
19664
19702
|
for (const [id, data] of Object.entries(raw)) {
|
|
19665
19703
|
if (now > data.deadline + 3e5) {
|
|
@@ -20419,12 +20457,12 @@ async function overviewHandler(projectRoot, ctx2) {
|
|
|
20419
20457
|
const hourlyActivity = new Array(12).fill(0);
|
|
20420
20458
|
const now = Date.now();
|
|
20421
20459
|
const hourMs = 60 * 60 * 1e3;
|
|
20422
|
-
const graphPath = (0,
|
|
20423
|
-
if ((0,
|
|
20460
|
+
const graphPath = (0, import_path37.join)(projectRoot, ".gossip", "task-graph.jsonl");
|
|
20461
|
+
if ((0, import_fs35.existsSync)(graphPath)) {
|
|
20424
20462
|
try {
|
|
20425
20463
|
const created = /* @__PURE__ */ new Map();
|
|
20426
20464
|
const finished = /* @__PURE__ */ new Set();
|
|
20427
|
-
const lines = (0,
|
|
20465
|
+
const lines = (0, import_fs35.readFileSync)(graphPath, "utf-8").trim().split("\n").filter(Boolean);
|
|
20428
20466
|
for (const line of lines) {
|
|
20429
20467
|
try {
|
|
20430
20468
|
const ev = JSON.parse(line);
|
|
@@ -20474,17 +20512,27 @@ async function overviewHandler(projectRoot, ctx2) {
|
|
|
20474
20512
|
let confirmedFindings = 0;
|
|
20475
20513
|
let lastConsensusTimestamp = "";
|
|
20476
20514
|
let actionableFindings = 0;
|
|
20477
|
-
const
|
|
20478
|
-
const perfPath = (0,
|
|
20479
|
-
if ((0,
|
|
20515
|
+
const runBuckets = /* @__PURE__ */ new Map();
|
|
20516
|
+
const perfPath = (0, import_path37.join)(projectRoot, ".gossip", "agent-performance.jsonl");
|
|
20517
|
+
if ((0, import_fs35.existsSync)(perfPath)) {
|
|
20480
20518
|
try {
|
|
20481
|
-
const lines = (0,
|
|
20519
|
+
const lines = (0, import_fs35.readFileSync)(perfPath, "utf-8").trim().split("\n").filter(Boolean);
|
|
20482
20520
|
for (const line of lines) {
|
|
20483
20521
|
try {
|
|
20484
20522
|
const entry = JSON.parse(line);
|
|
20485
|
-
|
|
20523
|
+
if (entry.type === "consensus" && typeof entry.signal === "string") {
|
|
20524
|
+
totalSignals++;
|
|
20525
|
+
}
|
|
20486
20526
|
if (entry.type === "consensus" && (entry.consensusId || entry.taskId)) {
|
|
20487
|
-
|
|
20527
|
+
const runId = entry.consensusId ?? entry.taskId;
|
|
20528
|
+
let bucket = runBuckets.get(runId);
|
|
20529
|
+
if (!bucket) {
|
|
20530
|
+
bucket = { agents: /* @__PURE__ */ new Set(), signalCount: 0 };
|
|
20531
|
+
runBuckets.set(runId, bucket);
|
|
20532
|
+
}
|
|
20533
|
+
bucket.signalCount++;
|
|
20534
|
+
if (entry.agentId) bucket.agents.add(entry.agentId);
|
|
20535
|
+
if (entry.counterpartId) bucket.agents.add(entry.counterpartId);
|
|
20488
20536
|
}
|
|
20489
20537
|
if (entry.consensusId && entry.timestamp > lastConsensusTimestamp) {
|
|
20490
20538
|
lastConsensusTimestamp = entry.timestamp;
|
|
@@ -20507,27 +20555,29 @@ async function overviewHandler(projectRoot, ctx2) {
|
|
|
20507
20555
|
} catch {
|
|
20508
20556
|
}
|
|
20509
20557
|
}
|
|
20510
|
-
|
|
20558
|
+
for (const bucket of runBuckets.values()) {
|
|
20559
|
+
if (bucket.agents.size >= 2 && bucket.signalCount >= 3) consensusRuns++;
|
|
20560
|
+
}
|
|
20511
20561
|
const avgDurationMs = durationCount > 0 ? Math.round(totalDuration / durationCount) : 0;
|
|
20512
20562
|
return { agentsOnline, relayCount, relayConnected, nativeCount, consensusRuns, totalFindings, confirmedFindings, totalSignals, tasksCompleted, tasksFailed, avgDurationMs, lastConsensusTimestamp, actionableFindings, hourlyActivity };
|
|
20513
20563
|
}
|
|
20514
|
-
var
|
|
20564
|
+
var import_fs35, import_path37;
|
|
20515
20565
|
var init_api_overview = __esm({
|
|
20516
20566
|
"packages/relay/src/dashboard/api-overview.ts"() {
|
|
20517
20567
|
"use strict";
|
|
20518
|
-
|
|
20519
|
-
|
|
20568
|
+
import_fs35 = require("fs");
|
|
20569
|
+
import_path37 = require("path");
|
|
20520
20570
|
}
|
|
20521
20571
|
});
|
|
20522
20572
|
|
|
20523
20573
|
// packages/relay/src/dashboard/api-agents.ts
|
|
20524
20574
|
function readTaskGraphByAgent(projectRoot) {
|
|
20525
|
-
const taskGraphPath = (0,
|
|
20575
|
+
const taskGraphPath = (0, import_path38.join)(projectRoot, ".gossip", "task-graph.jsonl");
|
|
20526
20576
|
const result = /* @__PURE__ */ new Map();
|
|
20527
|
-
if (!(0,
|
|
20577
|
+
if (!(0, import_fs36.existsSync)(taskGraphPath)) return result;
|
|
20528
20578
|
let lines;
|
|
20529
20579
|
try {
|
|
20530
|
-
lines = (0,
|
|
20580
|
+
lines = (0, import_fs36.readFileSync)(taskGraphPath, "utf-8").trim().split("\n").filter(Boolean);
|
|
20531
20581
|
} catch {
|
|
20532
20582
|
return result;
|
|
20533
20583
|
}
|
|
@@ -20619,19 +20669,32 @@ async function agentsHandler(projectRoot, configs, onlineAgents = []) {
|
|
|
20619
20669
|
hallucinations: score.hallucinations,
|
|
20620
20670
|
consecutiveFailures: score.consecutiveFailures ?? 0,
|
|
20621
20671
|
circuitOpen: score.circuitOpen ?? false,
|
|
20622
|
-
|
|
20672
|
+
// categoryStrengths is an UNBOUNDED severity-weighted accumulator used
|
|
20673
|
+
// for dispatch routing (severity × decay × 0.15 per confirmed signal),
|
|
20674
|
+
// not a [0,1] ratio. The dashboard must NOT render it as a percentage —
|
|
20675
|
+
// it should render categoryAccuracy (c / (c + h)) which is a real ratio.
|
|
20676
|
+
// See performance-reader.ts:357 (increment) vs :496-505 (accuracy).
|
|
20677
|
+
categoryStrengths: score.categoryStrengths ?? {},
|
|
20678
|
+
categoryAccuracy: score.categoryAccuracy ?? {},
|
|
20679
|
+
// Forward raw counts so the dashboard can render "100% (3/3)" and
|
|
20680
|
+
// distinguish a sparse-but-clean category from a high-volume clean
|
|
20681
|
+
// one. categoryAccuracy already drops categories with fewer than
|
|
20682
|
+
// MIN_CATEGORY_N signals, but the counts let the UI surface them
|
|
20683
|
+
// as dimmed "sparse" rows instead of hiding them silently.
|
|
20684
|
+
categoryCorrect: score.categoryCorrect ?? {},
|
|
20685
|
+
categoryHallucinated: score.categoryHallucinated ?? {}
|
|
20623
20686
|
}
|
|
20624
20687
|
};
|
|
20625
20688
|
});
|
|
20626
20689
|
}
|
|
20627
|
-
var
|
|
20690
|
+
var import_fs36, import_path38, DEFAULT_SCORE;
|
|
20628
20691
|
var init_api_agents = __esm({
|
|
20629
20692
|
"packages/relay/src/dashboard/api-agents.ts"() {
|
|
20630
20693
|
"use strict";
|
|
20631
20694
|
init_performance_reader();
|
|
20632
20695
|
init_skill_index();
|
|
20633
|
-
|
|
20634
|
-
|
|
20696
|
+
import_fs36 = require("fs");
|
|
20697
|
+
import_path38 = require("path");
|
|
20635
20698
|
DEFAULT_SCORE = {
|
|
20636
20699
|
agentId: "",
|
|
20637
20700
|
accuracy: 0.5,
|
|
@@ -20655,7 +20718,7 @@ var init_api_agents = __esm({
|
|
|
20655
20718
|
|
|
20656
20719
|
// packages/relay/src/dashboard/api-skills.ts
|
|
20657
20720
|
function isCorrupt(projectRoot, index) {
|
|
20658
|
-
return (0,
|
|
20721
|
+
return (0, import_fs37.existsSync)((0, import_path39.join)(projectRoot, ".gossip", "skill-index.json")) && !index.exists();
|
|
20659
20722
|
}
|
|
20660
20723
|
async function skillsGetHandler(projectRoot) {
|
|
20661
20724
|
try {
|
|
@@ -20684,13 +20747,13 @@ async function skillsBindHandler(projectRoot, body) {
|
|
|
20684
20747
|
return { success: false, error: err instanceof Error ? err.message : "Unknown error" };
|
|
20685
20748
|
}
|
|
20686
20749
|
}
|
|
20687
|
-
var
|
|
20750
|
+
var import_fs37, import_path39, AGENT_ID_RE2;
|
|
20688
20751
|
var init_api_skills = __esm({
|
|
20689
20752
|
"packages/relay/src/dashboard/api-skills.ts"() {
|
|
20690
20753
|
"use strict";
|
|
20691
20754
|
init_skill_index();
|
|
20692
|
-
|
|
20693
|
-
|
|
20755
|
+
import_fs37 = require("fs");
|
|
20756
|
+
import_path39 = require("path");
|
|
20694
20757
|
AGENT_ID_RE2 = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
20695
20758
|
}
|
|
20696
20759
|
});
|
|
@@ -20698,25 +20761,25 @@ var init_api_skills = __esm({
|
|
|
20698
20761
|
// packages/relay/src/dashboard/api-memory.ts
|
|
20699
20762
|
async function memoryHandler(projectRoot, agentId) {
|
|
20700
20763
|
if (!agentId || !AGENT_ID_RE3.test(agentId) || DANGEROUS_IDS.has(agentId)) throw new Error("Invalid agent ID");
|
|
20701
|
-
const memDir = (0,
|
|
20764
|
+
const memDir = (0, import_path40.join)(projectRoot, ".gossip", "agents", agentId, "memory");
|
|
20702
20765
|
let index = "";
|
|
20703
|
-
const indexPath = (0,
|
|
20704
|
-
if ((0,
|
|
20766
|
+
const indexPath = (0, import_path40.join)(memDir, "MEMORY.md");
|
|
20767
|
+
if ((0, import_fs38.existsSync)(indexPath)) {
|
|
20705
20768
|
try {
|
|
20706
|
-
index = (0,
|
|
20769
|
+
index = (0, import_fs38.readFileSync)(indexPath, "utf-8");
|
|
20707
20770
|
} catch {
|
|
20708
20771
|
}
|
|
20709
20772
|
}
|
|
20710
20773
|
const knowledge = [];
|
|
20711
|
-
const knowledgeDir = (0,
|
|
20774
|
+
const knowledgeDir = (0, import_path40.join)(memDir, "knowledge");
|
|
20712
20775
|
const knowledgeDirs = [knowledgeDir, memDir];
|
|
20713
20776
|
for (const dir of knowledgeDirs) {
|
|
20714
|
-
if (!(0,
|
|
20777
|
+
if (!(0, import_fs38.existsSync)(dir)) continue;
|
|
20715
20778
|
try {
|
|
20716
|
-
const files = (0,
|
|
20779
|
+
const files = (0, import_fs38.readdirSync)(dir).filter((f) => f.endsWith(".md") && f !== "MEMORY.md");
|
|
20717
20780
|
for (const filename of files) {
|
|
20718
20781
|
try {
|
|
20719
|
-
const raw = (0,
|
|
20782
|
+
const raw = (0, import_fs38.readFileSync)((0, import_path40.join)(dir, filename), "utf-8");
|
|
20720
20783
|
const { frontmatter, content } = parseFrontmatter(raw);
|
|
20721
20784
|
knowledge.push({ filename, frontmatter, content });
|
|
20722
20785
|
} catch {
|
|
@@ -20726,10 +20789,10 @@ async function memoryHandler(projectRoot, agentId) {
|
|
|
20726
20789
|
}
|
|
20727
20790
|
}
|
|
20728
20791
|
const tasks = [];
|
|
20729
|
-
const tasksPath = (0,
|
|
20730
|
-
if ((0,
|
|
20792
|
+
const tasksPath = (0, import_path40.join)(memDir, "tasks.jsonl");
|
|
20793
|
+
if ((0, import_fs38.existsSync)(tasksPath)) {
|
|
20731
20794
|
try {
|
|
20732
|
-
const lines = (0,
|
|
20795
|
+
const lines = (0, import_fs38.readFileSync)(tasksPath, "utf-8").trim().split("\n").filter(Boolean).slice(-200);
|
|
20733
20796
|
for (const line of lines) {
|
|
20734
20797
|
try {
|
|
20735
20798
|
tasks.push(JSON.parse(line));
|
|
@@ -20757,12 +20820,12 @@ function parseFrontmatter(raw) {
|
|
|
20757
20820
|
}
|
|
20758
20821
|
return { frontmatter: fm, content: raw.slice(end + 3).trim() };
|
|
20759
20822
|
}
|
|
20760
|
-
var
|
|
20823
|
+
var import_fs38, import_path40, AGENT_ID_RE3, DANGEROUS_IDS;
|
|
20761
20824
|
var init_api_memory = __esm({
|
|
20762
20825
|
"packages/relay/src/dashboard/api-memory.ts"() {
|
|
20763
20826
|
"use strict";
|
|
20764
|
-
|
|
20765
|
-
|
|
20827
|
+
import_fs38 = require("fs");
|
|
20828
|
+
import_path40 = require("path");
|
|
20766
20829
|
AGENT_ID_RE3 = /^[a-zA-Z0-9_-]{1,64}$/;
|
|
20767
20830
|
DANGEROUS_IDS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
20768
20831
|
}
|
|
@@ -20774,11 +20837,11 @@ async function consensusHandler(projectRoot, query) {
|
|
|
20774
20837
|
const rawPageSize = parseInt(query?.get("pageSize") ?? "", 10);
|
|
20775
20838
|
const page = isNaN(rawPage) || rawPage < 1 ? 1 : rawPage;
|
|
20776
20839
|
const pageSize = isNaN(rawPageSize) || rawPageSize < 1 ? DEFAULT_PAGE_SIZE : Math.min(rawPageSize, MAX_PAGE_SIZE);
|
|
20777
|
-
const perfPath = (0,
|
|
20778
|
-
if (!(0,
|
|
20840
|
+
const perfPath = (0, import_path41.join)(projectRoot, ".gossip", "agent-performance.jsonl");
|
|
20841
|
+
if (!(0, import_fs39.existsSync)(perfPath)) return { runs: [], totalRuns: 0, totalSignals: 0, page, pageSize };
|
|
20779
20842
|
const signals = [];
|
|
20780
20843
|
try {
|
|
20781
|
-
const lines = (0,
|
|
20844
|
+
const lines = (0, import_fs39.readFileSync)(perfPath, "utf-8").trim().split("\n").filter(Boolean);
|
|
20782
20845
|
for (const line of lines) {
|
|
20783
20846
|
try {
|
|
20784
20847
|
const parsed = JSON.parse(line);
|
|
@@ -20849,12 +20912,12 @@ async function consensusHandler(projectRoot, query) {
|
|
|
20849
20912
|
const paginatedRuns = runs.slice(offset, offset + pageSize);
|
|
20850
20913
|
return { runs: paginatedRuns, totalRuns, totalSignals: signals.length, page, pageSize };
|
|
20851
20914
|
}
|
|
20852
|
-
var
|
|
20915
|
+
var import_fs39, import_path41, RESOLUTION_SIGNALS, DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE;
|
|
20853
20916
|
var init_api_consensus = __esm({
|
|
20854
20917
|
"packages/relay/src/dashboard/api-consensus.ts"() {
|
|
20855
20918
|
"use strict";
|
|
20856
|
-
|
|
20857
|
-
|
|
20919
|
+
import_fs39 = require("fs");
|
|
20920
|
+
import_path41 = require("path");
|
|
20858
20921
|
RESOLUTION_SIGNALS = /* @__PURE__ */ new Set(["agreement", "unique_confirmed", "consensus_verified"]);
|
|
20859
20922
|
DEFAULT_PAGE_SIZE = 10;
|
|
20860
20923
|
MAX_PAGE_SIZE = 50;
|
|
@@ -20866,11 +20929,11 @@ async function signalsHandler(projectRoot, query) {
|
|
|
20866
20929
|
const agentFilter = query?.get("agent") ?? null;
|
|
20867
20930
|
const limit = Math.min(Math.max(parseInt(query?.get("limit") ?? "", 10) || DEFAULT_LIMIT, 1), MAX_LIMIT);
|
|
20868
20931
|
const offset = Math.max(parseInt(query?.get("offset") ?? "", 10) || 0, 0);
|
|
20869
|
-
const perfPath = (0,
|
|
20870
|
-
if (!(0,
|
|
20932
|
+
const perfPath = (0, import_path42.join)(projectRoot, ".gossip", "agent-performance.jsonl");
|
|
20933
|
+
if (!(0, import_fs40.existsSync)(perfPath)) return { items: [], total: 0, offset, limit };
|
|
20871
20934
|
const all = [];
|
|
20872
20935
|
try {
|
|
20873
|
-
const lines = (0,
|
|
20936
|
+
const lines = (0, import_fs40.readFileSync)(perfPath, "utf-8").trim().split("\n").filter(Boolean);
|
|
20874
20937
|
for (const line of lines) {
|
|
20875
20938
|
try {
|
|
20876
20939
|
const entry = JSON.parse(line);
|
|
@@ -20886,12 +20949,12 @@ async function signalsHandler(projectRoot, query) {
|
|
|
20886
20949
|
all.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
20887
20950
|
return { items: all.slice(offset, offset + limit), total: all.length, offset, limit };
|
|
20888
20951
|
}
|
|
20889
|
-
var
|
|
20952
|
+
var import_fs40, import_path42, MAX_LIMIT, DEFAULT_LIMIT;
|
|
20890
20953
|
var init_api_signals = __esm({
|
|
20891
20954
|
"packages/relay/src/dashboard/api-signals.ts"() {
|
|
20892
20955
|
"use strict";
|
|
20893
|
-
|
|
20894
|
-
|
|
20956
|
+
import_fs40 = require("fs");
|
|
20957
|
+
import_path42 = require("path");
|
|
20895
20958
|
MAX_LIMIT = 200;
|
|
20896
20959
|
DEFAULT_LIMIT = 50;
|
|
20897
20960
|
}
|
|
@@ -20899,29 +20962,29 @@ var init_api_signals = __esm({
|
|
|
20899
20962
|
|
|
20900
20963
|
// packages/relay/src/dashboard/api-learnings.ts
|
|
20901
20964
|
async function learningsHandler(projectRoot) {
|
|
20902
|
-
const agentsDir = (0,
|
|
20903
|
-
if (!(0,
|
|
20965
|
+
const agentsDir = (0, import_path43.join)(projectRoot, ".gossip", "agents");
|
|
20966
|
+
if (!(0, import_fs41.existsSync)(agentsDir)) return { learnings: [] };
|
|
20904
20967
|
const all = [];
|
|
20905
20968
|
let agentIds;
|
|
20906
20969
|
try {
|
|
20907
|
-
agentIds = (0,
|
|
20970
|
+
agentIds = (0, import_fs41.readdirSync)(agentsDir).filter((f) => !f.startsWith("."));
|
|
20908
20971
|
} catch {
|
|
20909
20972
|
return { learnings: [] };
|
|
20910
20973
|
}
|
|
20911
20974
|
for (const agentId of agentIds) {
|
|
20912
|
-
const knowledgeDir = (0,
|
|
20913
|
-
if (!(0,
|
|
20975
|
+
const knowledgeDir = (0, import_path43.join)(agentsDir, agentId, "memory", "knowledge");
|
|
20976
|
+
if (!(0, import_fs41.existsSync)(knowledgeDir)) continue;
|
|
20914
20977
|
let files;
|
|
20915
20978
|
try {
|
|
20916
|
-
files = (0,
|
|
20979
|
+
files = (0, import_fs41.readdirSync)(knowledgeDir).filter((f) => f.endsWith(".md"));
|
|
20917
20980
|
} catch {
|
|
20918
20981
|
continue;
|
|
20919
20982
|
}
|
|
20920
20983
|
for (const filename of files) {
|
|
20921
|
-
const filepath = (0,
|
|
20984
|
+
const filepath = (0, import_path43.join)(knowledgeDir, filename);
|
|
20922
20985
|
try {
|
|
20923
|
-
const stat3 = (0,
|
|
20924
|
-
const raw = (0,
|
|
20986
|
+
const stat3 = (0, import_fs41.statSync)(filepath);
|
|
20987
|
+
const raw = (0, import_fs41.readFileSync)(filepath, "utf-8");
|
|
20925
20988
|
const fm = parseFrontmatter2(raw);
|
|
20926
20989
|
all.push({
|
|
20927
20990
|
agentId,
|
|
@@ -20948,12 +21011,12 @@ function parseFrontmatter2(raw) {
|
|
|
20948
21011
|
}
|
|
20949
21012
|
return fm;
|
|
20950
21013
|
}
|
|
20951
|
-
var
|
|
21014
|
+
var import_fs41, import_path43, MAX_LEARNINGS;
|
|
20952
21015
|
var init_api_learnings = __esm({
|
|
20953
21016
|
"packages/relay/src/dashboard/api-learnings.ts"() {
|
|
20954
21017
|
"use strict";
|
|
20955
|
-
|
|
20956
|
-
|
|
21018
|
+
import_fs41 = require("fs");
|
|
21019
|
+
import_path43 = require("path");
|
|
20957
21020
|
MAX_LEARNINGS = 10;
|
|
20958
21021
|
}
|
|
20959
21022
|
});
|
|
@@ -20964,12 +21027,12 @@ async function tasksHandler(projectRoot, query) {
|
|
|
20964
21027
|
const rawOffset = parseInt(query?.get("offset") ?? "0", 10);
|
|
20965
21028
|
const limit = isNaN(rawLimit) || rawLimit < 1 ? 50 : Math.min(rawLimit, 200);
|
|
20966
21029
|
const offset = isNaN(rawOffset) || rawOffset < 0 ? 0 : rawOffset;
|
|
20967
|
-
const graphPath = (0,
|
|
20968
|
-
if (!(0,
|
|
21030
|
+
const graphPath = (0, import_path44.join)(projectRoot, ".gossip", "task-graph.jsonl");
|
|
21031
|
+
if (!(0, import_fs42.existsSync)(graphPath)) return { items: [], total: 0, offset, limit };
|
|
20969
21032
|
const created = /* @__PURE__ */ new Map();
|
|
20970
21033
|
const completed = /* @__PURE__ */ new Map();
|
|
20971
21034
|
try {
|
|
20972
|
-
const lines = (0,
|
|
21035
|
+
const lines = (0, import_fs42.readFileSync)(graphPath, "utf-8").trim().split("\n").filter(Boolean);
|
|
20973
21036
|
for (const line of lines) {
|
|
20974
21037
|
try {
|
|
20975
21038
|
const entry = JSON.parse(line);
|
|
@@ -21024,23 +21087,23 @@ async function tasksHandler(projectRoot, query) {
|
|
|
21024
21087
|
tasks.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
21025
21088
|
return { items: tasks.slice(offset, offset + limit), total: tasks.length, offset, limit };
|
|
21026
21089
|
}
|
|
21027
|
-
var
|
|
21090
|
+
var import_fs42, import_path44;
|
|
21028
21091
|
var init_api_tasks = __esm({
|
|
21029
21092
|
"packages/relay/src/dashboard/api-tasks.ts"() {
|
|
21030
21093
|
"use strict";
|
|
21031
|
-
|
|
21032
|
-
|
|
21094
|
+
import_fs42 = require("fs");
|
|
21095
|
+
import_path44 = require("path");
|
|
21033
21096
|
}
|
|
21034
21097
|
});
|
|
21035
21098
|
|
|
21036
21099
|
// packages/relay/src/dashboard/api-active-tasks.ts
|
|
21037
21100
|
async function activeTasksHandler(projectRoot) {
|
|
21038
|
-
const taskGraphPath = (0,
|
|
21039
|
-
if (!(0,
|
|
21101
|
+
const taskGraphPath = (0, import_path45.join)(projectRoot, ".gossip", "task-graph.jsonl");
|
|
21102
|
+
if (!(0, import_fs43.existsSync)(taskGraphPath)) return { tasks: [] };
|
|
21040
21103
|
const created = /* @__PURE__ */ new Map();
|
|
21041
21104
|
const finished = /* @__PURE__ */ new Set();
|
|
21042
21105
|
try {
|
|
21043
|
-
const lines = (0,
|
|
21106
|
+
const lines = (0, import_fs43.readFileSync)(taskGraphPath, "utf-8").trim().split("\n").filter(Boolean);
|
|
21044
21107
|
for (const line of lines) {
|
|
21045
21108
|
try {
|
|
21046
21109
|
const ev = JSON.parse(line);
|
|
@@ -21067,12 +21130,12 @@ async function activeTasksHandler(projectRoot) {
|
|
|
21067
21130
|
active.sort((a, b) => b.startedAt.localeCompare(a.startedAt));
|
|
21068
21131
|
return { tasks: active.slice(0, 10) };
|
|
21069
21132
|
}
|
|
21070
|
-
var
|
|
21133
|
+
var import_fs43, import_path45;
|
|
21071
21134
|
var init_api_active_tasks = __esm({
|
|
21072
21135
|
"packages/relay/src/dashboard/api-active-tasks.ts"() {
|
|
21073
21136
|
"use strict";
|
|
21074
|
-
|
|
21075
|
-
|
|
21137
|
+
import_fs43 = require("fs");
|
|
21138
|
+
import_path45 = require("path");
|
|
21076
21139
|
}
|
|
21077
21140
|
});
|
|
21078
21141
|
|
|
@@ -21085,25 +21148,25 @@ function categorize(text) {
|
|
|
21085
21148
|
return "other";
|
|
21086
21149
|
}
|
|
21087
21150
|
function logsHandler(projectRoot, query) {
|
|
21088
|
-
const logPath = (0,
|
|
21089
|
-
if (!(0,
|
|
21151
|
+
const logPath = (0, import_path46.join)(projectRoot, ".gossip", "mcp.log");
|
|
21152
|
+
if (!(0, import_fs44.existsSync)(logPath)) {
|
|
21090
21153
|
return { entries: [], totalLines: 0, fileSize: 0 };
|
|
21091
21154
|
}
|
|
21092
21155
|
const filter = query?.get("filter") || void 0;
|
|
21093
21156
|
const tail = parseInt(query?.get("tail") || "200", 10);
|
|
21094
21157
|
const clampedTail = Math.min(Math.max(tail, 10), 2e3);
|
|
21095
|
-
const fileSize = (0,
|
|
21158
|
+
const fileSize = (0, import_fs44.statSync)(logPath).size;
|
|
21096
21159
|
const MAX_READ = 512 * 1024;
|
|
21097
21160
|
const readFrom = Math.max(0, fileSize - MAX_READ);
|
|
21098
21161
|
const readLen = fileSize - readFrom;
|
|
21099
|
-
const fd = (0,
|
|
21162
|
+
const fd = (0, import_fs44.openSync)(logPath, "r");
|
|
21100
21163
|
let buf = Buffer.alloc(0);
|
|
21101
21164
|
try {
|
|
21102
21165
|
buf = Buffer.allocUnsafe(readLen);
|
|
21103
|
-
const bytesRead = (0,
|
|
21166
|
+
const bytesRead = (0, import_fs44.readSync)(fd, buf, 0, readLen, readFrom);
|
|
21104
21167
|
buf = buf.subarray(0, bytesRead);
|
|
21105
21168
|
} finally {
|
|
21106
|
-
(0,
|
|
21169
|
+
(0, import_fs44.closeSync)(fd);
|
|
21107
21170
|
}
|
|
21108
21171
|
let raw = buf.toString("utf-8");
|
|
21109
21172
|
let lineOffset = 0;
|
|
@@ -21111,13 +21174,13 @@ function logsHandler(projectRoot, query) {
|
|
|
21111
21174
|
const nl = raw.indexOf("\n");
|
|
21112
21175
|
raw = nl >= 0 ? raw.slice(nl + 1) : raw;
|
|
21113
21176
|
const SCAN_CHUNK = 64 * 1024;
|
|
21114
|
-
const scanFd = (0,
|
|
21177
|
+
const scanFd = (0, import_fs44.openSync)(logPath, "r");
|
|
21115
21178
|
try {
|
|
21116
21179
|
let pos = 0;
|
|
21117
21180
|
const chunk = Buffer.allocUnsafe(SCAN_CHUNK);
|
|
21118
21181
|
while (pos < readFrom) {
|
|
21119
21182
|
const len = Math.min(SCAN_CHUNK, readFrom - pos);
|
|
21120
|
-
const n = (0,
|
|
21183
|
+
const n = (0, import_fs44.readSync)(scanFd, chunk, 0, len, pos);
|
|
21121
21184
|
if (n === 0) break;
|
|
21122
21185
|
for (let j = 0; j < n; j++) {
|
|
21123
21186
|
if (chunk[j] === 10) lineOffset++;
|
|
@@ -21125,7 +21188,7 @@ function logsHandler(projectRoot, query) {
|
|
|
21125
21188
|
pos += n;
|
|
21126
21189
|
}
|
|
21127
21190
|
} finally {
|
|
21128
|
-
(0,
|
|
21191
|
+
(0, import_fs44.closeSync)(scanFd);
|
|
21129
21192
|
}
|
|
21130
21193
|
}
|
|
21131
21194
|
const allLines = raw.split("\n").filter(Boolean);
|
|
@@ -21143,12 +21206,12 @@ function logsHandler(projectRoot, query) {
|
|
|
21143
21206
|
entries = entries.slice(-clampedTail);
|
|
21144
21207
|
return { entries, totalLines, fileSize, filter };
|
|
21145
21208
|
}
|
|
21146
|
-
var
|
|
21209
|
+
var import_fs44, import_path46, CATEGORY_PATTERNS2;
|
|
21147
21210
|
var init_api_logs = __esm({
|
|
21148
21211
|
"packages/relay/src/dashboard/api-logs.ts"() {
|
|
21149
21212
|
"use strict";
|
|
21150
|
-
|
|
21151
|
-
|
|
21213
|
+
import_fs44 = require("fs");
|
|
21214
|
+
import_path46 = require("path");
|
|
21152
21215
|
CATEGORY_PATTERNS2 = [
|
|
21153
21216
|
[/^\[worker:/, "worker"],
|
|
21154
21217
|
[/^\[Gemini\]/, "gemini"],
|
|
@@ -21178,15 +21241,15 @@ var init_api_logs = __esm({
|
|
|
21178
21241
|
// packages/relay/src/dashboard/routes.ts
|
|
21179
21242
|
function resolveDashboardRoot(projectRoot) {
|
|
21180
21243
|
const candidates = [
|
|
21181
|
-
(0,
|
|
21244
|
+
(0, import_path47.resolve)(__dirname, "..", "dist-dashboard"),
|
|
21182
21245
|
// bundled: dist-mcp/mcp-server.js → ../dist-dashboard
|
|
21183
|
-
(0,
|
|
21246
|
+
(0, import_path47.resolve)(__dirname, "..", "..", "..", "..", "dist-dashboard"),
|
|
21184
21247
|
// tsc dev: packages/relay/dist/dashboard → repo-root
|
|
21185
|
-
(0,
|
|
21248
|
+
(0, import_path47.join)(projectRoot, "dist-dashboard")
|
|
21186
21249
|
// legacy dev fallback (git-clone running from repo root)
|
|
21187
21250
|
];
|
|
21188
21251
|
for (const p of candidates) {
|
|
21189
|
-
if ((0,
|
|
21252
|
+
if ((0, import_fs45.existsSync)(p)) return p;
|
|
21190
21253
|
}
|
|
21191
21254
|
return null;
|
|
21192
21255
|
}
|
|
@@ -21213,7 +21276,7 @@ function readBody(req) {
|
|
|
21213
21276
|
});
|
|
21214
21277
|
});
|
|
21215
21278
|
}
|
|
21216
|
-
var
|
|
21279
|
+
var import_fs45, import_path47, AUTH_MAX_ATTEMPTS, AUTH_LOCKOUT_MS, DashboardRouter, MAX_BODY_SIZE;
|
|
21217
21280
|
var init_routes = __esm({
|
|
21218
21281
|
"packages/relay/src/dashboard/routes.ts"() {
|
|
21219
21282
|
"use strict";
|
|
@@ -21227,8 +21290,8 @@ var init_routes = __esm({
|
|
|
21227
21290
|
init_api_tasks();
|
|
21228
21291
|
init_api_active_tasks();
|
|
21229
21292
|
init_api_logs();
|
|
21230
|
-
|
|
21231
|
-
|
|
21293
|
+
import_fs45 = require("fs");
|
|
21294
|
+
import_path47 = require("path");
|
|
21232
21295
|
AUTH_MAX_ATTEMPTS = 10;
|
|
21233
21296
|
AUTH_LOCKOUT_MS = 6e4;
|
|
21234
21297
|
DashboardRouter = class {
|
|
@@ -21410,13 +21473,13 @@ var init_routes = __esm({
|
|
|
21410
21473
|
res.end("Dashboard assets not found. Reinstall gossipcat or rebuild from source.");
|
|
21411
21474
|
return true;
|
|
21412
21475
|
}
|
|
21413
|
-
const htmlPath = (0,
|
|
21414
|
-
if (!(0,
|
|
21476
|
+
const htmlPath = (0, import_path47.join)(this.dashboardRoot, "index.html");
|
|
21477
|
+
if (!(0, import_fs45.existsSync)(htmlPath)) {
|
|
21415
21478
|
res.writeHead(503, { "Content-Type": "text/plain" });
|
|
21416
21479
|
res.end(`Dashboard index.html missing at ${this.dashboardRoot}. Reinstall gossipcat.`);
|
|
21417
21480
|
return true;
|
|
21418
21481
|
}
|
|
21419
|
-
const html = (0,
|
|
21482
|
+
const html = (0, import_fs45.readFileSync)(htmlPath, "utf-8");
|
|
21420
21483
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
21421
21484
|
res.end(html);
|
|
21422
21485
|
return true;
|
|
@@ -21442,16 +21505,16 @@ var init_routes = __esm({
|
|
|
21442
21505
|
const ext = "." + (relativePath.split(".").pop() || "");
|
|
21443
21506
|
const mime = MIME[ext];
|
|
21444
21507
|
if (!mime) return false;
|
|
21445
|
-
const filePath = (0,
|
|
21508
|
+
const filePath = (0, import_path47.join)(this.dashboardRoot, relativePath);
|
|
21446
21509
|
try {
|
|
21447
|
-
const realFile = (0,
|
|
21448
|
-
const realBase = (0,
|
|
21510
|
+
const realFile = (0, import_fs45.realpathSync)(filePath);
|
|
21511
|
+
const realBase = (0, import_fs45.realpathSync)(this.dashboardRoot);
|
|
21449
21512
|
if (!realFile.startsWith(realBase + "/")) {
|
|
21450
21513
|
res.writeHead(404);
|
|
21451
21514
|
res.end();
|
|
21452
21515
|
return true;
|
|
21453
21516
|
}
|
|
21454
|
-
const data = (0,
|
|
21517
|
+
const data = (0, import_fs45.readFileSync)(realFile);
|
|
21455
21518
|
res.writeHead(200, { "Content-Type": mime, "Cache-Control": "public, max-age=86400" });
|
|
21456
21519
|
res.end(data);
|
|
21457
21520
|
return true;
|
|
@@ -21466,15 +21529,15 @@ var init_routes = __esm({
|
|
|
21466
21529
|
return match ? match[1] : null;
|
|
21467
21530
|
}
|
|
21468
21531
|
getConsensusReports(page = 1, pageSize = 5) {
|
|
21469
|
-
const { readdirSync: readdirSync13, readFileSync:
|
|
21470
|
-
const reportsDir = (0,
|
|
21471
|
-
if (!
|
|
21532
|
+
const { readdirSync: readdirSync13, readFileSync: readFileSync41, existsSync: existsSync44 } = require("fs");
|
|
21533
|
+
const reportsDir = (0, import_path47.join)(this.projectRoot, ".gossip", "consensus-reports");
|
|
21534
|
+
if (!existsSync44(reportsDir)) return { reports: [], totalReports: 0, page, pageSize };
|
|
21472
21535
|
try {
|
|
21473
21536
|
const { statSync: statSync9 } = require("fs");
|
|
21474
21537
|
const allFiles = readdirSync13(reportsDir).filter((f) => f.endsWith(".json")).sort((a, b) => {
|
|
21475
21538
|
try {
|
|
21476
|
-
const aTime = statSync9((0,
|
|
21477
|
-
const bTime = statSync9((0,
|
|
21539
|
+
const aTime = statSync9((0, import_path47.join)(reportsDir, a)).mtimeMs;
|
|
21540
|
+
const bTime = statSync9((0, import_path47.join)(reportsDir, b)).mtimeMs;
|
|
21478
21541
|
return bTime - aTime;
|
|
21479
21542
|
} catch {
|
|
21480
21543
|
return 0;
|
|
@@ -21485,13 +21548,13 @@ var init_routes = __esm({
|
|
|
21485
21548
|
const clampedPage = Math.max(page, 1);
|
|
21486
21549
|
const start = (clampedPage - 1) * clampedPageSize;
|
|
21487
21550
|
const files = allFiles.slice(start, start + clampedPageSize);
|
|
21488
|
-
const realReportsDir = (0,
|
|
21551
|
+
const realReportsDir = (0, import_fs45.realpathSync)(reportsDir);
|
|
21489
21552
|
const reports = files.map((f) => {
|
|
21490
21553
|
try {
|
|
21491
|
-
const filePath = (0,
|
|
21492
|
-
const realFile = (0,
|
|
21554
|
+
const filePath = (0, import_path47.join)(reportsDir, f);
|
|
21555
|
+
const realFile = (0, import_fs45.realpathSync)(filePath);
|
|
21493
21556
|
if (!realFile.startsWith(realReportsDir + "/")) return null;
|
|
21494
|
-
return JSON.parse(
|
|
21557
|
+
return JSON.parse(readFileSync41(realFile, "utf-8"));
|
|
21495
21558
|
} catch {
|
|
21496
21559
|
return null;
|
|
21497
21560
|
}
|
|
@@ -21502,29 +21565,29 @@ var init_routes = __esm({
|
|
|
21502
21565
|
}
|
|
21503
21566
|
}
|
|
21504
21567
|
archiveFindings() {
|
|
21505
|
-
const { readdirSync: readdirSync13, readFileSync:
|
|
21506
|
-
const reportsDir = (0,
|
|
21507
|
-
const archiveDir = (0,
|
|
21568
|
+
const { readdirSync: readdirSync13, readFileSync: readFileSync41, renameSync: renameSync2, writeFileSync: writeFileSync18, mkdirSync: mkdirSync22, existsSync: existsSync44 } = require("fs");
|
|
21569
|
+
const reportsDir = (0, import_path47.join)(this.projectRoot, ".gossip", "consensus-reports");
|
|
21570
|
+
const archiveDir = (0, import_path47.join)(this.projectRoot, ".gossip", "consensus-reports-archive");
|
|
21508
21571
|
let archived = 0;
|
|
21509
|
-
if (
|
|
21572
|
+
if (existsSync44(reportsDir)) {
|
|
21510
21573
|
const files = readdirSync13(reportsDir).filter((f) => f.endsWith(".json")).sort().reverse();
|
|
21511
21574
|
if (files.length > 5) {
|
|
21512
|
-
|
|
21575
|
+
mkdirSync22(archiveDir, { recursive: true });
|
|
21513
21576
|
const toArchive = files.slice(5);
|
|
21514
21577
|
for (const f of toArchive) {
|
|
21515
21578
|
try {
|
|
21516
|
-
renameSync2((0,
|
|
21579
|
+
renameSync2((0, import_path47.join)(reportsDir, f), (0, import_path47.join)(archiveDir, f));
|
|
21517
21580
|
archived++;
|
|
21518
21581
|
} catch {
|
|
21519
21582
|
}
|
|
21520
21583
|
}
|
|
21521
21584
|
}
|
|
21522
21585
|
}
|
|
21523
|
-
const findingsPath = (0,
|
|
21586
|
+
const findingsPath = (0, import_path47.join)(this.projectRoot, ".gossip", "implementation-findings.jsonl");
|
|
21524
21587
|
let findingsCleared = 0;
|
|
21525
|
-
if (
|
|
21588
|
+
if (existsSync44(findingsPath)) {
|
|
21526
21589
|
try {
|
|
21527
|
-
const lines =
|
|
21590
|
+
const lines = readFileSync41(findingsPath, "utf-8").trim().split("\n").filter(Boolean);
|
|
21528
21591
|
const kept = lines.filter((line) => {
|
|
21529
21592
|
try {
|
|
21530
21593
|
const entry = JSON.parse(line);
|
|
@@ -21537,11 +21600,11 @@ var init_routes = __esm({
|
|
|
21537
21600
|
return true;
|
|
21538
21601
|
}
|
|
21539
21602
|
});
|
|
21540
|
-
|
|
21603
|
+
writeFileSync18(findingsPath, kept.join("\n") + (kept.length > 0 ? "\n" : ""));
|
|
21541
21604
|
} catch {
|
|
21542
21605
|
}
|
|
21543
21606
|
}
|
|
21544
|
-
const remaining =
|
|
21607
|
+
const remaining = existsSync44(reportsDir) ? readdirSync13(reportsDir).filter((f) => f.endsWith(".json")).length : 0;
|
|
21545
21608
|
return { archived, remaining, findingsCleared };
|
|
21546
21609
|
}
|
|
21547
21610
|
json(res, status, data) {
|
|
@@ -21554,14 +21617,14 @@ var init_routes = __esm({
|
|
|
21554
21617
|
});
|
|
21555
21618
|
|
|
21556
21619
|
// packages/relay/src/dashboard/ws.ts
|
|
21557
|
-
var import_ws3,
|
|
21620
|
+
var import_ws3, import_fs46, import_fs47, import_path48, DashboardWs;
|
|
21558
21621
|
var init_ws = __esm({
|
|
21559
21622
|
"packages/relay/src/dashboard/ws.ts"() {
|
|
21560
21623
|
"use strict";
|
|
21561
21624
|
import_ws3 = require("ws");
|
|
21562
|
-
import_fs45 = require("fs");
|
|
21563
21625
|
import_fs46 = require("fs");
|
|
21564
|
-
|
|
21626
|
+
import_fs47 = require("fs");
|
|
21627
|
+
import_path48 = require("path");
|
|
21565
21628
|
DashboardWs = class {
|
|
21566
21629
|
clients = /* @__PURE__ */ new Set();
|
|
21567
21630
|
logWatcher = null;
|
|
@@ -21586,15 +21649,15 @@ var init_ws = __esm({
|
|
|
21586
21649
|
/** Start watching mcp.log for new lines and broadcasting them to connected clients. */
|
|
21587
21650
|
startLogWatcher(projectRoot) {
|
|
21588
21651
|
this.stopLogWatcher();
|
|
21589
|
-
this.logPath = (0,
|
|
21590
|
-
if (!(0,
|
|
21652
|
+
this.logPath = (0, import_path48.join)(projectRoot, ".gossip", "mcp.log");
|
|
21653
|
+
if (!(0, import_fs46.existsSync)(this.logPath)) return;
|
|
21591
21654
|
try {
|
|
21592
|
-
this.logOffset = (0,
|
|
21655
|
+
this.logOffset = (0, import_fs46.statSync)(this.logPath).size;
|
|
21593
21656
|
} catch {
|
|
21594
21657
|
this.logOffset = 0;
|
|
21595
21658
|
}
|
|
21596
21659
|
try {
|
|
21597
|
-
this.logWatcher = (0,
|
|
21660
|
+
this.logWatcher = (0, import_fs47.watch)(this.logPath, () => {
|
|
21598
21661
|
if (this.clients.size === 0) return;
|
|
21599
21662
|
this.readNewLines();
|
|
21600
21663
|
});
|
|
@@ -21611,7 +21674,7 @@ var init_ws = __esm({
|
|
|
21611
21674
|
if (this.logReading) return;
|
|
21612
21675
|
this.logReading = true;
|
|
21613
21676
|
try {
|
|
21614
|
-
const currentSize = (0,
|
|
21677
|
+
const currentSize = (0, import_fs46.statSync)(this.logPath).size;
|
|
21615
21678
|
if (currentSize < this.logOffset) {
|
|
21616
21679
|
this.logOffset = 0;
|
|
21617
21680
|
this.logCarry = "";
|
|
@@ -21624,7 +21687,7 @@ var init_ws = __esm({
|
|
|
21624
21687
|
const readFrom = capped ? (this.logCarry = "", this.logCapped = true, currentSize - 65536) : (this.logCapped = false, this.logOffset);
|
|
21625
21688
|
let stream;
|
|
21626
21689
|
try {
|
|
21627
|
-
stream = (0,
|
|
21690
|
+
stream = (0, import_fs46.createReadStream)(this.logPath, { start: readFrom, end: currentSize - 1 });
|
|
21628
21691
|
} catch {
|
|
21629
21692
|
this.logReading = false;
|
|
21630
21693
|
return;
|
|
@@ -21980,18 +22043,18 @@ __export(config_exports, {
|
|
|
21980
22043
|
function findConfigPath(projectRoot) {
|
|
21981
22044
|
const root = projectRoot || process.cwd();
|
|
21982
22045
|
const candidates = [
|
|
21983
|
-
(0,
|
|
21984
|
-
(0,
|
|
21985
|
-
(0,
|
|
21986
|
-
(0,
|
|
22046
|
+
(0, import_path49.resolve)(root, ".gossip", "config.json"),
|
|
22047
|
+
(0, import_path49.resolve)(root, "gossip.agents.json"),
|
|
22048
|
+
(0, import_path49.resolve)(root, "gossip.agents.yaml"),
|
|
22049
|
+
(0, import_path49.resolve)(root, "gossip.agents.yml")
|
|
21987
22050
|
];
|
|
21988
22051
|
for (const p of candidates) {
|
|
21989
|
-
if ((0,
|
|
22052
|
+
if ((0, import_fs48.existsSync)(p)) return p;
|
|
21990
22053
|
}
|
|
21991
22054
|
return null;
|
|
21992
22055
|
}
|
|
21993
22056
|
function loadConfig(configPath) {
|
|
21994
|
-
const raw = (0,
|
|
22057
|
+
const raw = (0, import_fs48.readFileSync)(configPath, "utf-8");
|
|
21995
22058
|
let parsed;
|
|
21996
22059
|
try {
|
|
21997
22060
|
parsed = JSON.parse(raw);
|
|
@@ -22070,19 +22133,19 @@ function configToAgentConfigs(config2) {
|
|
|
22070
22133
|
}
|
|
22071
22134
|
function loadClaudeSubagents(projectRoot, existingIds) {
|
|
22072
22135
|
const root = projectRoot || process.cwd();
|
|
22073
|
-
const agentsDir = (0,
|
|
22074
|
-
if (!(0,
|
|
22136
|
+
const agentsDir = (0, import_path49.join)(root, ".claude", "agents");
|
|
22137
|
+
if (!(0, import_fs48.existsSync)(agentsDir)) return [];
|
|
22075
22138
|
let files;
|
|
22076
22139
|
try {
|
|
22077
|
-
files = (0,
|
|
22140
|
+
files = (0, import_fs48.readdirSync)(agentsDir).filter((f) => f.endsWith(".md"));
|
|
22078
22141
|
} catch {
|
|
22079
22142
|
return [];
|
|
22080
22143
|
}
|
|
22081
22144
|
const agents = [];
|
|
22082
22145
|
for (const file2 of files) {
|
|
22083
|
-
const filePath = (0,
|
|
22146
|
+
const filePath = (0, import_path49.join)(agentsDir, file2);
|
|
22084
22147
|
try {
|
|
22085
|
-
const content = (0,
|
|
22148
|
+
const content = (0, import_fs48.readFileSync)(filePath, "utf-8");
|
|
22086
22149
|
const frontmatter = content.match(/^---\n([\s\S]*?)\n---/);
|
|
22087
22150
|
if (!frontmatter) continue;
|
|
22088
22151
|
const fm = frontmatter[1];
|
|
@@ -22137,12 +22200,12 @@ function inferSkills(description, name) {
|
|
|
22137
22200
|
if (skills.length === 0) skills.push("general");
|
|
22138
22201
|
return skills;
|
|
22139
22202
|
}
|
|
22140
|
-
var
|
|
22203
|
+
var import_fs48, import_path49, VALID_PROVIDERS, CLAUDE_MODEL_MAP;
|
|
22141
22204
|
var init_config = __esm({
|
|
22142
22205
|
"apps/cli/src/config.ts"() {
|
|
22143
22206
|
"use strict";
|
|
22144
|
-
|
|
22145
|
-
|
|
22207
|
+
import_fs48 = require("fs");
|
|
22208
|
+
import_path49 = require("path");
|
|
22146
22209
|
VALID_PROVIDERS = ["anthropic", "openai", "openclaw", "google", "local", "native"];
|
|
22147
22210
|
CLAUDE_MODEL_MAP = {
|
|
22148
22211
|
opus: { provider: "anthropic", model: "claude-opus-4-6" },
|
|
@@ -22157,14 +22220,14 @@ var keychain_exports = {};
|
|
|
22157
22220
|
__export(keychain_exports, {
|
|
22158
22221
|
Keychain: () => Keychain
|
|
22159
22222
|
});
|
|
22160
|
-
var import_child_process5, import_os2,
|
|
22223
|
+
var import_child_process5, import_os2, import_fs49, import_path50, import_crypto15, SERVICE_NAME, VALID_PROVIDERS2, ENCRYPTED_FILE, ALGO, Keychain;
|
|
22161
22224
|
var init_keychain = __esm({
|
|
22162
22225
|
"apps/cli/src/keychain.ts"() {
|
|
22163
22226
|
"use strict";
|
|
22164
22227
|
import_child_process5 = require("child_process");
|
|
22165
22228
|
import_os2 = require("os");
|
|
22166
|
-
|
|
22167
|
-
|
|
22229
|
+
import_fs49 = require("fs");
|
|
22230
|
+
import_path50 = require("path");
|
|
22168
22231
|
import_crypto15 = require("crypto");
|
|
22169
22232
|
SERVICE_NAME = "gossip-mesh";
|
|
22170
22233
|
VALID_PROVIDERS2 = /^[a-zA-Z0-9_-]{1,32}$/;
|
|
@@ -22206,10 +22269,10 @@ var init_keychain = __esm({
|
|
|
22206
22269
|
return (0, import_crypto15.pbkdf2Sync)(seed, salt, 6e5, 32, "sha256");
|
|
22207
22270
|
}
|
|
22208
22271
|
loadEncryptedFile() {
|
|
22209
|
-
const filePath = (0,
|
|
22210
|
-
if (!(0,
|
|
22272
|
+
const filePath = (0, import_path50.join)(process.cwd(), ENCRYPTED_FILE);
|
|
22273
|
+
if (!(0, import_fs49.existsSync)(filePath)) return;
|
|
22211
22274
|
try {
|
|
22212
|
-
const raw = (0,
|
|
22275
|
+
const raw = (0, import_fs49.readFileSync)(filePath);
|
|
22213
22276
|
if (raw.length < 61) return;
|
|
22214
22277
|
const salt = raw.subarray(0, 32);
|
|
22215
22278
|
const iv = raw.subarray(32, 44);
|
|
@@ -22227,9 +22290,9 @@ var init_keychain = __esm({
|
|
|
22227
22290
|
}
|
|
22228
22291
|
}
|
|
22229
22292
|
saveEncryptedFile() {
|
|
22230
|
-
const filePath = (0,
|
|
22231
|
-
const dir = (0,
|
|
22232
|
-
if (!(0,
|
|
22293
|
+
const filePath = (0, import_path50.join)(process.cwd(), ENCRYPTED_FILE);
|
|
22294
|
+
const dir = (0, import_path50.join)(process.cwd(), ".gossip");
|
|
22295
|
+
if (!(0, import_fs49.existsSync)(dir)) (0, import_fs49.mkdirSync)(dir, { recursive: true });
|
|
22233
22296
|
const data = JSON.stringify(Object.fromEntries(this.inMemoryStore));
|
|
22234
22297
|
const salt = (0, import_crypto15.randomBytes)(32);
|
|
22235
22298
|
const iv = (0, import_crypto15.randomBytes)(12);
|
|
@@ -22237,7 +22300,7 @@ var init_keychain = __esm({
|
|
|
22237
22300
|
const cipher = (0, import_crypto15.createCipheriv)(ALGO, key, iv);
|
|
22238
22301
|
const encrypted = Buffer.concat([cipher.update(data, "utf8"), cipher.final()]);
|
|
22239
22302
|
const tag = cipher.getAuthTag();
|
|
22240
|
-
(0,
|
|
22303
|
+
(0, import_fs49.writeFileSync)(filePath, Buffer.concat([salt, iv, tag, encrypted]), { mode: 384 });
|
|
22241
22304
|
}
|
|
22242
22305
|
isKeychainAvailable() {
|
|
22243
22306
|
if ((0, import_os2.platform)() === "darwin") {
|
|
@@ -22337,17 +22400,17 @@ __export(identity_exports, {
|
|
|
22337
22400
|
normalizeGitUrl: () => normalizeGitUrl
|
|
22338
22401
|
});
|
|
22339
22402
|
function getOrCreateSalt(projectRoot) {
|
|
22340
|
-
const saltPath = (0,
|
|
22403
|
+
const saltPath = (0, import_path51.join)(projectRoot, ".gossip", "local-salt");
|
|
22341
22404
|
try {
|
|
22342
|
-
return (0,
|
|
22405
|
+
return (0, import_fs50.readFileSync)(saltPath, "utf-8").trim();
|
|
22343
22406
|
} catch {
|
|
22344
22407
|
const salt = (0, import_crypto16.randomBytes)(16).toString("hex");
|
|
22345
|
-
(0,
|
|
22408
|
+
(0, import_fs50.mkdirSync)((0, import_path51.join)(projectRoot, ".gossip"), { recursive: true });
|
|
22346
22409
|
try {
|
|
22347
|
-
(0,
|
|
22410
|
+
(0, import_fs50.writeFileSync)(saltPath, salt, { flag: "wx" });
|
|
22348
22411
|
return salt;
|
|
22349
22412
|
} catch {
|
|
22350
|
-
return (0,
|
|
22413
|
+
return (0, import_fs50.readFileSync)(saltPath, "utf-8").trim();
|
|
22351
22414
|
}
|
|
22352
22415
|
}
|
|
22353
22416
|
}
|
|
@@ -22397,12 +22460,12 @@ function getProjectId(projectRoot) {
|
|
|
22397
22460
|
}
|
|
22398
22461
|
return (0, import_crypto16.createHash)("sha256").update(projectRoot).digest("hex").slice(0, 16);
|
|
22399
22462
|
}
|
|
22400
|
-
var
|
|
22463
|
+
var import_fs50, import_path51, import_crypto16, import_child_process6;
|
|
22401
22464
|
var init_identity = __esm({
|
|
22402
22465
|
"apps/cli/src/identity.ts"() {
|
|
22403
22466
|
"use strict";
|
|
22404
|
-
|
|
22405
|
-
|
|
22467
|
+
import_fs50 = require("fs");
|
|
22468
|
+
import_path51 = require("path");
|
|
22406
22469
|
import_crypto16 = require("crypto");
|
|
22407
22470
|
import_child_process6 = require("child_process");
|
|
22408
22471
|
}
|
|
@@ -22424,9 +22487,9 @@ async function getLatestVersion() {
|
|
|
22424
22487
|
return data.version;
|
|
22425
22488
|
}
|
|
22426
22489
|
function detectInstallMethod() {
|
|
22427
|
-
const packageRoot = (0,
|
|
22490
|
+
const packageRoot = (0, import_path52.resolve)(__dirname, "..", "..", "..", "..");
|
|
22428
22491
|
if (process.env.npm_config_global === "true") return "global";
|
|
22429
|
-
if ((0,
|
|
22492
|
+
if ((0, import_fs51.existsSync)((0, import_path52.join)(packageRoot, ".git"))) return "git-clone";
|
|
22430
22493
|
return "local";
|
|
22431
22494
|
}
|
|
22432
22495
|
function updateCommand(method, version2) {
|
|
@@ -22477,7 +22540,7 @@ Check your internet connection or visit https://www.npmjs.com/package/gossipcat
|
|
|
22477
22540
|
try {
|
|
22478
22541
|
(0, import_child_process7.execSync)(command, {
|
|
22479
22542
|
stdio: "inherit",
|
|
22480
|
-
cwd: method === "git-clone" ? (0,
|
|
22543
|
+
cwd: method === "git-clone" ? (0, import_path52.resolve)(__dirname, "..", "..", "..", "..") : process.cwd()
|
|
22481
22544
|
});
|
|
22482
22545
|
} catch (err) {
|
|
22483
22546
|
return {
|
|
@@ -22499,13 +22562,13 @@ Run /mcp reconnect in Claude Code to load the new version.`
|
|
|
22499
22562
|
}]
|
|
22500
22563
|
};
|
|
22501
22564
|
}
|
|
22502
|
-
var import_child_process7,
|
|
22565
|
+
var import_child_process7, import_fs51, import_path52;
|
|
22503
22566
|
var init_gossip_update = __esm({
|
|
22504
22567
|
"apps/cli/src/handlers/gossip-update.ts"() {
|
|
22505
22568
|
"use strict";
|
|
22506
22569
|
import_child_process7 = require("child_process");
|
|
22507
|
-
|
|
22508
|
-
|
|
22570
|
+
import_fs51 = require("fs");
|
|
22571
|
+
import_path52 = require("path");
|
|
22509
22572
|
init_version();
|
|
22510
22573
|
}
|
|
22511
22574
|
});
|
|
@@ -22669,8 +22732,8 @@ var init_verify_memory = __esm({
|
|
|
22669
22732
|
});
|
|
22670
22733
|
|
|
22671
22734
|
// apps/cli/src/mcp-server-sdk.ts
|
|
22672
|
-
var
|
|
22673
|
-
var
|
|
22735
|
+
var import_fs52 = require("fs");
|
|
22736
|
+
var import_path53 = require("path");
|
|
22674
22737
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
22675
22738
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
22676
22739
|
var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
@@ -38030,12 +38093,83 @@ ${gaps.map((g) => ` - ${g.agentId} needs "${g.category}" (score: ${g.score.toFi
|
|
|
38030
38093
|
|
|
38031
38094
|
// apps/cli/src/mcp-server-sdk.ts
|
|
38032
38095
|
init_relay_cross_review();
|
|
38033
|
-
|
|
38096
|
+
|
|
38097
|
+
// apps/cli/src/stickyPort.ts
|
|
38098
|
+
var import_fs34 = require("fs");
|
|
38099
|
+
var import_path36 = require("path");
|
|
38100
|
+
var import_net = require("net");
|
|
38101
|
+
var RELAY_STICKY_FILE = (0, import_path36.join)(".gossip", "relay.port");
|
|
38102
|
+
var HTTP_MCP_STICKY_FILE = (0, import_path36.join)(".gossip", "http-mcp.port");
|
|
38103
|
+
function stickyPath(filename) {
|
|
38104
|
+
return (0, import_path36.join)(process.cwd(), filename);
|
|
38105
|
+
}
|
|
38106
|
+
function readStickyPort(filename) {
|
|
38107
|
+
const p = stickyPath(filename);
|
|
38108
|
+
if (!(0, import_fs34.existsSync)(p)) return null;
|
|
38109
|
+
try {
|
|
38110
|
+
const raw = (0, import_fs34.readFileSync)(p, "utf-8").trim();
|
|
38111
|
+
const n = parseInt(raw, 10);
|
|
38112
|
+
if (!Number.isFinite(n) || n < 1 || n > 65535) return null;
|
|
38113
|
+
return n;
|
|
38114
|
+
} catch {
|
|
38115
|
+
return null;
|
|
38116
|
+
}
|
|
38117
|
+
}
|
|
38118
|
+
function writeStickyPort(filename, port) {
|
|
38119
|
+
if (!Number.isFinite(port) || port < 1 || port > 65535) return;
|
|
38120
|
+
const p = stickyPath(filename);
|
|
38121
|
+
try {
|
|
38122
|
+
(0, import_fs34.mkdirSync)((0, import_path36.dirname)(p), { recursive: true });
|
|
38123
|
+
(0, import_fs34.writeFileSync)(p, String(port), "utf-8");
|
|
38124
|
+
} catch {
|
|
38125
|
+
}
|
|
38126
|
+
}
|
|
38127
|
+
function probePort(port, host = "127.0.0.1") {
|
|
38128
|
+
return new Promise((resolve18) => {
|
|
38129
|
+
const srv = (0, import_net.createServer)();
|
|
38130
|
+
let settled = false;
|
|
38131
|
+
const done = (ok) => {
|
|
38132
|
+
if (settled) return;
|
|
38133
|
+
settled = true;
|
|
38134
|
+
try {
|
|
38135
|
+
srv.close();
|
|
38136
|
+
} catch {
|
|
38137
|
+
}
|
|
38138
|
+
resolve18(ok);
|
|
38139
|
+
};
|
|
38140
|
+
srv.once("error", () => done(false));
|
|
38141
|
+
try {
|
|
38142
|
+
srv.listen(port, host, () => {
|
|
38143
|
+
srv.close(() => done(true));
|
|
38144
|
+
});
|
|
38145
|
+
} catch {
|
|
38146
|
+
done(false);
|
|
38147
|
+
}
|
|
38148
|
+
});
|
|
38149
|
+
}
|
|
38150
|
+
async function pickStickyPort(envVar, stickyFile, probeHost = "127.0.0.1") {
|
|
38151
|
+
const envRaw = process.env[envVar];
|
|
38152
|
+
if (envRaw !== void 0 && envRaw !== "") {
|
|
38153
|
+
const envPort = parseInt(envRaw, 10);
|
|
38154
|
+
if (Number.isFinite(envPort) && envPort >= 0 && envPort <= 65535) {
|
|
38155
|
+
return { port: envPort, source: "env" };
|
|
38156
|
+
}
|
|
38157
|
+
}
|
|
38158
|
+
const sticky = readStickyPort(stickyFile);
|
|
38159
|
+
if (sticky !== null) {
|
|
38160
|
+
const free = await probePort(sticky, probeHost);
|
|
38161
|
+
if (free) return { port: sticky, source: "sticky" };
|
|
38162
|
+
}
|
|
38163
|
+
return { port: 0, source: "auto" };
|
|
38164
|
+
}
|
|
38165
|
+
|
|
38166
|
+
// apps/cli/src/mcp-server-sdk.ts
|
|
38167
|
+
var gossipDir = (0, import_path53.join)(process.cwd(), ".gossip");
|
|
38034
38168
|
try {
|
|
38035
|
-
(0,
|
|
38169
|
+
(0, import_fs52.mkdirSync)(gossipDir, { recursive: true });
|
|
38036
38170
|
} catch {
|
|
38037
38171
|
}
|
|
38038
|
-
var logStream = (0,
|
|
38172
|
+
var logStream = (0, import_fs52.createWriteStream)((0, import_path53.join)(gossipDir, "mcp.log"), { flags: "a" });
|
|
38039
38173
|
process.stderr.write = ((chunk, ...args) => {
|
|
38040
38174
|
return logStream.write(chunk, ...args);
|
|
38041
38175
|
});
|
|
@@ -38199,17 +38333,18 @@ var booted = false;
|
|
|
38199
38333
|
var bootPromise = null;
|
|
38200
38334
|
var planExecutionDepth = 0;
|
|
38201
38335
|
var _pendingSessionData = /* @__PURE__ */ new Map();
|
|
38336
|
+
var _pendingSkillData = /* @__PURE__ */ new Map();
|
|
38202
38337
|
var _pendingVerifyData = /* @__PURE__ */ new Map();
|
|
38203
38338
|
var _modules = null;
|
|
38204
38339
|
function lookupFindingSeverity(findingId, projectRoot) {
|
|
38205
|
-
const { existsSync:
|
|
38206
|
-
const { join:
|
|
38207
|
-
const reportsDir =
|
|
38208
|
-
if (!
|
|
38340
|
+
const { existsSync: existsSync44, readdirSync: readdirSync13, readFileSync: readFileSync41 } = require("fs");
|
|
38341
|
+
const { join: join49 } = require("path");
|
|
38342
|
+
const reportsDir = join49(projectRoot, ".gossip", "consensus-reports");
|
|
38343
|
+
if (!existsSync44(reportsDir)) return null;
|
|
38209
38344
|
try {
|
|
38210
38345
|
const files = readdirSync13(reportsDir).filter((f) => f.endsWith(".json"));
|
|
38211
38346
|
for (const file2 of files) {
|
|
38212
|
-
const report = JSON.parse(
|
|
38347
|
+
const report = JSON.parse(readFileSync41(join49(reportsDir, file2), "utf-8"));
|
|
38213
38348
|
for (const bucket of ["confirmed", "disputed", "unverified", "unique"]) {
|
|
38214
38349
|
for (const finding of report[bucket] || []) {
|
|
38215
38350
|
if (finding.id === findingId && finding.severity) {
|
|
@@ -38286,7 +38421,7 @@ async function doBoot() {
|
|
|
38286
38421
|
const agentConfigs = m.configToAgentConfigs(config2);
|
|
38287
38422
|
ctx.keychain = new m.Keychain();
|
|
38288
38423
|
const { existsSync: pidExists, readFileSync: readPid, writeFileSync: writePid, unlinkSync: delPid } = require("fs");
|
|
38289
|
-
const pidFile = (0,
|
|
38424
|
+
const pidFile = (0, import_path53.join)(process.cwd(), ".gossip", "relay.pid");
|
|
38290
38425
|
if (pidExists(pidFile)) {
|
|
38291
38426
|
const oldPid = parseInt(readPid(pidFile, "utf-8").trim(), 10);
|
|
38292
38427
|
if (!isNaN(oldPid) && oldPid !== process.pid) {
|
|
@@ -38299,8 +38434,9 @@ async function doBoot() {
|
|
|
38299
38434
|
}
|
|
38300
38435
|
}
|
|
38301
38436
|
const relayApiKey = (0, import_crypto17.randomBytes)(32).toString("hex");
|
|
38302
|
-
const
|
|
38303
|
-
const relayPort =
|
|
38437
|
+
const relayPick = await pickStickyPort("GOSSIPCAT_PORT", RELAY_STICKY_FILE);
|
|
38438
|
+
const relayPort = relayPick.port;
|
|
38439
|
+
ctx.relayPortSource = relayPick.source;
|
|
38304
38440
|
ctx.relay = new m.RelayServer({
|
|
38305
38441
|
port: relayPort,
|
|
38306
38442
|
apiKey: relayApiKey,
|
|
@@ -38310,7 +38446,10 @@ async function doBoot() {
|
|
|
38310
38446
|
}
|
|
38311
38447
|
});
|
|
38312
38448
|
await ctx.relay.start();
|
|
38313
|
-
|
|
38449
|
+
if (typeof ctx.relay.port === "number" && ctx.relay.port > 0) {
|
|
38450
|
+
writeStickyPort(RELAY_STICKY_FILE, ctx.relay.port);
|
|
38451
|
+
}
|
|
38452
|
+
await startHttpMcpTransport();
|
|
38314
38453
|
try {
|
|
38315
38454
|
writePid(pidFile, String(process.pid));
|
|
38316
38455
|
} catch {
|
|
@@ -38376,10 +38515,10 @@ async function doBoot() {
|
|
|
38376
38515
|
}
|
|
38377
38516
|
const key = await ctx.keychain.getKey(ac.provider);
|
|
38378
38517
|
const llm = m.createProvider(ac.provider, ac.model, key ?? void 0, void 0, ac.base_url);
|
|
38379
|
-
const { existsSync:
|
|
38380
|
-
const { join:
|
|
38381
|
-
const instructionsPath =
|
|
38382
|
-
const baseInstructions =
|
|
38518
|
+
const { existsSync: existsSync44, readFileSync: readFileSync41 } = require("fs");
|
|
38519
|
+
const { join: join49 } = require("path");
|
|
38520
|
+
const instructionsPath = join49(process.cwd(), ".gossip", "agents", ac.id, "instructions.md");
|
|
38521
|
+
const baseInstructions = existsSync44(instructionsPath) ? readFileSync41(instructionsPath, "utf-8") : "";
|
|
38383
38522
|
const identity = identityRegistry.get(ac.id);
|
|
38384
38523
|
const identityBlock = identity ? m.formatIdentityBlock(identity) + "\n" : "";
|
|
38385
38524
|
const instructions = (identityBlock + baseInstructions).trim() || void 0;
|
|
@@ -38940,18 +39079,21 @@ server.tool(
|
|
|
38940
39079
|
"Status:",
|
|
38941
39080
|
` Host: ${env.host}${env.supportsNativeAgents ? " (native agents supported)" : ""}`,
|
|
38942
39081
|
` Native agent dir: ${env.nativeAgentDir || "n/a"}`,
|
|
38943
|
-
` Relay: ${ctx.relay ? `running :${ctx.relay.port}` : "not started"}`,
|
|
39082
|
+
` Relay: ${ctx.relay ? `running :${ctx.relay.port}${ctx.relayPortSource === "sticky" ? " (sticky)" : ""}` : "not started"}`,
|
|
38944
39083
|
` Tool Server: ${ctx.toolServer ? "running" : "not started"}`,
|
|
38945
39084
|
` Workers: ${ctx.workers.size} (${Array.from(ctx.workers.keys()).join(", ") || "none"})`,
|
|
38946
39085
|
` Claude subagents found: ${claudeSubagentsList.length}`
|
|
38947
39086
|
];
|
|
38948
39087
|
if (ctx.relay?.dashboardUrl) {
|
|
38949
|
-
lines.push(` Dashboard: ${ctx.relay.dashboardUrl} (key: ${ctx.relay.dashboardKey})`);
|
|
39088
|
+
lines.push(` Dashboard: ${ctx.relay.dashboardUrl}${ctx.relayPortSource === "sticky" ? " (sticky)" : ""} (key: ${ctx.relay.dashboardKey})`);
|
|
39089
|
+
}
|
|
39090
|
+
if (ctx.httpMcpPort) {
|
|
39091
|
+
lines.push(` HTTP MCP: :${ctx.httpMcpPort}/mcp${ctx.httpMcpPortSource === "sticky" ? " (sticky)" : ""}`);
|
|
38950
39092
|
}
|
|
38951
39093
|
try {
|
|
38952
|
-
const { readFileSync:
|
|
38953
|
-
const quotaPath = (0,
|
|
38954
|
-
const quotaRaw =
|
|
39094
|
+
const { readFileSync: readFileSync41 } = await import("fs");
|
|
39095
|
+
const quotaPath = (0, import_path53.join)(process.cwd(), ".gossip", "quota-state.json");
|
|
39096
|
+
const quotaRaw = readFileSync41(quotaPath, "utf8");
|
|
38955
39097
|
const quotaState = JSON.parse(quotaRaw);
|
|
38956
39098
|
for (const [provider, state] of Object.entries(quotaState)) {
|
|
38957
39099
|
const now = Date.now();
|
|
@@ -38965,16 +39107,16 @@ server.tool(
|
|
|
38965
39107
|
} catch {
|
|
38966
39108
|
}
|
|
38967
39109
|
try {
|
|
38968
|
-
const { readFileSync:
|
|
38969
|
-
const reportsDir = (0,
|
|
38970
|
-
const perfPath = (0,
|
|
39110
|
+
const { readFileSync: readFileSync41, readdirSync: readdirSync13, statSync: statSync9 } = await import("fs");
|
|
39111
|
+
const reportsDir = (0, import_path53.join)(process.cwd(), ".gossip", "consensus-reports");
|
|
39112
|
+
const perfPath = (0, import_path53.join)(process.cwd(), ".gossip", "agent-performance.jsonl");
|
|
38971
39113
|
const WINDOW_MS = 24 * 60 * 60 * 1e3;
|
|
38972
39114
|
const now = Date.now();
|
|
38973
39115
|
const recentReports = [];
|
|
38974
39116
|
try {
|
|
38975
39117
|
for (const fname of readdirSync13(reportsDir)) {
|
|
38976
39118
|
if (!fname.endsWith(".json")) continue;
|
|
38977
|
-
const fpath = (0,
|
|
39119
|
+
const fpath = (0, import_path53.join)(reportsDir, fname);
|
|
38978
39120
|
const st = statSync9(fpath);
|
|
38979
39121
|
if (now - st.mtimeMs > WINDOW_MS) continue;
|
|
38980
39122
|
recentReports.push({ id: fname.replace(/\.json$/, ""), mtimeMs: st.mtimeMs });
|
|
@@ -38984,7 +39126,7 @@ server.tool(
|
|
|
38984
39126
|
if (recentReports.length > 0) {
|
|
38985
39127
|
const covered = /* @__PURE__ */ new Set();
|
|
38986
39128
|
try {
|
|
38987
|
-
const perfRaw =
|
|
39129
|
+
const perfRaw = readFileSync41(perfPath, "utf8");
|
|
38988
39130
|
for (const line of perfRaw.split("\n")) {
|
|
38989
39131
|
if (!line) continue;
|
|
38990
39132
|
try {
|
|
@@ -39116,7 +39258,73 @@ ${toolsMatch[1].trim()}`;
|
|
|
39116
39258
|
}
|
|
39117
39259
|
} catch {
|
|
39118
39260
|
}
|
|
39119
|
-
|
|
39261
|
+
let handbookSection = "";
|
|
39262
|
+
try {
|
|
39263
|
+
const { readFileSync: rfHB, statSync: stHB } = require("fs");
|
|
39264
|
+
const { join: jHB } = require("path");
|
|
39265
|
+
const handbookPath = jHB(process.cwd(), "docs", "HANDBOOK.md");
|
|
39266
|
+
const stat3 = stHB(handbookPath);
|
|
39267
|
+
const HANDBOOK_CAP_BYTES = 24 * 1024;
|
|
39268
|
+
let body = rfHB(handbookPath, "utf-8");
|
|
39269
|
+
const truncated = body.length > HANDBOOK_CAP_BYTES;
|
|
39270
|
+
if (truncated) {
|
|
39271
|
+
body = body.slice(0, HANDBOOK_CAP_BYTES);
|
|
39272
|
+
}
|
|
39273
|
+
handbookSection = "\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n## Project Handbook (auto-loaded from docs/HANDBOOK.md)\n\n" + body.trim() + (truncated ? `
|
|
39274
|
+
|
|
39275
|
+
[handbook truncated at ${HANDBOOK_CAP_BYTES / 1024}KB \u2014 full file at docs/HANDBOOK.md, ${stat3.size} bytes total]` : "");
|
|
39276
|
+
} catch {
|
|
39277
|
+
}
|
|
39278
|
+
return { content: [{ type: "text", text: lines.join("\n") + "\n\n" + agentSections.join("\n") + sessionContextSection + handbookSection }] };
|
|
39279
|
+
}
|
|
39280
|
+
);
|
|
39281
|
+
server.tool(
|
|
39282
|
+
"gossip_guide",
|
|
39283
|
+
"Show the gossipcat handbook for humans \u2014 architectural invariants, operator playbook, caveats, hallucination patterns, glossary. Call this when you want to READ the docs, not when you need LLM context. The LLM-facing auto-load lives in gossip_status().",
|
|
39284
|
+
{},
|
|
39285
|
+
async () => {
|
|
39286
|
+
try {
|
|
39287
|
+
const { readFileSync: rfG, existsSync: exG } = require("fs");
|
|
39288
|
+
const { join: jG, dirname: dG } = require("path");
|
|
39289
|
+
const projectHandbook = jG(process.cwd(), "docs", "HANDBOOK.md");
|
|
39290
|
+
if (exG(projectHandbook)) {
|
|
39291
|
+
const body = rfG(projectHandbook, "utf-8");
|
|
39292
|
+
return {
|
|
39293
|
+
content: [{
|
|
39294
|
+
type: "text",
|
|
39295
|
+
text: `# Gossipcat Handbook (from ${projectHandbook})
|
|
39296
|
+
|
|
39297
|
+
${body}`
|
|
39298
|
+
}]
|
|
39299
|
+
};
|
|
39300
|
+
}
|
|
39301
|
+
const bundleRoot = dG(require.resolve("../../dist-mcp/mcp-server.js").replace(/\/dist-mcp\/mcp-server\.js$/, "/dist-mcp/mcp-server.js"));
|
|
39302
|
+
const defaultHandbook = jG(bundleRoot, "..", "docs", "HANDBOOK.md");
|
|
39303
|
+
if (exG(defaultHandbook)) {
|
|
39304
|
+
const body = rfG(defaultHandbook, "utf-8");
|
|
39305
|
+
return {
|
|
39306
|
+
content: [{
|
|
39307
|
+
type: "text",
|
|
39308
|
+
text: `# Gossipcat Handbook (bundled default \u2014 your project has no docs/HANDBOOK.md yet)
|
|
39309
|
+
|
|
39310
|
+
${body}`
|
|
39311
|
+
}]
|
|
39312
|
+
};
|
|
39313
|
+
}
|
|
39314
|
+
return {
|
|
39315
|
+
content: [{
|
|
39316
|
+
type: "text",
|
|
39317
|
+
text: "No handbook found. Expected at docs/HANDBOOK.md in the current project or bundled with gossipcat. If you are inside a gossipcat project, run `gossip_setup` first."
|
|
39318
|
+
}]
|
|
39319
|
+
};
|
|
39320
|
+
} catch (err) {
|
|
39321
|
+
return {
|
|
39322
|
+
content: [{
|
|
39323
|
+
type: "text",
|
|
39324
|
+
text: `Error reading handbook: ${err.message}`
|
|
39325
|
+
}]
|
|
39326
|
+
};
|
|
39327
|
+
}
|
|
39120
39328
|
}
|
|
39121
39329
|
);
|
|
39122
39330
|
server.tool(
|
|
@@ -39223,8 +39431,8 @@ server.tool(
|
|
|
39223
39431
|
}
|
|
39224
39432
|
return { content: [{ type: "text", text: results.join("\n") }] };
|
|
39225
39433
|
}
|
|
39226
|
-
const { writeFileSync:
|
|
39227
|
-
const { join:
|
|
39434
|
+
const { writeFileSync: writeFileSync18, mkdirSync: mkdirSync22, existsSync: existsSync44 } = require("fs");
|
|
39435
|
+
const { join: join49 } = require("path");
|
|
39228
39436
|
const root = process.cwd();
|
|
39229
39437
|
const CLAUDE_MODEL_MAP2 = {
|
|
39230
39438
|
opus: { provider: "anthropic", model: "claude-opus-4-6" },
|
|
@@ -39238,8 +39446,8 @@ server.tool(
|
|
|
39238
39446
|
let existingAgents = {};
|
|
39239
39447
|
if (mode === "merge") {
|
|
39240
39448
|
try {
|
|
39241
|
-
const { readFileSync:
|
|
39242
|
-
const existing = JSON.parse(
|
|
39449
|
+
const { readFileSync: readFileSync41 } = require("fs");
|
|
39450
|
+
const existing = JSON.parse(readFileSync41(join49(root, ".gossip", "config.json"), "utf-8"));
|
|
39243
39451
|
existingAgents = existing.agents || {};
|
|
39244
39452
|
} catch {
|
|
39245
39453
|
}
|
|
@@ -39271,9 +39479,9 @@ server.tool(
|
|
|
39271
39479
|
"",
|
|
39272
39480
|
body
|
|
39273
39481
|
].join("\n");
|
|
39274
|
-
const agentsDir =
|
|
39275
|
-
|
|
39276
|
-
|
|
39482
|
+
const agentsDir = join49(root, ".claude", "agents");
|
|
39483
|
+
mkdirSync22(agentsDir, { recursive: true });
|
|
39484
|
+
writeFileSync18(join49(agentsDir, `${agent.id}.md`), md, "utf-8");
|
|
39277
39485
|
nativeCreated.push(agent.id);
|
|
39278
39486
|
configAgents[agent.id] = {
|
|
39279
39487
|
provider: mapped.provider,
|
|
@@ -39291,8 +39499,8 @@ server.tool(
|
|
|
39291
39499
|
errors.push(`${agent.id}: custom agent requires "custom_model" field`);
|
|
39292
39500
|
continue;
|
|
39293
39501
|
}
|
|
39294
|
-
const nativeFile =
|
|
39295
|
-
const wasNative = existingAgents[agent.id]?.native ||
|
|
39502
|
+
const nativeFile = join49(root, ".claude", "agents", `${agent.id}.md`);
|
|
39503
|
+
const wasNative = existingAgents[agent.id]?.native || existsSync44(nativeFile);
|
|
39296
39504
|
if (wasNative) {
|
|
39297
39505
|
errors.push(`${agent.id}: cannot re-register native agent as custom \u2014 .claude/agents/${agent.id}.md exists. Remove the file first or keep it as native.`);
|
|
39298
39506
|
continue;
|
|
@@ -39306,9 +39514,9 @@ server.tool(
|
|
|
39306
39514
|
};
|
|
39307
39515
|
customCreated.push(agent.id);
|
|
39308
39516
|
if (agent.instructions) {
|
|
39309
|
-
const instrDir =
|
|
39310
|
-
|
|
39311
|
-
|
|
39517
|
+
const instrDir = join49(root, ".gossip", "agents", agent.id);
|
|
39518
|
+
mkdirSync22(instrDir, { recursive: true });
|
|
39519
|
+
writeFileSync18(join49(instrDir, "instructions.md"), agent.instructions, "utf-8");
|
|
39312
39520
|
}
|
|
39313
39521
|
}
|
|
39314
39522
|
}
|
|
@@ -39322,13 +39530,13 @@ server.tool(
|
|
|
39322
39530
|
} catch (err) {
|
|
39323
39531
|
return { content: [{ type: "text", text: `Invalid config: ${err.message}` }] };
|
|
39324
39532
|
}
|
|
39325
|
-
|
|
39326
|
-
|
|
39533
|
+
mkdirSync22(join49(root, ".gossip"), { recursive: true });
|
|
39534
|
+
writeFileSync18(join49(root, ".gossip", "config.json"), JSON.stringify(config2, null, 2));
|
|
39327
39535
|
const agentList = Object.entries(config2.agents).map(([id, a]) => `- ${id}: ${a.provider}/${a.model} (${a.preset || "custom"})${a.native ? " \u2014 native" : ""}`).join("\n");
|
|
39328
|
-
const rulesDir =
|
|
39329
|
-
const rulesFile =
|
|
39330
|
-
|
|
39331
|
-
|
|
39536
|
+
const rulesDir = join49(root, env.rulesDir);
|
|
39537
|
+
const rulesFile = join49(root, env.rulesFile);
|
|
39538
|
+
mkdirSync22(rulesDir, { recursive: true });
|
|
39539
|
+
writeFileSync18(rulesFile, generateRulesContent(agentList));
|
|
39332
39540
|
const lines = [`Host: ${env.host}`, ""];
|
|
39333
39541
|
if (nativeCreated.length > 0) {
|
|
39334
39542
|
lines.push(`Native agents created (${nativeCreated.length}):`);
|
|
@@ -40034,9 +40242,11 @@ server.tool(
|
|
|
40034
40242
|
skills: external_exports.array(external_exports.object({
|
|
40035
40243
|
name: external_exports.string().describe("Skill name (kebab-case)"),
|
|
40036
40244
|
content: external_exports.string().describe("Full .md content with frontmatter")
|
|
40037
|
-
})).optional().describe("For build: generated skill files to save. Omit for discovery mode.")
|
|
40245
|
+
})).optional().describe("For build: generated skill files to save. Omit for discovery mode."),
|
|
40246
|
+
// Internal: re-entry param for native-utility dispatch path (develop action only)
|
|
40247
|
+
_utility_task_id: external_exports.string().optional().describe("Internal: utility task ID for re-entry after native skill generation")
|
|
40038
40248
|
},
|
|
40039
|
-
async ({ action, agent_id, skill, enabled, category, skill_names, skills }) => {
|
|
40249
|
+
async ({ action, agent_id, skill, enabled, category, skill_names, skills, _utility_task_id }) => {
|
|
40040
40250
|
await boot();
|
|
40041
40251
|
if (action === "list") {
|
|
40042
40252
|
const index = ctx.mainAgent.getSkillIndex();
|
|
@@ -40092,6 +40302,98 @@ ${sections.join("\n\n")}` }] };
|
|
|
40092
40302
|
if (!ctx.skillEngine) {
|
|
40093
40303
|
return { content: [{ type: "text", text: "Skill engine not available. Check boot logs." }] };
|
|
40094
40304
|
}
|
|
40305
|
+
if (_utility_task_id) {
|
|
40306
|
+
const stashedMeta = _pendingSkillData.get(_utility_task_id);
|
|
40307
|
+
_pendingSkillData.delete(_utility_task_id);
|
|
40308
|
+
const utilityResult = ctx.nativeResultMap.get(_utility_task_id);
|
|
40309
|
+
ctx.nativeResultMap.delete(_utility_task_id);
|
|
40310
|
+
ctx.nativeTaskMap.delete(_utility_task_id);
|
|
40311
|
+
if (utilityResult?.status === "completed" && utilityResult.result && stashedMeta) {
|
|
40312
|
+
try {
|
|
40313
|
+
const result = ctx.skillEngine.saveFromRaw(agent_id, category, utilityResult.result, stashedMeta);
|
|
40314
|
+
const { normalizeSkillName: nsn } = await Promise.resolve().then(() => (init_src4(), src_exports3));
|
|
40315
|
+
const skillName = nsn(category);
|
|
40316
|
+
if (ctx.mainAgent) {
|
|
40317
|
+
const skillIndex = ctx.mainAgent.getSkillIndex();
|
|
40318
|
+
if (skillIndex) {
|
|
40319
|
+
skillIndex.bind(agent_id, skillName, { source: "auto", mode: "permanent" });
|
|
40320
|
+
}
|
|
40321
|
+
const registry2 = ctx.mainAgent.registry;
|
|
40322
|
+
const agentConfig = registry2?.get(agent_id);
|
|
40323
|
+
const normalizedCategory = nsn(category);
|
|
40324
|
+
if (agentConfig && !agentConfig.skills.includes(normalizedCategory)) {
|
|
40325
|
+
agentConfig.skills.push(normalizedCategory);
|
|
40326
|
+
}
|
|
40327
|
+
const pipeline = ctx.mainAgent.pipeline;
|
|
40328
|
+
if (pipeline?.suppressSkillGapAlert) {
|
|
40329
|
+
pipeline.suppressSkillGapAlert(agent_id, category);
|
|
40330
|
+
}
|
|
40331
|
+
}
|
|
40332
|
+
const preview = result.content.length > 1e3 ? result.content.slice(0, 1e3) + "\n\n... (truncated)" : result.content;
|
|
40333
|
+
process.stderr.write(`[gossipcat] Skill developed (native): "${skillName}" for ${agent_id} (category: ${category})
|
|
40334
|
+
`);
|
|
40335
|
+
return {
|
|
40336
|
+
content: [{ type: "text", text: `Skill generated and saved:
|
|
40337
|
+
|
|
40338
|
+
Path: ${result.path}
|
|
40339
|
+
|
|
40340
|
+
Auto-bound "${skillName}" to ${agent_id} in skill index.
|
|
40341
|
+
|
|
40342
|
+
${preview}` }]
|
|
40343
|
+
};
|
|
40344
|
+
} catch (err) {
|
|
40345
|
+
process.stderr.write(`[gossipcat] Skill develop native post-processing failed: ${err.message}
|
|
40346
|
+
`);
|
|
40347
|
+
return {
|
|
40348
|
+
content: [{ type: "text", text: `Skill generation failed: ${err.message}` }]
|
|
40349
|
+
};
|
|
40350
|
+
}
|
|
40351
|
+
} else {
|
|
40352
|
+
process.stderr.write(`[gossipcat] Skill develop utility ${_utility_task_id} failed/missing, falling back to direct LLM
|
|
40353
|
+
`);
|
|
40354
|
+
}
|
|
40355
|
+
}
|
|
40356
|
+
if (ctx.nativeUtilityConfig && !_utility_task_id) {
|
|
40357
|
+
try {
|
|
40358
|
+
const { system, user, skillName, skillPath, baseline_accuracy_correct, baseline_accuracy_hallucinated, bound_at } = await ctx.skillEngine.buildPrompt(agent_id, category);
|
|
40359
|
+
const taskId = (0, import_crypto17.randomUUID)().slice(0, 8);
|
|
40360
|
+
_pendingSkillData.set(taskId, { agentId: agent_id, category, skillName, skillPath, baseline_accuracy_correct, baseline_accuracy_hallucinated, bound_at });
|
|
40361
|
+
ctx.nativeTaskMap.set(taskId, {
|
|
40362
|
+
agentId: "_utility",
|
|
40363
|
+
task: `skill_develop:${category}`,
|
|
40364
|
+
startedAt: Date.now(),
|
|
40365
|
+
timeoutMs: 12e4,
|
|
40366
|
+
utilityType: "skill_develop"
|
|
40367
|
+
});
|
|
40368
|
+
spawnTimeoutWatcher(taskId, ctx.nativeTaskMap.get(taskId));
|
|
40369
|
+
const modelShort = ctx.nativeUtilityConfig.model;
|
|
40370
|
+
return {
|
|
40371
|
+
content: [
|
|
40372
|
+
{
|
|
40373
|
+
type: "text",
|
|
40374
|
+
text: `Skill prompt built. Dispatching native utility for generation.
|
|
40375
|
+
|
|
40376
|
+
\u26A0\uFE0F EXECUTE NOW \u2014 launch this Agent and re-call gossip_skills:
|
|
40377
|
+
|
|
40378
|
+
1. Agent(model: "${modelShort}", prompt: <AGENT_PROMPT:${taskId} below>, run_in_background: true) \u2014 pass the AGENT_PROMPT:${taskId} content item verbatim
|
|
40379
|
+
2. When agent completes \u2192 gossip_relay(task_id: "${taskId}", result: "<full agent output>")
|
|
40380
|
+
3. Then re-call: gossip_skills(action: "develop", agent_id: "${agent_id}", category: "${category}", _utility_task_id: "${taskId}")
|
|
40381
|
+
|
|
40382
|
+
Do ALL steps in order. Do not wait for user input between them.`
|
|
40383
|
+
},
|
|
40384
|
+
{ type: "text", text: `AGENT_PROMPT:${taskId} (_utility)
|
|
40385
|
+
${system}
|
|
40386
|
+
|
|
40387
|
+
---
|
|
40388
|
+
|
|
40389
|
+
${user}` }
|
|
40390
|
+
]
|
|
40391
|
+
};
|
|
40392
|
+
} catch (err) {
|
|
40393
|
+
process.stderr.write(`[gossipcat] Skill develop native prompt build failed: ${err.message}, falling back to direct LLM
|
|
40394
|
+
`);
|
|
40395
|
+
}
|
|
40396
|
+
}
|
|
40095
40397
|
try {
|
|
40096
40398
|
const result = await ctx.skillEngine.generate(agent_id, category);
|
|
40097
40399
|
const { normalizeSkillName: nsn } = await Promise.resolve().then(() => (init_src4(), src_exports3));
|
|
@@ -40135,16 +40437,16 @@ ${preview}` }]
|
|
|
40135
40437
|
const { SkillGapTracker: SkillGapTracker2, parseSkillFrontmatter: parseSkillFrontmatter2, normalizeSkillName: normalizeSkillName2 } = await Promise.resolve().then(() => (init_src4(), src_exports3));
|
|
40136
40438
|
const tracker = new SkillGapTracker2(process.cwd());
|
|
40137
40439
|
if (skills && skills.length > 0) {
|
|
40138
|
-
const { writeFileSync:
|
|
40139
|
-
const { join:
|
|
40140
|
-
const dir =
|
|
40141
|
-
|
|
40440
|
+
const { writeFileSync: writeFileSync18, mkdirSync: mkdirSync22, existsSync: existsSync44, readFileSync: readFileSync41 } = require("fs");
|
|
40441
|
+
const { join: join49 } = require("path");
|
|
40442
|
+
const dir = join49(process.cwd(), ".gossip", "skills");
|
|
40443
|
+
mkdirSync22(dir, { recursive: true });
|
|
40142
40444
|
const results = [];
|
|
40143
40445
|
for (const sk of skills) {
|
|
40144
40446
|
const name = normalizeSkillName2(sk.name);
|
|
40145
|
-
const filePath =
|
|
40146
|
-
if (
|
|
40147
|
-
const existing =
|
|
40447
|
+
const filePath = join49(dir, `${name}.md`);
|
|
40448
|
+
if (existsSync44(filePath)) {
|
|
40449
|
+
const existing = readFileSync41(filePath, "utf-8");
|
|
40148
40450
|
const fm = parseSkillFrontmatter2(existing);
|
|
40149
40451
|
if (fm) {
|
|
40150
40452
|
if (fm.generated_by === "manual") {
|
|
@@ -40161,7 +40463,7 @@ ${preview}` }]
|
|
|
40161
40463
|
}
|
|
40162
40464
|
}
|
|
40163
40465
|
}
|
|
40164
|
-
|
|
40466
|
+
writeFileSync18(filePath, sk.content);
|
|
40165
40467
|
tracker.recordResolution(name);
|
|
40166
40468
|
results.push(`Created .gossip/skills/${name}.md`);
|
|
40167
40469
|
}
|
|
@@ -40205,9 +40507,10 @@ server.tool(
|
|
|
40205
40507
|
"Save a cognitive session summary to project memory. The next session will load this context automatically on MCP connect. Call before ending your session to preserve what was learned.",
|
|
40206
40508
|
{
|
|
40207
40509
|
notes: external_exports.string().optional().describe('Optional freeform user context (e.g., "focusing on security hardening")'),
|
|
40510
|
+
force: external_exports.boolean().optional().describe("Bypass the refuse-gate for pending native tasks / consensus rounds. Audited to .gossip/forced-saves.jsonl."),
|
|
40208
40511
|
_utility_task_id: external_exports.string().optional().describe("Internal: utility task ID for re-entry after native session summary")
|
|
40209
40512
|
},
|
|
40210
|
-
async ({ notes, _utility_task_id }) => {
|
|
40513
|
+
async ({ notes, force, _utility_task_id }) => {
|
|
40211
40514
|
await boot();
|
|
40212
40515
|
if (_utility_task_id) {
|
|
40213
40516
|
const stashed = _pendingSessionData.get(_utility_task_id);
|
|
@@ -40308,6 +40611,48 @@ server.tool(
|
|
|
40308
40611
|
|
|
40309
40612
|
${summary2}` }] };
|
|
40310
40613
|
}
|
|
40614
|
+
{
|
|
40615
|
+
const pendingNative = [];
|
|
40616
|
+
for (const [taskId, info] of ctx.nativeTaskMap) {
|
|
40617
|
+
if (!ctx.nativeResultMap.has(taskId)) pendingNative.push(`${taskId} (${info.agentId})`);
|
|
40618
|
+
}
|
|
40619
|
+
const pendingConsensus = [];
|
|
40620
|
+
const now = Date.now();
|
|
40621
|
+
for (const [cid, round] of ctx.pendingConsensusRounds) {
|
|
40622
|
+
if (round.deadline && now > round.deadline) continue;
|
|
40623
|
+
if (round.pendingNativeAgents && round.pendingNativeAgents.size > 0) {
|
|
40624
|
+
pendingConsensus.push(`${cid} (${round.pendingNativeAgents.size} agents)`);
|
|
40625
|
+
}
|
|
40626
|
+
}
|
|
40627
|
+
if ((pendingNative.length > 0 || pendingConsensus.length > 0) && !force) {
|
|
40628
|
+
const lines = ["\u26D4 session_save refused \u2014 work still in flight.\n"];
|
|
40629
|
+
if (pendingNative.length > 0) {
|
|
40630
|
+
lines.push(`Pending native tasks (${pendingNative.length}):`);
|
|
40631
|
+
for (const t of pendingNative.slice(0, 10)) lines.push(` - ${t}`);
|
|
40632
|
+
lines.push("");
|
|
40633
|
+
}
|
|
40634
|
+
if (pendingConsensus.length > 0) {
|
|
40635
|
+
lines.push(`Pending consensus rounds (${pendingConsensus.length}):`);
|
|
40636
|
+
for (const c of pendingConsensus.slice(0, 10)) lines.push(` - ${c}`);
|
|
40637
|
+
lines.push("");
|
|
40638
|
+
}
|
|
40639
|
+
lines.push("Relay the results via gossip_relay / gossip_relay_cross_review, then re-call gossip_session_save.");
|
|
40640
|
+
lines.push("To override (snapshot is intentional / tasks are abandoned): gossip_session_save(force: true).");
|
|
40641
|
+
return { content: [{ type: "text", text: lines.join("\n") }], isError: true };
|
|
40642
|
+
}
|
|
40643
|
+
if (force && (pendingNative.length > 0 || pendingConsensus.length > 0)) {
|
|
40644
|
+
try {
|
|
40645
|
+
const { appendFileSync: af, mkdirSync: md } = require("fs");
|
|
40646
|
+
const { join: j } = require("path");
|
|
40647
|
+
md(j(process.cwd(), ".gossip"), { recursive: true });
|
|
40648
|
+
af(
|
|
40649
|
+
j(process.cwd(), ".gossip", "forced-saves.jsonl"),
|
|
40650
|
+
JSON.stringify({ at: (/* @__PURE__ */ new Date()).toISOString(), pendingNative, pendingConsensus, notes: notes || null }) + "\n"
|
|
40651
|
+
);
|
|
40652
|
+
} catch {
|
|
40653
|
+
}
|
|
40654
|
+
}
|
|
40655
|
+
}
|
|
40311
40656
|
let gossipText = "";
|
|
40312
40657
|
try {
|
|
40313
40658
|
const { existsSync: ex, readFileSync: rf } = require("fs");
|
|
@@ -40677,6 +41022,7 @@ server.tool(
|
|
|
40677
41022
|
{ name: "gossip_remember", desc: "Search an agent's archived knowledge files by keyword query." },
|
|
40678
41023
|
{ name: "gossip_verify_memory", desc: "On-demand staleness check for a memory file claim. Returns FRESH | STALE | CONTRADICTED | INCONCLUSIVE with file:line evidence." },
|
|
40679
41024
|
{ name: "gossip_tools", desc: "List available tools (this command)." },
|
|
41025
|
+
{ name: "gossip_guide", desc: "Show the gossipcat handbook for humans \u2014 invariants, operator playbook, caveats, hallucination patterns, glossary. Read the docs, not LLM context." },
|
|
40680
41026
|
{ name: "gossip_progress", desc: "Show active task progress and consensus phase. No params." },
|
|
40681
41027
|
{ name: "gossip_format", desc: "Return the CONSENSUS_OUTPUT_FORMAT block to paste into ad-hoc Agent() prompts so native subagents emit parseable <agent_finding> tags." },
|
|
40682
41028
|
{ name: "gossip_bug_feedback", desc: "File a GitHub issue on the gossipcat repo from an in-session bug report. Dedupes against open issues." }
|
|
@@ -40888,8 +41234,10 @@ function touchSession(sid) {
|
|
|
40888
41234
|
`);
|
|
40889
41235
|
}, HTTP_SESSION_TTL_MS);
|
|
40890
41236
|
}
|
|
40891
|
-
function startHttpMcpTransport() {
|
|
40892
|
-
const
|
|
41237
|
+
async function startHttpMcpTransport() {
|
|
41238
|
+
const httpPick = await pickStickyPort("GOSSIPCAT_HTTP_PORT", HTTP_MCP_STICKY_FILE);
|
|
41239
|
+
const port = httpPick.source === "auto" ? 24421 : httpPick.port;
|
|
41240
|
+
ctx.httpMcpPortSource = httpPick.source;
|
|
40893
41241
|
const token = process.env.GOSSIPCAT_HTTP_TOKEN ?? "";
|
|
40894
41242
|
const bindHost = process.env.GOSSIPCAT_HTTP_BIND === "0.0.0.0" && token ? "0.0.0.0" : "127.0.0.1";
|
|
40895
41243
|
const tokenBuf = token ? Buffer.from(token) : null;
|
|
@@ -40983,9 +41331,13 @@ function startHttpMcpTransport() {
|
|
|
40983
41331
|
res.end(JSON.stringify({ error: "Bad request" }));
|
|
40984
41332
|
});
|
|
40985
41333
|
httpServer.listen(port, bindHost, () => {
|
|
41334
|
+
const addr = httpServer.address();
|
|
41335
|
+
const bound = typeof addr === "object" && addr ? addr.port : port;
|
|
41336
|
+
ctx.httpMcpPort = bound;
|
|
41337
|
+
if (bound > 0) writeStickyPort(HTTP_MCP_STICKY_FILE, bound);
|
|
40986
41338
|
const authNote = token ? " (token protected)" : " (no auth \u2014 set GOSSIPCAT_HTTP_TOKEN to secure)";
|
|
40987
41339
|
const bindNote = bindHost === "0.0.0.0" ? " [remote]" : " [localhost only]";
|
|
40988
|
-
process.stderr.write(`[gossipcat] HTTP MCP listening on :${
|
|
41340
|
+
process.stderr.write(`[gossipcat] HTTP MCP listening on :${bound}/mcp${authNote}${bindNote}
|
|
40989
41341
|
`);
|
|
40990
41342
|
});
|
|
40991
41343
|
httpServer.on("error", (err) => {
|