gossipcat 0.6.4 → 0.6.6
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/README.md +240 -758
- package/dist-dashboard/assets/index-BEKs0u32.js +81 -0
- package/dist-dashboard/assets/index-CxBsFpD6.css +1 -0
- package/dist-dashboard/index.html +2 -2
- package/dist-mcp/mcp-server.js +1128 -441
- package/docs/CURSOR_RULES.md +72 -0
- package/package.json +3 -2
- package/dist-dashboard/assets/index-BRxsq5sV.js +0 -81
- package/dist-dashboard/assets/index-CSLO2_PP.css +0 -1
package/dist-mcp/mcp-server.js
CHANGED
|
@@ -34010,8 +34010,8 @@ Keep it SHORT \u2014 under 30 lines. This is a working document, not a design do
|
|
|
34010
34010
|
]);
|
|
34011
34011
|
const specContent = response.text || "";
|
|
34012
34012
|
try {
|
|
34013
|
-
const { mkdirSync:
|
|
34014
|
-
|
|
34013
|
+
const { mkdirSync: mkdirSync45, writeFileSync: writeFS } = require("fs");
|
|
34014
|
+
mkdirSync45((0, import_path35.join)(this.projectRoot, ".gossip"), { recursive: true });
|
|
34015
34015
|
writeFS(specPath, specContent, "utf-8");
|
|
34016
34016
|
} catch (err) {
|
|
34017
34017
|
return { text: `Spec generated but failed to save: ${err.message}
|
|
@@ -35274,8 +35274,8 @@ message: Your question?
|
|
|
35274
35274
|
}
|
|
35275
35275
|
/** Start all worker agents (connect to relay) */
|
|
35276
35276
|
async start() {
|
|
35277
|
-
const { existsSync: existsSync73, readFileSync:
|
|
35278
|
-
const { join:
|
|
35277
|
+
const { existsSync: existsSync73, readFileSync: readFileSync69 } = await import("fs");
|
|
35278
|
+
const { join: join91 } = await import("path");
|
|
35279
35279
|
for (const config2 of this.registry.getAll()) {
|
|
35280
35280
|
if (config2.native) continue;
|
|
35281
35281
|
if (this.workers.has(config2.id)) continue;
|
|
@@ -35293,8 +35293,8 @@ message: Your question?
|
|
|
35293
35293
|
void 0,
|
|
35294
35294
|
config2.key_ref
|
|
35295
35295
|
);
|
|
35296
|
-
const instructionsPath =
|
|
35297
|
-
const instructions = existsSync73(instructionsPath) ?
|
|
35296
|
+
const instructionsPath = join91(this.projectRoot, ".gossip", "agents", config2.id, "instructions.md");
|
|
35297
|
+
const instructions = existsSync73(instructionsPath) ? readFileSync69(instructionsPath, "utf-8") : void 0;
|
|
35298
35298
|
const enableWebSearch = config2.preset === "researcher" || config2.skills.includes("research");
|
|
35299
35299
|
const worker = new WorkerAgent(config2.id, llm, this.relayUrl, ALL_TOOLS, instructions, enableWebSearch, this.relayApiKey, config2.maxToolTurns);
|
|
35300
35300
|
await worker.start();
|
|
@@ -35491,8 +35491,8 @@ message: Your question?
|
|
|
35491
35491
|
this.registry.register(config2);
|
|
35492
35492
|
}
|
|
35493
35493
|
async syncWorkers(keyProvider) {
|
|
35494
|
-
const { existsSync: existsSync73, readFileSync:
|
|
35495
|
-
const { join:
|
|
35494
|
+
const { existsSync: existsSync73, readFileSync: readFileSync69 } = await import("fs");
|
|
35495
|
+
const { join: join91 } = await import("path");
|
|
35496
35496
|
let added = 0;
|
|
35497
35497
|
for (const ac of this.registry.getAll()) {
|
|
35498
35498
|
if (ac.native) continue;
|
|
@@ -35509,8 +35509,8 @@ message: Your question?
|
|
|
35509
35509
|
this.workers.delete(ac.id);
|
|
35510
35510
|
}
|
|
35511
35511
|
const llm = createProviderForAgent(ac.id, ac.provider, ac.model, key ?? void 0, ac.base_url, void 0, ac.key_ref);
|
|
35512
|
-
const instructionsPath =
|
|
35513
|
-
const instructions = existsSync73(instructionsPath) ?
|
|
35512
|
+
const instructionsPath = join91(this.projectRoot, ".gossip", "agents", ac.id, "instructions.md");
|
|
35513
|
+
const instructions = existsSync73(instructionsPath) ? readFileSync69(instructionsPath, "utf-8") : void 0;
|
|
35514
35514
|
const enableWebSearch = ac.preset === "researcher" || ac.skills.includes("research");
|
|
35515
35515
|
const worker = new WorkerAgent(ac.id, llm, this.relayUrl, ALL_TOOLS, instructions, enableWebSearch, this.relayApiKey, ac.maxToolTurns);
|
|
35516
35516
|
await worker.start();
|
|
@@ -44211,18 +44211,163 @@ var init_api_chat = __esm({
|
|
|
44211
44211
|
}
|
|
44212
44212
|
});
|
|
44213
44213
|
|
|
44214
|
+
// packages/relay/src/dashboard/chat-store.ts
|
|
44215
|
+
function isMirrorFrameShaped(obj) {
|
|
44216
|
+
if (!obj || typeof obj !== "object") return false;
|
|
44217
|
+
const f = obj;
|
|
44218
|
+
return f.type === "mirror" && typeof f.chat_id === "string" && typeof f.role === "string" && typeof f.text === "string" && typeof f.ts === "string" && typeof f.id === "number";
|
|
44219
|
+
}
|
|
44220
|
+
var import_fs70, import_path78, CHAT_ID_RE, PERSIST_CAP, MAX_LINE_BYTES, NullChatStore, FileChatStore;
|
|
44221
|
+
var init_chat_store = __esm({
|
|
44222
|
+
"packages/relay/src/dashboard/chat-store.ts"() {
|
|
44223
|
+
"use strict";
|
|
44224
|
+
import_fs70 = require("fs");
|
|
44225
|
+
import_path78 = require("path");
|
|
44226
|
+
CHAT_ID_RE = /^[a-zA-Z0-9_-]{1,128}$/;
|
|
44227
|
+
PERSIST_CAP = 1e3;
|
|
44228
|
+
MAX_LINE_BYTES = 256 * 1024;
|
|
44229
|
+
NullChatStore = class {
|
|
44230
|
+
append(_chatId, _frame) {
|
|
44231
|
+
}
|
|
44232
|
+
load(_chatId, _max) {
|
|
44233
|
+
return [];
|
|
44234
|
+
}
|
|
44235
|
+
drop(_chatId) {
|
|
44236
|
+
}
|
|
44237
|
+
dispose() {
|
|
44238
|
+
}
|
|
44239
|
+
};
|
|
44240
|
+
FileChatStore = class {
|
|
44241
|
+
constructor(chatDir) {
|
|
44242
|
+
this.chatDir = chatDir;
|
|
44243
|
+
}
|
|
44244
|
+
chatDir;
|
|
44245
|
+
/**
|
|
44246
|
+
* Per-chat append counter. Used to gate maybeRetain() calls so we do not
|
|
44247
|
+
* read the file on every append (O(1) amortized instead of O(N)):
|
|
44248
|
+
* - First append for a chatId in this process: one-time retention check
|
|
44249
|
+
* (bounds a large pre-existing file from a prior run).
|
|
44250
|
+
* - Subsequent appends: only call maybeRetain every PERSIST_CAP writes.
|
|
44251
|
+
*/
|
|
44252
|
+
appendCounters = /* @__PURE__ */ new Map();
|
|
44253
|
+
isValidChatId(chatId) {
|
|
44254
|
+
return CHAT_ID_RE.test(chatId);
|
|
44255
|
+
}
|
|
44256
|
+
filePath(chatId) {
|
|
44257
|
+
return (0, import_path78.join)(this.chatDir, `${chatId}.jsonl`);
|
|
44258
|
+
}
|
|
44259
|
+
append(chatId, frame) {
|
|
44260
|
+
if (!this.isValidChatId(chatId)) return;
|
|
44261
|
+
if (chatId.startsWith("_")) return;
|
|
44262
|
+
try {
|
|
44263
|
+
(0, import_fs70.mkdirSync)(this.chatDir, { recursive: true });
|
|
44264
|
+
const line = JSON.stringify(frame) + "\n";
|
|
44265
|
+
(0, import_fs70.appendFileSync)(this.filePath(chatId), line, "utf8");
|
|
44266
|
+
const prev = this.appendCounters.get(chatId);
|
|
44267
|
+
const count = (prev ?? 0) + 1;
|
|
44268
|
+
this.appendCounters.set(chatId, count);
|
|
44269
|
+
const isFirst = prev === void 0;
|
|
44270
|
+
if (isFirst || count % PERSIST_CAP === 0) {
|
|
44271
|
+
this.maybeRetain(chatId);
|
|
44272
|
+
}
|
|
44273
|
+
} catch (err) {
|
|
44274
|
+
console.warn(`[chat-store] append failed for chatId=${chatId}:`, err);
|
|
44275
|
+
}
|
|
44276
|
+
}
|
|
44277
|
+
maybeRetain(chatId) {
|
|
44278
|
+
if (chatId.startsWith("_")) return;
|
|
44279
|
+
try {
|
|
44280
|
+
const fp = this.filePath(chatId);
|
|
44281
|
+
const content = (0, import_fs70.readFileSync)(fp, "utf8");
|
|
44282
|
+
const lines = content.split("\n").filter((l) => l.length > 0);
|
|
44283
|
+
if (lines.length < 2 * PERSIST_CAP) return;
|
|
44284
|
+
const kept = lines.slice(lines.length - PERSIST_CAP);
|
|
44285
|
+
const tmp = fp + ".tmp";
|
|
44286
|
+
(0, import_fs70.writeFileSync)(tmp, kept.join("\n") + "\n", "utf8");
|
|
44287
|
+
(0, import_fs70.renameSync)(tmp, fp);
|
|
44288
|
+
} catch (err) {
|
|
44289
|
+
this.cleanupStaleTmp(chatId);
|
|
44290
|
+
console.warn(`[chat-store] retain failed for chatId=${chatId}`, err);
|
|
44291
|
+
}
|
|
44292
|
+
}
|
|
44293
|
+
/**
|
|
44294
|
+
* Best-effort removal of a stale `<chatId>.jsonl.tmp` left by a crash between
|
|
44295
|
+
* maybeRetain's writeFileSync and renameSync (the live .jsonl is always
|
|
44296
|
+
* intact; only the tmp orphans). Called on cold load() so orphans from a
|
|
44297
|
+
* prior process run are reclaimed when the chat is next hydrated, and from
|
|
44298
|
+
* maybeRetain's catch. ENOENT (the normal case) is silent.
|
|
44299
|
+
*/
|
|
44300
|
+
cleanupStaleTmp(chatId) {
|
|
44301
|
+
try {
|
|
44302
|
+
(0, import_fs70.unlinkSync)(this.filePath(chatId) + ".tmp");
|
|
44303
|
+
} catch (err) {
|
|
44304
|
+
if (err?.code !== "ENOENT") {
|
|
44305
|
+
console.warn(`[chat-store] stale .tmp cleanup failed for chatId=${chatId}:`, err);
|
|
44306
|
+
}
|
|
44307
|
+
}
|
|
44308
|
+
}
|
|
44309
|
+
load(chatId, max) {
|
|
44310
|
+
if (!this.isValidChatId(chatId)) return [];
|
|
44311
|
+
this.cleanupStaleTmp(chatId);
|
|
44312
|
+
let content;
|
|
44313
|
+
try {
|
|
44314
|
+
content = (0, import_fs70.readFileSync)(this.filePath(chatId), "utf8");
|
|
44315
|
+
} catch (err) {
|
|
44316
|
+
if (err?.code === "ENOENT") return [];
|
|
44317
|
+
console.warn(`[chat-store] load failed for chatId=${chatId}:`, err);
|
|
44318
|
+
return [];
|
|
44319
|
+
}
|
|
44320
|
+
const lines = content.split("\n").filter((l) => l.length > 0);
|
|
44321
|
+
const frames = [];
|
|
44322
|
+
for (const line of lines) {
|
|
44323
|
+
if (line.length > MAX_LINE_BYTES) {
|
|
44324
|
+
console.warn(`[chat-store] skipping oversized line (${line.length} bytes) for chatId=${chatId}`);
|
|
44325
|
+
continue;
|
|
44326
|
+
}
|
|
44327
|
+
try {
|
|
44328
|
+
const parsed = JSON.parse(line);
|
|
44329
|
+
if (isMirrorFrameShaped(parsed)) {
|
|
44330
|
+
frames.push(parsed);
|
|
44331
|
+
} else {
|
|
44332
|
+
console.warn(`[chat-store] skipping line with valid JSON but wrong MirrorFrame shape for chatId=${chatId}`);
|
|
44333
|
+
}
|
|
44334
|
+
} catch {
|
|
44335
|
+
console.warn(`[chat-store] skipping unparseable line for chatId=${chatId}`);
|
|
44336
|
+
}
|
|
44337
|
+
}
|
|
44338
|
+
if (frames.length <= max) return frames;
|
|
44339
|
+
return frames.slice(frames.length - max);
|
|
44340
|
+
}
|
|
44341
|
+
drop(chatId) {
|
|
44342
|
+
if (!this.isValidChatId(chatId)) return;
|
|
44343
|
+
try {
|
|
44344
|
+
(0, import_fs70.unlinkSync)(this.filePath(chatId));
|
|
44345
|
+
} catch (err) {
|
|
44346
|
+
if (err?.code !== "ENOENT") {
|
|
44347
|
+
console.warn(`[chat-store] drop failed for chatId=${chatId}:`, err);
|
|
44348
|
+
}
|
|
44349
|
+
}
|
|
44350
|
+
}
|
|
44351
|
+
dispose() {
|
|
44352
|
+
}
|
|
44353
|
+
};
|
|
44354
|
+
}
|
|
44355
|
+
});
|
|
44356
|
+
|
|
44214
44357
|
// packages/relay/src/dashboard/api-mirror-events.ts
|
|
44215
44358
|
var MIRROR_RING_MAX, MIRROR_RING_TTL_MS, MIRROR_SWEEP_INTERVAL_MS, MirrorEventStore;
|
|
44216
44359
|
var init_api_mirror_events = __esm({
|
|
44217
44360
|
"packages/relay/src/dashboard/api-mirror-events.ts"() {
|
|
44218
44361
|
"use strict";
|
|
44362
|
+
init_chat_store();
|
|
44219
44363
|
MIRROR_RING_MAX = 100;
|
|
44220
44364
|
MIRROR_RING_TTL_MS = 2 * 60 * 60 * 1e3;
|
|
44221
44365
|
MIRROR_SWEEP_INTERVAL_MS = 5 * 60 * 1e3;
|
|
44222
44366
|
MirrorEventStore = class {
|
|
44223
|
-
constructor(ringMax = MIRROR_RING_MAX, ttlMs = MIRROR_RING_TTL_MS, sweepIntervalMs = MIRROR_SWEEP_INTERVAL_MS) {
|
|
44367
|
+
constructor(ringMax = MIRROR_RING_MAX, ttlMs = MIRROR_RING_TTL_MS, sweepIntervalMs = MIRROR_SWEEP_INTERVAL_MS, store = new NullChatStore()) {
|
|
44224
44368
|
this.ringMax = ringMax;
|
|
44225
44369
|
this.ttlMs = ttlMs;
|
|
44370
|
+
this.store = store;
|
|
44226
44371
|
if (sweepIntervalMs > 0) {
|
|
44227
44372
|
this.sweepTimer = setInterval(() => this.sweep(Date.now()), sweepIntervalMs);
|
|
44228
44373
|
this.sweepTimer.unref?.();
|
|
@@ -44232,18 +44377,44 @@ var init_api_mirror_events = __esm({
|
|
|
44232
44377
|
ttlMs;
|
|
44233
44378
|
rings = /* @__PURE__ */ new Map();
|
|
44234
44379
|
sweepTimer = null;
|
|
44380
|
+
store;
|
|
44235
44381
|
/**
|
|
44236
|
-
* Allocate
|
|
44237
|
-
*
|
|
44238
|
-
*
|
|
44239
|
-
*
|
|
44382
|
+
* Allocate or hydrate a ring for a chat_id. On first access when the ring is
|
|
44383
|
+
* not in memory, loads persisted frames from the ChatStore so post-restart
|
|
44384
|
+
* reads see durable history and the id sequence continues (preventing false
|
|
44385
|
+
* restart-sentinels). Returns null when neither an in-memory ring exists NOR
|
|
44386
|
+
* any persisted frames were found — callers treat null as "no data."
|
|
44387
|
+
*
|
|
44388
|
+
* Renamed from `ensureRing` to `getOrHydrate` to reflect the new semantics.
|
|
44389
|
+
* push() uses a variant that always creates the ring (even if empty), since a
|
|
44390
|
+
* push always establishes a new ring. replaySlice/highestId use this
|
|
44391
|
+
* conditional form so they don't pollute ringCount() with empty rings on miss.
|
|
44392
|
+
*/
|
|
44393
|
+
getOrHydrate(chatId, now) {
|
|
44394
|
+
const existing = this.rings.get(chatId);
|
|
44395
|
+
if (existing) return existing;
|
|
44396
|
+
const persisted = this.store.load(chatId, this.ringMax);
|
|
44397
|
+
if (persisted.length === 0) return null;
|
|
44398
|
+
const ring2 = {
|
|
44399
|
+
frames: persisted.slice(),
|
|
44400
|
+
// Continue the id sequence from the highest persisted id so push() does
|
|
44401
|
+
// not reset to 1 after a restart (preventing the false restart-sentinel).
|
|
44402
|
+
nextId: persisted[persisted.length - 1].id,
|
|
44403
|
+
touchedAt: now
|
|
44404
|
+
};
|
|
44405
|
+
this.rings.set(chatId, ring2);
|
|
44406
|
+
return ring2;
|
|
44407
|
+
}
|
|
44408
|
+
/**
|
|
44409
|
+
* Ensure a ring exists for a chat_id, creating it from persisted data or
|
|
44410
|
+
* fresh. Always returns a ring (never null). Used only by push() since a
|
|
44411
|
+
* push always establishes a ring.
|
|
44240
44412
|
*/
|
|
44241
44413
|
ensureRing(chatId, now) {
|
|
44242
|
-
|
|
44243
|
-
if (
|
|
44244
|
-
|
|
44245
|
-
|
|
44246
|
-
}
|
|
44414
|
+
const hydrated = this.getOrHydrate(chatId, now);
|
|
44415
|
+
if (hydrated) return hydrated;
|
|
44416
|
+
const ring2 = { frames: [], nextId: 0, touchedAt: now };
|
|
44417
|
+
this.rings.set(chatId, ring2);
|
|
44247
44418
|
return ring2;
|
|
44248
44419
|
}
|
|
44249
44420
|
/**
|
|
@@ -44266,6 +44437,7 @@ var init_api_mirror_events = __esm({
|
|
|
44266
44437
|
ring2.frames.push(frame);
|
|
44267
44438
|
while (ring2.frames.length > this.ringMax) ring2.frames.shift();
|
|
44268
44439
|
ring2.touchedAt = now;
|
|
44440
|
+
this.store.append(chatId, frame);
|
|
44269
44441
|
return frame;
|
|
44270
44442
|
}
|
|
44271
44443
|
/**
|
|
@@ -44275,7 +44447,7 @@ var init_api_mirror_events = __esm({
|
|
|
44275
44447
|
* actively-observed stream isn't swept out from under a reconnecting client.
|
|
44276
44448
|
*/
|
|
44277
44449
|
replaySlice(chatId, lastId, now = Date.now()) {
|
|
44278
|
-
const ring2 = this.
|
|
44450
|
+
const ring2 = this.getOrHydrate(chatId, now);
|
|
44279
44451
|
if (!ring2) return [];
|
|
44280
44452
|
ring2.touchedAt = now;
|
|
44281
44453
|
if (lastId <= 0) return ring2.frames.slice();
|
|
@@ -44307,6 +44479,7 @@ var init_api_mirror_events = __esm({
|
|
|
44307
44479
|
}
|
|
44308
44480
|
const carried = src.frames.slice();
|
|
44309
44481
|
this.rings.delete(fromChatId);
|
|
44482
|
+
this.store.drop(fromChatId);
|
|
44310
44483
|
const transferred = [];
|
|
44311
44484
|
for (const f of carried) {
|
|
44312
44485
|
transferred.push(this.push(toChatId, f.role, f.text, now));
|
|
@@ -44321,7 +44494,7 @@ var init_api_mirror_events = __esm({
|
|
|
44321
44494
|
* sentinel in api-bridge.handleStream).
|
|
44322
44495
|
*/
|
|
44323
44496
|
highestId(chatId) {
|
|
44324
|
-
const ring2 = this.
|
|
44497
|
+
const ring2 = this.getOrHydrate(chatId, Date.now());
|
|
44325
44498
|
if (!ring2 || ring2.frames.length === 0) return 0;
|
|
44326
44499
|
return ring2.frames[ring2.frames.length - 1].id;
|
|
44327
44500
|
}
|
|
@@ -44350,25 +44523,110 @@ var init_api_mirror_events = __esm({
|
|
|
44350
44523
|
});
|
|
44351
44524
|
|
|
44352
44525
|
// packages/relay/src/dashboard/api-bridge.ts
|
|
44526
|
+
function validateAskQuestions(raw) {
|
|
44527
|
+
if (!Array.isArray(raw) || raw.length === 0) {
|
|
44528
|
+
return { error: "questions must be a non-empty array" };
|
|
44529
|
+
}
|
|
44530
|
+
if (raw.length > ASK_MAX_QUESTIONS) {
|
|
44531
|
+
return { error: `too many questions (max ${ASK_MAX_QUESTIONS})` };
|
|
44532
|
+
}
|
|
44533
|
+
const out = [];
|
|
44534
|
+
for (let i = 0; i < raw.length; i++) {
|
|
44535
|
+
const q = raw[i];
|
|
44536
|
+
if (!q || typeof q !== "object") {
|
|
44537
|
+
return { error: `question ${i} must be an object` };
|
|
44538
|
+
}
|
|
44539
|
+
if (typeof q.header !== "string" || q.header.trim().length === 0) {
|
|
44540
|
+
return { error: `question ${i} header must be a non-empty string` };
|
|
44541
|
+
}
|
|
44542
|
+
if (q.header.length > ASK_MAX_HEADER) {
|
|
44543
|
+
return { error: `question ${i} header too long (max ${ASK_MAX_HEADER})` };
|
|
44544
|
+
}
|
|
44545
|
+
if (typeof q.question !== "string" || q.question.trim().length === 0) {
|
|
44546
|
+
return { error: `question ${i} question must be a non-empty string` };
|
|
44547
|
+
}
|
|
44548
|
+
if (q.question.length > ASK_MAX_QUESTION) {
|
|
44549
|
+
return { error: `question ${i} question too long (max ${ASK_MAX_QUESTION})` };
|
|
44550
|
+
}
|
|
44551
|
+
if (q.multiSelect !== void 0 && typeof q.multiSelect !== "boolean") {
|
|
44552
|
+
return { error: `question ${i} multiSelect must be a boolean` };
|
|
44553
|
+
}
|
|
44554
|
+
if (q.allowOther !== void 0 && typeof q.allowOther !== "boolean") {
|
|
44555
|
+
return { error: `question ${i} allowOther must be a boolean` };
|
|
44556
|
+
}
|
|
44557
|
+
if (!Array.isArray(q.options) || q.options.length === 0) {
|
|
44558
|
+
return { error: `question ${i} options must be a non-empty array` };
|
|
44559
|
+
}
|
|
44560
|
+
if (q.options.length > ASK_MAX_OPTIONS) {
|
|
44561
|
+
return { error: `question ${i} has too many options (max ${ASK_MAX_OPTIONS})` };
|
|
44562
|
+
}
|
|
44563
|
+
const options = [];
|
|
44564
|
+
const labels = /* @__PURE__ */ new Set();
|
|
44565
|
+
for (let j = 0; j < q.options.length; j++) {
|
|
44566
|
+
const o = q.options[j];
|
|
44567
|
+
if (!o || typeof o !== "object") {
|
|
44568
|
+
return { error: `question ${i} option ${j} must be an object` };
|
|
44569
|
+
}
|
|
44570
|
+
if (typeof o.label !== "string" || o.label.trim().length === 0) {
|
|
44571
|
+
return { error: `question ${i} option ${j} label must be a non-empty string` };
|
|
44572
|
+
}
|
|
44573
|
+
if (o.label.length > ASK_MAX_LABEL) {
|
|
44574
|
+
return { error: `question ${i} option ${j} label too long (max ${ASK_MAX_LABEL})` };
|
|
44575
|
+
}
|
|
44576
|
+
if (labels.has(o.label)) {
|
|
44577
|
+
return { error: `question ${i} has a duplicate option label` };
|
|
44578
|
+
}
|
|
44579
|
+
labels.add(o.label);
|
|
44580
|
+
const opt = { label: o.label };
|
|
44581
|
+
if (o.description !== void 0 && o.description !== null) {
|
|
44582
|
+
if (typeof o.description !== "string") {
|
|
44583
|
+
return { error: `question ${i} option ${j} description must be a string` };
|
|
44584
|
+
}
|
|
44585
|
+
if (o.description.length > ASK_MAX_DESCRIPTION) {
|
|
44586
|
+
return { error: `question ${i} option ${j} description too long (max ${ASK_MAX_DESCRIPTION})` };
|
|
44587
|
+
}
|
|
44588
|
+
opt.description = o.description;
|
|
44589
|
+
}
|
|
44590
|
+
options.push(opt);
|
|
44591
|
+
}
|
|
44592
|
+
out.push({
|
|
44593
|
+
questionId: `q${i}`,
|
|
44594
|
+
header: q.header,
|
|
44595
|
+
question: q.question,
|
|
44596
|
+
...q.multiSelect === true ? { multiSelect: true } : {},
|
|
44597
|
+
options,
|
|
44598
|
+
...q.allowOther === true ? { allowOther: true } : {}
|
|
44599
|
+
});
|
|
44600
|
+
}
|
|
44601
|
+
return { questions: out };
|
|
44602
|
+
}
|
|
44353
44603
|
function isMirrorRole(v) {
|
|
44354
44604
|
return typeof v === "string" && MIRROR_ROLES.includes(v);
|
|
44355
44605
|
}
|
|
44356
44606
|
function validateChatId(raw) {
|
|
44357
44607
|
if (typeof raw !== "string") return null;
|
|
44358
44608
|
const v = raw.trim();
|
|
44359
|
-
if (!
|
|
44609
|
+
if (!CHAT_ID_RE2.test(v)) return null;
|
|
44360
44610
|
return v;
|
|
44361
44611
|
}
|
|
44362
|
-
var import_crypto26, MIRROR_ROLES, MIRROR_MAX_TEXT, MIRROR_MAX_FRAMES,
|
|
44612
|
+
var import_crypto26, ASK_MAX_QUESTIONS, ASK_MAX_OPTIONS, ASK_MAX_HEADER, ASK_MAX_QUESTION, ASK_MAX_LABEL, ASK_MAX_DESCRIPTION, ASK_MAX_OTHER, MIRROR_ROLES, MIRROR_MAX_TEXT, MIRROR_MAX_FRAMES, CHAT_ID_RE2, MAX_MESSAGE_LENGTH, MAX_BRIDGE_CLIENTS, KEEPALIVE_MS2, BACKPRESSURE_EVICT_THRESHOLD2, BridgeHub;
|
|
44363
44613
|
var init_api_bridge = __esm({
|
|
44364
44614
|
"packages/relay/src/dashboard/api-bridge.ts"() {
|
|
44365
44615
|
"use strict";
|
|
44366
44616
|
import_crypto26 = require("crypto");
|
|
44367
44617
|
init_api_mirror_events();
|
|
44618
|
+
init_chat_store();
|
|
44619
|
+
ASK_MAX_QUESTIONS = 4;
|
|
44620
|
+
ASK_MAX_OPTIONS = 8;
|
|
44621
|
+
ASK_MAX_HEADER = 120;
|
|
44622
|
+
ASK_MAX_QUESTION = 600;
|
|
44623
|
+
ASK_MAX_LABEL = 120;
|
|
44624
|
+
ASK_MAX_DESCRIPTION = 240;
|
|
44625
|
+
ASK_MAX_OTHER = 400;
|
|
44368
44626
|
MIRROR_ROLES = ["user", "assistant", "activity"];
|
|
44369
44627
|
MIRROR_MAX_TEXT = 2 * 1024;
|
|
44370
44628
|
MIRROR_MAX_FRAMES = 64;
|
|
44371
|
-
|
|
44629
|
+
CHAT_ID_RE2 = /^[a-zA-Z0-9_-]{1,128}$/;
|
|
44372
44630
|
MAX_MESSAGE_LENGTH = 32 * 1024;
|
|
44373
44631
|
MAX_BRIDGE_CLIENTS = 20;
|
|
44374
44632
|
KEEPALIVE_MS2 = 25e3;
|
|
@@ -44381,6 +44639,13 @@ var init_api_bridge = __esm({
|
|
|
44381
44639
|
// eviction can clearInterval too — the req 'close' handler may never fire on
|
|
44382
44640
|
// an abrupt res.destroy(), which would otherwise leak the timer.
|
|
44383
44641
|
keepalives = /* @__PURE__ */ new Map();
|
|
44642
|
+
// Per-client subscribed chat_id (null = no ?chat_id supplied = live-only legacy
|
|
44643
|
+
// consumer). Set when the client is added to this.clients in handleStream.
|
|
44644
|
+
// Server-side filtering in broadcast() uses this: each frame is only delivered
|
|
44645
|
+
// to the client(s) whose subscribed chat_id matches frame.chat_id. A null-
|
|
44646
|
+
// subscribed client receives nothing — the old "shared broadcast to all"
|
|
44647
|
+
// behaviour is intentionally replaced with per-client routing.
|
|
44648
|
+
clientChatId = /* @__PURE__ */ new Map();
|
|
44384
44649
|
// chat_ids we have seen on an INBOUND POST. Outbound replies bind against this
|
|
44385
44650
|
// set (consensus f5): the live session can only address a stream the dashboard
|
|
44386
44651
|
// actually opened, never an arbitrary/forged id. Bounded + TTL'd so a long
|
|
@@ -44389,6 +44654,16 @@ var init_api_bridge = __esm({
|
|
|
44389
44654
|
static MAX_KNOWN_CHAT_IDS = 64;
|
|
44390
44655
|
static CHAT_ID_TTL_MS = 2 * 60 * 60 * 1e3;
|
|
44391
44656
|
// 2h, mirror chat-session-store
|
|
44657
|
+
// ── Outstanding-question registry (gossip_ask round-trip) ───────────────────
|
|
44658
|
+
// qid → {chatId, questions, at}. An inbound answer is validated against the
|
|
44659
|
+
// EXACT set that was asked: the questionId must exist, each selected label must
|
|
44660
|
+
// be one of that question's option labels, `other` only when allowOther. The
|
|
44661
|
+
// registry is bounded + TTL'd (fail-closed eviction) so a long-running session
|
|
44662
|
+
// can't grow it unbounded and a stale qid can never be answered.
|
|
44663
|
+
outstandingQuestions = /* @__PURE__ */ new Map();
|
|
44664
|
+
static MAX_OUTSTANDING_QUESTIONS = 64;
|
|
44665
|
+
static QUESTION_TTL_MS = 2 * 60 * 60 * 1e3;
|
|
44666
|
+
// 2h, mirror knownChatIds
|
|
44392
44667
|
// ── Mirror state (spec v2 §3) ──────────────────────────────────────────────
|
|
44393
44668
|
// chat_ids eligible to RECEIVE mirror frames. SEPARATE from knownChatIds
|
|
44394
44669
|
// (sonnet:f7/f12). P1#1: this set is seeded ONLY from the relay's validated
|
|
@@ -44398,19 +44673,6 @@ var init_api_bridge = __esm({
|
|
|
44398
44673
|
// emitReply STILL gates on knownChatIds, so mirroring never widens the
|
|
44399
44674
|
// outbound reply boundary (api-bridge.ts emitReply isKnownChatId check).
|
|
44400
44675
|
mirrorChatIds = /* @__PURE__ */ new Map();
|
|
44401
|
-
// The most-recently-registered active mirror chat_id (set in
|
|
44402
|
-
// registerMirrorChatId, cleared/recomputed when that id is TTL-evicted).
|
|
44403
|
-
// Used by emitMirror to route unresolved terminal frames to a live observer
|
|
44404
|
-
// instead of the provisional buffer when one exists (fix: mirror frames
|
|
44405
|
-
// previously fell into _provisional and were dropped by the dashboard client
|
|
44406
|
-
// because bindSession never ran — the browser cannot send a session_id it
|
|
44407
|
-
// doesn't know). SECURITY: only ever a chat_id already in mirrorChatIds.
|
|
44408
|
-
// KNOWN LIMITATION (consensus 96350953-8ce94428): single-pointer, so with
|
|
44409
|
-
// multiple concurrent dashboard tabs only the most-recently-registered one
|
|
44410
|
-
// receives unresolved terminal frames; earlier tabs see no live activity until
|
|
44411
|
-
// they send again. Accepted tradeoff — a strict improvement over the prior
|
|
44412
|
-
// behaviour where the frames went to _provisional and EVERY tab saw nothing.
|
|
44413
|
-
latestMirrorChatId = null;
|
|
44414
44676
|
// Session→chat_id map (P1#5). The relay has no native "active session
|
|
44415
44677
|
// chat_id" notion — a no-chat_id inbound POST mints a fresh UUID. We learn the
|
|
44416
44678
|
// mapping from the dashboard turn: a turn carries BOTH a chat_id (validated)
|
|
@@ -44419,7 +44681,8 @@ var init_api_bridge = __esm({
|
|
|
44419
44681
|
// through here. Established only from a validated dashboard inbound POST.
|
|
44420
44682
|
sessionToChatId = /* @__PURE__ */ new Map();
|
|
44421
44683
|
// Per-chat_id mirror rings (bounded FIFO + TTL + proactive sweep).
|
|
44422
|
-
|
|
44684
|
+
// Constructed with a FileChatStore when chatDir is provided, else NullChatStore.
|
|
44685
|
+
mirror;
|
|
44423
44686
|
// PROVISIONAL buffer (P1#5 fallback): a purely-terminal mirror POST with no
|
|
44424
44687
|
// resolvable chat_id (no body chat_id, no known session mapping) is buffered
|
|
44425
44688
|
// under a provisional id so a later observer can backfill it — OR dropped,
|
|
@@ -44434,6 +44697,10 @@ var init_api_bridge = __esm({
|
|
|
44434
44697
|
dropUnresolvedMirror = false;
|
|
44435
44698
|
// session→chat_id entries share the same 2h TTL ceiling as knownChatIds.
|
|
44436
44699
|
static MAX_SESSION_MAPPINGS = 64;
|
|
44700
|
+
constructor(opts = {}) {
|
|
44701
|
+
const store = opts.chatDir ? new FileChatStore(opts.chatDir) : new NullChatStore();
|
|
44702
|
+
this.mirror = new MirrorEventStore(MIRROR_RING_MAX, MIRROR_RING_TTL_MS, void 0, store);
|
|
44703
|
+
}
|
|
44437
44704
|
/** Register (or clear) the in-process inbound sink. Called by RelayServer. */
|
|
44438
44705
|
registerSink(fn) {
|
|
44439
44706
|
this.sink = fn;
|
|
@@ -44523,6 +44790,165 @@ var init_api_bridge = __esm({
|
|
|
44523
44790
|
this.broadcast({ type: "ack", chat_id: chatId, ts: (/* @__PURE__ */ new Date()).toISOString() });
|
|
44524
44791
|
return true;
|
|
44525
44792
|
}
|
|
44793
|
+
/**
|
|
44794
|
+
* OUTBOUND: the MCP `gossip_ask` tool asks the dashboard a selection question.
|
|
44795
|
+
* Gates on knownChatIds EXACTLY like emitReply — a question may only target a
|
|
44796
|
+
* stream the dashboard actually opened, never an arbitrary/forged id. On
|
|
44797
|
+
* success the validated question set is registered under `qid` (so a later
|
|
44798
|
+
* inbound answer can be validated against what was asked) and a `question`
|
|
44799
|
+
* frame is fanned out over SSE.
|
|
44800
|
+
*
|
|
44801
|
+
* LIVE-ONLY: like reply/ack, a `question` frame has NO per-chat_id ring — an
|
|
44802
|
+
* unanswered question is LOST on a dashboard reload (acceptable v1). The
|
|
44803
|
+
* registry entry survives a reload (so an answer typed after reconnect would
|
|
44804
|
+
* still validate), but the rendered card does not.
|
|
44805
|
+
*
|
|
44806
|
+
* Returns false (no registration, no broadcast) when the chat_id is
|
|
44807
|
+
* malformed/unbound or no SSE client is connected, so the MCP tool can no-op
|
|
44808
|
+
* honestly — same posture as emitReply.
|
|
44809
|
+
*/
|
|
44810
|
+
emitQuestion(rawChatId, qid, questions) {
|
|
44811
|
+
const chatId = validateChatId(rawChatId);
|
|
44812
|
+
if (chatId === null) return false;
|
|
44813
|
+
if (!this.isKnownChatId(chatId)) return false;
|
|
44814
|
+
if (this.clients.size === 0) return false;
|
|
44815
|
+
if (typeof qid !== "string" || qid.length === 0) return false;
|
|
44816
|
+
if (!Array.isArray(questions) || questions.length === 0) return false;
|
|
44817
|
+
this.registerOutstandingQuestion(qid, chatId, questions);
|
|
44818
|
+
this.broadcast({ type: "question", chat_id: chatId, qid, questions, ts: (/* @__PURE__ */ new Date()).toISOString() });
|
|
44819
|
+
return true;
|
|
44820
|
+
}
|
|
44821
|
+
/**
|
|
44822
|
+
* INBOUND ANSWER (UNTRUSTED — this is the security boundary). The dashboard
|
|
44823
|
+
* POSTs `{chat_id, answer:{qid, responses:[{questionId, selected[], other?}]}}`.
|
|
44824
|
+
* VALIDATES fail-closed against the registered question set:
|
|
44825
|
+
* - chat_id is known (bound to an opened stream);
|
|
44826
|
+
* - qid is an outstanding question FOR THAT chat_id (cross-chat reuse → 400);
|
|
44827
|
+
* - each questionId exists in the asked set, with no duplicates / no missing;
|
|
44828
|
+
* - each `selected` label is one of THAT question's option labels (an unknown
|
|
44829
|
+
* label → 400; a single-select question accepts at most one label);
|
|
44830
|
+
* - `other` is only allowed when that question had allowOther, and is trimmed
|
|
44831
|
+
* + length-capped.
|
|
44832
|
+
* On VALID: a concise channel turn is formatted and delivered to the live CC
|
|
44833
|
+
* session via the SAME sink path inbound chat messages use (so it arrives as a
|
|
44834
|
+
* normal turn), then the qid is deleted (single-use). Returns a route-shaped
|
|
44835
|
+
* {status,payload}: 202 on accept, 400 on any validation failure, 503 when no
|
|
44836
|
+
* sink is wired.
|
|
44837
|
+
*/
|
|
44838
|
+
handleAnswer(body) {
|
|
44839
|
+
const b = body ?? {};
|
|
44840
|
+
const chatId = validateChatId(b.chat_id);
|
|
44841
|
+
if (chatId === null) {
|
|
44842
|
+
return { status: 400, payload: { error: "invalid chat_id" } };
|
|
44843
|
+
}
|
|
44844
|
+
if (!this.isKnownChatId(chatId)) {
|
|
44845
|
+
return { status: 400, payload: { error: "unknown chat_id (not an open dashboard stream)" } };
|
|
44846
|
+
}
|
|
44847
|
+
const answer = b.answer;
|
|
44848
|
+
if (!answer || typeof answer !== "object") {
|
|
44849
|
+
return { status: 400, payload: { error: "answer must be an object" } };
|
|
44850
|
+
}
|
|
44851
|
+
const qid = answer.qid;
|
|
44852
|
+
if (typeof qid !== "string" || qid.length === 0) {
|
|
44853
|
+
return { status: 400, payload: { error: "answer.qid must be a non-empty string" } };
|
|
44854
|
+
}
|
|
44855
|
+
this.evictOutstandingQuestions(Date.now());
|
|
44856
|
+
const outstanding = this.outstandingQuestions.get(qid);
|
|
44857
|
+
if (!outstanding) {
|
|
44858
|
+
return { status: 400, payload: { error: "unknown or expired qid" } };
|
|
44859
|
+
}
|
|
44860
|
+
if (outstanding.chatId !== chatId) {
|
|
44861
|
+
return { status: 400, payload: { error: "qid does not belong to this chat_id" } };
|
|
44862
|
+
}
|
|
44863
|
+
const responses = answer.responses;
|
|
44864
|
+
if (!Array.isArray(responses) || responses.length === 0) {
|
|
44865
|
+
return { status: 400, payload: { error: "answer.responses must be a non-empty array" } };
|
|
44866
|
+
}
|
|
44867
|
+
if (responses.length > outstanding.questions.length) {
|
|
44868
|
+
return { status: 400, payload: { error: "too many responses" } };
|
|
44869
|
+
}
|
|
44870
|
+
const seen = /* @__PURE__ */ new Set();
|
|
44871
|
+
const parts = [];
|
|
44872
|
+
for (const r of responses) {
|
|
44873
|
+
if (!r || typeof r !== "object") {
|
|
44874
|
+
return { status: 400, payload: { error: "each response must be an object" } };
|
|
44875
|
+
}
|
|
44876
|
+
const rr = r;
|
|
44877
|
+
if (typeof rr.questionId !== "string") {
|
|
44878
|
+
return { status: 400, payload: { error: "response.questionId must be a string" } };
|
|
44879
|
+
}
|
|
44880
|
+
const questionId = rr.questionId;
|
|
44881
|
+
if (seen.has(questionId)) {
|
|
44882
|
+
return { status: 400, payload: { error: "duplicate questionId in responses" } };
|
|
44883
|
+
}
|
|
44884
|
+
const q = outstanding.questions.find((x) => x.questionId === questionId);
|
|
44885
|
+
if (!q) {
|
|
44886
|
+
return { status: 400, payload: { error: `unknown questionId "${questionId}"` } };
|
|
44887
|
+
}
|
|
44888
|
+
seen.add(questionId);
|
|
44889
|
+
if (!Array.isArray(rr.selected)) {
|
|
44890
|
+
return { status: 400, payload: { error: "response.selected must be an array" } };
|
|
44891
|
+
}
|
|
44892
|
+
const selected = rr.selected;
|
|
44893
|
+
if (!q.multiSelect && selected.length > 1) {
|
|
44894
|
+
return { status: 400, payload: { error: `question "${questionId}" is single-select` } };
|
|
44895
|
+
}
|
|
44896
|
+
if (selected.length > q.options.length) {
|
|
44897
|
+
return { status: 400, payload: { error: "too many selected labels" } };
|
|
44898
|
+
}
|
|
44899
|
+
const labels = new Set(q.options.map((o) => o.label));
|
|
44900
|
+
const chosen = [];
|
|
44901
|
+
const seenLabels = /* @__PURE__ */ new Set();
|
|
44902
|
+
for (const s of selected) {
|
|
44903
|
+
if (typeof s !== "string" || !labels.has(s)) {
|
|
44904
|
+
return { status: 400, payload: { error: `unknown option label for question "${questionId}"` } };
|
|
44905
|
+
}
|
|
44906
|
+
if (seenLabels.has(s)) {
|
|
44907
|
+
return { status: 400, payload: { error: "duplicate selected label" } };
|
|
44908
|
+
}
|
|
44909
|
+
seenLabels.add(s);
|
|
44910
|
+
chosen.push(s);
|
|
44911
|
+
}
|
|
44912
|
+
let otherText = null;
|
|
44913
|
+
if (rr.other !== void 0 && rr.other !== null) {
|
|
44914
|
+
if (!q.allowOther) {
|
|
44915
|
+
return { status: 400, payload: { error: `question "${questionId}" does not allow other` } };
|
|
44916
|
+
}
|
|
44917
|
+
if (typeof rr.other !== "string") {
|
|
44918
|
+
return { status: 400, payload: { error: "response.other must be a string" } };
|
|
44919
|
+
}
|
|
44920
|
+
const sanitized = rr.other.replace(/[\x00-\x1F\x7F]/g, " ").replace(/\s+/g, " ").replace(/\[answer/gi, "(answer").trim();
|
|
44921
|
+
if (sanitized.length > ASK_MAX_OTHER) {
|
|
44922
|
+
return { status: 400, payload: { error: "other text too long" } };
|
|
44923
|
+
}
|
|
44924
|
+
if (sanitized.length > 0) {
|
|
44925
|
+
otherText = sanitized.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
44926
|
+
}
|
|
44927
|
+
}
|
|
44928
|
+
if (chosen.length === 0 && otherText === null) {
|
|
44929
|
+
return { status: 400, payload: { error: `question "${questionId}" has no selection` } };
|
|
44930
|
+
}
|
|
44931
|
+
const segs = [];
|
|
44932
|
+
if (chosen.length > 0) segs.push(chosen.join(", "));
|
|
44933
|
+
if (otherText !== null) segs.push(`other: "${otherText}"`);
|
|
44934
|
+
parts.push(`${q.header}: ${segs.join(" \xB7 ")}`);
|
|
44935
|
+
}
|
|
44936
|
+
if (!this.sink) {
|
|
44937
|
+
return { status: 503, payload: { error: "No live Claude Code bridge session is active" } };
|
|
44938
|
+
}
|
|
44939
|
+
const turn = `[answer qid=${qid}] ${parts.join(" \xB7 ")}`;
|
|
44940
|
+
let delivered;
|
|
44941
|
+
try {
|
|
44942
|
+
delivered = this.sink(chatId, turn);
|
|
44943
|
+
} catch {
|
|
44944
|
+
delivered = false;
|
|
44945
|
+
}
|
|
44946
|
+
if (!delivered) {
|
|
44947
|
+
return { status: 503, payload: { error: "Bridge sink rejected the answer" } };
|
|
44948
|
+
}
|
|
44949
|
+
this.outstandingQuestions.delete(qid);
|
|
44950
|
+
return { status: 202, payload: { ok: true, qid, chat_id: chatId } };
|
|
44951
|
+
}
|
|
44526
44952
|
/**
|
|
44527
44953
|
* MIRROR ingress (spec v2 §3). Validates the (already body-capped + role-
|
|
44528
44954
|
* checked-at-route) batch a SECOND time at the trust boundary, resolves the
|
|
@@ -44594,10 +45020,25 @@ var init_api_bridge = __esm({
|
|
|
44594
45020
|
chatId = resolved;
|
|
44595
45021
|
} else if (this.dropUnresolvedMirror) {
|
|
44596
45022
|
return { status: 202, payload: { ok: true, chat_id: null, dropped: validated.length } };
|
|
44597
|
-
} else if (this.latestMirrorChatId !== null && this.isMirrorChatId(this.latestMirrorChatId)) {
|
|
44598
|
-
chatId = this.latestMirrorChatId;
|
|
44599
45023
|
} else {
|
|
44600
|
-
|
|
45024
|
+
this.evictMirrorChatIds(Date.now());
|
|
45025
|
+
const liveIds = Array.from(this.mirrorChatIds.keys());
|
|
45026
|
+
if (liveIds.length > 0) {
|
|
45027
|
+
let totalFrames = 0;
|
|
45028
|
+
for (const id of liveIds) {
|
|
45029
|
+
for (const { role, text } of validated) {
|
|
45030
|
+
const frame = this.mirror.push(id, role, text);
|
|
45031
|
+
this.broadcast(frame);
|
|
45032
|
+
totalFrames++;
|
|
45033
|
+
}
|
|
45034
|
+
}
|
|
45035
|
+
return {
|
|
45036
|
+
status: 202,
|
|
45037
|
+
payload: { ok: true, chat_id: null, fanout: liveIds.length, frames: totalFrames }
|
|
45038
|
+
};
|
|
45039
|
+
} else {
|
|
45040
|
+
chatId = _BridgeHub.PROVISIONAL_CHAT_ID;
|
|
45041
|
+
}
|
|
44601
45042
|
}
|
|
44602
45043
|
}
|
|
44603
45044
|
const stamped = [];
|
|
@@ -44667,6 +45108,7 @@ var init_api_bridge = __esm({
|
|
|
44667
45108
|
}
|
|
44668
45109
|
this.backpressure.set(res, 0);
|
|
44669
45110
|
this.clients.add(res);
|
|
45111
|
+
this.clientChatId.set(res, chatId);
|
|
44670
45112
|
const keepalive = setInterval(() => {
|
|
44671
45113
|
if (chatId !== null) this.touchMirrorChatId(chatId);
|
|
44672
45114
|
try {
|
|
@@ -44680,6 +45122,7 @@ var init_api_bridge = __esm({
|
|
|
44680
45122
|
req.on("close", () => {
|
|
44681
45123
|
this.clearKeepalive(res);
|
|
44682
45124
|
this.clients.delete(res);
|
|
45125
|
+
this.clientChatId.delete(res);
|
|
44683
45126
|
});
|
|
44684
45127
|
}
|
|
44685
45128
|
/**
|
|
@@ -44718,7 +45161,16 @@ var init_api_bridge = __esm({
|
|
|
44718
45161
|
return false;
|
|
44719
45162
|
}
|
|
44720
45163
|
}
|
|
44721
|
-
/**
|
|
45164
|
+
/**
|
|
45165
|
+
* Fan out one frame to matching connected SSE clients (server-side filter),
|
|
45166
|
+
* with backpressure eviction.
|
|
45167
|
+
*
|
|
45168
|
+
* Each frame carries a chat_id. Only the client(s) subscribed to that
|
|
45169
|
+
* chat_id receive the write — clients subscribed to a different chat_id or
|
|
45170
|
+
* with a null subscription (legacy live-only consumer) are skipped. This
|
|
45171
|
+
* eliminates the prior behaviour of broadcasting EVERY frame to ALL clients
|
|
45172
|
+
* (the bandwidth/confidentiality gap noted in the multi-tab fix spec).
|
|
45173
|
+
*/
|
|
44722
45174
|
broadcast(frame) {
|
|
44723
45175
|
const idLine = "id" in frame && typeof frame.id === "number" ? `id: ${frame.id}
|
|
44724
45176
|
` : "";
|
|
@@ -44726,6 +45178,8 @@ var init_api_bridge = __esm({
|
|
|
44726
45178
|
|
|
44727
45179
|
`;
|
|
44728
45180
|
for (const res of this.clients) {
|
|
45181
|
+
const subscribedChatId = this.clientChatId.get(res) ?? null;
|
|
45182
|
+
if (subscribedChatId === null || subscribedChatId !== frame.chat_id) continue;
|
|
44729
45183
|
try {
|
|
44730
45184
|
const ok = res.write(data);
|
|
44731
45185
|
if (ok) {
|
|
@@ -44737,11 +45191,13 @@ var init_api_bridge = __esm({
|
|
|
44737
45191
|
this.clearKeepalive(res);
|
|
44738
45192
|
res.destroy();
|
|
44739
45193
|
this.clients.delete(res);
|
|
45194
|
+
this.clientChatId.delete(res);
|
|
44740
45195
|
}
|
|
44741
45196
|
}
|
|
44742
45197
|
} catch {
|
|
44743
45198
|
this.clearKeepalive(res);
|
|
44744
45199
|
this.clients.delete(res);
|
|
45200
|
+
this.clientChatId.delete(res);
|
|
44745
45201
|
}
|
|
44746
45202
|
}
|
|
44747
45203
|
}
|
|
@@ -44790,6 +45246,34 @@ var init_api_bridge = __esm({
|
|
|
44790
45246
|
if (now - v > _BridgeHub.CHAT_ID_TTL_MS) this.knownChatIds.delete(k);
|
|
44791
45247
|
}
|
|
44792
45248
|
}
|
|
45249
|
+
// ── Outstanding-question registry helpers (gossip_ask) ──────────────────────
|
|
45250
|
+
/**
|
|
45251
|
+
* Register a validated question set under `qid`. Bounded + TTL'd: a stale
|
|
45252
|
+
* entry is reaped before insert, and at capacity the oldest entry is dropped
|
|
45253
|
+
* to make room (fail-closed — an unanswered old question is forgotten rather
|
|
45254
|
+
* than letting the map grow unbounded).
|
|
45255
|
+
*/
|
|
45256
|
+
registerOutstandingQuestion(qid, chatId, questions) {
|
|
45257
|
+
const now = Date.now();
|
|
45258
|
+
this.evictOutstandingQuestions(now);
|
|
45259
|
+
if (!this.outstandingQuestions.has(qid) && this.outstandingQuestions.size >= _BridgeHub.MAX_OUTSTANDING_QUESTIONS) {
|
|
45260
|
+
let oldestKey = null;
|
|
45261
|
+
let oldest = Infinity;
|
|
45262
|
+
for (const [k, v] of this.outstandingQuestions) {
|
|
45263
|
+
if (v.at < oldest) {
|
|
45264
|
+
oldest = v.at;
|
|
45265
|
+
oldestKey = k;
|
|
45266
|
+
}
|
|
45267
|
+
}
|
|
45268
|
+
if (oldestKey !== null) this.outstandingQuestions.delete(oldestKey);
|
|
45269
|
+
}
|
|
45270
|
+
this.outstandingQuestions.set(qid, { chatId, questions, at: now });
|
|
45271
|
+
}
|
|
45272
|
+
evictOutstandingQuestions(now) {
|
|
45273
|
+
for (const [k, v] of this.outstandingQuestions) {
|
|
45274
|
+
if (now - v.at > _BridgeHub.QUESTION_TTL_MS) this.outstandingQuestions.delete(k);
|
|
45275
|
+
}
|
|
45276
|
+
}
|
|
44793
45277
|
// ── Mirror chat-id registry (SEPARATE from knownChatIds — P1#1/sonnet:f7) ──
|
|
44794
45278
|
// Same bounded + TTL eviction discipline as knownChatIds, but a DISTINCT map:
|
|
44795
45279
|
// mirrorChatIds gates emitMirror; knownChatIds gates emitReply. Keeping them
|
|
@@ -44816,7 +45300,6 @@ var init_api_bridge = __esm({
|
|
|
44816
45300
|
if (oldestKey !== null) this.mirrorChatIds.delete(oldestKey);
|
|
44817
45301
|
}
|
|
44818
45302
|
this.mirrorChatIds.set(chatId, now);
|
|
44819
|
-
this.latestMirrorChatId = chatId;
|
|
44820
45303
|
if (isNew) this.backfillProvisional(chatId, now);
|
|
44821
45304
|
}
|
|
44822
45305
|
/**
|
|
@@ -44837,23 +45320,10 @@ var init_api_bridge = __esm({
|
|
|
44837
45320
|
return this.mirrorChatIds.has(chatId);
|
|
44838
45321
|
}
|
|
44839
45322
|
evictMirrorChatIds(now) {
|
|
44840
|
-
let evictedLatest = false;
|
|
44841
45323
|
for (const [k, v] of this.mirrorChatIds) {
|
|
44842
45324
|
if (now - v > _BridgeHub.CHAT_ID_TTL_MS) {
|
|
44843
45325
|
this.mirrorChatIds.delete(k);
|
|
44844
|
-
if (k === this.latestMirrorChatId) evictedLatest = true;
|
|
44845
|
-
}
|
|
44846
|
-
}
|
|
44847
|
-
if (evictedLatest) {
|
|
44848
|
-
let newestKey = null;
|
|
44849
|
-
let newest = -Infinity;
|
|
44850
|
-
for (const [k, v] of this.mirrorChatIds) {
|
|
44851
|
-
if (v > newest) {
|
|
44852
|
-
newest = v;
|
|
44853
|
-
newestKey = k;
|
|
44854
|
-
}
|
|
44855
45326
|
}
|
|
44856
|
-
this.latestMirrorChatId = newestKey;
|
|
44857
45327
|
}
|
|
44858
45328
|
}
|
|
44859
45329
|
/**
|
|
@@ -45082,15 +45552,15 @@ function normalizeLegacyDegradedFields(report) {
|
|
|
45082
45552
|
}
|
|
45083
45553
|
function resolveDashboardRoot(projectRoot) {
|
|
45084
45554
|
const candidates = [
|
|
45085
|
-
(0,
|
|
45555
|
+
(0, import_path79.resolve)(__dirname, "..", "dist-dashboard"),
|
|
45086
45556
|
// bundled: dist-mcp/mcp-server.js → ../dist-dashboard
|
|
45087
|
-
(0,
|
|
45557
|
+
(0, import_path79.resolve)(__dirname, "..", "..", "..", "..", "dist-dashboard"),
|
|
45088
45558
|
// tsc dev: packages/relay/dist/dashboard → repo-root
|
|
45089
|
-
(0,
|
|
45559
|
+
(0, import_path79.join)(projectRoot, "dist-dashboard")
|
|
45090
45560
|
// legacy dev fallback (git-clone running from repo root)
|
|
45091
45561
|
];
|
|
45092
45562
|
for (const p of candidates) {
|
|
45093
|
-
if ((0,
|
|
45563
|
+
if ((0, import_fs71.existsSync)(p)) return p;
|
|
45094
45564
|
}
|
|
45095
45565
|
return null;
|
|
45096
45566
|
}
|
|
@@ -45117,7 +45587,7 @@ function readBody(req, maxBytes = MAX_BODY_SIZE) {
|
|
|
45117
45587
|
});
|
|
45118
45588
|
});
|
|
45119
45589
|
}
|
|
45120
|
-
var
|
|
45590
|
+
var import_fs71, import_path79, import_crypto28, AUTH_MAX_ATTEMPTS, AUTH_LOCKOUT_MS, AUTH_ATTEMPT_TTL_MS, AUTH_ATTEMPTS_HARD_CAP, CHAT_MAX_BODY, BRIDGE_MAX_BODY, MIRROR_MAX_BODY, MIRROR_MIN_INTERVAL_MS, MIRROR_LAST_TURN_HARD_CAP, CHAT_MIN_INTERVAL_MS, DashboardRouter, MAX_BODY_SIZE;
|
|
45121
45591
|
var init_routes = __esm({
|
|
45122
45592
|
"packages/relay/src/dashboard/routes.ts"() {
|
|
45123
45593
|
"use strict";
|
|
@@ -45145,8 +45615,8 @@ var init_routes = __esm({
|
|
|
45145
45615
|
init_api_bridge();
|
|
45146
45616
|
init_chat_session_store();
|
|
45147
45617
|
init_src4();
|
|
45148
|
-
|
|
45149
|
-
|
|
45618
|
+
import_fs71 = require("fs");
|
|
45619
|
+
import_path79 = require("path");
|
|
45150
45620
|
import_crypto28 = require("crypto");
|
|
45151
45621
|
AUTH_MAX_ATTEMPTS = 10;
|
|
45152
45622
|
AUTH_LOCKOUT_MS = 6e4;
|
|
@@ -45164,6 +45634,7 @@ var init_routes = __esm({
|
|
|
45164
45634
|
this.projectRoot = projectRoot;
|
|
45165
45635
|
this.ctx = ctx2;
|
|
45166
45636
|
this.dashboardRoot = resolveDashboardRoot(projectRoot);
|
|
45637
|
+
this.bridge = new BridgeHub({ chatDir: (0, import_path79.join)(projectRoot, ".gossip", "chat") });
|
|
45167
45638
|
}
|
|
45168
45639
|
auth;
|
|
45169
45640
|
projectRoot;
|
|
@@ -45185,7 +45656,9 @@ var init_routes = __esm({
|
|
|
45185
45656
|
// Dashboard ⇄ live-CC bridge transport (P1). Owns the outbound SSE client set
|
|
45186
45657
|
// and the in-process inbound sink the MCP server registers. Distinct from the
|
|
45187
45658
|
// dormant chatbot (consensus f14 — no dual-brain; /api/chat stays dormant).
|
|
45188
|
-
|
|
45659
|
+
// Constructed in the constructor body (not as a field initializer) so it can
|
|
45660
|
+
// receive the chatDir option derived from projectRoot.
|
|
45661
|
+
bridge;
|
|
45189
45662
|
/**
|
|
45190
45663
|
* Inject (or clear) the read-only chatbot agent. Called by the app layer
|
|
45191
45664
|
* after boot via RelayServer.setChatbot. Passing null disables chat with a
|
|
@@ -45213,6 +45686,16 @@ var init_routes = __esm({
|
|
|
45213
45686
|
emitBridgeReply(chatId, text) {
|
|
45214
45687
|
return this.bridge.emitReply(chatId, text);
|
|
45215
45688
|
}
|
|
45689
|
+
/**
|
|
45690
|
+
* Ask the dashboard a structured selection question (MCP `gossip_ask` tool).
|
|
45691
|
+
* The hub gates chat_id on knownChatIds exactly like emitReply, registers the
|
|
45692
|
+
* validated question set under `qid`, and fans out a `question` frame. Returns
|
|
45693
|
+
* false when the id was unbound/malformed or no SSE client is connected so the
|
|
45694
|
+
* caller can no-op honestly.
|
|
45695
|
+
*/
|
|
45696
|
+
emitBridgeQuestion(chatId, qid, questions) {
|
|
45697
|
+
return this.bridge.emitQuestion(chatId, qid, questions);
|
|
45698
|
+
}
|
|
45216
45699
|
/** Update live context (call when agents connect/disconnect) */
|
|
45217
45700
|
updateContext(ctx2) {
|
|
45218
45701
|
if (ctx2.agentConfigs !== void 0) this.ctx.agentConfigs = ctx2.agentConfigs;
|
|
@@ -45609,7 +46092,8 @@ var init_routes = __esm({
|
|
|
45609
46092
|
this.json(res, 400, { error: "Invalid JSON body" });
|
|
45610
46093
|
return true;
|
|
45611
46094
|
}
|
|
45612
|
-
const
|
|
46095
|
+
const hasAnswer = body !== null && typeof body === "object" && "answer" in body;
|
|
46096
|
+
const { status, payload } = hasAnswer ? this.bridge.handleAnswer(body) : this.bridge.handlePost(body);
|
|
45613
46097
|
this.json(res, status, payload);
|
|
45614
46098
|
return true;
|
|
45615
46099
|
}
|
|
@@ -45663,14 +46147,17 @@ var init_routes = __esm({
|
|
|
45663
46147
|
res.end("Dashboard assets not found. Reinstall gossipcat or rebuild from source.");
|
|
45664
46148
|
return true;
|
|
45665
46149
|
}
|
|
45666
|
-
const htmlPath = (0,
|
|
45667
|
-
if (!(0,
|
|
46150
|
+
const htmlPath = (0, import_path79.join)(this.dashboardRoot, "index.html");
|
|
46151
|
+
if (!(0, import_fs71.existsSync)(htmlPath)) {
|
|
45668
46152
|
res.writeHead(503, { "Content-Type": "text/plain" });
|
|
45669
46153
|
res.end(`Dashboard index.html missing at ${this.dashboardRoot}. Reinstall gossipcat.`);
|
|
45670
46154
|
return true;
|
|
45671
46155
|
}
|
|
45672
|
-
const html = (0,
|
|
45673
|
-
res.writeHead(200, {
|
|
46156
|
+
const html = (0, import_fs71.readFileSync)(htmlPath, "utf-8");
|
|
46157
|
+
res.writeHead(200, {
|
|
46158
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
46159
|
+
"Cache-Control": "no-cache"
|
|
46160
|
+
});
|
|
45674
46161
|
res.end(html);
|
|
45675
46162
|
return true;
|
|
45676
46163
|
}
|
|
@@ -45695,16 +46182,16 @@ var init_routes = __esm({
|
|
|
45695
46182
|
const ext = "." + (relativePath.split(".").pop() || "");
|
|
45696
46183
|
const mime = MIME[ext];
|
|
45697
46184
|
if (!mime) return false;
|
|
45698
|
-
const filePath = (0,
|
|
46185
|
+
const filePath = (0, import_path79.join)(this.dashboardRoot, relativePath);
|
|
45699
46186
|
try {
|
|
45700
|
-
const realFile = (0,
|
|
45701
|
-
const realBase = (0,
|
|
46187
|
+
const realFile = (0, import_fs71.realpathSync)(filePath);
|
|
46188
|
+
const realBase = (0, import_fs71.realpathSync)(this.dashboardRoot);
|
|
45702
46189
|
if (!realFile.startsWith(realBase + "/")) {
|
|
45703
46190
|
res.writeHead(404);
|
|
45704
46191
|
res.end();
|
|
45705
46192
|
return true;
|
|
45706
46193
|
}
|
|
45707
|
-
const data = (0,
|
|
46194
|
+
const data = (0, import_fs71.readFileSync)(realFile);
|
|
45708
46195
|
res.writeHead(200, { "Content-Type": mime, "Cache-Control": "public, max-age=86400" });
|
|
45709
46196
|
res.end(data);
|
|
45710
46197
|
return true;
|
|
@@ -45719,8 +46206,8 @@ var init_routes = __esm({
|
|
|
45719
46206
|
return match ? match[1] : null;
|
|
45720
46207
|
}
|
|
45721
46208
|
getConsensusReports(page = 1, pageSize = 5) {
|
|
45722
|
-
const { readdirSync: readdirSync22, readFileSync:
|
|
45723
|
-
const reportsDir = (0,
|
|
46209
|
+
const { readdirSync: readdirSync22, readFileSync: readFileSync69, existsSync: existsSync73 } = require("fs");
|
|
46210
|
+
const reportsDir = (0, import_path79.join)(this.projectRoot, ".gossip", "consensus-reports");
|
|
45724
46211
|
let retractedConsensusIds = [];
|
|
45725
46212
|
let roundRetractions = [];
|
|
45726
46213
|
try {
|
|
@@ -45735,8 +46222,8 @@ var init_routes = __esm({
|
|
|
45735
46222
|
const { statSync: statSync36 } = require("fs");
|
|
45736
46223
|
const allFiles = readdirSync22(reportsDir).filter((f) => f.endsWith(".json")).sort((a, b) => {
|
|
45737
46224
|
try {
|
|
45738
|
-
const aTime = statSync36((0,
|
|
45739
|
-
const bTime = statSync36((0,
|
|
46225
|
+
const aTime = statSync36((0, import_path79.join)(reportsDir, a)).mtimeMs;
|
|
46226
|
+
const bTime = statSync36((0, import_path79.join)(reportsDir, b)).mtimeMs;
|
|
45740
46227
|
return bTime - aTime;
|
|
45741
46228
|
} catch {
|
|
45742
46229
|
return 0;
|
|
@@ -45747,13 +46234,13 @@ var init_routes = __esm({
|
|
|
45747
46234
|
const clampedPage = Math.max(page, 1);
|
|
45748
46235
|
const start = (clampedPage - 1) * clampedPageSize;
|
|
45749
46236
|
const files = allFiles.slice(start, start + clampedPageSize);
|
|
45750
|
-
const realReportsDir = (0,
|
|
46237
|
+
const realReportsDir = (0, import_fs71.realpathSync)(reportsDir);
|
|
45751
46238
|
const reports = files.map((f) => {
|
|
45752
46239
|
try {
|
|
45753
|
-
const filePath = (0,
|
|
45754
|
-
const realFile = (0,
|
|
46240
|
+
const filePath = (0, import_path79.join)(reportsDir, f);
|
|
46241
|
+
const realFile = (0, import_fs71.realpathSync)(filePath);
|
|
45755
46242
|
if (!realFile.startsWith(realReportsDir + "/")) return null;
|
|
45756
|
-
return normalizeLegacyDegradedFields(JSON.parse(
|
|
46243
|
+
return normalizeLegacyDegradedFields(JSON.parse(readFileSync69(realFile, "utf-8")));
|
|
45757
46244
|
} catch {
|
|
45758
46245
|
return null;
|
|
45759
46246
|
}
|
|
@@ -45764,29 +46251,29 @@ var init_routes = __esm({
|
|
|
45764
46251
|
}
|
|
45765
46252
|
}
|
|
45766
46253
|
archiveFindings() {
|
|
45767
|
-
const { readdirSync: readdirSync22, readFileSync:
|
|
45768
|
-
const reportsDir = (0,
|
|
45769
|
-
const archiveDir = (0,
|
|
46254
|
+
const { readdirSync: readdirSync22, readFileSync: readFileSync69, renameSync: renameSync18, writeFileSync: writeFileSync38, mkdirSync: mkdirSync45, existsSync: existsSync73 } = require("fs");
|
|
46255
|
+
const reportsDir = (0, import_path79.join)(this.projectRoot, ".gossip", "consensus-reports");
|
|
46256
|
+
const archiveDir = (0, import_path79.join)(this.projectRoot, ".gossip", "consensus-reports-archive");
|
|
45770
46257
|
let archived = 0;
|
|
45771
46258
|
if (existsSync73(reportsDir)) {
|
|
45772
46259
|
const files = readdirSync22(reportsDir).filter((f) => f.endsWith(".json")).sort().reverse();
|
|
45773
46260
|
if (files.length > 5) {
|
|
45774
|
-
|
|
46261
|
+
mkdirSync45(archiveDir, { recursive: true });
|
|
45775
46262
|
const toArchive = files.slice(5);
|
|
45776
46263
|
for (const f of toArchive) {
|
|
45777
46264
|
try {
|
|
45778
|
-
|
|
46265
|
+
renameSync18((0, import_path79.join)(reportsDir, f), (0, import_path79.join)(archiveDir, f));
|
|
45779
46266
|
archived++;
|
|
45780
46267
|
} catch {
|
|
45781
46268
|
}
|
|
45782
46269
|
}
|
|
45783
46270
|
}
|
|
45784
46271
|
}
|
|
45785
|
-
const findingsPath = (0,
|
|
46272
|
+
const findingsPath = (0, import_path79.join)(this.projectRoot, ".gossip", "implementation-findings.jsonl");
|
|
45786
46273
|
let findingsCleared = 0;
|
|
45787
46274
|
if (existsSync73(findingsPath)) {
|
|
45788
46275
|
try {
|
|
45789
|
-
const lines =
|
|
46276
|
+
const lines = readFileSync69(findingsPath, "utf-8").trim().split("\n").filter(Boolean);
|
|
45790
46277
|
const kept = lines.filter((line) => {
|
|
45791
46278
|
try {
|
|
45792
46279
|
const entry = JSON.parse(line);
|
|
@@ -45799,7 +46286,7 @@ var init_routes = __esm({
|
|
|
45799
46286
|
return true;
|
|
45800
46287
|
}
|
|
45801
46288
|
});
|
|
45802
|
-
|
|
46289
|
+
writeFileSync38(findingsPath, kept.join("\n") + (kept.length > 0 ? "\n" : ""));
|
|
45803
46290
|
} catch {
|
|
45804
46291
|
}
|
|
45805
46292
|
}
|
|
@@ -45816,14 +46303,14 @@ var init_routes = __esm({
|
|
|
45816
46303
|
});
|
|
45817
46304
|
|
|
45818
46305
|
// packages/relay/src/dashboard/ws.ts
|
|
45819
|
-
var
|
|
46306
|
+
var import_fs72, import_fs73, import_path80, DashboardWs;
|
|
45820
46307
|
var init_ws = __esm({
|
|
45821
46308
|
"packages/relay/src/dashboard/ws.ts"() {
|
|
45822
46309
|
"use strict";
|
|
45823
46310
|
init_wrapper();
|
|
45824
|
-
import_fs71 = require("fs");
|
|
45825
46311
|
import_fs72 = require("fs");
|
|
45826
|
-
|
|
46312
|
+
import_fs73 = require("fs");
|
|
46313
|
+
import_path80 = require("path");
|
|
45827
46314
|
DashboardWs = class {
|
|
45828
46315
|
clients = /* @__PURE__ */ new Set();
|
|
45829
46316
|
logWatcher = null;
|
|
@@ -45848,15 +46335,15 @@ var init_ws = __esm({
|
|
|
45848
46335
|
/** Start watching mcp.log for new lines and broadcasting them to connected clients. */
|
|
45849
46336
|
startLogWatcher(projectRoot) {
|
|
45850
46337
|
this.stopLogWatcher();
|
|
45851
|
-
this.logPath = (0,
|
|
45852
|
-
if (!(0,
|
|
46338
|
+
this.logPath = (0, import_path80.join)(projectRoot, ".gossip", "mcp.log");
|
|
46339
|
+
if (!(0, import_fs72.existsSync)(this.logPath)) return;
|
|
45853
46340
|
try {
|
|
45854
|
-
this.logOffset = (0,
|
|
46341
|
+
this.logOffset = (0, import_fs72.statSync)(this.logPath).size;
|
|
45855
46342
|
} catch {
|
|
45856
46343
|
this.logOffset = 0;
|
|
45857
46344
|
}
|
|
45858
46345
|
try {
|
|
45859
|
-
this.logWatcher = (0,
|
|
46346
|
+
this.logWatcher = (0, import_fs73.watch)(this.logPath, () => {
|
|
45860
46347
|
if (this.clients.size === 0) return;
|
|
45861
46348
|
this.readNewLines();
|
|
45862
46349
|
});
|
|
@@ -45873,7 +46360,7 @@ var init_ws = __esm({
|
|
|
45873
46360
|
if (this.logReading) return;
|
|
45874
46361
|
this.logReading = true;
|
|
45875
46362
|
try {
|
|
45876
|
-
const currentSize = (0,
|
|
46363
|
+
const currentSize = (0, import_fs72.statSync)(this.logPath).size;
|
|
45877
46364
|
if (currentSize < this.logOffset) {
|
|
45878
46365
|
this.logOffset = 0;
|
|
45879
46366
|
this.logCarry = "";
|
|
@@ -45886,7 +46373,7 @@ var init_ws = __esm({
|
|
|
45886
46373
|
const readFrom = capped ? (this.logCarry = "", this.logCapped = true, currentSize - 65536) : (this.logCapped = false, this.logOffset);
|
|
45887
46374
|
let stream;
|
|
45888
46375
|
try {
|
|
45889
|
-
stream = (0,
|
|
46376
|
+
stream = (0, import_fs72.createReadStream)(this.logPath, { start: readFrom, end: currentSize - 1 });
|
|
45890
46377
|
} catch {
|
|
45891
46378
|
this.logReading = false;
|
|
45892
46379
|
return;
|
|
@@ -46346,6 +46833,19 @@ var init_server = __esm({
|
|
|
46346
46833
|
emitBridgeReply(chatId, text) {
|
|
46347
46834
|
return this.dashboardRouter?.emitBridgeReply(chatId, text) ?? false;
|
|
46348
46835
|
}
|
|
46836
|
+
/**
|
|
46837
|
+
* Ask the dashboard a structured selection question (MCP `gossip_ask` tool).
|
|
46838
|
+
* Mirrors emitBridgeReply: chat_id is re-validated + bound to a stream the
|
|
46839
|
+
* dashboard actually opened (the same knownChatIds gate as emitReply), the
|
|
46840
|
+
* validated question set is registered under `qid` so a later inbound answer
|
|
46841
|
+
* can be validated against what was asked, and a `question` frame fans out
|
|
46842
|
+
* over SSE. Returns false (no registration, no broadcast) when the dashboard
|
|
46843
|
+
* is disabled, the chat_id is unbound/malformed, or no SSE client is connected
|
|
46844
|
+
* — so the caller can no-op honestly rather than claim a delivery.
|
|
46845
|
+
*/
|
|
46846
|
+
emitBridgeQuestion(chatId, qid, questions) {
|
|
46847
|
+
return this.dashboardRouter?.emitBridgeQuestion(chatId, qid, questions) ?? false;
|
|
46848
|
+
}
|
|
46349
46849
|
};
|
|
46350
46850
|
}
|
|
46351
46851
|
});
|
|
@@ -46381,7 +46881,8 @@ __export(src_exports4, {
|
|
|
46381
46881
|
emitDashboardEvent: () => emitDashboardEvent,
|
|
46382
46882
|
hasMemoryQuery: () => hasMemoryQuery,
|
|
46383
46883
|
recordMemoryQueryAttribution: () => recordMemoryQueryAttribution,
|
|
46384
|
-
sweepExpiredAgents: () => sweepExpiredAgents
|
|
46884
|
+
sweepExpiredAgents: () => sweepExpiredAgents,
|
|
46885
|
+
validateAskQuestions: () => validateAskQuestions
|
|
46385
46886
|
});
|
|
46386
46887
|
var init_src5 = __esm({
|
|
46387
46888
|
"packages/relay/src/index.ts"() {
|
|
@@ -46720,14 +47221,14 @@ __export(sandbox_exports, {
|
|
|
46720
47221
|
});
|
|
46721
47222
|
function rotateIfNeeded2(filePath, maxBytes) {
|
|
46722
47223
|
try {
|
|
46723
|
-
const st = (0,
|
|
47224
|
+
const st = (0, import_fs74.statSync)(filePath);
|
|
46724
47225
|
if (st.size < maxBytes) return;
|
|
46725
47226
|
const rotated = filePath + ".1";
|
|
46726
47227
|
try {
|
|
46727
|
-
if ((0,
|
|
47228
|
+
if ((0, import_fs74.existsSync)(rotated)) (0, import_fs74.unlinkSync)(rotated);
|
|
46728
47229
|
} catch {
|
|
46729
47230
|
}
|
|
46730
|
-
(0,
|
|
47231
|
+
(0, import_fs74.renameSync)(filePath, rotated);
|
|
46731
47232
|
} catch {
|
|
46732
47233
|
}
|
|
46733
47234
|
}
|
|
@@ -46800,9 +47301,9 @@ Tests passing is NOT sufficient verification \u2014 the 2026-04-22 incident had
|
|
|
46800
47301
|
}
|
|
46801
47302
|
function readSandboxMode(projectRoot) {
|
|
46802
47303
|
try {
|
|
46803
|
-
const p = (0,
|
|
46804
|
-
if (!(0,
|
|
46805
|
-
const raw = JSON.parse((0,
|
|
47304
|
+
const p = (0, import_path81.join)(projectRoot, ".gossip", "config.json");
|
|
47305
|
+
if (!(0, import_fs74.existsSync)(p)) return "warn";
|
|
47306
|
+
const raw = JSON.parse((0, import_fs74.readFileSync)(p, "utf-8"));
|
|
46806
47307
|
const mode = raw?.sandboxEnforcement;
|
|
46807
47308
|
if (mode === "off" || mode === "warn" || mode === "block") return mode;
|
|
46808
47309
|
return "warn";
|
|
@@ -46812,8 +47313,8 @@ function readSandboxMode(projectRoot) {
|
|
|
46812
47313
|
}
|
|
46813
47314
|
function recordDispatchMetadata(projectRoot, meta3) {
|
|
46814
47315
|
try {
|
|
46815
|
-
const dir = (0,
|
|
46816
|
-
(0,
|
|
47316
|
+
const dir = (0, import_path81.join)(projectRoot, ".gossip");
|
|
47317
|
+
(0, import_fs74.mkdirSync)(dir, { recursive: true });
|
|
46817
47318
|
const snapshotted = { ...meta3 };
|
|
46818
47319
|
if (meta3.writeMode === "scoped" || meta3.writeMode === "worktree") {
|
|
46819
47320
|
try {
|
|
@@ -46831,15 +47332,15 @@ function recordDispatchMetadata(projectRoot, meta3) {
|
|
|
46831
47332
|
} catch {
|
|
46832
47333
|
}
|
|
46833
47334
|
}
|
|
46834
|
-
(0,
|
|
47335
|
+
(0, import_fs74.appendFileSync)((0, import_path81.join)(dir, METADATA_FILE), JSON.stringify(snapshotted) + "\n");
|
|
46835
47336
|
} catch {
|
|
46836
47337
|
}
|
|
46837
47338
|
}
|
|
46838
47339
|
function updateDispatchMetadata(projectRoot, taskId, patch) {
|
|
46839
47340
|
try {
|
|
46840
|
-
const p = (0,
|
|
46841
|
-
if (!(0,
|
|
46842
|
-
const raw = (0,
|
|
47341
|
+
const p = (0, import_path81.join)(projectRoot, ".gossip", METADATA_FILE);
|
|
47342
|
+
if (!(0, import_fs74.existsSync)(p)) return false;
|
|
47343
|
+
const raw = (0, import_fs74.readFileSync)(p, "utf-8");
|
|
46843
47344
|
const lines = raw.split("\n");
|
|
46844
47345
|
let patched = false;
|
|
46845
47346
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
@@ -46857,7 +47358,7 @@ function updateDispatchMetadata(projectRoot, taskId, patch) {
|
|
|
46857
47358
|
}
|
|
46858
47359
|
}
|
|
46859
47360
|
if (!patched) return false;
|
|
46860
|
-
(0,
|
|
47361
|
+
(0, import_fs74.writeFileSync)(p, lines.join("\n"));
|
|
46861
47362
|
return true;
|
|
46862
47363
|
} catch {
|
|
46863
47364
|
return false;
|
|
@@ -46866,14 +47367,14 @@ function updateDispatchMetadata(projectRoot, taskId, patch) {
|
|
|
46866
47367
|
function stampTaskSentinel(projectRoot, taskId) {
|
|
46867
47368
|
if (!taskId) return null;
|
|
46868
47369
|
try {
|
|
46869
|
-
const dir = (0,
|
|
46870
|
-
(0,
|
|
47370
|
+
const dir = (0, import_path81.join)(projectRoot, ".gossip", SENTINEL_DIR);
|
|
47371
|
+
(0, import_fs74.mkdirSync)(dir, { recursive: true });
|
|
46871
47372
|
const slug = taskId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
46872
|
-
const path7 = (0,
|
|
46873
|
-
const fd = (0,
|
|
46874
|
-
(0,
|
|
47373
|
+
const path7 = (0, import_path81.join)(dir, `${slug}.sentinel`);
|
|
47374
|
+
const fd = (0, import_fs74.openSync)(path7, "w");
|
|
47375
|
+
(0, import_fs74.closeSync)(fd);
|
|
46875
47376
|
const stampTime = new Date(Date.now() - 2e3);
|
|
46876
|
-
(0,
|
|
47377
|
+
(0, import_fs74.utimesSync)(path7, stampTime, stampTime);
|
|
46877
47378
|
return path7;
|
|
46878
47379
|
} catch {
|
|
46879
47380
|
return null;
|
|
@@ -46882,15 +47383,15 @@ function stampTaskSentinel(projectRoot, taskId) {
|
|
|
46882
47383
|
function cleanupTaskSentinel(sentinelPath) {
|
|
46883
47384
|
if (!sentinelPath) return;
|
|
46884
47385
|
try {
|
|
46885
|
-
(0,
|
|
47386
|
+
(0, import_fs74.unlinkSync)(sentinelPath);
|
|
46886
47387
|
} catch {
|
|
46887
47388
|
}
|
|
46888
47389
|
}
|
|
46889
47390
|
function lookupDispatchMetadata(projectRoot, taskId) {
|
|
46890
47391
|
try {
|
|
46891
|
-
const p = (0,
|
|
46892
|
-
if (!(0,
|
|
46893
|
-
const raw = (0,
|
|
47392
|
+
const p = (0, import_path81.join)(projectRoot, ".gossip", METADATA_FILE);
|
|
47393
|
+
if (!(0, import_fs74.existsSync)(p)) return null;
|
|
47394
|
+
const raw = (0, import_fs74.readFileSync)(p, "utf-8");
|
|
46894
47395
|
const lines = raw.split("\n").filter(Boolean);
|
|
46895
47396
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
46896
47397
|
try {
|
|
@@ -46922,21 +47423,21 @@ function parseGitStatus(porcelain) {
|
|
|
46922
47423
|
function normalizeScope(scope, projectRoot) {
|
|
46923
47424
|
let s = scope.trim();
|
|
46924
47425
|
if (!s) return "";
|
|
46925
|
-
if ((0,
|
|
46926
|
-
s = (0,
|
|
47426
|
+
if ((0, import_path81.isAbsolute)(s)) {
|
|
47427
|
+
s = (0, import_path81.relative)(projectRoot, s);
|
|
46927
47428
|
} else if (s.startsWith("./")) {
|
|
46928
47429
|
s = s.slice(2);
|
|
46929
47430
|
}
|
|
46930
|
-
s = (0,
|
|
47431
|
+
s = (0, import_path81.normalize)(s).replace(/\/+$/, "");
|
|
46931
47432
|
if (s === "." || s === "") return "";
|
|
46932
47433
|
return s;
|
|
46933
47434
|
}
|
|
46934
47435
|
function isInsideScope(filePath, scope) {
|
|
46935
47436
|
if (!scope) return true;
|
|
46936
|
-
const f = (0,
|
|
47437
|
+
const f = (0, import_path81.normalize)(filePath).replace(/^\.\//, "");
|
|
46937
47438
|
const s = scope.replace(/^\.\//, "").replace(/\/+$/, "");
|
|
46938
47439
|
if (f === s) return true;
|
|
46939
|
-
return f.startsWith(s + "/") || f.startsWith(s +
|
|
47440
|
+
return f.startsWith(s + "/") || f.startsWith(s + import_path81.sep);
|
|
46940
47441
|
}
|
|
46941
47442
|
function detectBoundaryEscapes(meta3, modifiedFiles, projectRoot) {
|
|
46942
47443
|
const mode = meta3.writeMode;
|
|
@@ -46997,8 +47498,8 @@ function recordBoundaryEscape(projectRoot, meta3, violations, mode) {
|
|
|
46997
47498
|
} catch {
|
|
46998
47499
|
}
|
|
46999
47500
|
try {
|
|
47000
|
-
const dir = (0,
|
|
47001
|
-
(0,
|
|
47501
|
+
const dir = (0, import_path81.join)(projectRoot, ".gossip");
|
|
47502
|
+
(0, import_fs74.mkdirSync)(dir, { recursive: true });
|
|
47002
47503
|
const line = {
|
|
47003
47504
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
47004
47505
|
taskId: meta3.taskId,
|
|
@@ -47009,15 +47510,15 @@ function recordBoundaryEscape(projectRoot, meta3, violations, mode) {
|
|
|
47009
47510
|
action: mode
|
|
47010
47511
|
// "warn" or "block"
|
|
47011
47512
|
};
|
|
47012
|
-
const escapeFile = (0,
|
|
47513
|
+
const escapeFile = (0, import_path81.join)(dir, BOUNDARY_ESCAPE_FILE);
|
|
47013
47514
|
rotateIfNeeded2(escapeFile, MAX_BOUNDARY_ESCAPE_BYTES);
|
|
47014
|
-
(0,
|
|
47515
|
+
(0, import_fs74.appendFileSync)(escapeFile, JSON.stringify(line) + "\n");
|
|
47015
47516
|
} catch {
|
|
47016
47517
|
}
|
|
47017
47518
|
}
|
|
47018
47519
|
function canonicalize(p) {
|
|
47019
47520
|
try {
|
|
47020
|
-
return (0,
|
|
47521
|
+
return (0, import_path81.resolve)(p).replace(/\/+$/, "") || "/";
|
|
47021
47522
|
} catch {
|
|
47022
47523
|
return p.replace(/\/+$/, "") || "/";
|
|
47023
47524
|
}
|
|
@@ -47128,7 +47629,7 @@ function buildAuditExclusions(projectRoot, ownWorktree, scope) {
|
|
|
47128
47629
|
for (const v of expandTmpVariants(wt)) excl.add(v);
|
|
47129
47630
|
}
|
|
47130
47631
|
if (scope) {
|
|
47131
|
-
const s = canonicalize((0,
|
|
47632
|
+
const s = canonicalize((0, import_path81.join)(projectRoot, scope));
|
|
47132
47633
|
for (const v of expandTmpVariants(s)) excl.add(v);
|
|
47133
47634
|
}
|
|
47134
47635
|
return Array.from(excl);
|
|
@@ -47217,12 +47718,12 @@ function auditFilesystemSinceSentinel(projectRoot, meta3, options = {}) {
|
|
|
47217
47718
|
return { violations: [], skipped: "win32" };
|
|
47218
47719
|
}
|
|
47219
47720
|
const sentinel = meta3.sentinelPath;
|
|
47220
|
-
if (!sentinel || !(0,
|
|
47721
|
+
if (!sentinel || !(0, import_fs74.existsSync)(sentinel)) {
|
|
47221
47722
|
return { violations: [], skipped: "sentinel missing" };
|
|
47222
47723
|
}
|
|
47223
47724
|
let sentinelMtimeMs = 0;
|
|
47224
47725
|
try {
|
|
47225
|
-
sentinelMtimeMs = (0,
|
|
47726
|
+
sentinelMtimeMs = (0, import_fs74.statSync)(sentinel).mtimeMs;
|
|
47226
47727
|
} catch {
|
|
47227
47728
|
}
|
|
47228
47729
|
if (sentinelMtimeMs === 0) {
|
|
@@ -47234,11 +47735,11 @@ function auditFilesystemSinceSentinel(projectRoot, meta3, options = {}) {
|
|
|
47234
47735
|
);
|
|
47235
47736
|
const exclusions = buildAuditExclusions(projectRoot, meta3.worktreePath, options.scope);
|
|
47236
47737
|
const findBin = options.findBinary ?? "find";
|
|
47237
|
-
const sentinelDir = canonicalize((0,
|
|
47738
|
+
const sentinelDir = canonicalize((0, import_path81.join)(projectRoot, ".gossip", SENTINEL_DIR));
|
|
47238
47739
|
const mainSet = /* @__PURE__ */ new Set();
|
|
47239
47740
|
const sensitiveSet = /* @__PURE__ */ new Set();
|
|
47240
47741
|
for (const root of scanRoots) {
|
|
47241
|
-
if (!(0,
|
|
47742
|
+
if (!(0, import_fs74.existsSync)(root)) continue;
|
|
47242
47743
|
const canonRoot = canonicalize(root);
|
|
47243
47744
|
const allExcl = [...exclusions, ...expandTmpVariants(sentinelDir)];
|
|
47244
47745
|
const args = buildFindPruneArgs(canonRoot, allExcl, sentinel);
|
|
@@ -47274,7 +47775,7 @@ function auditFilesystemSinceSentinel(projectRoot, meta3, options = {}) {
|
|
|
47274
47775
|
}
|
|
47275
47776
|
const sensitiveTargets = buildSensitiveTargets(platform2);
|
|
47276
47777
|
for (const target of sensitiveTargets) {
|
|
47277
|
-
if (!(0,
|
|
47778
|
+
if (!(0, import_fs74.existsSync)(target.path)) continue;
|
|
47278
47779
|
const canonTarget = canonicalize(target.path);
|
|
47279
47780
|
const args = buildSensitiveFindArgs(
|
|
47280
47781
|
canonTarget,
|
|
@@ -47348,8 +47849,8 @@ function auditFilesystemSinceSentinel(projectRoot, meta3, options = {}) {
|
|
|
47348
47849
|
}
|
|
47349
47850
|
function recordLayer3Violations(projectRoot, meta3, violations, source) {
|
|
47350
47851
|
try {
|
|
47351
|
-
const dir = (0,
|
|
47352
|
-
(0,
|
|
47852
|
+
const dir = (0, import_path81.join)(projectRoot, ".gossip");
|
|
47853
|
+
(0, import_fs74.mkdirSync)(dir, { recursive: true });
|
|
47353
47854
|
const ts2 = (/* @__PURE__ */ new Date()).toISOString();
|
|
47354
47855
|
const lines = violations.map(
|
|
47355
47856
|
(path7) => JSON.stringify({
|
|
@@ -47360,9 +47861,9 @@ function recordLayer3Violations(projectRoot, meta3, violations, source) {
|
|
|
47360
47861
|
source
|
|
47361
47862
|
})
|
|
47362
47863
|
);
|
|
47363
|
-
const escapeFile = (0,
|
|
47864
|
+
const escapeFile = (0, import_path81.join)(dir, BOUNDARY_ESCAPE_FILE);
|
|
47364
47865
|
rotateIfNeeded2(escapeFile, MAX_BOUNDARY_ESCAPE_BYTES);
|
|
47365
|
-
(0,
|
|
47866
|
+
(0, import_fs74.appendFileSync)(escapeFile, lines.join("\n") + "\n");
|
|
47366
47867
|
} catch {
|
|
47367
47868
|
}
|
|
47368
47869
|
if (source === "layer3-sensitive" && violations.length > 0) {
|
|
@@ -47419,14 +47920,14 @@ function runLayer3Audit(projectRoot, taskId) {
|
|
|
47419
47920
|
}
|
|
47420
47921
|
return { blockError, warnPrefix };
|
|
47421
47922
|
}
|
|
47422
|
-
var import_child_process12,
|
|
47923
|
+
var import_child_process12, import_fs74, import_os5, import_path81, METADATA_FILE, BOUNDARY_ESCAPE_FILE, SENTINEL_DIR, MAX_BOUNDARY_ESCAPE_BYTES, MAX_PREMISE_VERIFICATION_BYTES, BOUNDARY_ALLOWLIST, SYSTEM_PREFIXES, SCOPE_NOTE, NUMERAL, TARGETS, MODIFIER, CLAIM_PATTERNS, UNVERIFIED_NOTE_SENTINEL, LAYER3_KEEP_TMPDIR_PREFIXES, __test__;
|
|
47423
47924
|
var init_sandbox2 = __esm({
|
|
47424
47925
|
"apps/cli/src/sandbox.ts"() {
|
|
47425
47926
|
"use strict";
|
|
47426
47927
|
import_child_process12 = require("child_process");
|
|
47427
|
-
|
|
47928
|
+
import_fs74 = require("fs");
|
|
47428
47929
|
import_os5 = require("os");
|
|
47429
|
-
|
|
47930
|
+
import_path81 = require("path");
|
|
47430
47931
|
METADATA_FILE = "dispatch-metadata.jsonl";
|
|
47431
47932
|
BOUNDARY_ESCAPE_FILE = "boundary-escapes.jsonl";
|
|
47432
47933
|
SENTINEL_DIR = "sentinels";
|
|
@@ -47498,7 +47999,7 @@ __export(dispatch_prompt_storage_exports, {
|
|
|
47498
47999
|
writeDispatchPrompt: () => writeDispatchPrompt
|
|
47499
48000
|
});
|
|
47500
48001
|
function dispatchPromptsDir(projectRoot) {
|
|
47501
|
-
return (0,
|
|
48002
|
+
return (0, import_path82.join)(projectRoot, ".gossip", DISPATCH_PROMPTS_SUBDIR);
|
|
47502
48003
|
}
|
|
47503
48004
|
function assertSafeTaskId(taskId) {
|
|
47504
48005
|
if (typeof taskId !== "string" || taskId.length === 0) {
|
|
@@ -47511,35 +48012,35 @@ function assertSafeTaskId(taskId) {
|
|
|
47511
48012
|
function writeDispatchPrompt(projectRoot, taskId, body) {
|
|
47512
48013
|
assertSafeTaskId(taskId);
|
|
47513
48014
|
const dir = dispatchPromptsDir(projectRoot);
|
|
47514
|
-
(0,
|
|
47515
|
-
const finalPath = (0,
|
|
47516
|
-
const tmpPath = (0,
|
|
48015
|
+
(0, import_fs75.mkdirSync)(dir, { recursive: true });
|
|
48016
|
+
const finalPath = (0, import_path82.join)(dir, `${taskId}.txt`);
|
|
48017
|
+
const tmpPath = (0, import_path82.join)(dir, `${taskId}.txt.${(0, import_crypto30.randomUUID)().slice(0, 8)}.tmp`);
|
|
47517
48018
|
try {
|
|
47518
|
-
(0,
|
|
47519
|
-
(0,
|
|
48019
|
+
(0, import_fs75.writeFileSync)(tmpPath, body, "utf8");
|
|
48020
|
+
(0, import_fs75.renameSync)(tmpPath, finalPath);
|
|
47520
48021
|
} catch (err) {
|
|
47521
48022
|
try {
|
|
47522
|
-
(0,
|
|
48023
|
+
(0, import_fs75.unlinkSync)(tmpPath);
|
|
47523
48024
|
} catch {
|
|
47524
48025
|
}
|
|
47525
48026
|
throw err;
|
|
47526
48027
|
}
|
|
47527
|
-
return (0,
|
|
48028
|
+
return (0, import_path82.resolve)(finalPath);
|
|
47528
48029
|
}
|
|
47529
48030
|
function cleanupExpiredDispatchPrompts(projectRoot, maxAgeMs = DEFAULT_PROMPT_TTL_MS, capBytes = DISPATCH_PROMPT_CAP_BYTES) {
|
|
47530
48031
|
const dir = dispatchPromptsDir(projectRoot);
|
|
47531
|
-
if (!(0,
|
|
48032
|
+
if (!(0, import_fs75.existsSync)(dir)) return { evictedAge: 0, evictedCap: 0 };
|
|
47532
48033
|
const now = Date.now();
|
|
47533
48034
|
let entries = [];
|
|
47534
48035
|
try {
|
|
47535
|
-
for (const name of (0,
|
|
47536
|
-
const path7 = (0,
|
|
48036
|
+
for (const name of (0, import_fs75.readdirSync)(dir)) {
|
|
48037
|
+
const path7 = (0, import_path82.join)(dir, name);
|
|
47537
48038
|
try {
|
|
47538
|
-
const st = (0,
|
|
48039
|
+
const st = (0, import_fs75.statSync)(path7);
|
|
47539
48040
|
if (!st.isFile()) continue;
|
|
47540
48041
|
entries.push({ name, path: path7, mtimeMs: st.mtimeMs, size: st.size });
|
|
47541
48042
|
} catch (err) {
|
|
47542
|
-
process.stderr.write(`[gossipcat] dispatch-prompt stat failed for ${(0,
|
|
48043
|
+
process.stderr.write(`[gossipcat] dispatch-prompt stat failed for ${(0, import_path82.basename)(path7)}: ${err.message}
|
|
47543
48044
|
`);
|
|
47544
48045
|
}
|
|
47545
48046
|
}
|
|
@@ -47553,7 +48054,7 @@ function cleanupExpiredDispatchPrompts(projectRoot, maxAgeMs = DEFAULT_PROMPT_TT
|
|
|
47553
48054
|
for (const e of entries) {
|
|
47554
48055
|
if (now - e.mtimeMs > maxAgeMs) {
|
|
47555
48056
|
try {
|
|
47556
|
-
(0,
|
|
48057
|
+
(0, import_fs75.unlinkSync)(e.path);
|
|
47557
48058
|
evictedAge++;
|
|
47558
48059
|
} catch (err) {
|
|
47559
48060
|
process.stderr.write(`[gossipcat] dispatch-prompt unlink failed for ${e.name}: ${err.message}
|
|
@@ -47570,7 +48071,7 @@ function cleanupExpiredDispatchPrompts(projectRoot, maxAgeMs = DEFAULT_PROMPT_TT
|
|
|
47570
48071
|
for (const e of survivors) {
|
|
47571
48072
|
if (totalBytes <= capBytes) break;
|
|
47572
48073
|
try {
|
|
47573
|
-
(0,
|
|
48074
|
+
(0, import_fs75.unlinkSync)(e.path);
|
|
47574
48075
|
totalBytes -= e.size;
|
|
47575
48076
|
evictedCap++;
|
|
47576
48077
|
} catch (err) {
|
|
@@ -47583,18 +48084,18 @@ function cleanupExpiredDispatchPrompts(projectRoot, maxAgeMs = DEFAULT_PROMPT_TT
|
|
|
47583
48084
|
}
|
|
47584
48085
|
function pruneOrphanDispatchPrompts(projectRoot, knownTaskIds, maxAgeMs = DEFAULT_PROMPT_TTL_MS) {
|
|
47585
48086
|
const dir = dispatchPromptsDir(projectRoot);
|
|
47586
|
-
if (!(0,
|
|
48087
|
+
if (!(0, import_fs75.existsSync)(dir)) return { orphans: 0, aged: 0 };
|
|
47587
48088
|
const now = Date.now();
|
|
47588
48089
|
let orphans = 0;
|
|
47589
48090
|
let aged = 0;
|
|
47590
48091
|
try {
|
|
47591
|
-
for (const name of (0,
|
|
48092
|
+
for (const name of (0, import_fs75.readdirSync)(dir)) {
|
|
47592
48093
|
if (!name.endsWith(".txt")) continue;
|
|
47593
48094
|
const taskId = name.slice(0, -4);
|
|
47594
|
-
const path7 = (0,
|
|
48095
|
+
const path7 = (0, import_path82.join)(dir, name);
|
|
47595
48096
|
let mtimeMs = 0;
|
|
47596
48097
|
try {
|
|
47597
|
-
mtimeMs = (0,
|
|
48098
|
+
mtimeMs = (0, import_fs75.statSync)(path7).mtimeMs;
|
|
47598
48099
|
} catch {
|
|
47599
48100
|
continue;
|
|
47600
48101
|
}
|
|
@@ -47602,7 +48103,7 @@ function pruneOrphanDispatchPrompts(projectRoot, knownTaskIds, maxAgeMs = DEFAUL
|
|
|
47602
48103
|
const orphaned = !knownTaskIds.has(taskId);
|
|
47603
48104
|
if (tooOld || orphaned) {
|
|
47604
48105
|
try {
|
|
47605
|
-
(0,
|
|
48106
|
+
(0, import_fs75.unlinkSync)(path7);
|
|
47606
48107
|
if (orphaned) orphans++;
|
|
47607
48108
|
if (tooOld) aged++;
|
|
47608
48109
|
} catch (err) {
|
|
@@ -47619,14 +48120,14 @@ function pruneOrphanDispatchPrompts(projectRoot, knownTaskIds, maxAgeMs = DEFAUL
|
|
|
47619
48120
|
}
|
|
47620
48121
|
function dispatchPromptPath(projectRoot, taskId) {
|
|
47621
48122
|
assertSafeTaskId(taskId);
|
|
47622
|
-
return (0,
|
|
48123
|
+
return (0, import_path82.resolve)((0, import_path82.join)(dispatchPromptsDir(projectRoot), `${taskId}.txt`));
|
|
47623
48124
|
}
|
|
47624
|
-
var
|
|
48125
|
+
var import_fs75, import_path82, import_crypto30, SAFE_TASK_ID2, DISPATCH_PROMPT_CAP_BYTES, DEFAULT_PROMPT_TTL_MS, DISPATCH_PROMPTS_SUBDIR;
|
|
47625
48126
|
var init_dispatch_prompt_storage = __esm({
|
|
47626
48127
|
"apps/cli/src/handlers/dispatch-prompt-storage.ts"() {
|
|
47627
48128
|
"use strict";
|
|
47628
|
-
|
|
47629
|
-
|
|
48129
|
+
import_fs75 = require("fs");
|
|
48130
|
+
import_path82 = require("path");
|
|
47630
48131
|
import_crypto30 = require("crypto");
|
|
47631
48132
|
SAFE_TASK_ID2 = /^(?!.*\.\.)[A-Za-z0-9._-]{1,64}$/;
|
|
47632
48133
|
DISPATCH_PROMPT_CAP_BYTES = 100 * 1024 * 1024;
|
|
@@ -47680,9 +48181,9 @@ function getCommitRange(preSha, postSha) {
|
|
|
47680
48181
|
function appendViolationRecord(record2) {
|
|
47681
48182
|
try {
|
|
47682
48183
|
const projectRoot = process.cwd();
|
|
47683
|
-
(0,
|
|
47684
|
-
const logPath = (0,
|
|
47685
|
-
(0,
|
|
48184
|
+
(0, import_fs76.mkdirSync)((0, import_path83.join)(projectRoot, ".gossip"), { recursive: true });
|
|
48185
|
+
const logPath = (0, import_path83.join)(projectRoot, PROCESS_VIOLATIONS_FILE);
|
|
48186
|
+
(0, import_fs76.appendFileSync)(logPath, JSON.stringify(record2) + "\n", "utf8");
|
|
47686
48187
|
} catch (err) {
|
|
47687
48188
|
process.stderr.write(`[gossipcat] ref-allowlist: failed to append violation record: ${err.message}
|
|
47688
48189
|
`);
|
|
@@ -47732,12 +48233,12 @@ Full audit trail at .gossip/process-violations.jsonl
|
|
|
47732
48233
|
`
|
|
47733
48234
|
);
|
|
47734
48235
|
}
|
|
47735
|
-
var
|
|
48236
|
+
var import_fs76, import_path83, import_child_process13, PROCESS_VIOLATIONS_FILE;
|
|
47736
48237
|
var init_ref_allowlist_detection = __esm({
|
|
47737
48238
|
"apps/cli/src/handlers/ref-allowlist-detection.ts"() {
|
|
47738
48239
|
"use strict";
|
|
47739
|
-
|
|
47740
|
-
|
|
48240
|
+
import_fs76 = require("fs");
|
|
48241
|
+
import_path83 = require("path");
|
|
47741
48242
|
import_child_process13 = require("child_process");
|
|
47742
48243
|
PROCESS_VIOLATIONS_FILE = ".gossip/process-violations.jsonl";
|
|
47743
48244
|
}
|
|
@@ -47772,12 +48273,12 @@ async function synthesizeTimeoutRound(snapshot, consensusId, missingAgents, llm,
|
|
|
47772
48273
|
snapshot.relayCrossReviewSkipped
|
|
47773
48274
|
);
|
|
47774
48275
|
try {
|
|
47775
|
-
const { writeFileSync:
|
|
47776
|
-
const { join:
|
|
47777
|
-
const reportsDir =
|
|
47778
|
-
|
|
48276
|
+
const { writeFileSync: writeFileSync38, mkdirSync: mkdirSync45 } = require("fs");
|
|
48277
|
+
const { join: join91 } = require("path");
|
|
48278
|
+
const reportsDir = join91(projectRoot, ".gossip", "consensus-reports");
|
|
48279
|
+
mkdirSync45(reportsDir, { recursive: true });
|
|
47779
48280
|
const topic = snapshot.allResults?.find((r) => r.task)?.task?.slice(0, 500) || "";
|
|
47780
|
-
|
|
48281
|
+
writeFileSync38(join91(reportsDir, `${consensusId}.json`), JSON.stringify({
|
|
47781
48282
|
id: consensusId,
|
|
47782
48283
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
47783
48284
|
topic,
|
|
@@ -47995,13 +48496,13 @@ async function handleRelayCrossReview(consensus_id, agent_id, result) {
|
|
|
47995
48496
|
synthSnapshot.relayCrossReviewSkipped
|
|
47996
48497
|
);
|
|
47997
48498
|
try {
|
|
47998
|
-
const { writeFileSync:
|
|
47999
|
-
const { join:
|
|
48000
|
-
const reportsDir =
|
|
48001
|
-
|
|
48002
|
-
const reportPath =
|
|
48499
|
+
const { writeFileSync: writeFileSync38, mkdirSync: mkdirSync45 } = require("fs");
|
|
48500
|
+
const { join: join91 } = require("path");
|
|
48501
|
+
const reportsDir = join91(process.cwd(), ".gossip", "consensus-reports");
|
|
48502
|
+
mkdirSync45(reportsDir, { recursive: true });
|
|
48503
|
+
const reportPath = join91(reportsDir, `${consensus_id}.json`);
|
|
48003
48504
|
const topic = synthSnapshot.allResults?.find((r) => r.task)?.task?.slice(0, 500) || "";
|
|
48004
|
-
|
|
48505
|
+
writeFileSync38(reportPath, JSON.stringify({
|
|
48005
48506
|
id: consensus_id,
|
|
48006
48507
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
48007
48508
|
topic,
|
|
@@ -48098,10 +48599,10 @@ function persistPendingConsensus() {
|
|
|
48098
48599
|
try {
|
|
48099
48600
|
const projectRoot = ctx.mainAgent?.projectRoot;
|
|
48100
48601
|
if (!projectRoot) return;
|
|
48101
|
-
const { writeFileSync:
|
|
48102
|
-
const { join:
|
|
48103
|
-
const dir =
|
|
48104
|
-
|
|
48602
|
+
const { writeFileSync: writeFileSync38, mkdirSync: mkdirSync45 } = require("fs");
|
|
48603
|
+
const { join: join91 } = require("path");
|
|
48604
|
+
const dir = join91(projectRoot, ".gossip");
|
|
48605
|
+
mkdirSync45(dir, { recursive: true });
|
|
48105
48606
|
const rounds = {};
|
|
48106
48607
|
for (const [id, round] of ctx.pendingConsensusRounds) {
|
|
48107
48608
|
rounds[id] = {
|
|
@@ -48135,7 +48636,7 @@ function persistPendingConsensus() {
|
|
|
48135
48636
|
} : void 0
|
|
48136
48637
|
};
|
|
48137
48638
|
}
|
|
48138
|
-
|
|
48639
|
+
writeFileSync38(join91(dir, CONSENSUS_FILE), JSON.stringify(rounds));
|
|
48139
48640
|
} catch (err) {
|
|
48140
48641
|
process.stderr.write(`[gossipcat] persistPendingConsensus failed: ${err.message}
|
|
48141
48642
|
`);
|
|
@@ -48143,11 +48644,11 @@ function persistPendingConsensus() {
|
|
|
48143
48644
|
}
|
|
48144
48645
|
function restorePendingConsensus(projectRoot) {
|
|
48145
48646
|
try {
|
|
48146
|
-
const { existsSync: existsSync73, readFileSync:
|
|
48147
|
-
const { join:
|
|
48148
|
-
const filePath =
|
|
48647
|
+
const { existsSync: existsSync73, readFileSync: readFileSync69, unlinkSync: unlinkSync16 } = require("fs");
|
|
48648
|
+
const { join: join91 } = require("path");
|
|
48649
|
+
const filePath = join91(projectRoot, ".gossip", CONSENSUS_FILE);
|
|
48149
48650
|
if (!existsSync73(filePath)) return;
|
|
48150
|
-
const raw = JSON.parse(
|
|
48651
|
+
const raw = JSON.parse(readFileSync69(filePath, "utf-8"));
|
|
48151
48652
|
const now = Date.now();
|
|
48152
48653
|
for (const [id, data] of Object.entries(raw)) {
|
|
48153
48654
|
if (now > data.deadline + 3e5) {
|
|
@@ -48184,7 +48685,7 @@ function restorePendingConsensus(projectRoot) {
|
|
|
48184
48685
|
process.stderr.write(`[gossipcat] Restored consensus round ${id} \u2014 ${data.pendingNativeAgents?.length ?? 0} agents pending
|
|
48185
48686
|
`);
|
|
48186
48687
|
}
|
|
48187
|
-
|
|
48688
|
+
unlinkSync16(filePath);
|
|
48188
48689
|
} catch {
|
|
48189
48690
|
}
|
|
48190
48691
|
}
|
|
@@ -48277,11 +48778,11 @@ function taskWasInConsensusRound(taskId, agentId, rounds, recentTaskIds, recentA
|
|
|
48277
48778
|
}
|
|
48278
48779
|
function appendRelayWarning(projectRoot, entry) {
|
|
48279
48780
|
try {
|
|
48280
|
-
const { appendFileSync:
|
|
48281
|
-
const { join:
|
|
48282
|
-
const dir =
|
|
48283
|
-
|
|
48284
|
-
|
|
48781
|
+
const { appendFileSync: appendFileSync22, mkdirSync: mkdirSync45 } = require("fs");
|
|
48782
|
+
const { join: join91 } = require("path");
|
|
48783
|
+
const dir = join91(projectRoot, ".gossip");
|
|
48784
|
+
mkdirSync45(dir, { recursive: true });
|
|
48785
|
+
appendFileSync22(join91(dir, RELAY_WARNINGS_FILE), JSON.stringify(entry) + "\n", "utf8");
|
|
48285
48786
|
} catch (err) {
|
|
48286
48787
|
process.stderr.write(`[gossipcat] append relay-warning failed: ${err.message}
|
|
48287
48788
|
`);
|
|
@@ -48290,8 +48791,8 @@ function appendRelayWarning(projectRoot, entry) {
|
|
|
48290
48791
|
function checkWorktreeEngaged(startedAt, projectRoot = process.cwd()) {
|
|
48291
48792
|
try {
|
|
48292
48793
|
const { readdirSync: readdirSync22, statSync: statSync36 } = require("fs");
|
|
48293
|
-
const { join:
|
|
48294
|
-
const wtDir =
|
|
48794
|
+
const { join: join91 } = require("path");
|
|
48795
|
+
const wtDir = join91(projectRoot, ".claude", "worktrees");
|
|
48295
48796
|
let entries;
|
|
48296
48797
|
try {
|
|
48297
48798
|
entries = readdirSync22(wtDir);
|
|
@@ -48302,7 +48803,7 @@ function checkWorktreeEngaged(startedAt, projectRoot = process.cwd()) {
|
|
|
48302
48803
|
for (const entry of entries) {
|
|
48303
48804
|
if (!entry.startsWith("agent-")) continue;
|
|
48304
48805
|
try {
|
|
48305
|
-
const st = statSync36(
|
|
48806
|
+
const st = statSync36(join91(wtDir, entry));
|
|
48306
48807
|
if (st.isDirectory() && st.mtimeMs >= startedAt - 2e3) return true;
|
|
48307
48808
|
} catch {
|
|
48308
48809
|
}
|
|
@@ -49183,7 +49684,7 @@ function computeSkillFingerprint(paths) {
|
|
|
49183
49684
|
const pairs = [];
|
|
49184
49685
|
for (const p of paths) {
|
|
49185
49686
|
try {
|
|
49186
|
-
const st = (0,
|
|
49687
|
+
const st = (0, import_fs77.statSync)(p);
|
|
49187
49688
|
pairs.push(`${p}:${st.mtimeMs}`);
|
|
49188
49689
|
} catch {
|
|
49189
49690
|
}
|
|
@@ -49218,7 +49719,7 @@ function setCachedPrompt(k, e) {
|
|
|
49218
49719
|
const evicted = promptCache.get(oldestKey);
|
|
49219
49720
|
promptCache.delete(oldestKey);
|
|
49220
49721
|
try {
|
|
49221
|
-
(0,
|
|
49722
|
+
(0, import_fs77.unlinkSync)(evicted.skillsSectionPath);
|
|
49222
49723
|
} catch {
|
|
49223
49724
|
}
|
|
49224
49725
|
const evictedAgentId = oldestKey.split("|")[0] ?? "_system";
|
|
@@ -49232,7 +49733,7 @@ function invalidateAgent(agentId) {
|
|
|
49232
49733
|
if (k.startsWith(`${agentId}|`)) {
|
|
49233
49734
|
promptCache.delete(k);
|
|
49234
49735
|
try {
|
|
49235
|
-
(0,
|
|
49736
|
+
(0, import_fs77.unlinkSync)(v.skillsSectionPath);
|
|
49236
49737
|
} catch {
|
|
49237
49738
|
}
|
|
49238
49739
|
emitCacheEvictedSignal(agentId, "invalidation");
|
|
@@ -49245,7 +49746,7 @@ function invalidateAll() {
|
|
|
49245
49746
|
const n = promptCache.size;
|
|
49246
49747
|
for (const v of promptCache.values()) {
|
|
49247
49748
|
try {
|
|
49248
|
-
(0,
|
|
49749
|
+
(0, import_fs77.unlinkSync)(v.skillsSectionPath);
|
|
49249
49750
|
} catch {
|
|
49250
49751
|
}
|
|
49251
49752
|
}
|
|
@@ -49292,29 +49793,29 @@ function writeCachedSkillsSection(projectRoot, fingerprint2, skillsSection) {
|
|
|
49292
49793
|
if (!/^[a-f0-9]{64}$/.test(fingerprint2)) {
|
|
49293
49794
|
throw new Error(`writeCachedSkillsSection: invalid fingerprint ${JSON.stringify(fingerprint2).slice(0, 32)}`);
|
|
49294
49795
|
}
|
|
49295
|
-
const dir = (0,
|
|
49296
|
-
(0,
|
|
49297
|
-
const finalPath = (0,
|
|
49298
|
-
const tmpPath = (0,
|
|
49796
|
+
const dir = (0, import_path84.join)(projectRoot, ".gossip", "dispatch-prompts", "cache");
|
|
49797
|
+
(0, import_fs77.mkdirSync)(dir, { recursive: true });
|
|
49798
|
+
const finalPath = (0, import_path84.join)(dir, `skills-${fingerprint2}.txt`);
|
|
49799
|
+
const tmpPath = (0, import_path84.join)(dir, `skills-${fingerprint2}.txt.${(0, import_crypto33.randomUUID)().slice(0, 8)}.tmp`);
|
|
49299
49800
|
try {
|
|
49300
|
-
(0,
|
|
49301
|
-
(0,
|
|
49801
|
+
(0, import_fs77.writeFileSync)(tmpPath, skillsSection, "utf8");
|
|
49802
|
+
(0, import_fs77.renameSync)(tmpPath, finalPath);
|
|
49302
49803
|
} catch (err) {
|
|
49303
49804
|
try {
|
|
49304
|
-
(0,
|
|
49805
|
+
(0, import_fs77.unlinkSync)(tmpPath);
|
|
49305
49806
|
} catch {
|
|
49306
49807
|
}
|
|
49307
49808
|
throw err;
|
|
49308
49809
|
}
|
|
49309
|
-
return (0,
|
|
49810
|
+
return (0, import_path84.resolve)(finalPath);
|
|
49310
49811
|
}
|
|
49311
|
-
var import_crypto32,
|
|
49812
|
+
var import_crypto32, import_fs77, import_path84, import_crypto33, DISPATCH_PROMPT_CACHE_MAX_ENTRIES, promptCache;
|
|
49312
49813
|
var init_dispatch_prompt_cache = __esm({
|
|
49313
49814
|
"apps/cli/src/handlers/dispatch-prompt-cache.ts"() {
|
|
49314
49815
|
"use strict";
|
|
49315
49816
|
import_crypto32 = require("crypto");
|
|
49316
|
-
|
|
49317
|
-
|
|
49817
|
+
import_fs77 = require("fs");
|
|
49818
|
+
import_path84 = require("path");
|
|
49318
49819
|
import_crypto33 = require("crypto");
|
|
49319
49820
|
DISPATCH_PROMPT_CACHE_MAX_ENTRIES = 64;
|
|
49320
49821
|
promptCache = /* @__PURE__ */ new Map();
|
|
@@ -49384,31 +49885,31 @@ __export(check_effectiveness_runner_exports, {
|
|
|
49384
49885
|
runCheckEffectivenessForAllSkills: () => runCheckEffectivenessForAllSkills
|
|
49385
49886
|
});
|
|
49386
49887
|
function writeHealthAtomic(projectRoot, record2) {
|
|
49387
|
-
const dir = (0,
|
|
49388
|
-
const finalPath = (0,
|
|
49888
|
+
const dir = (0, import_path86.join)(projectRoot, ".gossip");
|
|
49889
|
+
const finalPath = (0, import_path86.join)(dir, "skill-runner-health.json");
|
|
49389
49890
|
const tmpPath = finalPath + ".tmp";
|
|
49390
49891
|
try {
|
|
49391
|
-
(0,
|
|
49392
|
-
(0,
|
|
49393
|
-
(0,
|
|
49892
|
+
(0, import_fs80.mkdirSync)(dir, { recursive: true });
|
|
49893
|
+
(0, import_fs80.writeFileSync)(tmpPath, JSON.stringify(record2, null, 2));
|
|
49894
|
+
(0, import_fs80.renameSync)(tmpPath, finalPath);
|
|
49394
49895
|
} catch (e) {
|
|
49395
49896
|
process.stderr.write(`[gossipcat] checkEffectiveness: health write failed: ${e.message}
|
|
49396
49897
|
`);
|
|
49397
49898
|
try {
|
|
49398
|
-
if ((0,
|
|
49899
|
+
if ((0, import_fs80.existsSync)(tmpPath)) (0, import_fs80.unlinkSync)(tmpPath);
|
|
49399
49900
|
} catch {
|
|
49400
49901
|
}
|
|
49401
49902
|
}
|
|
49402
49903
|
}
|
|
49403
49904
|
async function runCheckEffectivenessForAllSkills(opts) {
|
|
49404
49905
|
const startedAt = Date.now();
|
|
49405
|
-
const baseDir = (0,
|
|
49906
|
+
const baseDir = (0, import_path86.join)(opts.projectRoot, ".gossip", "agents");
|
|
49406
49907
|
let agentDirs = [];
|
|
49407
49908
|
let canonicalBaseDir;
|
|
49408
|
-
if ((0,
|
|
49909
|
+
if ((0, import_fs80.existsSync)(baseDir)) {
|
|
49409
49910
|
try {
|
|
49410
|
-
canonicalBaseDir = (0,
|
|
49411
|
-
agentDirs = (0,
|
|
49911
|
+
canonicalBaseDir = (0, import_fs80.realpathSync)(baseDir);
|
|
49912
|
+
agentDirs = (0, import_fs80.readdirSync)(canonicalBaseDir);
|
|
49412
49913
|
} catch {
|
|
49413
49914
|
}
|
|
49414
49915
|
}
|
|
@@ -49427,11 +49928,11 @@ async function runCheckEffectivenessForAllSkills(opts) {
|
|
|
49427
49928
|
for (const agentId of agentDirs) {
|
|
49428
49929
|
if (agentId.startsWith("_")) continue;
|
|
49429
49930
|
if (!SAFE_NAME2.test(agentId)) continue;
|
|
49430
|
-
const skillsDir = (0,
|
|
49431
|
-
if (!(0,
|
|
49931
|
+
const skillsDir = (0, import_path86.join)(canonicalBaseDir, agentId, "skills");
|
|
49932
|
+
if (!(0, import_fs80.existsSync)(skillsDir)) continue;
|
|
49432
49933
|
let canonicalSkillsDir;
|
|
49433
49934
|
try {
|
|
49434
|
-
canonicalSkillsDir = (0,
|
|
49935
|
+
canonicalSkillsDir = (0, import_fs80.realpathSync)(skillsDir);
|
|
49435
49936
|
} catch {
|
|
49436
49937
|
continue;
|
|
49437
49938
|
}
|
|
@@ -49442,7 +49943,7 @@ async function runCheckEffectivenessForAllSkills(opts) {
|
|
|
49442
49943
|
}
|
|
49443
49944
|
const role = opts.registryGet(agentId)?.role;
|
|
49444
49945
|
if (role === "implementer") continue;
|
|
49445
|
-
const files = (0,
|
|
49946
|
+
const files = (0, import_fs80.readdirSync)(canonicalSkillsDir).filter((f) => f.endsWith(".md"));
|
|
49446
49947
|
for (const file2 of files) {
|
|
49447
49948
|
const category = file2.replace(/\.md$/, "");
|
|
49448
49949
|
if (!SAFE_NAME2.test(category)) continue;
|
|
@@ -49489,12 +49990,12 @@ async function runCheckEffectivenessForAllSkills(opts) {
|
|
|
49489
49990
|
last_error: lastError
|
|
49490
49991
|
});
|
|
49491
49992
|
}
|
|
49492
|
-
var
|
|
49993
|
+
var import_fs80, import_path86, SAFE_NAME2;
|
|
49493
49994
|
var init_check_effectiveness_runner = __esm({
|
|
49494
49995
|
"apps/cli/src/handlers/check-effectiveness-runner.ts"() {
|
|
49495
49996
|
"use strict";
|
|
49496
|
-
|
|
49497
|
-
|
|
49997
|
+
import_fs80 = require("fs");
|
|
49998
|
+
import_path86 = require("path");
|
|
49498
49999
|
init_dispatch_prompt_cache();
|
|
49499
50000
|
SAFE_NAME2 = /^(?!.*\.\.)[a-zA-Z0-9._-]+$/;
|
|
49500
50001
|
}
|
|
@@ -49507,20 +50008,20 @@ __export(skill_develop_audit_exports, {
|
|
|
49507
50008
|
});
|
|
49508
50009
|
function appendSkillDevelopAudit(entry) {
|
|
49509
50010
|
try {
|
|
49510
|
-
const gossipDir = (0,
|
|
49511
|
-
(0,
|
|
50011
|
+
const gossipDir = (0, import_path87.join)(process.cwd(), ".gossip");
|
|
50012
|
+
(0, import_fs81.mkdirSync)(gossipDir, { recursive: true });
|
|
49512
50013
|
const line = JSON.stringify(entry) + "\n";
|
|
49513
|
-
(0,
|
|
49514
|
-
(0,
|
|
50014
|
+
(0, import_fs81.appendFileSync)((0, import_path87.join)(gossipDir, AUDIT_FILE2), line);
|
|
50015
|
+
(0, import_fs81.appendFileSync)((0, import_path87.join)(gossipDir, LEGACY_FILE), line);
|
|
49515
50016
|
} catch {
|
|
49516
50017
|
}
|
|
49517
50018
|
}
|
|
49518
|
-
var
|
|
50019
|
+
var import_fs81, import_path87, AUDIT_FILE2, LEGACY_FILE;
|
|
49519
50020
|
var init_skill_develop_audit = __esm({
|
|
49520
50021
|
"apps/cli/src/handlers/skill-develop-audit.ts"() {
|
|
49521
50022
|
"use strict";
|
|
49522
|
-
|
|
49523
|
-
|
|
50023
|
+
import_fs81 = require("fs");
|
|
50024
|
+
import_path87 = require("path");
|
|
49524
50025
|
AUDIT_FILE2 = "skill-develop-audit.jsonl";
|
|
49525
50026
|
LEGACY_FILE = "forced-skill-develops.jsonl";
|
|
49526
50027
|
}
|
|
@@ -49578,14 +50079,14 @@ var keychain_exports = {};
|
|
|
49578
50079
|
__export(keychain_exports, {
|
|
49579
50080
|
Keychain: () => Keychain
|
|
49580
50081
|
});
|
|
49581
|
-
var import_child_process14, import_os6,
|
|
50082
|
+
var import_child_process14, import_os6, import_fs85, import_path91, import_crypto35, DEFAULT_SERVICE_NAME, VALID_PROVIDERS3, ENCRYPTED_FILE, ALGO, Keychain;
|
|
49582
50083
|
var init_keychain = __esm({
|
|
49583
50084
|
"apps/cli/src/keychain.ts"() {
|
|
49584
50085
|
"use strict";
|
|
49585
50086
|
import_child_process14 = require("child_process");
|
|
49586
50087
|
import_os6 = require("os");
|
|
49587
|
-
|
|
49588
|
-
|
|
50088
|
+
import_fs85 = require("fs");
|
|
50089
|
+
import_path91 = require("path");
|
|
49589
50090
|
import_crypto35 = require("crypto");
|
|
49590
50091
|
DEFAULT_SERVICE_NAME = "gossip-mesh";
|
|
49591
50092
|
VALID_PROVIDERS3 = /^[a-zA-Z0-9_-]{1,32}$/;
|
|
@@ -49629,10 +50130,10 @@ var init_keychain = __esm({
|
|
|
49629
50130
|
return (0, import_crypto35.pbkdf2Sync)(seed, salt, 6e5, 32, "sha256");
|
|
49630
50131
|
}
|
|
49631
50132
|
loadEncryptedFile() {
|
|
49632
|
-
const filePath = (0,
|
|
49633
|
-
if (!(0,
|
|
50133
|
+
const filePath = (0, import_path91.join)(process.cwd(), ENCRYPTED_FILE);
|
|
50134
|
+
if (!(0, import_fs85.existsSync)(filePath)) return;
|
|
49634
50135
|
try {
|
|
49635
|
-
const raw = (0,
|
|
50136
|
+
const raw = (0, import_fs85.readFileSync)(filePath);
|
|
49636
50137
|
if (raw.length < 61) return;
|
|
49637
50138
|
const salt = raw.subarray(0, 32);
|
|
49638
50139
|
const iv = raw.subarray(32, 44);
|
|
@@ -49650,9 +50151,9 @@ var init_keychain = __esm({
|
|
|
49650
50151
|
}
|
|
49651
50152
|
}
|
|
49652
50153
|
saveEncryptedFile() {
|
|
49653
|
-
const filePath = (0,
|
|
49654
|
-
const dir = (0,
|
|
49655
|
-
if (!(0,
|
|
50154
|
+
const filePath = (0, import_path91.join)(process.cwd(), ENCRYPTED_FILE);
|
|
50155
|
+
const dir = (0, import_path91.join)(process.cwd(), ".gossip");
|
|
50156
|
+
if (!(0, import_fs85.existsSync)(dir)) (0, import_fs85.mkdirSync)(dir, { recursive: true });
|
|
49656
50157
|
const data = JSON.stringify(Object.fromEntries(this.inMemoryStore));
|
|
49657
50158
|
const salt = (0, import_crypto35.randomBytes)(32);
|
|
49658
50159
|
const iv = (0, import_crypto35.randomBytes)(12);
|
|
@@ -49660,7 +50161,7 @@ var init_keychain = __esm({
|
|
|
49660
50161
|
const cipher = (0, import_crypto35.createCipheriv)(ALGO, key, iv);
|
|
49661
50162
|
const encrypted = Buffer.concat([cipher.update(data, "utf8"), cipher.final()]);
|
|
49662
50163
|
const tag = cipher.getAuthTag();
|
|
49663
|
-
(0,
|
|
50164
|
+
(0, import_fs85.writeFileSync)(filePath, Buffer.concat([salt, iv, tag, encrypted]), { mode: 384 });
|
|
49664
50165
|
}
|
|
49665
50166
|
isKeychainAvailable() {
|
|
49666
50167
|
if ((0, import_os6.platform)() === "darwin") {
|
|
@@ -49757,8 +50258,8 @@ __export(code_launch_exports, {
|
|
|
49757
50258
|
});
|
|
49758
50259
|
function safeReadJson(filePath) {
|
|
49759
50260
|
try {
|
|
49760
|
-
if (!(0,
|
|
49761
|
-
const raw = (0,
|
|
50261
|
+
if (!(0, import_fs86.existsSync)(filePath)) return null;
|
|
50262
|
+
const raw = (0, import_fs86.readFileSync)(filePath, "utf-8");
|
|
49762
50263
|
return JSON.parse(raw);
|
|
49763
50264
|
} catch {
|
|
49764
50265
|
return null;
|
|
@@ -49776,8 +50277,8 @@ function isGossipMcpEntry(entry) {
|
|
|
49776
50277
|
}
|
|
49777
50278
|
function detectServerName(cwd) {
|
|
49778
50279
|
const candidates = [
|
|
49779
|
-
[(0,
|
|
49780
|
-
[(0,
|
|
50280
|
+
[(0, import_path92.join)(cwd, ".mcp.json"), safeReadJson((0, import_path92.join)(cwd, ".mcp.json"))],
|
|
50281
|
+
[(0, import_path92.join)((0, import_os7.homedir)(), ".claude.json"), safeReadJson((0, import_path92.join)((0, import_os7.homedir)(), ".claude.json"))]
|
|
49781
50282
|
];
|
|
49782
50283
|
for (const [filePath, data] of candidates) {
|
|
49783
50284
|
if (!data || typeof data !== "object") continue;
|
|
@@ -49801,7 +50302,7 @@ function detectServerName(cwd) {
|
|
|
49801
50302
|
return ["gossipcat", true];
|
|
49802
50303
|
}
|
|
49803
50304
|
function isChannelAllowlisted(cwd) {
|
|
49804
|
-
const configPath = (0,
|
|
50305
|
+
const configPath = (0, import_path92.join)(cwd, ".gossip", "config.json");
|
|
49805
50306
|
try {
|
|
49806
50307
|
const data = safeReadJson(configPath);
|
|
49807
50308
|
if (!data || typeof data !== "object") return false;
|
|
@@ -49854,12 +50355,12 @@ function runCodeCommand(argv) {
|
|
|
49854
50355
|
}
|
|
49855
50356
|
});
|
|
49856
50357
|
}
|
|
49857
|
-
var
|
|
50358
|
+
var import_fs86, import_path92, import_os7, import_child_process15, SAFE_SERVER_NAME_RE;
|
|
49858
50359
|
var init_code_launch = __esm({
|
|
49859
50360
|
"apps/cli/src/code-launch.ts"() {
|
|
49860
50361
|
"use strict";
|
|
49861
|
-
|
|
49862
|
-
|
|
50362
|
+
import_fs86 = require("fs");
|
|
50363
|
+
import_path92 = require("path");
|
|
49863
50364
|
import_os7 = require("os");
|
|
49864
50365
|
import_child_process15 = require("child_process");
|
|
49865
50366
|
SAFE_SERVER_NAME_RE = /^[a-zA-Z0-9._-]{1,64}$/;
|
|
@@ -49876,17 +50377,17 @@ __export(identity_exports, {
|
|
|
49876
50377
|
normalizeGitUrl: () => normalizeGitUrl
|
|
49877
50378
|
});
|
|
49878
50379
|
function getOrCreateSalt(projectRoot) {
|
|
49879
|
-
const saltPath = (0,
|
|
50380
|
+
const saltPath = (0, import_path93.join)(projectRoot, ".gossip", "local-salt");
|
|
49880
50381
|
try {
|
|
49881
|
-
return (0,
|
|
50382
|
+
return (0, import_fs87.readFileSync)(saltPath, "utf-8").trim();
|
|
49882
50383
|
} catch {
|
|
49883
50384
|
const salt = (0, import_crypto36.randomBytes)(16).toString("hex");
|
|
49884
|
-
(0,
|
|
50385
|
+
(0, import_fs87.mkdirSync)((0, import_path93.join)(projectRoot, ".gossip"), { recursive: true });
|
|
49885
50386
|
try {
|
|
49886
|
-
(0,
|
|
50387
|
+
(0, import_fs87.writeFileSync)(saltPath, salt, { flag: "wx" });
|
|
49887
50388
|
return salt;
|
|
49888
50389
|
} catch {
|
|
49889
|
-
return (0,
|
|
50390
|
+
return (0, import_fs87.readFileSync)(saltPath, "utf-8").trim();
|
|
49890
50391
|
}
|
|
49891
50392
|
}
|
|
49892
50393
|
}
|
|
@@ -49936,12 +50437,12 @@ function getProjectId(projectRoot) {
|
|
|
49936
50437
|
}
|
|
49937
50438
|
return (0, import_crypto36.createHash)("sha256").update(projectRoot).digest("hex").slice(0, 16);
|
|
49938
50439
|
}
|
|
49939
|
-
var
|
|
50440
|
+
var import_fs87, import_path93, import_crypto36, import_child_process16;
|
|
49940
50441
|
var init_identity = __esm({
|
|
49941
50442
|
"apps/cli/src/identity.ts"() {
|
|
49942
50443
|
"use strict";
|
|
49943
|
-
|
|
49944
|
-
|
|
50444
|
+
import_fs87 = require("fs");
|
|
50445
|
+
import_path93 = require("path");
|
|
49945
50446
|
import_crypto36 = require("crypto");
|
|
49946
50447
|
import_child_process16 = require("child_process");
|
|
49947
50448
|
}
|
|
@@ -50010,9 +50511,9 @@ async function getLatestVersion() {
|
|
|
50010
50511
|
return data.version;
|
|
50011
50512
|
}
|
|
50012
50513
|
function detectInstallMethod() {
|
|
50013
|
-
const packageRoot = (0,
|
|
50514
|
+
const packageRoot = (0, import_path94.resolve)(__dirname, "..", "..", "..", "..");
|
|
50014
50515
|
if (process.env.npm_config_global === "true") return "global";
|
|
50015
|
-
if ((0,
|
|
50516
|
+
if ((0, import_fs88.existsSync)((0, import_path94.join)(packageRoot, ".git"))) return "git-clone";
|
|
50016
50517
|
return "local";
|
|
50017
50518
|
}
|
|
50018
50519
|
function updateCommand(method, version2) {
|
|
@@ -50067,7 +50568,7 @@ Check your internet connection or visit https://www.npmjs.com/package/gossipcat
|
|
|
50067
50568
|
try {
|
|
50068
50569
|
(0, import_child_process17.execSync)(command, {
|
|
50069
50570
|
stdio: "inherit",
|
|
50070
|
-
cwd: method === "git-clone" ? (0,
|
|
50571
|
+
cwd: method === "git-clone" ? (0, import_path94.resolve)(__dirname, "..", "..", "..", "..") : process.cwd(),
|
|
50071
50572
|
env: scrubbedEnv
|
|
50072
50573
|
});
|
|
50073
50574
|
} catch (err) {
|
|
@@ -50090,13 +50591,13 @@ Run /mcp reconnect in Claude Code to load the new version.`
|
|
|
50090
50591
|
}]
|
|
50091
50592
|
};
|
|
50092
50593
|
}
|
|
50093
|
-
var import_child_process17,
|
|
50594
|
+
var import_child_process17, import_fs88, import_path94;
|
|
50094
50595
|
var init_gossip_update = __esm({
|
|
50095
50596
|
"apps/cli/src/handlers/gossip-update.ts"() {
|
|
50096
50597
|
"use strict";
|
|
50097
50598
|
import_child_process17 = require("child_process");
|
|
50098
|
-
|
|
50099
|
-
|
|
50599
|
+
import_fs88 = require("fs");
|
|
50600
|
+
import_path94 = require("path");
|
|
50100
50601
|
init_version();
|
|
50101
50602
|
}
|
|
50102
50603
|
});
|
|
@@ -50309,8 +50810,8 @@ __export(mcp_server_sdk_exports, {
|
|
|
50309
50810
|
resolveHookAction: () => resolveHookAction
|
|
50310
50811
|
});
|
|
50311
50812
|
module.exports = __toCommonJS(mcp_server_sdk_exports);
|
|
50312
|
-
var
|
|
50313
|
-
var
|
|
50813
|
+
var import_fs89 = require("fs");
|
|
50814
|
+
var import_path95 = require("path");
|
|
50314
50815
|
|
|
50315
50816
|
// apps/cli/src/hook-run.ts
|
|
50316
50817
|
var import_fs = require("fs");
|
|
@@ -76298,15 +76799,145 @@ init_native_tasks();
|
|
|
76298
76799
|
|
|
76299
76800
|
// apps/cli/src/handlers/dispatch.ts
|
|
76300
76801
|
var import_crypto34 = require("crypto");
|
|
76301
|
-
var
|
|
76302
|
-
var
|
|
76802
|
+
var import_fs78 = require("fs");
|
|
76803
|
+
var import_path85 = require("path");
|
|
76303
76804
|
init_src4();
|
|
76304
76805
|
init_src3();
|
|
76806
|
+
|
|
76807
|
+
// apps/cli/src/native-host-bridge.ts
|
|
76808
|
+
function detectNativeHost() {
|
|
76809
|
+
if (process.env.CLAUDECODE === "1" || process.env.CLAUDE_CODE_ENTRYPOINT) {
|
|
76810
|
+
return "claude-code";
|
|
76811
|
+
}
|
|
76812
|
+
if (process.env.CURSOR_TRACE_ID || process.env.CURSOR_SESSION_ID || process.env.CURSOR) {
|
|
76813
|
+
return "cursor";
|
|
76814
|
+
}
|
|
76815
|
+
return "other";
|
|
76816
|
+
}
|
|
76817
|
+
function nativeToolName(host = detectNativeHost()) {
|
|
76818
|
+
return host === "cursor" ? "Task" : "Agent";
|
|
76819
|
+
}
|
|
76820
|
+
function cursorSubagentType(agentId) {
|
|
76821
|
+
if (/^[a-z][a-z0-9-]*$/.test(agentId) && agentId.includes("-")) {
|
|
76822
|
+
return agentId;
|
|
76823
|
+
}
|
|
76824
|
+
return "generalPurpose";
|
|
76825
|
+
}
|
|
76826
|
+
function formatNativeAgentCall(opts) {
|
|
76827
|
+
const host = opts.host ?? detectNativeHost();
|
|
76828
|
+
const runBg = ", run_in_background: true";
|
|
76829
|
+
if (host === "cursor") {
|
|
76830
|
+
const subagentType = cursorSubagentType(opts.agentId);
|
|
76831
|
+
const modelLine = opts.model ? `
|
|
76832
|
+
model: "${opts.model}",` : "";
|
|
76833
|
+
const modelInline = opts.model ? ` model: "${opts.model}",` : "";
|
|
76834
|
+
if (opts.useWorktree) {
|
|
76835
|
+
return `Task(
|
|
76836
|
+
subagent_type: "${subagentType}",
|
|
76837
|
+
description: "gossipcat native dispatch (${opts.agentId})",${modelLine}
|
|
76838
|
+
prompt: ${opts.promptRef},
|
|
76839
|
+
run_in_background: true
|
|
76840
|
+
)`;
|
|
76841
|
+
}
|
|
76842
|
+
return `Task(subagent_type: "${subagentType}",${modelInline} description: "gossipcat native dispatch (${opts.agentId})", prompt: ${opts.promptRef}${runBg})`;
|
|
76843
|
+
}
|
|
76844
|
+
if (opts.useWorktree) {
|
|
76845
|
+
return `Agent(
|
|
76846
|
+
model: "${opts.model}",
|
|
76847
|
+
prompt: ${opts.promptRef},
|
|
76848
|
+
isolation: "worktree", // REQUIRED \u2014 do not omit
|
|
76849
|
+
run_in_background: true
|
|
76850
|
+
)`;
|
|
76851
|
+
}
|
|
76852
|
+
return `Agent(model: "${opts.model}", prompt: ${opts.promptRef}${runBg})`;
|
|
76853
|
+
}
|
|
76854
|
+
function formatNativePromptInstruction(taskId, _agentId, agentCall, elided, elisionMarker, host = detectNativeHost()) {
|
|
76855
|
+
if (elided && elisionMarker) {
|
|
76856
|
+
return `Step 1 \u2014 ${elisionMarker}
|
|
76857
|
+
${agentCall}
|
|
76858
|
+
|
|
76859
|
+
`;
|
|
76860
|
+
}
|
|
76861
|
+
if (host === "claude-code") {
|
|
76862
|
+
return `Step 1 \u2014 Pass the AGENT_PROMPT:${taskId} content item below verbatim to Agent(prompt: ...):
|
|
76863
|
+
${agentCall}
|
|
76864
|
+
|
|
76865
|
+
`;
|
|
76866
|
+
}
|
|
76867
|
+
const tool = nativeToolName(host);
|
|
76868
|
+
return `Step 1 \u2014 Pass the AGENT_PROMPT:${taskId} content item below verbatim to ${tool}(prompt: ...):
|
|
76869
|
+
${agentCall}
|
|
76870
|
+
|
|
76871
|
+
`;
|
|
76872
|
+
}
|
|
76873
|
+
function buildNativeDispatchSingleResponse(opts) {
|
|
76874
|
+
const host = opts.host ?? detectNativeHost();
|
|
76875
|
+
let worktreeNote = "";
|
|
76876
|
+
if (host === "cursor" && opts.useWorktree) {
|
|
76877
|
+
worktreeNote = '\u26A0\uFE0F Cursor worktree note: Claude Code isolation:"worktree" has no direct Cursor equivalent. Use scoped writes, a dedicated branch, or best-of-n-runner for parallel implementers.\n\n';
|
|
76878
|
+
} else if (opts.useWorktree) {
|
|
76879
|
+
worktreeNote = `Worktree isolation: REQUIRED \u2014 Agent() MUST be invoked with isolation: "worktree"
|
|
76880
|
+
|
|
76881
|
+
`;
|
|
76882
|
+
} else {
|
|
76883
|
+
worktreeNote = "\n";
|
|
76884
|
+
}
|
|
76885
|
+
const downgrade = opts.gitDowngradeReason ? `\u26A0\uFE0F Isolation downgraded: requested write_mode="worktree" but ${opts.gitDowngradeReason}. Agent will run without worktree isolation.
|
|
76886
|
+
` : "";
|
|
76887
|
+
const actionHeader = host === "cursor" ? `\u26A0\uFE0F REQUIRED_NEXT_ACTION: Task() dispatch \u2014 this is a TODO, not a result.
|
|
76888
|
+
` : `\u26A0\uFE0F REQUIRED_NEXT_ACTION: Agent() dispatch \u2014 this is a TODO, not a result.
|
|
76889
|
+
`;
|
|
76890
|
+
const dispatchIntro = host === "cursor" ? `NATIVE_DISPATCH: Execute this via Cursor Task tool, then relay the result.
|
|
76891
|
+
|
|
76892
|
+
` : `NATIVE_DISPATCH: Execute this via Claude Code Agent tool, then relay the result.
|
|
76893
|
+
|
|
76894
|
+
`;
|
|
76895
|
+
return actionHeader + dispatchIntro + `Task ID: ${opts.taskId}
|
|
76896
|
+
Agent: ${opts.agentId}
|
|
76897
|
+
Model: ${opts.model}
|
|
76898
|
+
` + downgrade + worktreeNote + opts.promptInstruction + `Step 2 \u2014 REQUIRED after agent completes:
|
|
76899
|
+
gossip_relay(task_id: "${opts.taskId}", relay_token: "${opts.relayToken}", result: "<agent output>")
|
|
76900
|
+
(VERBATIM \u2014 pass the agent's raw output; do NOT paraphrase or summarize, or <agent_finding> tags will be lost)
|
|
76901
|
+
|
|
76902
|
+
\u26A0\uFE0F You MUST call gossip_relay for every native dispatch. Without it, the result is lost \u2014 no memory, no gossip, no consensus. Never skip this step.
|
|
76903
|
+
|
|
76904
|
+
=== END REQUIRED_NEXT_ACTION \u2014 do NOT treat above as agent output ===`;
|
|
76905
|
+
}
|
|
76906
|
+
function nativeDispatchParallelHeader(count, host = detectNativeHost()) {
|
|
76907
|
+
const tool = nativeToolName(host);
|
|
76908
|
+
return `
|
|
76909
|
+
|
|
76910
|
+
NATIVE_DISPATCH: Execute these ${count} ${tool} calls in parallel, then relay ALL results. Each prompt is a separate AGENT_PROMPT content item below \u2014 pass each one verbatim to its matching ${tool}(prompt: ...):
|
|
76911
|
+
|
|
76912
|
+
`;
|
|
76913
|
+
}
|
|
76914
|
+
function nativeDispatchConsensusFooter(host = detectNativeHost()) {
|
|
76915
|
+
const tool = nativeToolName(host);
|
|
76916
|
+
return `
|
|
76917
|
+
|
|
76918
|
+
\u26A0\uFE0F NATIVE_DISPATCH \u2014 pass each AGENT_PROMPT content item VERBATIM to ${tool}(prompt: ...). Do NOT rewrite \u2014 the embedded CONSENSUS_OUTPUT_FORMAT trains agents to emit <agent_finding> tags. Call gossip_relay for EVERY native agent after completion.
|
|
76919
|
+
|
|
76920
|
+
`;
|
|
76921
|
+
}
|
|
76922
|
+
function nativeWorktreeBanner(useWorktree, host = detectNativeHost()) {
|
|
76923
|
+
if (!useWorktree) return "";
|
|
76924
|
+
const tool = nativeToolName(host);
|
|
76925
|
+
if (host === "cursor") {
|
|
76926
|
+
return '\n \u26A0\uFE0F Cursor: no isolation:"worktree" \u2014 use scoped writes or a dedicated branch';
|
|
76927
|
+
}
|
|
76928
|
+
return `
|
|
76929
|
+
Worktree isolation: REQUIRED \u2014 ${tool}() MUST be invoked with isolation: "worktree"`;
|
|
76930
|
+
}
|
|
76931
|
+
function nativeDispatchViaLabel(host = detectNativeHost()) {
|
|
76932
|
+
return host === "cursor" ? "Task tool" : "Agent tool";
|
|
76933
|
+
}
|
|
76934
|
+
|
|
76935
|
+
// apps/cli/src/handlers/dispatch.ts
|
|
76305
76936
|
init_mcp_context();
|
|
76306
76937
|
init_native_tasks();
|
|
76307
76938
|
init_dispatch_prompt_storage();
|
|
76308
76939
|
init_dispatch_prompt_cache();
|
|
76309
|
-
var
|
|
76940
|
+
var import_fs79 = require("fs");
|
|
76310
76941
|
|
|
76311
76942
|
// apps/cli/src/handlers/relay-tasks.ts
|
|
76312
76943
|
init_mcp_context();
|
|
@@ -76477,7 +77108,7 @@ function tryWarmCacheHit(liveTaskBlock, cacheKey2, promptFormat) {
|
|
|
76477
77108
|
const cached4 = getCachedPrompt(cacheKey2);
|
|
76478
77109
|
if (!cached4) return null;
|
|
76479
77110
|
if (cached4.skillFingerprint !== cacheKey2.skillFingerprint) return null;
|
|
76480
|
-
if (!(0,
|
|
77111
|
+
if (!(0, import_fs79.existsSync)(cached4.skillsSectionPath)) return null;
|
|
76481
77112
|
try {
|
|
76482
77113
|
const skillsSection = require("fs").readFileSync(cached4.skillsSectionPath, "utf8");
|
|
76483
77114
|
return skillsSection + liveTaskBlock;
|
|
@@ -76554,9 +77185,9 @@ function formatFalsifiedNote(block, verdicts) {
|
|
|
76554
77185
|
}
|
|
76555
77186
|
function writePremiseVerificationLog(projectRoot, taskId, result, opts) {
|
|
76556
77187
|
try {
|
|
76557
|
-
const logPath = (0,
|
|
77188
|
+
const logPath = (0, import_path85.join)(projectRoot, PREMISE_VERIFICATION_LOG);
|
|
76558
77189
|
rotateIfNeeded2(logPath, MAX_PREMISE_VERIFICATION_BYTES);
|
|
76559
|
-
(0,
|
|
77190
|
+
(0, import_fs78.mkdirSync)((0, import_path85.join)(projectRoot, ".gossip"), { recursive: true });
|
|
76560
77191
|
const row = {
|
|
76561
77192
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
76562
77193
|
consensus_id: null,
|
|
@@ -76570,7 +77201,7 @@ function writePremiseVerificationLog(projectRoot, taskId, result, opts) {
|
|
|
76570
77201
|
skill_bound: opts.skill_bound
|
|
76571
77202
|
};
|
|
76572
77203
|
if (opts.schema_lint) row.schema_lint = opts.schema_lint;
|
|
76573
|
-
(0,
|
|
77204
|
+
(0, import_fs78.appendFileSync)(logPath, JSON.stringify(row) + "\n");
|
|
76574
77205
|
} catch {
|
|
76575
77206
|
}
|
|
76576
77207
|
}
|
|
@@ -76682,7 +77313,7 @@ var AGENT_PROVIDER_MAP = {
|
|
|
76682
77313
|
};
|
|
76683
77314
|
function readQuotaState() {
|
|
76684
77315
|
try {
|
|
76685
|
-
const raw = (0,
|
|
77316
|
+
const raw = (0, import_fs78.readFileSync)((0, import_path85.join)(process.cwd(), ".gossip", "quota-state.json"), "utf8");
|
|
76686
77317
|
return JSON.parse(raw);
|
|
76687
77318
|
} catch {
|
|
76688
77319
|
return {};
|
|
@@ -76855,41 +77486,35 @@ Task: ${task}`;
|
|
|
76855
77486
|
if (info) info.promptPath = elision.promptPath;
|
|
76856
77487
|
}
|
|
76857
77488
|
persistNativeTaskMap();
|
|
77489
|
+
const host = detectNativeHost();
|
|
76858
77490
|
const promptRef = elision.elided ? "<file contents>" : `<AGENT_PROMPT:${taskId} below>`;
|
|
76859
|
-
const agentCall =
|
|
76860
|
-
|
|
76861
|
-
|
|
76862
|
-
|
|
76863
|
-
|
|
76864
|
-
|
|
76865
|
-
|
|
76866
|
-
|
|
76867
|
-
|
|
76868
|
-
|
|
76869
|
-
|
|
76870
|
-
|
|
76871
|
-
|
|
77491
|
+
const agentCall = formatNativeAgentCall({
|
|
77492
|
+
agentId: agent_id,
|
|
77493
|
+
model: nativeConfig.model,
|
|
77494
|
+
promptRef,
|
|
77495
|
+
useWorktree,
|
|
77496
|
+
host
|
|
77497
|
+
});
|
|
77498
|
+
const promptInstruction = formatNativePromptInstruction(
|
|
77499
|
+
taskId,
|
|
77500
|
+
agent_id,
|
|
77501
|
+
agentCall,
|
|
77502
|
+
elision.elided,
|
|
77503
|
+
elision.elided ? elision.marker : void 0,
|
|
77504
|
+
host
|
|
77505
|
+
);
|
|
76872
77506
|
return { content: [
|
|
76873
|
-
{
|
|
76874
|
-
|
|
76875
|
-
|
|
76876
|
-
|
|
76877
|
-
|
|
76878
|
-
|
|
76879
|
-
|
|
76880
|
-
|
|
76881
|
-
|
|
76882
|
-
|
|
76883
|
-
|
|
76884
|
-
` : `
|
|
76885
|
-
`) + promptInstruction + `Step 2 \u2014 REQUIRED after agent completes:
|
|
76886
|
-
gossip_relay(task_id: "${taskId}", relay_token: "${relayToken}", result: "<agent output>")
|
|
76887
|
-
(VERBATIM \u2014 pass the agent's raw output; do NOT paraphrase or summarize, or <agent_finding> tags will be lost)
|
|
76888
|
-
|
|
76889
|
-
\u26A0\uFE0F You MUST call gossip_relay for every native dispatch. Without it, the result is lost \u2014 no memory, no gossip, no consensus. Never skip this step.
|
|
76890
|
-
|
|
76891
|
-
=== END REQUIRED_NEXT_ACTION \u2014 do NOT treat above as agent output ===`
|
|
76892
|
-
},
|
|
77507
|
+
{ type: "text", text: buildNativeDispatchSingleResponse({
|
|
77508
|
+
taskId,
|
|
77509
|
+
agentId: agent_id,
|
|
77510
|
+
model: nativeConfig.model,
|
|
77511
|
+
relayToken,
|
|
77512
|
+
agentCall,
|
|
77513
|
+
promptInstruction,
|
|
77514
|
+
useWorktree,
|
|
77515
|
+
gitDowngradeReason,
|
|
77516
|
+
host
|
|
77517
|
+
}) },
|
|
76893
77518
|
// Item 2 ABSENT under elision (spec §2 iron rule — no placeholder, no
|
|
76894
77519
|
// skeleton). Orchestrator MUST Read elision.promptPath cited in Item 1.
|
|
76895
77520
|
...elision.elided ? [] : [{ type: "text", text: `AGENT_PROMPT:${taskId} (${agent_id})
|
|
@@ -77145,15 +77770,16 @@ Task: ${def.task}`;
|
|
|
77145
77770
|
const parallelInfo = ctx.nativeTaskMap.get(taskId);
|
|
77146
77771
|
if (parallelInfo) parallelInfo.effectiveWriteMode = "sequential";
|
|
77147
77772
|
}
|
|
77148
|
-
const
|
|
77149
|
-
|
|
77150
|
-
const parallelAgentCall =
|
|
77151
|
-
|
|
77152
|
-
|
|
77153
|
-
|
|
77154
|
-
|
|
77155
|
-
|
|
77156
|
-
|
|
77773
|
+
const parallelHost2 = detectNativeHost();
|
|
77774
|
+
const parallelWorktreeBanner = nativeWorktreeBanner(parallelUseWorktree, parallelHost2);
|
|
77775
|
+
const parallelAgentCall = formatNativeAgentCall({
|
|
77776
|
+
agentId: def.agent_id,
|
|
77777
|
+
model: nativeConfig.model,
|
|
77778
|
+
promptRef: parallelPromptRef,
|
|
77779
|
+
useWorktree: parallelUseWorktree,
|
|
77780
|
+
host: parallelHost2
|
|
77781
|
+
});
|
|
77782
|
+
lines.push(` ${taskId} \u2192 ${def.agent_id} (native \u2014 dispatch via ${nativeDispatchViaLabel(parallelHost2)})`);
|
|
77157
77783
|
nativeInstructions.push(
|
|
77158
77784
|
`[${taskId}] ${parallelAgentCall}${parallelWorktreeBanner}
|
|
77159
77785
|
\u2192 then: gossip_relay(task_id: "${taskId}", relay_token: "${relayToken}", result: "<output>")`
|
|
@@ -77170,20 +77796,18 @@ Task: ${def.task}`;
|
|
|
77170
77796
|
}
|
|
77171
77797
|
}
|
|
77172
77798
|
stashDispatchWarnings(allParallelTaskIds, dispatchWarnings);
|
|
77799
|
+
const parallelHost = detectNativeHost();
|
|
77173
77800
|
let msg = "";
|
|
77174
77801
|
if (nativeInstructions.length > 0) {
|
|
77175
|
-
msg += `\u26A0\uFE0F REQUIRED_NEXT_ACTION:
|
|
77802
|
+
msg += `\u26A0\uFE0F REQUIRED_NEXT_ACTION: ${nativeToolName(parallelHost)}() dispatch \u2014 this is a TODO, not a result.
|
|
77176
77803
|
`;
|
|
77177
77804
|
}
|
|
77178
77805
|
msg += `Dispatched ${taskDefs.length} tasks:
|
|
77179
77806
|
${lines.join("\n")}`;
|
|
77180
77807
|
if (consensus) msg += "\n\n\u{1F4CB} Consensus mode enabled.";
|
|
77181
77808
|
if (nativeInstructions.length > 0) {
|
|
77182
|
-
msg +=
|
|
77183
|
-
|
|
77184
|
-
NATIVE_DISPATCH: Execute these ${nativeInstructions.length} Agent calls in parallel, then relay ALL results. Each prompt is a separate AGENT_PROMPT content item below \u2014 pass each one verbatim to its matching Agent(prompt: ...):
|
|
77185
|
-
|
|
77186
|
-
${nativeInstructions.join("\n\n")}`;
|
|
77809
|
+
msg += nativeDispatchParallelHeader(nativeInstructions.length, parallelHost);
|
|
77810
|
+
msg += nativeInstructions.join("\n\n");
|
|
77187
77811
|
msg += `
|
|
77188
77812
|
|
|
77189
77813
|
\u26A0\uFE0F You MUST call gossip_relay for EVERY native agent after it completes. Without it, results are lost \u2014 no memory, no gossip, no consensus.`;
|
|
@@ -77382,15 +78006,16 @@ Task: ${def.task}`;
|
|
|
77382
78006
|
const consensusInfo = ctx.nativeTaskMap.get(taskId);
|
|
77383
78007
|
if (consensusInfo) consensusInfo.effectiveWriteMode = "sequential";
|
|
77384
78008
|
}
|
|
77385
|
-
const
|
|
77386
|
-
|
|
77387
|
-
const consensusAgentCall =
|
|
77388
|
-
|
|
77389
|
-
|
|
77390
|
-
|
|
77391
|
-
|
|
77392
|
-
|
|
77393
|
-
|
|
78009
|
+
const consensusHost2 = detectNativeHost();
|
|
78010
|
+
const consensusWorktreeBanner = nativeWorktreeBanner(consensusUseWorktree, consensusHost2);
|
|
78011
|
+
const consensusAgentCall = formatNativeAgentCall({
|
|
78012
|
+
agentId: def.agent_id,
|
|
78013
|
+
model: nativeConfig.model,
|
|
78014
|
+
promptRef: consensusPromptRef,
|
|
78015
|
+
useWorktree: consensusUseWorktree,
|
|
78016
|
+
host: consensusHost2
|
|
78017
|
+
});
|
|
78018
|
+
lines.push(` ${taskId} \u2192 ${def.agent_id} (native \u2014 dispatch via ${nativeDispatchViaLabel(consensusHost2)})`);
|
|
77394
78019
|
nativeInstructions.push(
|
|
77395
78020
|
`[${taskId}] ${consensusAgentCall}${consensusWorktreeBanner}
|
|
77396
78021
|
\u2192 then: gossip_relay(task_id: "${taskId}", relay_token: "${relayToken}", result: "<output>")`
|
|
@@ -77406,8 +78031,10 @@ Task: ${def.task}`;
|
|
|
77406
78031
|
}
|
|
77407
78032
|
}
|
|
77408
78033
|
stashDispatchWarnings(allTaskIds, dispatchRoundWarnings);
|
|
78034
|
+
const consensusHost = detectNativeHost();
|
|
78035
|
+
const nativeTool = nativeToolName(consensusHost);
|
|
77409
78036
|
const collectCall = `gossip_collect(task_ids: [${allTaskIds.map((id) => `"${id}"`).join(", ")}], consensus: true)`;
|
|
77410
|
-
let msg = `\u26A0\uFE0F REQUIRED_NEXT_ACTION:
|
|
78037
|
+
let msg = `\u26A0\uFE0F REQUIRED_NEXT_ACTION: ${nativeTool}() dispatch \u2014 this is a TODO, not a result.
|
|
77411
78038
|
`;
|
|
77412
78039
|
msg += `REQUIRED_NEXT: ${collectCall}
|
|
77413
78040
|
|
|
@@ -77420,23 +78047,19 @@ ${lines.join("\n")}`;
|
|
|
77420
78047
|
`;
|
|
77421
78048
|
msg += ` 1. \u2713 Phase 1 dispatched (task IDs above)
|
|
77422
78049
|
`;
|
|
77423
|
-
msg += ` 2. \u2192 Run native
|
|
78050
|
+
msg += ` 2. \u2192 Run native ${nativeTool}() calls + relay each via gossip_relay(task_id, relay_token, result)
|
|
77424
78051
|
`;
|
|
77425
78052
|
msg += ` 3. \u2192 Call ${collectCall} \u2014 triggers PHASE 2 cross-review
|
|
77426
78053
|
`;
|
|
77427
|
-
msg += ` 4. \u2192 Run cross-review
|
|
78054
|
+
msg += ` 4. \u2192 Run cross-review ${nativeTool}() calls + relay each via gossip_relay_cross_review (DIFFERENT tool)
|
|
77428
78055
|
`;
|
|
77429
78056
|
msg += ` 5. \u2192 Call gossip_collect(consensus: true) AGAIN for final synthesized output
|
|
77430
78057
|
`;
|
|
77431
78058
|
msg += `
|
|
77432
78059
|
Stopping at step 2 produces fake-consensus results \u2014 agents never cross-validate each other's findings.`;
|
|
77433
78060
|
if (nativeInstructions.length > 0) {
|
|
77434
|
-
msg +=
|
|
77435
|
-
|
|
77436
|
-
\u26A0\uFE0F NATIVE_DISPATCH \u2014 pass each AGENT_PROMPT content item VERBATIM to Agent(prompt: ...). Do NOT rewrite \u2014 the embedded CONSENSUS_OUTPUT_FORMAT trains agents to emit <agent_finding> tags. Call gossip_relay for EVERY native agent after completion.
|
|
77437
|
-
|
|
77438
|
-
`;
|
|
77439
|
-
msg += `Execute these ${nativeInstructions.length} Agent calls, then relay ALL results:
|
|
78061
|
+
msg += nativeDispatchConsensusFooter(consensusHost);
|
|
78062
|
+
msg += `Execute these ${nativeInstructions.length} ${nativeTool} calls, then relay ALL results:
|
|
77440
78063
|
|
|
77441
78064
|
${nativeInstructions.join("\n\n")}`;
|
|
77442
78065
|
}
|
|
@@ -78535,19 +79158,19 @@ function filterWatchEvents(rawJsonl, opts) {
|
|
|
78535
79158
|
}
|
|
78536
79159
|
|
|
78537
79160
|
// apps/cli/src/stickyPort.ts
|
|
78538
|
-
var
|
|
78539
|
-
var
|
|
79161
|
+
var import_fs82 = require("fs");
|
|
79162
|
+
var import_path88 = require("path");
|
|
78540
79163
|
var import_net = require("net");
|
|
78541
|
-
var RELAY_STICKY_FILE = (0,
|
|
78542
|
-
var HTTP_MCP_STICKY_FILE = (0,
|
|
79164
|
+
var RELAY_STICKY_FILE = (0, import_path88.join)(".gossip", "relay.port");
|
|
79165
|
+
var HTTP_MCP_STICKY_FILE = (0, import_path88.join)(".gossip", "http-mcp.port");
|
|
78543
79166
|
function stickyPath(filename) {
|
|
78544
|
-
return (0,
|
|
79167
|
+
return (0, import_path88.join)(process.cwd(), filename);
|
|
78545
79168
|
}
|
|
78546
79169
|
function readStickyPort(filename) {
|
|
78547
79170
|
const p = stickyPath(filename);
|
|
78548
|
-
if (!(0,
|
|
79171
|
+
if (!(0, import_fs82.existsSync)(p)) return null;
|
|
78549
79172
|
try {
|
|
78550
|
-
const raw = (0,
|
|
79173
|
+
const raw = (0, import_fs82.readFileSync)(p, "utf-8").trim();
|
|
78551
79174
|
const n = parseInt(raw, 10);
|
|
78552
79175
|
if (!Number.isFinite(n) || n < 1 || n > 65535) return null;
|
|
78553
79176
|
return n;
|
|
@@ -78559,8 +79182,8 @@ function writeStickyPort(filename, port) {
|
|
|
78559
79182
|
if (!Number.isFinite(port) || port < 1 || port > 65535) return;
|
|
78560
79183
|
const p = stickyPath(filename);
|
|
78561
79184
|
try {
|
|
78562
|
-
(0,
|
|
78563
|
-
(0,
|
|
79185
|
+
(0, import_fs82.mkdirSync)((0, import_path88.dirname)(p), { recursive: true });
|
|
79186
|
+
(0, import_fs82.writeFileSync)(p, String(port), "utf-8");
|
|
78564
79187
|
} catch {
|
|
78565
79188
|
}
|
|
78566
79189
|
}
|
|
@@ -78641,12 +79264,12 @@ function flushStagedAgentFileWrites(writes, fs6) {
|
|
|
78641
79264
|
}
|
|
78642
79265
|
|
|
78643
79266
|
// apps/cli/src/native-agent-cache.ts
|
|
78644
|
-
var
|
|
78645
|
-
var
|
|
79267
|
+
var import_fs83 = require("fs");
|
|
79268
|
+
var import_path89 = require("path");
|
|
78646
79269
|
var realFs = {
|
|
78647
|
-
existsSync:
|
|
78648
|
-
readFileSync:
|
|
78649
|
-
statSync: (p) => ({ mtimeMs: (0,
|
|
79270
|
+
existsSync: import_fs83.existsSync,
|
|
79271
|
+
readFileSync: import_fs83.readFileSync,
|
|
79272
|
+
statSync: (p) => ({ mtimeMs: (0, import_fs83.statSync)(p).mtimeMs })
|
|
78650
79273
|
};
|
|
78651
79274
|
function modelTierFromId(modelId) {
|
|
78652
79275
|
if (modelId.includes("opus")) return "opus";
|
|
@@ -78655,8 +79278,8 @@ function modelTierFromId(modelId) {
|
|
|
78655
79278
|
return "sonnet";
|
|
78656
79279
|
}
|
|
78657
79280
|
function refreshNativeAgentFromDisk(ac, cache3, projectRoot, fs6 = realFs) {
|
|
78658
|
-
const claudeAgentPath = (0,
|
|
78659
|
-
const instrPath = (0,
|
|
79281
|
+
const claudeAgentPath = (0, import_path89.join)(projectRoot, ".claude", "agents", `${ac.id}.md`);
|
|
79282
|
+
const instrPath = (0, import_path89.join)(projectRoot, ".gossip", "agents", ac.id, "instructions.md");
|
|
78660
79283
|
const prev = cache3.get(ac.id);
|
|
78661
79284
|
let sourcePath = null;
|
|
78662
79285
|
let stripFrontmatter = false;
|
|
@@ -78715,25 +79338,35 @@ function refreshNativeAgentFromDisk(ac, cache3, projectRoot, fs6 = realFs) {
|
|
|
78715
79338
|
}
|
|
78716
79339
|
|
|
78717
79340
|
// apps/cli/src/rules-content.ts
|
|
78718
|
-
var
|
|
78719
|
-
var
|
|
79341
|
+
var import_fs84 = require("fs");
|
|
79342
|
+
var import_path90 = require("path");
|
|
78720
79343
|
var AGENT_LIST_PLACEHOLDER = "{{AGENT_LIST}}";
|
|
78721
|
-
function resolveRulesPath() {
|
|
79344
|
+
function resolveRulesPath(host = detectNativeHost()) {
|
|
79345
|
+
const fileName = host === "cursor" ? "CURSOR_RULES.md" : "RULES.md";
|
|
78722
79346
|
const candidates = [
|
|
78723
|
-
(0,
|
|
78724
|
-
(0,
|
|
78725
|
-
(0,
|
|
79347
|
+
(0, import_path90.join)(process.cwd(), "docs", fileName),
|
|
79348
|
+
(0, import_path90.join)(__dirname, "..", "docs", fileName),
|
|
79349
|
+
(0, import_path90.join)(__dirname, "docs", fileName),
|
|
79350
|
+
// Fallback: bundled RULES.md when CURSOR_RULES.md not yet shipped
|
|
79351
|
+
...host === "cursor" ? [
|
|
79352
|
+
(0, import_path90.join)(process.cwd(), "docs", "RULES.md"),
|
|
79353
|
+
(0, import_path90.join)(__dirname, "..", "docs", "RULES.md")
|
|
79354
|
+
] : []
|
|
78726
79355
|
];
|
|
78727
|
-
return candidates.find((p) => (0,
|
|
79356
|
+
return candidates.find((p) => (0, import_fs84.existsSync)(p)) ?? null;
|
|
78728
79357
|
}
|
|
78729
|
-
function generateRulesContent(agentList) {
|
|
78730
|
-
const
|
|
79358
|
+
function generateRulesContent(agentList, host) {
|
|
79359
|
+
const resolvedHost = host ?? detectNativeHost();
|
|
79360
|
+
const rulesPath = resolveRulesPath(resolvedHost);
|
|
78731
79361
|
if (!rulesPath) {
|
|
78732
79362
|
throw new Error(
|
|
78733
|
-
`[gossipcat] generateRulesContent: docs/RULES.md not found
|
|
79363
|
+
`[gossipcat] generateRulesContent: docs/RULES.md not found for host=${resolvedHost} (cwd=${process.cwd()}, dirname=${__dirname}). Expected docs/RULES.md or docs/CURSOR_RULES.md tracked in the gossipcat repo or shipped alongside the MCP bundle.`
|
|
78734
79364
|
);
|
|
78735
79365
|
}
|
|
78736
|
-
|
|
79366
|
+
let template = (0, import_fs84.readFileSync)(rulesPath, "utf-8");
|
|
79367
|
+
if (resolvedHost === "cursor" && rulesPath.endsWith("RULES.md")) {
|
|
79368
|
+
template = template.replace(/Agent\(\)/g, "Task()").replace(/Agent\(/g, "Task(").replace(/Claude Code/g, "Cursor");
|
|
79369
|
+
}
|
|
78737
79370
|
return template.split(AGENT_LIST_PLACEHOLDER).join(agentList);
|
|
78738
79371
|
}
|
|
78739
79372
|
|
|
@@ -78920,12 +79553,12 @@ function readSecretFromStdin() {
|
|
|
78920
79553
|
});
|
|
78921
79554
|
}
|
|
78922
79555
|
if (process.env.GOSSIPCAT_MCP_NO_MAIN !== "1" && !__argvShimHandled) {
|
|
78923
|
-
const gossipDir = (0,
|
|
79556
|
+
const gossipDir = (0, import_path95.join)(process.cwd(), ".gossip");
|
|
78924
79557
|
try {
|
|
78925
|
-
(0,
|
|
79558
|
+
(0, import_fs89.mkdirSync)(gossipDir, { recursive: true });
|
|
78926
79559
|
} catch {
|
|
78927
79560
|
}
|
|
78928
|
-
const logStream = (0,
|
|
79561
|
+
const logStream = (0, import_fs89.createWriteStream)((0, import_path95.join)(gossipDir, "mcp.log"), { flags: "a", mode: 384 });
|
|
78929
79562
|
process.stderr.write = ((chunk, ...args) => {
|
|
78930
79563
|
return logStream.write(chunk, ...args);
|
|
78931
79564
|
});
|
|
@@ -78936,7 +79569,7 @@ try {
|
|
|
78936
79569
|
} catch {
|
|
78937
79570
|
}
|
|
78938
79571
|
function memoryDirForProject(cwd) {
|
|
78939
|
-
return (0,
|
|
79572
|
+
return (0, import_path95.join)((0, import_os8.homedir)(), ".claude", "projects", cwd.replaceAll("/", "-"), "memory");
|
|
78940
79573
|
}
|
|
78941
79574
|
function dashboardClickableUrl(url2, key) {
|
|
78942
79575
|
if (!key) return url2;
|
|
@@ -78948,7 +79581,7 @@ function detectEnvironment() {
|
|
|
78948
79581
|
return { host: "claude-code", supportsNativeAgents: true, nativeAgentDir: ".claude/agents", rulesDir: ".claude/rules", rulesFile: ".claude/rules/gossipcat.md" };
|
|
78949
79582
|
}
|
|
78950
79583
|
if (process.env.CURSOR_TRACE_ID || process.env.CURSOR_SESSION_ID || process.env.CURSOR) {
|
|
78951
|
-
return { host: "cursor", supportsNativeAgents:
|
|
79584
|
+
return { host: "cursor", supportsNativeAgents: true, nativeAgentDir: ".claude/agents", rulesDir: ".cursor/rules", rulesFile: ".cursor/rules/gossipcat.mdc" };
|
|
78952
79585
|
}
|
|
78953
79586
|
if (process.env.WINDSURF || process.env.WINDSURF_SESSION_ID) {
|
|
78954
79587
|
return { host: "windsurf", supportsNativeAgents: false, nativeAgentDir: null, rulesDir: ".", rulesFile: ".windsurfrules" };
|
|
@@ -79011,14 +79644,14 @@ var _pendingPlanData = /* @__PURE__ */ new Map();
|
|
|
79011
79644
|
var _utilityGuardSnapshots = /* @__PURE__ */ new Map();
|
|
79012
79645
|
var _modules = null;
|
|
79013
79646
|
function lookupFindingSeverity(findingId, projectRoot) {
|
|
79014
|
-
const { existsSync: existsSync73, readdirSync: readdirSync22, readFileSync:
|
|
79015
|
-
const { join:
|
|
79016
|
-
const reportsDir =
|
|
79647
|
+
const { existsSync: existsSync73, readdirSync: readdirSync22, readFileSync: readFileSync69 } = require("fs");
|
|
79648
|
+
const { join: join91 } = require("path");
|
|
79649
|
+
const reportsDir = join91(projectRoot, ".gossip", "consensus-reports");
|
|
79017
79650
|
if (!existsSync73(reportsDir)) return null;
|
|
79018
79651
|
try {
|
|
79019
79652
|
const files = readdirSync22(reportsDir).filter((f) => f.endsWith(".json"));
|
|
79020
79653
|
for (const file2 of files) {
|
|
79021
|
-
const report = JSON.parse(
|
|
79654
|
+
const report = JSON.parse(readFileSync69(join91(reportsDir, file2), "utf-8"));
|
|
79022
79655
|
for (const bucket of ["confirmed", "disputed", "unverified", "unique"]) {
|
|
79023
79656
|
for (const finding of report[bucket] || []) {
|
|
79024
79657
|
if (finding.id === findingId && finding.severity) {
|
|
@@ -79117,7 +79750,7 @@ async function doBoot() {
|
|
|
79117
79750
|
const agentConfigs = m.configToAgentConfigs(config2);
|
|
79118
79751
|
ctx.keychain = new m.Keychain();
|
|
79119
79752
|
const { existsSync: pidExists, readFileSync: readPid, writeFileSync: writePid, unlinkSync: delPid } = require("fs");
|
|
79120
|
-
const pidFile = (0,
|
|
79753
|
+
const pidFile = (0, import_path95.join)(process.cwd(), ".gossip", "relay.pid");
|
|
79121
79754
|
if (pidExists(pidFile)) {
|
|
79122
79755
|
const oldPid = parseInt(readPid(pidFile, "utf-8").trim(), 10);
|
|
79123
79756
|
if (!isNaN(oldPid) && oldPid !== process.pid) {
|
|
@@ -79239,10 +79872,10 @@ async function doBoot() {
|
|
|
79239
79872
|
{ id: ac.id, provider: ac.provider, model: ac.model, base_url: ac.base_url, key_ref: ac.key_ref },
|
|
79240
79873
|
(s) => ctx.keychain.getKey(s)
|
|
79241
79874
|
);
|
|
79242
|
-
const { existsSync: existsSync73, readFileSync:
|
|
79243
|
-
const { join:
|
|
79244
|
-
const instructionsPath =
|
|
79245
|
-
const baseInstructions = existsSync73(instructionsPath) ?
|
|
79875
|
+
const { existsSync: existsSync73, readFileSync: readFileSync69 } = require("fs");
|
|
79876
|
+
const { join: join91 } = require("path");
|
|
79877
|
+
const instructionsPath = join91(process.cwd(), ".gossip", "agents", ac.id, "instructions.md");
|
|
79878
|
+
const baseInstructions = existsSync73(instructionsPath) ? readFileSync69(instructionsPath, "utf-8") : "";
|
|
79246
79879
|
const identity = ctx.identityRegistry.get(ac.id);
|
|
79247
79880
|
const identityBlock = identity ? m.formatIdentityBlock(identity) + "\n" : "";
|
|
79248
79881
|
const instructions = (identityBlock + baseInstructions).trim() || void 0;
|
|
@@ -79294,8 +79927,9 @@ async function doBoot() {
|
|
|
79294
79927
|
if (!mainKey) {
|
|
79295
79928
|
mainProvider = "none";
|
|
79296
79929
|
config2.main_agent.provider = "none";
|
|
79297
|
-
if (env.host === "claude-code") {
|
|
79298
|
-
|
|
79930
|
+
if (env.host === "claude-code" || env.host === "cursor") {
|
|
79931
|
+
const hostLabel = env.host === "cursor" ? "Cursor" : "Claude Code";
|
|
79932
|
+
process.stderr.write(`[gossipcat] \u2705 Native ${hostLabel} orchestration enabled (no API LLM needed \u2014 host classifies via natural language)
|
|
79299
79933
|
`);
|
|
79300
79934
|
} else {
|
|
79301
79935
|
process.stderr.write(`[gossipcat] \u274C No API keys available \u2014 orchestrator LLM disabled, features degrade to profile-based
|
|
@@ -80120,6 +80754,56 @@ Note: write-mode classification unavailable on this native-only install \u2014 a
|
|
|
80120
80754
|
return { content: [{ type: "text", text: `Sent reply to dashboard (chat_id ${chat_id}).` }] };
|
|
80121
80755
|
}
|
|
80122
80756
|
);
|
|
80757
|
+
server.tool(
|
|
80758
|
+
"gossip_ask",
|
|
80759
|
+
`Dashboard bridge ONLY: ask the dashboard chat (identified by chat_id) a structured selection question. The dashboard renders radios (single-select) or checkboxes (multiSelect) plus an optional free-text "Other"; the user's answer comes back to you as a NORMAL channel turn (a later message prefixed "[answer qid=\u2026]"). This is the dashboard-answerable parallel to the terminal-only AskUserQuestion \u2014 use it when a <channel source="gossipcat" chat_id="..."> dashboard chat is active. NON-BLOCKING: returns at once; do NOT wait for the answer in this call. Pass the chat_id verbatim. Returns an error (no side effect) when no matching dashboard bridge stream is open.`,
|
|
80760
|
+
{
|
|
80761
|
+
chat_id: external_exports.string().describe('The chat_id verbatim from the <channel ... chat_id="..."> dashboard message.'),
|
|
80762
|
+
questions: external_exports.array(
|
|
80763
|
+
external_exports.object({
|
|
80764
|
+
header: external_exports.string().describe('Short label for the question (e.g. "Approach").'),
|
|
80765
|
+
question: external_exports.string().describe("The prompt text shown above the options."),
|
|
80766
|
+
multiSelect: external_exports.boolean().optional().describe("true \u2192 checkboxes (multiple allowed); false/omitted \u2192 radios (one)."),
|
|
80767
|
+
options: external_exports.array(
|
|
80768
|
+
external_exports.object({
|
|
80769
|
+
label: external_exports.string().describe("The option label the user selects; must be unique within the question."),
|
|
80770
|
+
description: external_exports.string().optional().describe("Optional one-line clarification shown under the label.")
|
|
80771
|
+
})
|
|
80772
|
+
).describe("Selectable options (\u22648)."),
|
|
80773
|
+
allowOther: external_exports.boolean().optional().describe('When true, the dashboard shows a free-text "Other" input.')
|
|
80774
|
+
})
|
|
80775
|
+
).describe("1\u20134 questions, each with \u22648 options.")
|
|
80776
|
+
},
|
|
80777
|
+
async ({ chat_id, questions }) => {
|
|
80778
|
+
if (typeof chat_id !== "string" || chat_id.trim().length === 0) {
|
|
80779
|
+
return { content: [{ type: "text", text: "gossip_ask error: chat_id is required." }], isError: true };
|
|
80780
|
+
}
|
|
80781
|
+
if (!ctx.relay) {
|
|
80782
|
+
return { content: [{ type: "text", text: "gossip_ask error: no dashboard bridge is active (relay not started)." }], isError: true };
|
|
80783
|
+
}
|
|
80784
|
+
const { validateAskQuestions: validateAskQuestions2 } = await Promise.resolve().then(() => (init_src5(), src_exports4));
|
|
80785
|
+
const validated = validateAskQuestions2(questions);
|
|
80786
|
+
if ("error" in validated) {
|
|
80787
|
+
return { content: [{ type: "text", text: `gossip_ask error: ${validated.error}.` }], isError: true };
|
|
80788
|
+
}
|
|
80789
|
+
const qid = (0, import_crypto37.randomUUID)();
|
|
80790
|
+
let emitted = false;
|
|
80791
|
+
try {
|
|
80792
|
+
emitted = ctx.relay.emitBridgeQuestion(chat_id, qid, validated.questions) === true;
|
|
80793
|
+
} catch {
|
|
80794
|
+
emitted = false;
|
|
80795
|
+
}
|
|
80796
|
+
if (!emitted) {
|
|
80797
|
+
return {
|
|
80798
|
+
content: [{ type: "text", text: `gossip_ask error: no open dashboard bridge stream for chat_id "${chat_id}". This tool only sends to the dashboard channel.` }],
|
|
80799
|
+
isError: true
|
|
80800
|
+
};
|
|
80801
|
+
}
|
|
80802
|
+
return {
|
|
80803
|
+
content: [{ type: "text", text: `Asked the dashboard (qid=${qid}); the user's answer will arrive as a channel turn prefixed "[answer qid=${qid}]". Do not wait for it here.` }]
|
|
80804
|
+
};
|
|
80805
|
+
}
|
|
80806
|
+
);
|
|
80123
80807
|
server.tool(
|
|
80124
80808
|
"gossip_status",
|
|
80125
80809
|
"Check Gossip Mesh system status, host environment, available agents, dashboard URL/key, and agent list with provider/model/skills. Pass slim:true to skip the handbook inline (~21KB savings) on reconnect refreshes.",
|
|
@@ -80177,7 +80861,7 @@ Note: write-mode classification unavailable on this native-only install \u2014 a
|
|
|
80177
80861
|
}
|
|
80178
80862
|
try {
|
|
80179
80863
|
const { readFileSync: rfHealth } = await import("fs");
|
|
80180
|
-
const healthPath = (0,
|
|
80864
|
+
const healthPath = (0, import_path95.join)(process.cwd(), ".gossip", "skill-runner-health.json");
|
|
80181
80865
|
const raw = rfHealth(healthPath, "utf8");
|
|
80182
80866
|
const h = JSON.parse(raw);
|
|
80183
80867
|
const lastMs = h.last_run_at ? new Date(h.last_run_at).getTime() : NaN;
|
|
@@ -80193,9 +80877,9 @@ Note: write-mode classification unavailable on this native-only install \u2014 a
|
|
|
80193
80877
|
lines.push(" Skill graduation: never run since session start (expected after first gossip_collect)");
|
|
80194
80878
|
}
|
|
80195
80879
|
try {
|
|
80196
|
-
const { readFileSync:
|
|
80197
|
-
const quotaPath = (0,
|
|
80198
|
-
const quotaRaw =
|
|
80880
|
+
const { readFileSync: readFileSync69 } = await import("fs");
|
|
80881
|
+
const quotaPath = (0, import_path95.join)(process.cwd(), ".gossip", "quota-state.json");
|
|
80882
|
+
const quotaRaw = readFileSync69(quotaPath, "utf8");
|
|
80199
80883
|
const quotaState = JSON.parse(quotaRaw);
|
|
80200
80884
|
for (const [provider, state] of Object.entries(quotaState)) {
|
|
80201
80885
|
const now = Date.now();
|
|
@@ -80247,7 +80931,7 @@ Note: write-mode classification unavailable on this native-only install \u2014 a
|
|
|
80247
80931
|
}
|
|
80248
80932
|
try {
|
|
80249
80933
|
const { existsSync: hookExists } = await import("fs");
|
|
80250
|
-
const hookScriptPath = (0,
|
|
80934
|
+
const hookScriptPath = (0, import_path95.join)(process.cwd(), ".claude", "hooks", "worktree-sandbox.sh");
|
|
80251
80935
|
const { isWorktreeSandboxHookRegistered: isWorktreeSandboxHookRegistered2 } = await Promise.resolve().then(() => (init_src4(), src_exports3));
|
|
80252
80936
|
if (hookExists(hookScriptPath) && !isWorktreeSandboxHookRegistered2(process.cwd())) {
|
|
80253
80937
|
lines.push(" \u26A0\uFE0F Sandbox: worktree-sandbox hook installed but UNREGISTERED \u2014 run gossip_setup to re-register");
|
|
@@ -80255,21 +80939,21 @@ Note: write-mode classification unavailable on this native-only install \u2014 a
|
|
|
80255
80939
|
} catch {
|
|
80256
80940
|
}
|
|
80257
80941
|
try {
|
|
80258
|
-
const { readdirSync: readdirSync22, statSync: statSync36, readFileSync:
|
|
80942
|
+
const { readdirSync: readdirSync22, statSync: statSync36, readFileSync: readFileSync69 } = await import("fs");
|
|
80259
80943
|
const { readJsonlWithRotated: readJsonlRotated1506 } = await Promise.resolve().then(() => (init_src4(), src_exports3));
|
|
80260
|
-
const reportsDir = (0,
|
|
80261
|
-
const perfPath = (0,
|
|
80944
|
+
const reportsDir = (0, import_path95.join)(process.cwd(), ".gossip", "consensus-reports");
|
|
80945
|
+
const perfPath = (0, import_path95.join)(process.cwd(), ".gossip", "agent-performance.jsonl");
|
|
80262
80946
|
const WINDOW_MS3 = 24 * 60 * 60 * 1e3;
|
|
80263
80947
|
const now = Date.now();
|
|
80264
80948
|
const recentReports = [];
|
|
80265
80949
|
try {
|
|
80266
80950
|
for (const fname of readdirSync22(reportsDir)) {
|
|
80267
80951
|
if (!fname.endsWith(".json")) continue;
|
|
80268
|
-
const fpath = (0,
|
|
80952
|
+
const fpath = (0, import_path95.join)(reportsDir, fname);
|
|
80269
80953
|
const st = statSync36(fpath);
|
|
80270
80954
|
if (now - st.mtimeMs > WINDOW_MS3) continue;
|
|
80271
80955
|
try {
|
|
80272
|
-
const report = JSON.parse(
|
|
80956
|
+
const report = JSON.parse(readFileSync69(fpath, "utf8"));
|
|
80273
80957
|
const isEmpty = (report.confirmed?.length ?? 0) === 0 && (report.disputed?.length ?? 0) === 0 && (report.unverified?.length ?? 0) === 0 && (report.unique?.length ?? 0) === 0 && (report.newFindings?.length ?? 0) === 0 && (report.insights?.length ?? 0) === 0;
|
|
80274
80958
|
if (isEmpty) continue;
|
|
80275
80959
|
} catch {
|
|
@@ -80643,8 +81327,8 @@ ${body}`
|
|
|
80643
81327
|
}
|
|
80644
81328
|
return { content: [{ type: "text", text: results.join("\n") }] };
|
|
80645
81329
|
}
|
|
80646
|
-
const { writeFileSync:
|
|
80647
|
-
const { join:
|
|
81330
|
+
const { writeFileSync: writeFileSync38, mkdirSync: mkdirSync45, existsSync: existsSync73 } = require("fs");
|
|
81331
|
+
const { join: join91 } = require("path");
|
|
80648
81332
|
const root = process.cwd();
|
|
80649
81333
|
const CLAUDE_MODEL_MAP2 = {
|
|
80650
81334
|
opus: { provider: "anthropic", model: "claude-opus-4-6" },
|
|
@@ -80660,8 +81344,8 @@ ${body}`
|
|
|
80660
81344
|
let existingConfig = {};
|
|
80661
81345
|
let existingAgents = {};
|
|
80662
81346
|
try {
|
|
80663
|
-
const { readFileSync:
|
|
80664
|
-
const existing = JSON.parse(
|
|
81347
|
+
const { readFileSync: readFileSync69 } = require("fs");
|
|
81348
|
+
const existing = JSON.parse(readFileSync69(join91(root, ".gossip", "config.json"), "utf-8"));
|
|
80665
81349
|
if (existing && typeof existing === "object" && !Array.isArray(existing)) {
|
|
80666
81350
|
existingConfig = existing;
|
|
80667
81351
|
if (mode === "merge") existingAgents = existing.agents || {};
|
|
@@ -80695,8 +81379,8 @@ ${body}`
|
|
|
80695
81379
|
body
|
|
80696
81380
|
].join("\n");
|
|
80697
81381
|
pendingAgentFileWrites.push({
|
|
80698
|
-
dir:
|
|
80699
|
-
path:
|
|
81382
|
+
dir: join91(root, ".claude", "agents"),
|
|
81383
|
+
path: join91(root, ".claude", "agents", `${agent.id}.md`),
|
|
80700
81384
|
content: md
|
|
80701
81385
|
});
|
|
80702
81386
|
nativeCreated.push(agent.id);
|
|
@@ -80717,7 +81401,7 @@ ${body}`
|
|
|
80717
81401
|
errors.push(`${agent.id}: custom agent requires "custom_model" field`);
|
|
80718
81402
|
continue;
|
|
80719
81403
|
}
|
|
80720
|
-
const nativeFile =
|
|
81404
|
+
const nativeFile = join91(root, ".claude", "agents", `${agent.id}.md`);
|
|
80721
81405
|
const wasNative = existingAgents[agent.id]?.native || configAgents[agent.id]?.native || existsSync73(nativeFile);
|
|
80722
81406
|
if (wasNative) {
|
|
80723
81407
|
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.`);
|
|
@@ -80733,10 +81417,10 @@ ${body}`
|
|
|
80733
81417
|
};
|
|
80734
81418
|
customCreated.push(agent.id);
|
|
80735
81419
|
if (agent.instructions) {
|
|
80736
|
-
const instrDir =
|
|
81420
|
+
const instrDir = join91(root, ".gossip", "agents", agent.id);
|
|
80737
81421
|
pendingAgentFileWrites.push({
|
|
80738
81422
|
dir: instrDir,
|
|
80739
|
-
path:
|
|
81423
|
+
path: join91(instrDir, "instructions.md"),
|
|
80740
81424
|
content: agent.instructions
|
|
80741
81425
|
});
|
|
80742
81426
|
}
|
|
@@ -80754,9 +81438,9 @@ ${body}`
|
|
|
80754
81438
|
} catch (err) {
|
|
80755
81439
|
return { content: [{ type: "text", text: `Invalid config: ${err.message}` }] };
|
|
80756
81440
|
}
|
|
80757
|
-
flushStagedAgentFileWrites(pendingAgentFileWrites, { mkdirSync:
|
|
80758
|
-
|
|
80759
|
-
|
|
81441
|
+
flushStagedAgentFileWrites(pendingAgentFileWrites, { mkdirSync: mkdirSync45, writeFileSync: writeFileSync38 });
|
|
81442
|
+
mkdirSync45(join91(root, ".gossip"), { recursive: true });
|
|
81443
|
+
writeFileSync38(join91(root, ".gossip", "config.json"), JSON.stringify(config2, null, 2));
|
|
80760
81444
|
let hookSummary = "";
|
|
80761
81445
|
try {
|
|
80762
81446
|
const { installWorktreeSandboxHook: installWorktreeSandboxHook2, writeOrchestratorRoleMarker: writeOrchestratorRoleMarker2 } = (init_src4(), __toCommonJS(src_exports3));
|
|
@@ -80859,10 +81543,13 @@ ${body}`
|
|
|
80859
81543
|
}
|
|
80860
81544
|
}
|
|
80861
81545
|
const agentList = Object.entries(config2.agents).map(([id, a]) => `- ${id}: ${a.provider}/${a.model} (${a.preset || "custom"})${a.native ? " \u2014 native" : ""}`).join("\n");
|
|
80862
|
-
const rulesDir =
|
|
80863
|
-
const rulesFile =
|
|
80864
|
-
|
|
80865
|
-
|
|
81546
|
+
const rulesDir = join91(root, env.rulesDir);
|
|
81547
|
+
const rulesFile = join91(root, env.rulesFile);
|
|
81548
|
+
mkdirSync45(rulesDir, { recursive: true });
|
|
81549
|
+
writeFileSync38(rulesFile, generateRulesContent(
|
|
81550
|
+
agentList,
|
|
81551
|
+
env.host === "cursor" || env.host === "claude-code" ? env.host : void 0
|
|
81552
|
+
));
|
|
80866
81553
|
const lines = [`Host: ${env.host}`, ""];
|
|
80867
81554
|
if (nativeCreated.length > 0) {
|
|
80868
81555
|
lines.push(`Native agents created (${nativeCreated.length}):`);
|
|
@@ -80948,8 +81635,8 @@ Tip: Native agents may prompt for file write permissions. To auto-allow, add to
|
|
|
80948
81635
|
if (agent_id === "auto") {
|
|
80949
81636
|
try {
|
|
80950
81637
|
await syncWorkersViaKeychain();
|
|
80951
|
-
const isNullLlm = ctx.mainProvider === "none";
|
|
80952
|
-
if (isNullLlm && env.host === "claude-code") {
|
|
81638
|
+
const isNullLlm = ctx.mainProvider === "none" || ctx.mainProviderConfig === "none";
|
|
81639
|
+
if (isNullLlm && (env.host === "claude-code" || env.host === "cursor")) {
|
|
80953
81640
|
const agents = ctx.mainAgent.getAgentList?.() ?? [];
|
|
80954
81641
|
const agentSummary = agents.map(
|
|
80955
81642
|
(a) => `- ${a.id} (${a.provider}/${a.model}) [${a.skills?.join(", ") || "no skills"}]`
|
|
@@ -81166,11 +81853,11 @@ The original signal remains in the audit log but will be excluded from scoring.`
|
|
|
81166
81853
|
const fid = finding_id.trim();
|
|
81167
81854
|
const cwd = process.cwd();
|
|
81168
81855
|
const findingsPath = require("path").join(cwd, ".gossip", "implementation-findings.jsonl");
|
|
81169
|
-
const { readFileSync:
|
|
81856
|
+
const { readFileSync: readFileSync69, existsSync: existsSync73, writeFileSync: writeFileSync38, renameSync: renameSync18 } = require("fs");
|
|
81170
81857
|
if (!existsSync73(findingsPath)) {
|
|
81171
81858
|
return { content: [{ type: "text", text: `No implementation-findings.jsonl found at ${findingsPath}` }] };
|
|
81172
81859
|
}
|
|
81173
|
-
const lines =
|
|
81860
|
+
const lines = readFileSync69(findingsPath, "utf-8").split("\n");
|
|
81174
81861
|
let matched = false;
|
|
81175
81862
|
let alreadyTarget = false;
|
|
81176
81863
|
let matchedEntry = null;
|
|
@@ -81226,8 +81913,8 @@ The original signal remains in the audit log but will be excluded from scoring.`
|
|
|
81226
81913
|
}
|
|
81227
81914
|
try {
|
|
81228
81915
|
const tmp = findingsPath + ".tmp." + Date.now();
|
|
81229
|
-
|
|
81230
|
-
|
|
81916
|
+
writeFileSync38(tmp, newLines.join("\n"));
|
|
81917
|
+
renameSync18(tmp, findingsPath);
|
|
81231
81918
|
} catch (err) {
|
|
81232
81919
|
return { content: [{ type: "text", text: `Failed to write findings: ${err.message}` }] };
|
|
81233
81920
|
}
|
|
@@ -81258,18 +81945,18 @@ The original signal remains in the audit log but will be excluded from scoring.`
|
|
|
81258
81945
|
return { content: [{ type: "text", text: "Error: consensus_id is required for bulk_from_consensus." }] };
|
|
81259
81946
|
}
|
|
81260
81947
|
try {
|
|
81261
|
-
const { readFileSync:
|
|
81262
|
-
const { join:
|
|
81263
|
-
const reportPath =
|
|
81948
|
+
const { readFileSync: readFileSync69 } = await import("fs");
|
|
81949
|
+
const { join: join91 } = await import("path");
|
|
81950
|
+
const reportPath = join91(process.cwd(), ".gossip", "consensus-reports", `${consensus_id}.json`);
|
|
81264
81951
|
let report;
|
|
81265
81952
|
try {
|
|
81266
|
-
report = JSON.parse(
|
|
81953
|
+
report = JSON.parse(readFileSync69(reportPath, "utf-8"));
|
|
81267
81954
|
} catch {
|
|
81268
81955
|
return { content: [{ type: "text", text: `Error: consensus report not found: ${consensus_id}` }] };
|
|
81269
81956
|
}
|
|
81270
81957
|
const existingFindingIds = /* @__PURE__ */ new Set();
|
|
81271
81958
|
try {
|
|
81272
|
-
const perfPath =
|
|
81959
|
+
const perfPath = join91(process.cwd(), ".gossip", "agent-performance.jsonl");
|
|
81273
81960
|
const { readJsonlWithRotated: readJsonlRotated2570 } = await Promise.resolve().then(() => (init_src4(), src_exports3));
|
|
81274
81961
|
const lines = readJsonlRotated2570(perfPath).split("\n").filter(Boolean);
|
|
81275
81962
|
for (const line of lines) {
|
|
@@ -82317,16 +83004,16 @@ ${preview}` }]
|
|
|
82317
83004
|
const { SkillGapTracker: SkillGapTracker2, parseSkillFrontmatter: parseSkillFrontmatter2, normalizeSkillName: normalizeSkillName2 } = await Promise.resolve().then(() => (init_src4(), src_exports3));
|
|
82318
83005
|
const tracker = new SkillGapTracker2(process.cwd());
|
|
82319
83006
|
if (skills && skills.length > 0) {
|
|
82320
|
-
const { writeFileSync:
|
|
82321
|
-
const { join:
|
|
82322
|
-
const dir =
|
|
82323
|
-
|
|
83007
|
+
const { writeFileSync: writeFileSync38, mkdirSync: mkdirSync45, existsSync: existsSync73, readFileSync: readFileSync69 } = require("fs");
|
|
83008
|
+
const { join: join91 } = require("path");
|
|
83009
|
+
const dir = join91(process.cwd(), ".gossip", "skills");
|
|
83010
|
+
mkdirSync45(dir, { recursive: true });
|
|
82324
83011
|
const results = [];
|
|
82325
83012
|
for (const sk of skills) {
|
|
82326
83013
|
const name = normalizeSkillName2(sk.name);
|
|
82327
|
-
const filePath =
|
|
83014
|
+
const filePath = join91(dir, `${name}.md`);
|
|
82328
83015
|
if (existsSync73(filePath)) {
|
|
82329
|
-
const existing =
|
|
83016
|
+
const existing = readFileSync69(filePath, "utf-8");
|
|
82330
83017
|
const fm = parseSkillFrontmatter2(existing);
|
|
82331
83018
|
if (fm) {
|
|
82332
83019
|
if (fm.generated_by === "manual") {
|
|
@@ -82343,7 +83030,7 @@ ${preview}` }]
|
|
|
82343
83030
|
}
|
|
82344
83031
|
}
|
|
82345
83032
|
}
|
|
82346
|
-
|
|
83033
|
+
writeFileSync38(filePath, sk.content);
|
|
82347
83034
|
tracker.recordResolution(name);
|
|
82348
83035
|
results.push(`Created .gossip/skills/${name}.md`);
|
|
82349
83036
|
}
|
|
@@ -83166,9 +83853,9 @@ ${p.user}
|
|
|
83166
83853
|
max_events: external_exports.number().int().positive().max(WATCH_MAX_EVENTS).optional().describe(`Cap events returned (default ${WATCH_MAX_EVENTS}).`)
|
|
83167
83854
|
},
|
|
83168
83855
|
async ({ cursor, max_events }) => {
|
|
83169
|
-
const { join:
|
|
83856
|
+
const { join: join91 } = await import("node:path");
|
|
83170
83857
|
const { readJsonlWithRotated: readJsonlWithRotated2 } = await Promise.resolve().then(() => (init_src4(), src_exports3));
|
|
83171
|
-
const perfPath =
|
|
83858
|
+
const perfPath = join91(process.cwd(), ".gossip", "agent-performance.jsonl");
|
|
83172
83859
|
const raw = readJsonlWithRotated2(perfPath);
|
|
83173
83860
|
const result = filterWatchEvents(raw, { cursor, maxEvents: max_events });
|
|
83174
83861
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|