@essentialai/cogent-plugin 3.20.4 → 3.20.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/.claude-plugin/plugin.json +1 -1
- package/bridge/cogent-bridge.mjs +171 -87
- package/hooks/check-on-stop.lib.mjs +93 -3
- package/hooks/check-on-stop.mjs +27 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cogent",
|
|
3
|
-
"version": "3.20.
|
|
3
|
+
"version": "3.20.6",
|
|
4
4
|
"description": "Inter-session communication bridge for Claude Code with Slack integration. Enables CC agents and Slack team members to communicate in real time.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Essential AI Solutions",
|
package/bridge/cogent-bridge.mjs
CHANGED
|
@@ -1259,8 +1259,8 @@ var init_parseUtil = __esm({
|
|
|
1259
1259
|
init_errors();
|
|
1260
1260
|
init_en();
|
|
1261
1261
|
makeIssue = (params) => {
|
|
1262
|
-
const { data, path:
|
|
1263
|
-
const fullPath = [...
|
|
1262
|
+
const { data, path: path19, errorMaps, issueData } = params;
|
|
1263
|
+
const fullPath = [...path19, ...issueData.path || []];
|
|
1264
1264
|
const fullIssue = {
|
|
1265
1265
|
...issueData,
|
|
1266
1266
|
path: fullPath
|
|
@@ -1540,11 +1540,11 @@ var init_types = __esm({
|
|
|
1540
1540
|
init_parseUtil();
|
|
1541
1541
|
init_util();
|
|
1542
1542
|
ParseInputLazyPath = class {
|
|
1543
|
-
constructor(parent, value,
|
|
1543
|
+
constructor(parent, value, path19, key) {
|
|
1544
1544
|
this._cachedPath = [];
|
|
1545
1545
|
this.parent = parent;
|
|
1546
1546
|
this.data = value;
|
|
1547
|
-
this._path =
|
|
1547
|
+
this._path = path19;
|
|
1548
1548
|
this._key = key;
|
|
1549
1549
|
}
|
|
1550
1550
|
get path() {
|
|
@@ -4996,10 +4996,10 @@ function assignProp(target, prop, value) {
|
|
|
4996
4996
|
configurable: true
|
|
4997
4997
|
});
|
|
4998
4998
|
}
|
|
4999
|
-
function getElementAtPath(obj,
|
|
5000
|
-
if (!
|
|
4999
|
+
function getElementAtPath(obj, path19) {
|
|
5000
|
+
if (!path19)
|
|
5001
5001
|
return obj;
|
|
5002
|
-
return
|
|
5002
|
+
return path19.reduce((acc, key) => acc?.[key], obj);
|
|
5003
5003
|
}
|
|
5004
5004
|
function promiseAllObject(promisesObj) {
|
|
5005
5005
|
const keys = Object.keys(promisesObj);
|
|
@@ -5248,11 +5248,11 @@ function aborted(x, startIndex = 0) {
|
|
|
5248
5248
|
}
|
|
5249
5249
|
return false;
|
|
5250
5250
|
}
|
|
5251
|
-
function prefixIssues(
|
|
5251
|
+
function prefixIssues(path19, issues) {
|
|
5252
5252
|
return issues.map((iss) => {
|
|
5253
5253
|
var _a;
|
|
5254
5254
|
(_a = iss).path ?? (_a.path = []);
|
|
5255
|
-
iss.path.unshift(
|
|
5255
|
+
iss.path.unshift(path19);
|
|
5256
5256
|
return iss;
|
|
5257
5257
|
});
|
|
5258
5258
|
}
|
|
@@ -17058,8 +17058,8 @@ var require_utils = __commonJS({
|
|
|
17058
17058
|
}
|
|
17059
17059
|
return ind;
|
|
17060
17060
|
}
|
|
17061
|
-
function removeDotSegments(
|
|
17062
|
-
let input =
|
|
17061
|
+
function removeDotSegments(path19) {
|
|
17062
|
+
let input = path19;
|
|
17063
17063
|
const output = [];
|
|
17064
17064
|
let nextSlash = -1;
|
|
17065
17065
|
let len = 0;
|
|
@@ -17258,8 +17258,8 @@ var require_schemes = __commonJS({
|
|
|
17258
17258
|
wsComponent.secure = void 0;
|
|
17259
17259
|
}
|
|
17260
17260
|
if (wsComponent.resourceName) {
|
|
17261
|
-
const [
|
|
17262
|
-
wsComponent.path =
|
|
17261
|
+
const [path19, query] = wsComponent.resourceName.split("?");
|
|
17262
|
+
wsComponent.path = path19 && path19 !== "/" ? path19 : void 0;
|
|
17263
17263
|
wsComponent.query = query;
|
|
17264
17264
|
wsComponent.resourceName = void 0;
|
|
17265
17265
|
}
|
|
@@ -20612,12 +20612,12 @@ var require_dist = __commonJS({
|
|
|
20612
20612
|
throw new Error(`Unknown format "${name}"`);
|
|
20613
20613
|
return f;
|
|
20614
20614
|
};
|
|
20615
|
-
function addFormats(ajv, list,
|
|
20615
|
+
function addFormats(ajv, list, fs17, exportName) {
|
|
20616
20616
|
var _a;
|
|
20617
20617
|
var _b;
|
|
20618
20618
|
(_a = (_b = ajv.opts.code).formats) !== null && _a !== void 0 ? _a : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
|
|
20619
20619
|
for (const f of list)
|
|
20620
|
-
ajv.addFormat(f,
|
|
20620
|
+
ajv.addFormat(f, fs17[f]);
|
|
20621
20621
|
}
|
|
20622
20622
|
module.exports = exports = formatsPlugin;
|
|
20623
20623
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -21729,8 +21729,8 @@ var require_parseUtil = __commonJS({
|
|
|
21729
21729
|
var errors_js_1 = require_errors2();
|
|
21730
21730
|
var en_js_1 = __importDefault(require_en());
|
|
21731
21731
|
var makeIssue2 = (params) => {
|
|
21732
|
-
const { data, path:
|
|
21733
|
-
const fullPath = [...
|
|
21732
|
+
const { data, path: path19, errorMaps, issueData } = params;
|
|
21733
|
+
const fullPath = [...path19, ...issueData.path || []];
|
|
21734
21734
|
const fullIssue = {
|
|
21735
21735
|
...issueData,
|
|
21736
21736
|
path: fullPath
|
|
@@ -21884,11 +21884,11 @@ var require_types2 = __commonJS({
|
|
|
21884
21884
|
var parseUtil_js_1 = require_parseUtil();
|
|
21885
21885
|
var util_js_1 = require_util2();
|
|
21886
21886
|
var ParseInputLazyPath2 = class {
|
|
21887
|
-
constructor(parent, value,
|
|
21887
|
+
constructor(parent, value, path19, key) {
|
|
21888
21888
|
this._cachedPath = [];
|
|
21889
21889
|
this.parent = parent;
|
|
21890
21890
|
this.data = value;
|
|
21891
|
-
this._path =
|
|
21891
|
+
this._path = path19;
|
|
21892
21892
|
this._key = key;
|
|
21893
21893
|
}
|
|
21894
21894
|
get path() {
|
|
@@ -26389,8 +26389,8 @@ var init_stdio2 = __esm({
|
|
|
26389
26389
|
// src/constants.ts
|
|
26390
26390
|
import { createRequire } from "node:module";
|
|
26391
26391
|
function resolveVersion() {
|
|
26392
|
-
if ("3.20.
|
|
26393
|
-
return "3.20.
|
|
26392
|
+
if ("3.20.6") {
|
|
26393
|
+
return "3.20.6";
|
|
26394
26394
|
}
|
|
26395
26395
|
try {
|
|
26396
26396
|
const require2 = createRequire(import.meta.url);
|
|
@@ -27624,7 +27624,7 @@ var init_http_backend = __esm({
|
|
|
27624
27624
|
* in cloud mode we ignore it and use this.sessionId (the cloud session).
|
|
27625
27625
|
*/
|
|
27626
27626
|
async registerPeer(peerId, _sessionId, cwd, label, clientVersion, mode, _channelSessionId, capabilities, workspaceId, threadId) {
|
|
27627
|
-
const
|
|
27627
|
+
const path19 = PATHS.peers.replace(":sessionId", this.sessionId);
|
|
27628
27628
|
const body = {
|
|
27629
27629
|
peerId,
|
|
27630
27630
|
cwd,
|
|
@@ -27652,7 +27652,7 @@ var init_http_backend = __esm({
|
|
|
27652
27652
|
body.role = roleCap.slice("role:".length);
|
|
27653
27653
|
}
|
|
27654
27654
|
}
|
|
27655
|
-
return this.http.post(
|
|
27655
|
+
return this.http.post(path19, body);
|
|
27656
27656
|
}
|
|
27657
27657
|
/**
|
|
27658
27658
|
* Deregister a peer from the cloud session.
|
|
@@ -27664,9 +27664,9 @@ var init_http_backend = __esm({
|
|
|
27664
27664
|
* local mail creds. A 404 (peer not found) resolves to `{ removed: false, ... }`, not a throw.
|
|
27665
27665
|
*/
|
|
27666
27666
|
async deregisterPeer(peerId) {
|
|
27667
|
-
const
|
|
27667
|
+
const path19 = PATHS.peer.replace(":sessionId", this.sessionId).replace(":peerId", peerId);
|
|
27668
27668
|
try {
|
|
27669
|
-
const headers = await this.http.delete(
|
|
27669
|
+
const headers = await this.http.delete(path19, {});
|
|
27670
27670
|
const mailboxDeprovisioned = headers.get(MAILBOX_DEPROVISIONED_HEADER) === "true";
|
|
27671
27671
|
return { removed: true, mailboxDeprovisioned };
|
|
27672
27672
|
} catch {
|
|
@@ -27687,8 +27687,8 @@ var init_http_backend = __esm({
|
|
|
27687
27687
|
* GET /api/sessions/:sessionId/peers
|
|
27688
27688
|
*/
|
|
27689
27689
|
async listPeers() {
|
|
27690
|
-
const
|
|
27691
|
-
const result = await this.http.get(
|
|
27690
|
+
const path19 = PATHS.peers.replace(":sessionId", this.sessionId);
|
|
27691
|
+
const result = await this.http.get(path19);
|
|
27692
27692
|
return result.peers;
|
|
27693
27693
|
}
|
|
27694
27694
|
/**
|
|
@@ -27696,8 +27696,8 @@ var init_http_backend = __esm({
|
|
|
27696
27696
|
* Calls the lightweight heartbeat endpoint on the cloud relay server.
|
|
27697
27697
|
*/
|
|
27698
27698
|
async updateLastSeen(peerId) {
|
|
27699
|
-
const
|
|
27700
|
-
await this.http.post(
|
|
27699
|
+
const path19 = PATHS.heartbeat.replace(":sessionId", this.sessionId);
|
|
27700
|
+
await this.http.post(path19, { peerId });
|
|
27701
27701
|
}
|
|
27702
27702
|
/**
|
|
27703
27703
|
* Record a message in the cloud session.
|
|
@@ -27708,7 +27708,7 @@ var init_http_backend = __esm({
|
|
|
27708
27708
|
* the input record to construct a full MessageRecord.
|
|
27709
27709
|
*/
|
|
27710
27710
|
async recordMessage(record2) {
|
|
27711
|
-
const
|
|
27711
|
+
const path19 = PATHS.messages.replace(":sessionId", this.sessionId);
|
|
27712
27712
|
const body = {
|
|
27713
27713
|
fromPeerId: record2.fromPeerId,
|
|
27714
27714
|
toPeerId: record2.toPeerId,
|
|
@@ -27720,7 +27720,7 @@ var init_http_backend = __esm({
|
|
|
27720
27720
|
if (record2.attachments && record2.attachments.length > 0) {
|
|
27721
27721
|
body.attachments = record2.attachments;
|
|
27722
27722
|
}
|
|
27723
|
-
const resp = await this.http.post(
|
|
27723
|
+
const resp = await this.http.post(path19, body);
|
|
27724
27724
|
return {
|
|
27725
27725
|
...record2,
|
|
27726
27726
|
id: resp.id,
|
|
@@ -27734,7 +27734,7 @@ var init_http_backend = __esm({
|
|
|
27734
27734
|
* Supports optional peerId filter and limit (default 300).
|
|
27735
27735
|
*/
|
|
27736
27736
|
async getHistory(peerId, limit, includeRelayEchoes, metadataOnly, dedupInferredEchoes) {
|
|
27737
|
-
const
|
|
27737
|
+
const path19 = PATHS.messages.replace(":sessionId", this.sessionId);
|
|
27738
27738
|
const query = {};
|
|
27739
27739
|
if (peerId) {
|
|
27740
27740
|
query.peerId = peerId;
|
|
@@ -27749,7 +27749,7 @@ var init_http_backend = __esm({
|
|
|
27749
27749
|
if (dedupInferredEchoes) {
|
|
27750
27750
|
query.dedupInferredEchoes = "true";
|
|
27751
27751
|
}
|
|
27752
|
-
const result = await this.http.get(
|
|
27752
|
+
const result = await this.http.get(path19, query);
|
|
27753
27753
|
return result.messages;
|
|
27754
27754
|
}
|
|
27755
27755
|
/**
|
|
@@ -27760,12 +27760,12 @@ var init_http_backend = __esm({
|
|
|
27760
27760
|
* so that get-history shows the actual response (not null).
|
|
27761
27761
|
*/
|
|
27762
27762
|
async updateMessageResponse(messageId, response, durationMs, pending) {
|
|
27763
|
-
const
|
|
27763
|
+
const path19 = PATHS.message.replace(":sessionId", this.sessionId).replace(":messageId", messageId);
|
|
27764
27764
|
const body = { response, durationMs };
|
|
27765
27765
|
if (pending === true) {
|
|
27766
27766
|
body.pending = true;
|
|
27767
27767
|
}
|
|
27768
|
-
await this.http.patch(
|
|
27768
|
+
await this.http.patch(path19, body);
|
|
27769
27769
|
}
|
|
27770
27770
|
/**
|
|
27771
27771
|
* Validate a session.
|
|
@@ -27907,8 +27907,8 @@ var init_http_client = __esm({
|
|
|
27907
27907
|
* Perform an authenticated GET request.
|
|
27908
27908
|
* Appends query parameters to the URL if provided.
|
|
27909
27909
|
*/
|
|
27910
|
-
async get(
|
|
27911
|
-
const url = new URL(
|
|
27910
|
+
async get(path19, query) {
|
|
27911
|
+
const url = new URL(path19, this.baseUrl);
|
|
27912
27912
|
if (query) {
|
|
27913
27913
|
for (const [k, v] of Object.entries(query)) {
|
|
27914
27914
|
url.searchParams.set(k, v);
|
|
@@ -27923,8 +27923,8 @@ var init_http_client = __esm({
|
|
|
27923
27923
|
/**
|
|
27924
27924
|
* Perform an authenticated POST request with a JSON body.
|
|
27925
27925
|
*/
|
|
27926
|
-
async post(
|
|
27927
|
-
const url = new URL(
|
|
27926
|
+
async post(path19, body) {
|
|
27927
|
+
const url = new URL(path19, this.baseUrl);
|
|
27928
27928
|
const resp = await this.fetchWithTimeout(url, {
|
|
27929
27929
|
method: "POST",
|
|
27930
27930
|
headers: this.headers(),
|
|
@@ -27942,8 +27942,8 @@ var init_http_client = __esm({
|
|
|
27942
27942
|
* Perform an authenticated PATCH request with a JSON body.
|
|
27943
27943
|
* Used to update existing resources (e.g., message response fields).
|
|
27944
27944
|
*/
|
|
27945
|
-
async patch(
|
|
27946
|
-
const url = new URL(
|
|
27945
|
+
async patch(path19, body) {
|
|
27946
|
+
const url = new URL(path19, this.baseUrl);
|
|
27947
27947
|
const resp = await this.fetchWithTimeout(url, {
|
|
27948
27948
|
method: "PATCH",
|
|
27949
27949
|
headers: this.headers(),
|
|
@@ -27968,8 +27968,8 @@ var init_http_client = __esm({
|
|
|
27968
27968
|
* "Malformed JSON in request body" 400 from cogent-server <=3.1.2 when
|
|
27969
27969
|
* any client (including third-party tools) DELETEd a peer without a body.
|
|
27970
27970
|
*/
|
|
27971
|
-
async delete(
|
|
27972
|
-
const url = new URL(
|
|
27971
|
+
async delete(path19, body) {
|
|
27972
|
+
const url = new URL(path19, this.baseUrl);
|
|
27973
27973
|
const headers = {
|
|
27974
27974
|
"Authorization": `Bearer ${this.token}`
|
|
27975
27975
|
};
|
|
@@ -32152,14 +32152,14 @@ var init_ws_client = __esm({
|
|
|
32152
32152
|
if (this.pollTimer) return;
|
|
32153
32153
|
const poll = async () => {
|
|
32154
32154
|
try {
|
|
32155
|
-
const
|
|
32155
|
+
const path19 = `/api/sessions/${this.opts.sessionId}/poll`;
|
|
32156
32156
|
const query = {
|
|
32157
32157
|
peerId: this.opts.peerId
|
|
32158
32158
|
};
|
|
32159
32159
|
if (this.lastMessageId) {
|
|
32160
32160
|
query.lastMessageId = this.lastMessageId;
|
|
32161
32161
|
}
|
|
32162
|
-
const result = await this.opts.http.get(
|
|
32162
|
+
const result = await this.opts.http.get(path19, query);
|
|
32163
32163
|
if (result.messages && result.messages.length > 0) {
|
|
32164
32164
|
this.opts.onMessages(result.messages);
|
|
32165
32165
|
this.lastMessageId = result.messages[result.messages.length - 1].id;
|
|
@@ -32920,6 +32920,81 @@ var init_session_resolver = __esm({
|
|
|
32920
32920
|
}
|
|
32921
32921
|
});
|
|
32922
32922
|
|
|
32923
|
+
// src/services/wake-inflight.ts
|
|
32924
|
+
import crypto3 from "node:crypto";
|
|
32925
|
+
import fs12 from "node:fs";
|
|
32926
|
+
import os7 from "node:os";
|
|
32927
|
+
import path13 from "node:path";
|
|
32928
|
+
function hashCwd(cwd) {
|
|
32929
|
+
return crypto3.createHash("sha256").update(path13.resolve(cwd)).digest("hex").slice(0, 16);
|
|
32930
|
+
}
|
|
32931
|
+
function wakeInflightPath(peerId, cwd = process.cwd()) {
|
|
32932
|
+
return path13.join(os7.homedir(), ".cogent", "wake-inflight", `${hashCwd(cwd)}.${safe(peerId)}.json`);
|
|
32933
|
+
}
|
|
32934
|
+
function ttlMs() {
|
|
32935
|
+
let timeout = 3e5;
|
|
32936
|
+
try {
|
|
32937
|
+
const configured = getConfig()?.COGENT_TIMEOUT_MS;
|
|
32938
|
+
if (typeof configured === "number" && Number.isFinite(configured) && configured > 0) timeout = configured;
|
|
32939
|
+
} catch {
|
|
32940
|
+
}
|
|
32941
|
+
return Math.min(timeout * INFLIGHT_TIMEOUT_FACTOR, INFLIGHT_MAX_MS);
|
|
32942
|
+
}
|
|
32943
|
+
function readFileSafe(p) {
|
|
32944
|
+
try {
|
|
32945
|
+
const parsed = JSON.parse(fs12.readFileSync(p, "utf-8"));
|
|
32946
|
+
return parsed && typeof parsed === "object" && parsed.messages ? parsed : { messages: {} };
|
|
32947
|
+
} catch {
|
|
32948
|
+
return { messages: {} };
|
|
32949
|
+
}
|
|
32950
|
+
}
|
|
32951
|
+
function writeAtomic(p, data) {
|
|
32952
|
+
fs12.mkdirSync(path13.dirname(p), { recursive: true });
|
|
32953
|
+
const tmp = `${p}.${process.pid}.tmp`;
|
|
32954
|
+
fs12.writeFileSync(tmp, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
32955
|
+
fs12.renameSync(tmp, p);
|
|
32956
|
+
}
|
|
32957
|
+
function prune(f, now) {
|
|
32958
|
+
for (const [id, rec] of Object.entries(f.messages)) {
|
|
32959
|
+
const ok = rec && typeof rec.startedAt === "number" && typeof rec.expiresAt === "number" && Number.isFinite(rec.expiresAt) && rec.expiresAt > now;
|
|
32960
|
+
if (!ok) delete f.messages[id];
|
|
32961
|
+
}
|
|
32962
|
+
}
|
|
32963
|
+
function markWakeInFlight(peerId, messageId, traceId, cwd = process.cwd()) {
|
|
32964
|
+
try {
|
|
32965
|
+
const now = Date.now();
|
|
32966
|
+
const p = wakeInflightPath(peerId, cwd);
|
|
32967
|
+
const cur = readFileSafe(p);
|
|
32968
|
+
prune(cur, now);
|
|
32969
|
+
cur.messages[messageId] = { startedAt: now, expiresAt: now + ttlMs(), ...traceId ? { traceId } : {} };
|
|
32970
|
+
writeAtomic(p, cur);
|
|
32971
|
+
} catch (err) {
|
|
32972
|
+
logger.debug(`wake-inflight: could not mark ${messageId}: ${err}`);
|
|
32973
|
+
}
|
|
32974
|
+
}
|
|
32975
|
+
function clearWakeInFlight(peerId, messageId, cwd = process.cwd()) {
|
|
32976
|
+
try {
|
|
32977
|
+
const p = wakeInflightPath(peerId, cwd);
|
|
32978
|
+
const cur = readFileSafe(p);
|
|
32979
|
+
delete cur.messages[messageId];
|
|
32980
|
+
prune(cur, Date.now());
|
|
32981
|
+
writeAtomic(p, cur);
|
|
32982
|
+
} catch (err) {
|
|
32983
|
+
logger.debug(`wake-inflight: could not clear ${messageId}: ${err}`);
|
|
32984
|
+
}
|
|
32985
|
+
}
|
|
32986
|
+
var INFLIGHT_MAX_MS, INFLIGHT_TIMEOUT_FACTOR, safe;
|
|
32987
|
+
var init_wake_inflight = __esm({
|
|
32988
|
+
"src/services/wake-inflight.ts"() {
|
|
32989
|
+
"use strict";
|
|
32990
|
+
init_config();
|
|
32991
|
+
init_logger();
|
|
32992
|
+
INFLIGHT_MAX_MS = 18e5;
|
|
32993
|
+
INFLIGHT_TIMEOUT_FACTOR = 3;
|
|
32994
|
+
safe = (s) => String(s).replace(/[^A-Za-z0-9._-]/g, "_");
|
|
32995
|
+
}
|
|
32996
|
+
});
|
|
32997
|
+
|
|
32923
32998
|
// src/services/auto-relay.ts
|
|
32924
32999
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
32925
33000
|
function buildStatusNotice(status, peerId, traceId) {
|
|
@@ -32983,6 +33058,7 @@ var init_auto_relay = __esm({
|
|
|
32983
33058
|
init_config();
|
|
32984
33059
|
init_backend();
|
|
32985
33060
|
init_codex_cli();
|
|
33061
|
+
init_wake_inflight();
|
|
32986
33062
|
init_logger();
|
|
32987
33063
|
RELAY_STATUS = {
|
|
32988
33064
|
QUEUED: "queued",
|
|
@@ -33390,6 +33466,9 @@ var init_auto_relay = __esm({
|
|
|
33390
33466
|
);
|
|
33391
33467
|
}
|
|
33392
33468
|
}
|
|
33469
|
+
if (this.localPeerId && !this.awarenessOnly.has(msg.id)) {
|
|
33470
|
+
markWakeInFlight(this.localPeerId, msg.id, msg.traceId);
|
|
33471
|
+
}
|
|
33393
33472
|
if (this.processing) {
|
|
33394
33473
|
logger.debug(`Auto-relay: queuing message from ${msg.fromPeerId} (busy processing)`);
|
|
33395
33474
|
this.queue.push(msg);
|
|
@@ -33422,8 +33501,13 @@ var init_auto_relay = __esm({
|
|
|
33422
33501
|
async _dispatch(msg) {
|
|
33423
33502
|
if (this.awarenessOnly.delete(msg.id)) {
|
|
33424
33503
|
await this._injectPeerBroadcast(msg);
|
|
33425
|
-
|
|
33504
|
+
return;
|
|
33505
|
+
}
|
|
33506
|
+
const inflightPeer = this.localPeerId;
|
|
33507
|
+
try {
|
|
33426
33508
|
await this._processMessage(msg);
|
|
33509
|
+
} finally {
|
|
33510
|
+
if (inflightPeer) clearWakeInFlight(inflightPeer, msg.id);
|
|
33427
33511
|
}
|
|
33428
33512
|
}
|
|
33429
33513
|
/**
|
|
@@ -34017,15 +34101,15 @@ __export(startup_exports, {
|
|
|
34017
34101
|
runStartup: () => runStartup,
|
|
34018
34102
|
triggerReRegistration: () => triggerReRegistration
|
|
34019
34103
|
});
|
|
34020
|
-
import
|
|
34021
|
-
import
|
|
34022
|
-
import
|
|
34104
|
+
import os8 from "node:os";
|
|
34105
|
+
import path14 from "node:path";
|
|
34106
|
+
import fs13 from "node:fs/promises";
|
|
34023
34107
|
import readline2 from "node:readline";
|
|
34024
34108
|
import { execFile as execFile5 } from "node:child_process";
|
|
34025
34109
|
import { promisify as promisify4 } from "node:util";
|
|
34026
34110
|
async function loadPersistedConfig() {
|
|
34027
34111
|
try {
|
|
34028
|
-
const raw = await
|
|
34112
|
+
const raw = await fs13.readFile(PERSIST_PATH, "utf-8");
|
|
34029
34113
|
return JSON.parse(raw);
|
|
34030
34114
|
} catch {
|
|
34031
34115
|
return {};
|
|
@@ -34033,7 +34117,7 @@ async function loadPersistedConfig() {
|
|
|
34033
34117
|
}
|
|
34034
34118
|
async function savePersistedConfig(config2) {
|
|
34035
34119
|
try {
|
|
34036
|
-
await
|
|
34120
|
+
await fs13.writeFile(PERSIST_PATH, JSON.stringify(config2, null, 2) + "\n", "utf-8");
|
|
34037
34121
|
} catch (err) {
|
|
34038
34122
|
const msg = err instanceof Error ? err.message : String(err);
|
|
34039
34123
|
logger.warn(`Could not save config to ${PERSIST_PATH}: ${msg}`);
|
|
@@ -34068,11 +34152,11 @@ First run detected. Where should cogent-bridge store its data?
|
|
|
34068
34152
|
});
|
|
34069
34153
|
}
|
|
34070
34154
|
async function validateStateDir(statePath) {
|
|
34071
|
-
await
|
|
34072
|
-
const testFile =
|
|
34155
|
+
await fs13.mkdir(statePath, { recursive: true });
|
|
34156
|
+
const testFile = path14.join(statePath, ".write-test");
|
|
34073
34157
|
try {
|
|
34074
|
-
await
|
|
34075
|
-
await
|
|
34158
|
+
await fs13.writeFile(testFile, "test", "utf-8");
|
|
34159
|
+
await fs13.unlink(testFile);
|
|
34076
34160
|
} catch {
|
|
34077
34161
|
throw new BridgeError(
|
|
34078
34162
|
"STARTUP_FAILED" /* STARTUP_FAILED */,
|
|
@@ -34218,10 +34302,10 @@ async function runStartup() {
|
|
|
34218
34302
|
if (envStatePath) {
|
|
34219
34303
|
statePath = envStatePath;
|
|
34220
34304
|
} else {
|
|
34221
|
-
const defaultPath =
|
|
34305
|
+
const defaultPath = path14.join(os8.homedir(), ".cogent");
|
|
34222
34306
|
statePath = await firstRunPrompt(defaultPath);
|
|
34223
34307
|
}
|
|
34224
|
-
if (!envStatePath && statePath !==
|
|
34308
|
+
if (!envStatePath && statePath !== path14.join(os8.homedir(), ".cogent")) {
|
|
34225
34309
|
process.env.COGENT_STATE_PATH = statePath;
|
|
34226
34310
|
} else if (!envStatePath) {
|
|
34227
34311
|
process.env.COGENT_STATE_PATH = statePath;
|
|
@@ -34299,7 +34383,7 @@ async function runStartup() {
|
|
|
34299
34383
|
}
|
|
34300
34384
|
initLogger(
|
|
34301
34385
|
config2.COGENT_LOG_LEVEL,
|
|
34302
|
-
|
|
34386
|
+
path14.join(config2.COGENT_STATE_PATH, "logs")
|
|
34303
34387
|
);
|
|
34304
34388
|
if (isCloud && effectiveSessionId && effectiveToken) {
|
|
34305
34389
|
cloudInbox = new MessageInbox();
|
|
@@ -34360,7 +34444,7 @@ var init_startup = __esm({
|
|
|
34360
34444
|
init_auto_relay();
|
|
34361
34445
|
UUID_V4_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
34362
34446
|
execFileAsync3 = promisify4(execFile5);
|
|
34363
|
-
PERSIST_PATH =
|
|
34447
|
+
PERSIST_PATH = path14.join(os8.homedir(), ".cogent-config.json");
|
|
34364
34448
|
legacyWarnEmitted = false;
|
|
34365
34449
|
cloudWsClient = null;
|
|
34366
34450
|
cloudInbox = null;
|
|
@@ -34370,13 +34454,13 @@ var init_startup = __esm({
|
|
|
34370
34454
|
});
|
|
34371
34455
|
|
|
34372
34456
|
// src/cloud/mail-credential-store.ts
|
|
34373
|
-
import
|
|
34374
|
-
import
|
|
34375
|
-
import
|
|
34376
|
-
import
|
|
34457
|
+
import crypto4 from "node:crypto";
|
|
34458
|
+
import fs14 from "node:fs/promises";
|
|
34459
|
+
import path15 from "node:path";
|
|
34460
|
+
import os9 from "node:os";
|
|
34377
34461
|
function defaultMailCredentialPath(cwd = process.cwd()) {
|
|
34378
|
-
const hash =
|
|
34379
|
-
return
|
|
34462
|
+
const hash = crypto4.createHash("sha256").update(path15.resolve(cwd)).digest("hex").slice(0, 16);
|
|
34463
|
+
return path15.join(os9.homedir(), ".cogent", "mail-credentials", `${hash}.json`);
|
|
34380
34464
|
}
|
|
34381
34465
|
function resolveMailCredentialPath(credentialPath) {
|
|
34382
34466
|
if (credentialPath) return credentialPath;
|
|
@@ -34387,16 +34471,16 @@ function resolveMailCredentialPath(credentialPath) {
|
|
|
34387
34471
|
async function loadMailCredentials(credentialPath) {
|
|
34388
34472
|
const filePath = resolveMailCredentialPath(credentialPath);
|
|
34389
34473
|
try {
|
|
34390
|
-
return JSON.parse(await
|
|
34474
|
+
return JSON.parse(await fs14.readFile(filePath, "utf-8"));
|
|
34391
34475
|
} catch {
|
|
34392
34476
|
return null;
|
|
34393
34477
|
}
|
|
34394
34478
|
}
|
|
34395
34479
|
async function saveMailCredentials(creds, credentialPath) {
|
|
34396
34480
|
const filePath = resolveMailCredentialPath(credentialPath);
|
|
34397
|
-
await
|
|
34398
|
-
await
|
|
34399
|
-
await
|
|
34481
|
+
await fs14.mkdir(path15.dirname(filePath), { recursive: true, mode: 448 });
|
|
34482
|
+
await fs14.writeFile(filePath, JSON.stringify(creds, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
|
|
34483
|
+
await fs14.chmod(filePath, 384);
|
|
34400
34484
|
}
|
|
34401
34485
|
async function persistProvisionedMailbox(mailbox, credentialPath) {
|
|
34402
34486
|
if (!mailbox?.address || !mailbox.password) return false;
|
|
@@ -34417,7 +34501,7 @@ async function persistProvisionedMailbox(mailbox, credentialPath) {
|
|
|
34417
34501
|
async function clearMailCredentials(credentialPath) {
|
|
34418
34502
|
const filePath = resolveMailCredentialPath(credentialPath);
|
|
34419
34503
|
try {
|
|
34420
|
-
await
|
|
34504
|
+
await fs14.unlink(filePath);
|
|
34421
34505
|
} catch (err) {
|
|
34422
34506
|
if (err.code !== "ENOENT") throw err;
|
|
34423
34507
|
}
|
|
@@ -34431,7 +34515,7 @@ var init_mail_credential_store = __esm({
|
|
|
34431
34515
|
});
|
|
34432
34516
|
|
|
34433
34517
|
// src/tools/register-peer.ts
|
|
34434
|
-
import
|
|
34518
|
+
import crypto5 from "node:crypto";
|
|
34435
34519
|
function registerRegisterPeerTool(server) {
|
|
34436
34520
|
server.registerTool(
|
|
34437
34521
|
"cogent_register_peer",
|
|
@@ -34490,7 +34574,7 @@ function registerRegisterPeerTool(server) {
|
|
|
34490
34574
|
}
|
|
34491
34575
|
}
|
|
34492
34576
|
if (!cloudSessionId) {
|
|
34493
|
-
autoSecret =
|
|
34577
|
+
autoSecret = crypto5.randomBytes(16).toString("hex");
|
|
34494
34578
|
const resp = await fetch(
|
|
34495
34579
|
`${endpoint}/api/sessions`,
|
|
34496
34580
|
{
|
|
@@ -35748,7 +35832,7 @@ var init_health_check2 = __esm({
|
|
|
35748
35832
|
});
|
|
35749
35833
|
|
|
35750
35834
|
// src/tools/create-session.ts
|
|
35751
|
-
import
|
|
35835
|
+
import crypto6 from "node:crypto";
|
|
35752
35836
|
function registerCreateSessionTool(server) {
|
|
35753
35837
|
server.registerTool(
|
|
35754
35838
|
"cogent_create_session",
|
|
@@ -35779,7 +35863,7 @@ function registerCreateSessionTool(server) {
|
|
|
35779
35863
|
)
|
|
35780
35864
|
);
|
|
35781
35865
|
}
|
|
35782
|
-
const sessionSecret = secret ??
|
|
35866
|
+
const sessionSecret = secret ?? crypto6.randomBytes(16).toString("hex");
|
|
35783
35867
|
const resp = await fetch(
|
|
35784
35868
|
`${config2.COGENT_ENDPOINT}/api/sessions`,
|
|
35785
35869
|
{
|
|
@@ -36103,13 +36187,13 @@ var init_setup_mail = __esm({
|
|
|
36103
36187
|
});
|
|
36104
36188
|
|
|
36105
36189
|
// src/mail/mail-sender.ts
|
|
36106
|
-
import
|
|
36107
|
-
import
|
|
36190
|
+
import fs15 from "node:fs/promises";
|
|
36191
|
+
import path16 from "node:path";
|
|
36108
36192
|
async function buildAndSendMail(sender, creds, params) {
|
|
36109
36193
|
const attachments = [];
|
|
36110
36194
|
for (const p of params.attachmentPaths ?? []) {
|
|
36111
|
-
const content = await
|
|
36112
|
-
attachments.push({ filename:
|
|
36195
|
+
const content = await fs15.readFile(p);
|
|
36196
|
+
attachments.push({ filename: path16.basename(p), content });
|
|
36113
36197
|
}
|
|
36114
36198
|
const { messageId } = await sender.send({
|
|
36115
36199
|
from: creds.address,
|
|
@@ -36237,16 +36321,16 @@ var init_send_mail = __esm({
|
|
|
36237
36321
|
});
|
|
36238
36322
|
|
|
36239
36323
|
// src/mail/mail-fetcher.ts
|
|
36240
|
-
import
|
|
36241
|
-
import
|
|
36324
|
+
import fs16 from "node:fs/promises";
|
|
36325
|
+
import path17 from "node:path";
|
|
36242
36326
|
async function saveAttachments(attachments, downloadDir) {
|
|
36243
36327
|
if (attachments.length === 0) return [];
|
|
36244
|
-
await
|
|
36328
|
+
await fs16.mkdir(downloadDir, { recursive: true });
|
|
36245
36329
|
const saved = [];
|
|
36246
36330
|
for (const a of attachments) {
|
|
36247
|
-
const name =
|
|
36248
|
-
const savedPath =
|
|
36249
|
-
await
|
|
36331
|
+
const name = path17.basename(a.filename) || "attachment";
|
|
36332
|
+
const savedPath = path17.join(downloadDir, name);
|
|
36333
|
+
await fs16.writeFile(savedPath, a.content);
|
|
36250
36334
|
saved.push({ filename: name, savedPath, size: a.content.length });
|
|
36251
36335
|
}
|
|
36252
36336
|
return saved;
|
|
@@ -36377,8 +36461,8 @@ var init_mail_fetcher = __esm({
|
|
|
36377
36461
|
});
|
|
36378
36462
|
|
|
36379
36463
|
// src/tools/fetch-mail.ts
|
|
36380
|
-
import
|
|
36381
|
-
import
|
|
36464
|
+
import os10 from "node:os";
|
|
36465
|
+
import path18 from "node:path";
|
|
36382
36466
|
function registerFetchMailTool(server, deps = {}) {
|
|
36383
36467
|
const makeFetcher = deps.fetcherFactory ?? ((creds) => new ImapflowMailFetcher(creds));
|
|
36384
36468
|
server.registerTool(
|
|
@@ -36440,7 +36524,7 @@ var init_fetch_mail = __esm({
|
|
|
36440
36524
|
init_mail_fetcher();
|
|
36441
36525
|
init_auth_error();
|
|
36442
36526
|
init_validate();
|
|
36443
|
-
DEFAULT_DOWNLOAD_DIR =
|
|
36527
|
+
DEFAULT_DOWNLOAD_DIR = path18.join(os10.homedir(), ".cogent", "mail-downloads");
|
|
36444
36528
|
}
|
|
36445
36529
|
});
|
|
36446
36530
|
|
|
@@ -98,6 +98,21 @@ export function selectUnanswered({ messages, me, scope }) {
|
|
|
98
98
|
// My message (a reply or my own auto-relay echo) answers a pending item.
|
|
99
99
|
// A directed reply clears that sender's oldest; a broadcast reply clears
|
|
100
100
|
// only the oldest pending BROADCAST (never a directed message).
|
|
101
|
+
//
|
|
102
|
+
// COG-20: EXCEPT a meta-non-answer. When the real-time wake's capture fails, the agent
|
|
103
|
+
// sees its own (undelivered) answer plus this hook saying "unanswered", and resolves the
|
|
104
|
+
// contradiction by sending "Already answered… no duplicate sent." That is not an answer —
|
|
105
|
+
// but because this pairing used to clear on ANY outbound, it marked the message answered
|
|
106
|
+
// and silenced the alarm FOREVER, so the human only ever saw the meta-message. Skipping it
|
|
107
|
+
// here keeps the message pending until a real answer is sent.
|
|
108
|
+
//
|
|
109
|
+
// Same defect, second instance: rail B records its OWN failure notice ("Cogent could not
|
|
110
|
+
// capture a reply…" / "📨 Queued for …") as a message FROM me with success:false. Counting
|
|
111
|
+
// that as an answer would make C blind to exactly the miss C exists to catch. A record that
|
|
112
|
+
// is explicitly a failure is never an answer — check that first, it is stronger than any
|
|
113
|
+
// string match.
|
|
114
|
+
if (m.success === false || m.error) continue;
|
|
115
|
+
if (isMetaNonAnswer(m.message)) continue;
|
|
101
116
|
if (m.toPeerId === "*") dequeueOldestBroadcast();
|
|
102
117
|
else dequeueFrom(m.toPeerId);
|
|
103
118
|
continue;
|
|
@@ -113,6 +128,71 @@ export function selectUnanswered({ messages, me, scope }) {
|
|
|
113
128
|
return { items: items.map(({ _i, ...m }) => m) };
|
|
114
129
|
}
|
|
115
130
|
|
|
131
|
+
/**
|
|
132
|
+
* Is this outbound of mine a "meta non-answer" — a note ABOUT answering rather than an answer?
|
|
133
|
+
* (COG-20)
|
|
134
|
+
*
|
|
135
|
+
* Real observed values that reached a human instead of the answer:
|
|
136
|
+
* "Already answered through the automatic Slack relay. No duplicate sent."
|
|
137
|
+
* "Already answered through the automatic Slack relay with live Jira and repository status. …"
|
|
138
|
+
*
|
|
139
|
+
* Deliberately CONSERVATIVE — it must not swallow a genuine answer that happens to contain the
|
|
140
|
+
* words "already answered". Two gates: the text must be SHORT (real answers carry substance; every
|
|
141
|
+
* observed meta-message was < 120 chars) AND match the pattern. A false negative just restores the
|
|
142
|
+
* old behaviour; a false positive would re-nag the agent into sending a duplicate, so short-only.
|
|
143
|
+
* An empty reply is always a non-answer.
|
|
144
|
+
*/
|
|
145
|
+
export function isMetaNonAnswer(text) {
|
|
146
|
+
const t = String(text ?? "").replace(/\s+/g, " ").trim().toLowerCase();
|
|
147
|
+
if (!t) return true;
|
|
148
|
+
if (t.length > 200) return false; // long enough to carry a real answer — never suppress it
|
|
149
|
+
return /already (answered|replied|responded|sent|handled)|no duplicate|duplicate (not )?sent|answered (already|via|through)|responded (already|via|through)/.test(t);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Hard ceiling on how long ANY marker may suppress this hook, whatever the writer stamped.
|
|
154
|
+
* MUST equal wake-inflight.ts INFLIGHT_MAX_MS.
|
|
155
|
+
*/
|
|
156
|
+
export const INFLIGHT_MAX_MS = 1_800_000; // 30 min
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Message ids that rail B is CURRENTLY handling — read from the wake-inflight marker
|
|
160
|
+
* (src/services/wake-inflight.ts). COG-20: rail B records a reply only after it has
|
|
161
|
+
* captured it, but rail C fires at the end of the very turn B is capturing. Without this,
|
|
162
|
+
* C tells the agent "unanswered" about a message B is milliseconds from delivering; the
|
|
163
|
+
* agent appends an extra message, and B captures THAT instead of the real answer.
|
|
164
|
+
*
|
|
165
|
+
* EXPIRY COMES FROM THE WRITER. Only rail B knows how long its wake may legitimately run
|
|
166
|
+
* (COGENT_TIMEOUT_MS, and a wake can run execRemote twice via the rotated-session retry),
|
|
167
|
+
* so B stamps `expiresAt`. A fixed reader-side TTL would expire while long or retried wakes
|
|
168
|
+
* are still running and reopen the race on exactly the slow turns rail C exists for.
|
|
169
|
+
*
|
|
170
|
+
* But the reader must not trust it unconditionally: `startedAt + INFLIGHT_MAX_MS` caps every
|
|
171
|
+
* entry, so a corrupt, hostile, or forgotten marker can never mute this hook indefinitely.
|
|
172
|
+
* Anything malformed or expired is ignored — a bad marker degrades LOUD (we speak up, as
|
|
173
|
+
* before the fix), never SILENT (the safety net disappearing).
|
|
174
|
+
*
|
|
175
|
+
* @param {{messages?:Record<string,{startedAt?:number,expiresAt?:number}>}|null} marker
|
|
176
|
+
* @param {number} nowMs
|
|
177
|
+
* @param {number} [maxMs] - hard cap on suppression, default INFLIGHT_MAX_MS
|
|
178
|
+
* @returns {string[]} message ids still legitimately in flight
|
|
179
|
+
*/
|
|
180
|
+
export function activeInFlightIds(marker, nowMs, maxMs = INFLIGHT_MAX_MS) {
|
|
181
|
+
const msgs = marker && typeof marker === "object" ? marker.messages : null;
|
|
182
|
+
if (!msgs || typeof msgs !== "object") return [];
|
|
183
|
+
const out = [];
|
|
184
|
+
for (const [id, rec] of Object.entries(msgs)) {
|
|
185
|
+
if (!rec || typeof rec !== "object") continue;
|
|
186
|
+
const started = typeof rec.startedAt === "number" && Number.isFinite(rec.startedAt) ? rec.startedAt : null;
|
|
187
|
+
const expires = typeof rec.expiresAt === "number" && Number.isFinite(rec.expiresAt) ? rec.expiresAt : null;
|
|
188
|
+
if (started === null || expires === null) continue; // corrupt → do not suppress
|
|
189
|
+
if (expires <= nowMs) continue; // the writer's own deadline passed → the wake is dead
|
|
190
|
+
if (nowMs - started > maxMs) continue; // writer stamped an absurd deadline → cap it
|
|
191
|
+
out.push(id);
|
|
192
|
+
}
|
|
193
|
+
return out;
|
|
194
|
+
}
|
|
195
|
+
|
|
116
196
|
/** Build the { decision: "block" } reason string handed back to the agent. */
|
|
117
197
|
export function buildBlockReason(items, { firstUse }) {
|
|
118
198
|
const n = items.length;
|
|
@@ -120,11 +200,21 @@ export function buildBlockReason(items, { firstUse }) {
|
|
|
120
200
|
const snippet = String(m.message || "").replace(/\s+/g, " ").slice(0, 120);
|
|
121
201
|
return ` - from ${m.fromPeerId}${m.toPeerId === "*" ? " (channel)" : ""}: ${snippet}`;
|
|
122
202
|
});
|
|
203
|
+
// COG-20: the wording matters as much as the detection. The previous text ended with "Do not
|
|
204
|
+
// repeat work you already did this turn", which — combined with the real-time wake prompt telling
|
|
205
|
+
// the agent NOT to call cogent_send_message — pushed the agent to answer with a meta-message
|
|
206
|
+
// ("Already answered… no duplicate sent") instead of the substance. It had in fact answered; the
|
|
207
|
+
// capture just never delivered it. So: state plainly that nothing was delivered, and demand the
|
|
208
|
+
// answer itself.
|
|
123
209
|
let reason =
|
|
124
|
-
`You have ${n} unanswered Cogent message${n === 1 ? "" : "s"}
|
|
210
|
+
`You have ${n} unanswered Cogent message${n === 1 ? "" : "s"} — the channel has NO recorded reply ` +
|
|
211
|
+
`from you${n === 1 ? "" : " for these"}:\n` +
|
|
125
212
|
lines.join("\n") +
|
|
126
|
-
`\n\
|
|
127
|
-
`
|
|
213
|
+
`\n\nIf you already composed an answer this turn, it was NOT delivered — sending it again is ` +
|
|
214
|
+
`correct, not a duplicate. Read the channel with cogent_get_history if you need context, then ` +
|
|
215
|
+
`reply with the ANSWER ITSELF via cogent_send_message (or spawn a Task sub-agent) and continue. ` +
|
|
216
|
+
`NEVER reply with a note about having already answered (e.g. "already answered", "no duplicate ` +
|
|
217
|
+
`sent") — that is not an answer, and it is all the sender will ever see.`;
|
|
128
218
|
if (firstUse) {
|
|
129
219
|
reason +=
|
|
130
220
|
`\n\n(Cogent auto-check is on: it catches messages that arrive while you're busy. ` +
|
package/hooks/check-on-stop.mjs
CHANGED
|
@@ -17,10 +17,28 @@ import {
|
|
|
17
17
|
selectUnanswered,
|
|
18
18
|
buildBlockReason,
|
|
19
19
|
credHashForCwd,
|
|
20
|
+
activeInFlightIds,
|
|
20
21
|
} from "./check-on-stop.lib.mjs";
|
|
21
22
|
|
|
22
23
|
const HTTP_TIMEOUT_MS = 5000; // < the 8s hook timeout; only bites on a degraded relay
|
|
23
24
|
|
|
25
|
+
/**
|
|
26
|
+
* `~/.cogent/wake-inflight/<sha256(cwd)[..16]>.<peerId>.json` — MUST equal
|
|
27
|
+
* src/services/wake-inflight.ts wakeInflightPath(). Rail B writes it while it is handling a
|
|
28
|
+
* message; we read it so we never call a message "unanswered" that B is about to answer.
|
|
29
|
+
*
|
|
30
|
+
* HOMEDIR-BASED, deliberately NOT COGENT_STATE_PATH — same reasoning as credPath above. Rail
|
|
31
|
+
* B reads its config from the MCP server's env; this hook reads its own process env, which
|
|
32
|
+
* the host spawns from the user's shell. An operator setting COGENT_STATE_PATH in the
|
|
33
|
+
* .mcp.json env block would put the writer and the reader in different directories and the
|
|
34
|
+
* guard would silently stop working. Keyed by peerId too: two peers can share a cwd, and a
|
|
35
|
+
* human broadcast carries the SAME message id to every peer.
|
|
36
|
+
*/
|
|
37
|
+
function inflightPath(cwd, me) {
|
|
38
|
+
const safe = (s) => String(s).replace(/[^A-Za-z0-9._-]/g, "_");
|
|
39
|
+
return path.join(os.homedir(), ".cogent", "wake-inflight", `${credHashForCwd(cwd)}.${safe(me)}.json`);
|
|
40
|
+
}
|
|
41
|
+
|
|
24
42
|
async function readStdin() {
|
|
25
43
|
const chunks = [];
|
|
26
44
|
for await (const c of process.stdin) chunks.push(c);
|
|
@@ -129,7 +147,15 @@ async function main() {
|
|
|
129
147
|
return process.exit(0); // network/timeout → silent
|
|
130
148
|
}
|
|
131
149
|
|
|
132
|
-
const
|
|
150
|
+
const all = selectUnanswered({ messages, me, scope }).items;
|
|
151
|
+
|
|
152
|
+
// COG-20: drop anything rail B is CURRENTLY waking for. B records its reply only after
|
|
153
|
+
// capture, and we fire at the end of the very turn it is capturing — so without this we
|
|
154
|
+
// would tell the agent a message is unanswered milliseconds before B delivers it. The
|
|
155
|
+
// agent then appends an extra message and B captures THAT, not the real answer.
|
|
156
|
+
// Deliberately id-scoped, not a blanket mute: any OTHER unanswered message still surfaces.
|
|
157
|
+
const inFlight = activeInFlightIds(await readJson(inflightPath(cwd, me)), Date.now());
|
|
158
|
+
const items = inFlight.length ? all.filter((m) => !inFlight.includes(m.id)) : all;
|
|
133
159
|
const unansweredIds = items.map((m) => m.id);
|
|
134
160
|
|
|
135
161
|
// First run: PRIME — record the current backlog as already-surfaced so C never
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@essentialai/cogent-plugin",
|
|
3
|
-
"version": "3.20.
|
|
3
|
+
"version": "3.20.6",
|
|
4
4
|
"description": "Cogent — Claude Code plugin (skills + slash-commands + MCP server) for the cross-agent comms fabric.",
|
|
5
5
|
"author": { "name": "Essential AI Solutions Ltd.", "url": "https://essentialai.uk" },
|
|
6
6
|
"homepage": "https://cogent.tools",
|