@essentialai/cogent-plugin 3.17.0 → 3.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +1 -1
- package/bridge/cogent-bridge.mjs +168 -94
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cogent",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.19.0",
|
|
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
|
@@ -192,7 +192,7 @@ Your peer identity: **peerId=\`${peerId}\`**, **label=\`${label}\`**
|
|
|
192
192
|
### On Session Start
|
|
193
193
|
1. Find your Claude Code session ID:
|
|
194
194
|
\`\`\`bash
|
|
195
|
-
ls -t
|
|
195
|
+
ls -t "\${CLAUDE_CONFIG_DIR:-$HOME/.claude}"/projects/$(pwd | sed 's/[^a-zA-Z0-9-]/-/g')/*.jsonl 2>/dev/null | head -1 | xargs -I{} basename {} .jsonl
|
|
196
196
|
\`\`\`
|
|
197
197
|
2. Register yourself on the bridge:
|
|
198
198
|
- Use \`cogent_register_peer\` with peerId="${peerId}", label="${label}"
|
|
@@ -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: path17, errorMaps, issueData } = params;
|
|
1263
|
+
const fullPath = [...path17, ...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, path17, key) {
|
|
1544
1544
|
this._cachedPath = [];
|
|
1545
1545
|
this.parent = parent;
|
|
1546
1546
|
this.data = value;
|
|
1547
|
-
this._path =
|
|
1547
|
+
this._path = path17;
|
|
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, path17) {
|
|
5000
|
+
if (!path17)
|
|
5001
5001
|
return obj;
|
|
5002
|
-
return
|
|
5002
|
+
return path17.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(path17, 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(path17);
|
|
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(path17) {
|
|
17062
|
+
let input = path17;
|
|
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 [path17, query] = wsComponent.resourceName.split("?");
|
|
17262
|
+
wsComponent.path = path17 && path17 !== "/" ? path17 : void 0;
|
|
17263
17263
|
wsComponent.query = query;
|
|
17264
17264
|
wsComponent.resourceName = void 0;
|
|
17265
17265
|
}
|
|
@@ -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: path17, errorMaps, issueData } = params;
|
|
21733
|
+
const fullPath = [...path17, ...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, path17, key) {
|
|
21888
21888
|
this._cachedPath = [];
|
|
21889
21889
|
this.parent = parent;
|
|
21890
21890
|
this.data = value;
|
|
21891
|
-
this._path =
|
|
21891
|
+
this._path = path17;
|
|
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.
|
|
26393
|
-
return "3.
|
|
26392
|
+
if ("3.19.0") {
|
|
26393
|
+
return "3.19.0";
|
|
26394
26394
|
}
|
|
26395
26395
|
try {
|
|
26396
26396
|
const require2 = createRequire(import.meta.url);
|
|
@@ -27016,21 +27016,45 @@ var init_peer_registry = __esm({
|
|
|
27016
27016
|
}
|
|
27017
27017
|
});
|
|
27018
27018
|
|
|
27019
|
+
// src/services/agent-config.ts
|
|
27020
|
+
import os4 from "node:os";
|
|
27021
|
+
import path8 from "node:path";
|
|
27022
|
+
function claudeConfigDir() {
|
|
27023
|
+
const override = process.env.CLAUDE_CONFIG_DIR?.trim();
|
|
27024
|
+
return override ? override : path8.join(os4.homedir(), ".claude");
|
|
27025
|
+
}
|
|
27026
|
+
function codexHomeDir() {
|
|
27027
|
+
const override = process.env.CODEX_HOME?.trim();
|
|
27028
|
+
return override ? override : path8.join(os4.homedir(), ".codex");
|
|
27029
|
+
}
|
|
27030
|
+
function buildResumeEnv(source, vendorPrefix, preserve) {
|
|
27031
|
+
const keep = new Set(preserve);
|
|
27032
|
+
return Object.fromEntries(
|
|
27033
|
+
Object.entries(source).filter(
|
|
27034
|
+
([key]) => (!key.startsWith(vendorPrefix) || keep.has(key)) && key !== "MCP_TRANSPORT"
|
|
27035
|
+
)
|
|
27036
|
+
);
|
|
27037
|
+
}
|
|
27038
|
+
var init_agent_config = __esm({
|
|
27039
|
+
"src/services/agent-config.ts"() {
|
|
27040
|
+
"use strict";
|
|
27041
|
+
}
|
|
27042
|
+
});
|
|
27043
|
+
|
|
27019
27044
|
// src/services/cc-cli.ts
|
|
27020
27045
|
import { spawn } from "node:child_process";
|
|
27021
27046
|
import fs7 from "node:fs/promises";
|
|
27022
|
-
import
|
|
27023
|
-
import path8 from "node:path";
|
|
27047
|
+
import path9 from "node:path";
|
|
27024
27048
|
async function listCandidates(cwd) {
|
|
27025
27049
|
try {
|
|
27026
27050
|
const escapedCwd = cwd.replace(/[^a-zA-Z0-9-]/g, "-");
|
|
27027
|
-
const projectDir =
|
|
27051
|
+
const projectDir = path9.join(claudeConfigDir(), "projects", escapedCwd);
|
|
27028
27052
|
const entries = await fs7.readdir(projectDir);
|
|
27029
27053
|
const jsonlFiles = entries.filter((f) => f.endsWith(".jsonl"));
|
|
27030
27054
|
return await Promise.all(
|
|
27031
27055
|
jsonlFiles.map(async (f) => ({
|
|
27032
27056
|
sessionId: f.replace(".jsonl", ""),
|
|
27033
|
-
mtimeMs: (await fs7.stat(
|
|
27057
|
+
mtimeMs: (await fs7.stat(path9.join(projectDir, f))).mtimeMs
|
|
27034
27058
|
}))
|
|
27035
27059
|
);
|
|
27036
27060
|
} catch {
|
|
@@ -27045,9 +27069,8 @@ async function detectCurrentSession(cwd) {
|
|
|
27045
27069
|
}
|
|
27046
27070
|
async function validateSession(sessionId, cwd) {
|
|
27047
27071
|
const projectHash = cwd.replace(/[^a-zA-Z0-9-]/g, "-");
|
|
27048
|
-
const sessionPath =
|
|
27049
|
-
|
|
27050
|
-
".claude",
|
|
27072
|
+
const sessionPath = path9.join(
|
|
27073
|
+
claudeConfigDir(),
|
|
27051
27074
|
"projects",
|
|
27052
27075
|
projectHash,
|
|
27053
27076
|
`${sessionId}.jsonl`
|
|
@@ -27085,11 +27108,7 @@ function execClaude(sessionId, message, cwd, timeoutMs) {
|
|
|
27085
27108
|
"-p",
|
|
27086
27109
|
truncated
|
|
27087
27110
|
];
|
|
27088
|
-
const cleanEnv =
|
|
27089
|
-
Object.entries(process.env).filter(
|
|
27090
|
-
([key]) => !key.startsWith("CLAUDE") && key !== "MCP_TRANSPORT"
|
|
27091
|
-
)
|
|
27092
|
-
);
|
|
27111
|
+
const cleanEnv = buildResumeEnv(process.env, "CLAUDE", ["CLAUDE_CONFIG_DIR"]);
|
|
27093
27112
|
cleanEnv.COGENT_CHECK_ON_STOP = "0";
|
|
27094
27113
|
const child = spawn(config2.COGENT_CLAUDE_PATH, args, {
|
|
27095
27114
|
cwd,
|
|
@@ -27214,6 +27233,7 @@ var init_cc_cli = __esm({
|
|
|
27214
27233
|
"src/services/cc-cli.ts"() {
|
|
27215
27234
|
"use strict";
|
|
27216
27235
|
init_config();
|
|
27236
|
+
init_agent_config();
|
|
27217
27237
|
}
|
|
27218
27238
|
});
|
|
27219
27239
|
|
|
@@ -27345,14 +27365,14 @@ var init_codex_preflight = __esm({
|
|
|
27345
27365
|
|
|
27346
27366
|
// src/services/health-check.ts
|
|
27347
27367
|
import fs9 from "node:fs/promises";
|
|
27348
|
-
import
|
|
27368
|
+
import path10 from "node:path";
|
|
27349
27369
|
import { execFile as execFile3 } from "node:child_process";
|
|
27350
27370
|
function getStatePath2() {
|
|
27351
|
-
return
|
|
27371
|
+
return path10.join(getConfig().COGENT_STATE_PATH, "cogent-state.json");
|
|
27352
27372
|
}
|
|
27353
27373
|
async function checkStateFile() {
|
|
27354
27374
|
const statePath = getStatePath2();
|
|
27355
|
-
const dir =
|
|
27375
|
+
const dir = path10.dirname(statePath);
|
|
27356
27376
|
try {
|
|
27357
27377
|
await fs9.access(dir, fs9.constants.R_OK | fs9.constants.W_OK);
|
|
27358
27378
|
} catch {
|
|
@@ -27430,7 +27450,7 @@ async function checkHealth() {
|
|
|
27430
27450
|
return {
|
|
27431
27451
|
healthy,
|
|
27432
27452
|
mcpServerVersion: SERVER_VERSION,
|
|
27433
|
-
statePath:
|
|
27453
|
+
statePath: path10.join(config2.COGENT_STATE_PATH, "cogent-state.json"),
|
|
27434
27454
|
claudePath: config2.COGENT_CLAUDE_PATH,
|
|
27435
27455
|
checks,
|
|
27436
27456
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -27569,7 +27589,7 @@ var init_http_backend = __esm({
|
|
|
27569
27589
|
* in cloud mode we ignore it and use this.sessionId (the cloud session).
|
|
27570
27590
|
*/
|
|
27571
27591
|
async registerPeer(peerId, _sessionId, cwd, label, clientVersion, mode, _channelSessionId, capabilities, workspaceId, threadId) {
|
|
27572
|
-
const
|
|
27592
|
+
const path17 = PATHS.peers.replace(":sessionId", this.sessionId);
|
|
27573
27593
|
const body = {
|
|
27574
27594
|
peerId,
|
|
27575
27595
|
cwd,
|
|
@@ -27597,7 +27617,7 @@ var init_http_backend = __esm({
|
|
|
27597
27617
|
body.role = roleCap.slice("role:".length);
|
|
27598
27618
|
}
|
|
27599
27619
|
}
|
|
27600
|
-
return this.http.post(
|
|
27620
|
+
return this.http.post(path17, body);
|
|
27601
27621
|
}
|
|
27602
27622
|
/**
|
|
27603
27623
|
* Deregister a peer from the cloud session.
|
|
@@ -27609,9 +27629,9 @@ var init_http_backend = __esm({
|
|
|
27609
27629
|
* local mail creds. A 404 (peer not found) resolves to `{ removed: false, ... }`, not a throw.
|
|
27610
27630
|
*/
|
|
27611
27631
|
async deregisterPeer(peerId) {
|
|
27612
|
-
const
|
|
27632
|
+
const path17 = PATHS.peer.replace(":sessionId", this.sessionId).replace(":peerId", peerId);
|
|
27613
27633
|
try {
|
|
27614
|
-
const headers = await this.http.delete(
|
|
27634
|
+
const headers = await this.http.delete(path17, {});
|
|
27615
27635
|
const mailboxDeprovisioned = headers.get(MAILBOX_DEPROVISIONED_HEADER) === "true";
|
|
27616
27636
|
return { removed: true, mailboxDeprovisioned };
|
|
27617
27637
|
} catch {
|
|
@@ -27632,8 +27652,8 @@ var init_http_backend = __esm({
|
|
|
27632
27652
|
* GET /api/sessions/:sessionId/peers
|
|
27633
27653
|
*/
|
|
27634
27654
|
async listPeers() {
|
|
27635
|
-
const
|
|
27636
|
-
const result = await this.http.get(
|
|
27655
|
+
const path17 = PATHS.peers.replace(":sessionId", this.sessionId);
|
|
27656
|
+
const result = await this.http.get(path17);
|
|
27637
27657
|
return result.peers;
|
|
27638
27658
|
}
|
|
27639
27659
|
/**
|
|
@@ -27641,8 +27661,8 @@ var init_http_backend = __esm({
|
|
|
27641
27661
|
* Calls the lightweight heartbeat endpoint on the cloud relay server.
|
|
27642
27662
|
*/
|
|
27643
27663
|
async updateLastSeen(peerId) {
|
|
27644
|
-
const
|
|
27645
|
-
await this.http.post(
|
|
27664
|
+
const path17 = PATHS.heartbeat.replace(":sessionId", this.sessionId);
|
|
27665
|
+
await this.http.post(path17, { peerId });
|
|
27646
27666
|
}
|
|
27647
27667
|
/**
|
|
27648
27668
|
* Record a message in the cloud session.
|
|
@@ -27653,7 +27673,7 @@ var init_http_backend = __esm({
|
|
|
27653
27673
|
* the input record to construct a full MessageRecord.
|
|
27654
27674
|
*/
|
|
27655
27675
|
async recordMessage(record2) {
|
|
27656
|
-
const
|
|
27676
|
+
const path17 = PATHS.messages.replace(":sessionId", this.sessionId);
|
|
27657
27677
|
const body = {
|
|
27658
27678
|
fromPeerId: record2.fromPeerId,
|
|
27659
27679
|
toPeerId: record2.toPeerId,
|
|
@@ -27665,7 +27685,7 @@ var init_http_backend = __esm({
|
|
|
27665
27685
|
if (record2.attachments && record2.attachments.length > 0) {
|
|
27666
27686
|
body.attachments = record2.attachments;
|
|
27667
27687
|
}
|
|
27668
|
-
const resp = await this.http.post(
|
|
27688
|
+
const resp = await this.http.post(path17, body);
|
|
27669
27689
|
return {
|
|
27670
27690
|
...record2,
|
|
27671
27691
|
id: resp.id,
|
|
@@ -27679,7 +27699,7 @@ var init_http_backend = __esm({
|
|
|
27679
27699
|
* Supports optional peerId filter and limit (default 300).
|
|
27680
27700
|
*/
|
|
27681
27701
|
async getHistory(peerId, limit, includeRelayEchoes, metadataOnly, dedupInferredEchoes) {
|
|
27682
|
-
const
|
|
27702
|
+
const path17 = PATHS.messages.replace(":sessionId", this.sessionId);
|
|
27683
27703
|
const query = {};
|
|
27684
27704
|
if (peerId) {
|
|
27685
27705
|
query.peerId = peerId;
|
|
@@ -27694,7 +27714,7 @@ var init_http_backend = __esm({
|
|
|
27694
27714
|
if (dedupInferredEchoes) {
|
|
27695
27715
|
query.dedupInferredEchoes = "true";
|
|
27696
27716
|
}
|
|
27697
|
-
const result = await this.http.get(
|
|
27717
|
+
const result = await this.http.get(path17, query);
|
|
27698
27718
|
return result.messages;
|
|
27699
27719
|
}
|
|
27700
27720
|
/**
|
|
@@ -27705,12 +27725,12 @@ var init_http_backend = __esm({
|
|
|
27705
27725
|
* so that get-history shows the actual response (not null).
|
|
27706
27726
|
*/
|
|
27707
27727
|
async updateMessageResponse(messageId, response, durationMs, pending) {
|
|
27708
|
-
const
|
|
27728
|
+
const path17 = PATHS.message.replace(":sessionId", this.sessionId).replace(":messageId", messageId);
|
|
27709
27729
|
const body = { response, durationMs };
|
|
27710
27730
|
if (pending === true) {
|
|
27711
27731
|
body.pending = true;
|
|
27712
27732
|
}
|
|
27713
|
-
await this.http.patch(
|
|
27733
|
+
await this.http.patch(path17, body);
|
|
27714
27734
|
}
|
|
27715
27735
|
/**
|
|
27716
27736
|
* Validate a session.
|
|
@@ -27852,8 +27872,8 @@ var init_http_client = __esm({
|
|
|
27852
27872
|
* Perform an authenticated GET request.
|
|
27853
27873
|
* Appends query parameters to the URL if provided.
|
|
27854
27874
|
*/
|
|
27855
|
-
async get(
|
|
27856
|
-
const url = new URL(
|
|
27875
|
+
async get(path17, query) {
|
|
27876
|
+
const url = new URL(path17, this.baseUrl);
|
|
27857
27877
|
if (query) {
|
|
27858
27878
|
for (const [k, v] of Object.entries(query)) {
|
|
27859
27879
|
url.searchParams.set(k, v);
|
|
@@ -27868,8 +27888,8 @@ var init_http_client = __esm({
|
|
|
27868
27888
|
/**
|
|
27869
27889
|
* Perform an authenticated POST request with a JSON body.
|
|
27870
27890
|
*/
|
|
27871
|
-
async post(
|
|
27872
|
-
const url = new URL(
|
|
27891
|
+
async post(path17, body) {
|
|
27892
|
+
const url = new URL(path17, this.baseUrl);
|
|
27873
27893
|
const resp = await this.fetchWithTimeout(url, {
|
|
27874
27894
|
method: "POST",
|
|
27875
27895
|
headers: this.headers(),
|
|
@@ -27887,8 +27907,8 @@ var init_http_client = __esm({
|
|
|
27887
27907
|
* Perform an authenticated PATCH request with a JSON body.
|
|
27888
27908
|
* Used to update existing resources (e.g., message response fields).
|
|
27889
27909
|
*/
|
|
27890
|
-
async patch(
|
|
27891
|
-
const url = new URL(
|
|
27910
|
+
async patch(path17, body) {
|
|
27911
|
+
const url = new URL(path17, this.baseUrl);
|
|
27892
27912
|
const resp = await this.fetchWithTimeout(url, {
|
|
27893
27913
|
method: "PATCH",
|
|
27894
27914
|
headers: this.headers(),
|
|
@@ -27913,8 +27933,8 @@ var init_http_client = __esm({
|
|
|
27913
27933
|
* "Malformed JSON in request body" 400 from cogent-server <=3.1.2 when
|
|
27914
27934
|
* any client (including third-party tools) DELETEd a peer without a body.
|
|
27915
27935
|
*/
|
|
27916
|
-
async delete(
|
|
27917
|
-
const url = new URL(
|
|
27936
|
+
async delete(path17, body) {
|
|
27937
|
+
const url = new URL(path17, this.baseUrl);
|
|
27918
27938
|
const headers = {
|
|
27919
27939
|
"Authorization": `Bearer ${this.token}`
|
|
27920
27940
|
};
|
|
@@ -27979,6 +27999,9 @@ function isCloudEndpoint(value) {
|
|
|
27979
27999
|
function isCloudMode() {
|
|
27980
28000
|
return _backend instanceof HttpBackend;
|
|
27981
28001
|
}
|
|
28002
|
+
function isCloudDeferred(endpoint) {
|
|
28003
|
+
return !!endpoint && isCloudEndpoint(endpoint) && !isCloudMode();
|
|
28004
|
+
}
|
|
27982
28005
|
function createBackend(endpoint, sessionId, token) {
|
|
27983
28006
|
if (endpoint && isCloudEndpoint(endpoint)) {
|
|
27984
28007
|
if (!sessionId || !token) {
|
|
@@ -32094,14 +32117,14 @@ var init_ws_client = __esm({
|
|
|
32094
32117
|
if (this.pollTimer) return;
|
|
32095
32118
|
const poll = async () => {
|
|
32096
32119
|
try {
|
|
32097
|
-
const
|
|
32120
|
+
const path17 = `/api/sessions/${this.opts.sessionId}/poll`;
|
|
32098
32121
|
const query = {
|
|
32099
32122
|
peerId: this.opts.peerId
|
|
32100
32123
|
};
|
|
32101
32124
|
if (this.lastMessageId) {
|
|
32102
32125
|
query.lastMessageId = this.lastMessageId;
|
|
32103
32126
|
}
|
|
32104
|
-
const result = await this.opts.http.get(
|
|
32127
|
+
const result = await this.opts.http.get(path17, query);
|
|
32105
32128
|
if (result.messages && result.messages.length > 0) {
|
|
32106
32129
|
this.opts.onMessages(result.messages);
|
|
32107
32130
|
this.lastMessageId = result.messages[result.messages.length - 1].id;
|
|
@@ -32160,7 +32183,7 @@ var init_cloud = __esm({
|
|
|
32160
32183
|
import { spawn as spawn2 } from "node:child_process";
|
|
32161
32184
|
import fs10 from "node:fs/promises";
|
|
32162
32185
|
import os5 from "node:os";
|
|
32163
|
-
import
|
|
32186
|
+
import path11 from "node:path";
|
|
32164
32187
|
import { randomUUID } from "node:crypto";
|
|
32165
32188
|
import { createReadStream } from "node:fs";
|
|
32166
32189
|
import { createInterface as createInterface2 } from "node:readline";
|
|
@@ -32188,7 +32211,7 @@ function execCodex(sessionId, message, cwd, timeoutMs) {
|
|
|
32188
32211
|
const config2 = getConfig();
|
|
32189
32212
|
const effectiveTimeout = timeoutMs ?? config2.COGENT_TIMEOUT_MS;
|
|
32190
32213
|
const truncated = config2.COGENT_CHAR_LIMIT > 0 && message.length > config2.COGENT_CHAR_LIMIT ? message.slice(0, config2.COGENT_CHAR_LIMIT) + "\n...[truncated]" : message;
|
|
32191
|
-
const lastMsgFile =
|
|
32214
|
+
const lastMsgFile = path11.join(os5.tmpdir(), `cogent-codex-${randomUUID()}.txt`);
|
|
32192
32215
|
const args = [
|
|
32193
32216
|
"exec",
|
|
32194
32217
|
"resume",
|
|
@@ -32199,11 +32222,7 @@ function execCodex(sessionId, message, cwd, timeoutMs) {
|
|
|
32199
32222
|
lastMsgFile,
|
|
32200
32223
|
truncated
|
|
32201
32224
|
];
|
|
32202
|
-
const cleanEnv =
|
|
32203
|
-
Object.entries(process.env).filter(
|
|
32204
|
-
([key]) => !key.startsWith("CODEX") && key !== "MCP_TRANSPORT"
|
|
32205
|
-
)
|
|
32206
|
-
);
|
|
32225
|
+
const cleanEnv = buildResumeEnv(process.env, "CODEX", ["CODEX_HOME"]);
|
|
32207
32226
|
cleanEnv.COGENT_CHECK_ON_STOP = "0";
|
|
32208
32227
|
const child = spawn2(config2.COGENT_CODEX_PATH, args, {
|
|
32209
32228
|
cwd,
|
|
@@ -32295,7 +32314,7 @@ async function listRolloutFiles(root) {
|
|
|
32295
32314
|
return;
|
|
32296
32315
|
}
|
|
32297
32316
|
for (const e of entries) {
|
|
32298
|
-
const full =
|
|
32317
|
+
const full = path11.join(dir, e.name);
|
|
32299
32318
|
if (e.isDirectory()) await walk(full);
|
|
32300
32319
|
else if (e.isFile() && e.name.endsWith(".jsonl")) out.push(full);
|
|
32301
32320
|
}
|
|
@@ -32327,10 +32346,10 @@ async function readSessionMeta(file) {
|
|
|
32327
32346
|
}
|
|
32328
32347
|
}
|
|
32329
32348
|
function codexSessionsRoot() {
|
|
32330
|
-
return
|
|
32349
|
+
return path11.join(codexHomeDir(), "sessions");
|
|
32331
32350
|
}
|
|
32332
32351
|
async function cwdCanonicals(cwd) {
|
|
32333
|
-
const resolved =
|
|
32352
|
+
const resolved = path11.resolve(cwd);
|
|
32334
32353
|
const forms = /* @__PURE__ */ new Set([resolved]);
|
|
32335
32354
|
try {
|
|
32336
32355
|
forms.add(await fs10.realpath(resolved));
|
|
@@ -32339,7 +32358,7 @@ async function cwdCanonicals(cwd) {
|
|
|
32339
32358
|
return forms;
|
|
32340
32359
|
}
|
|
32341
32360
|
function metaCwdMatches(metaCwd, forms) {
|
|
32342
|
-
return forms.has(
|
|
32361
|
+
return forms.has(path11.resolve(metaCwd));
|
|
32343
32362
|
}
|
|
32344
32363
|
async function detectCurrentCodexSession(cwd) {
|
|
32345
32364
|
try {
|
|
@@ -32398,6 +32417,7 @@ var init_codex_cli = __esm({
|
|
|
32398
32417
|
"src/services/codex-cli.ts"() {
|
|
32399
32418
|
"use strict";
|
|
32400
32419
|
init_config();
|
|
32420
|
+
init_agent_config();
|
|
32401
32421
|
sandboxOverride = null;
|
|
32402
32422
|
SANDBOX_FAILURE_RE = /bwrap:|setting up uid ?map|\/proc\/self\/(uid|gid)_map|newuidmap|user namespaces?|RTM_NEWADDR|unshare[^\n]*(Operation not permitted|Permission denied)|clone3?[^\n]*(EPERM|not permitted)/i;
|
|
32403
32423
|
}
|
|
@@ -33599,11 +33619,12 @@ __export(startup_exports, {
|
|
|
33599
33619
|
cloudInbox: () => cloudInbox,
|
|
33600
33620
|
cloudWsClient: () => cloudWsClient,
|
|
33601
33621
|
reinitCloudBackend: () => reinitCloudBackend,
|
|
33622
|
+
runPreflights: () => runPreflights,
|
|
33602
33623
|
runStartup: () => runStartup,
|
|
33603
33624
|
triggerReRegistration: () => triggerReRegistration
|
|
33604
33625
|
});
|
|
33605
33626
|
import os6 from "node:os";
|
|
33606
|
-
import
|
|
33627
|
+
import path12 from "node:path";
|
|
33607
33628
|
import fs11 from "node:fs/promises";
|
|
33608
33629
|
import readline2 from "node:readline";
|
|
33609
33630
|
import { execFile as execFile5 } from "node:child_process";
|
|
@@ -33654,7 +33675,7 @@ First run detected. Where should cogent-bridge store its data?
|
|
|
33654
33675
|
}
|
|
33655
33676
|
async function validateStateDir(statePath) {
|
|
33656
33677
|
await fs11.mkdir(statePath, { recursive: true });
|
|
33657
|
-
const testFile =
|
|
33678
|
+
const testFile = path12.join(statePath, ".write-test");
|
|
33658
33679
|
try {
|
|
33659
33680
|
await fs11.writeFile(testFile, "test", "utf-8");
|
|
33660
33681
|
await fs11.unlink(testFile);
|
|
@@ -33803,10 +33824,10 @@ async function runStartup() {
|
|
|
33803
33824
|
if (envStatePath) {
|
|
33804
33825
|
statePath = envStatePath;
|
|
33805
33826
|
} else {
|
|
33806
|
-
const defaultPath =
|
|
33827
|
+
const defaultPath = path12.join(os6.homedir(), ".cogent");
|
|
33807
33828
|
statePath = await firstRunPrompt(defaultPath);
|
|
33808
33829
|
}
|
|
33809
|
-
if (!envStatePath && statePath !==
|
|
33830
|
+
if (!envStatePath && statePath !== path12.join(os6.homedir(), ".cogent")) {
|
|
33810
33831
|
process.env.COGENT_STATE_PATH = statePath;
|
|
33811
33832
|
} else if (!envStatePath) {
|
|
33812
33833
|
process.env.COGENT_STATE_PATH = statePath;
|
|
@@ -33826,7 +33847,8 @@ async function runStartup() {
|
|
|
33826
33847
|
} else {
|
|
33827
33848
|
try {
|
|
33828
33849
|
const resolveResp = await fetch(
|
|
33829
|
-
`${effectiveEndpoint}/api/sessions/resolve/${encodeURIComponent(effectiveSessionId)}
|
|
33850
|
+
`${effectiveEndpoint}/api/sessions/resolve/${encodeURIComponent(effectiveSessionId)}`,
|
|
33851
|
+
{ signal: AbortSignal.timeout(2500) }
|
|
33830
33852
|
);
|
|
33831
33853
|
if (resolveResp.ok) {
|
|
33832
33854
|
const resolved = await resolveResp.json();
|
|
@@ -33883,7 +33905,7 @@ async function runStartup() {
|
|
|
33883
33905
|
}
|
|
33884
33906
|
initLogger(
|
|
33885
33907
|
config2.COGENT_LOG_LEVEL,
|
|
33886
|
-
|
|
33908
|
+
path12.join(config2.COGENT_STATE_PATH, "logs")
|
|
33887
33909
|
);
|
|
33888
33910
|
if (isCloud && effectiveSessionId && effectiveToken) {
|
|
33889
33911
|
cloudInbox = new MessageInbox();
|
|
@@ -33904,6 +33926,13 @@ async function runStartup() {
|
|
|
33904
33926
|
await validateStateDir(config2.COGENT_STATE_PATH);
|
|
33905
33927
|
await checkClaudeCli2(config2.COGENT_CLAUDE_PATH);
|
|
33906
33928
|
}
|
|
33929
|
+
const mode = isCloud ? "cloud" : "local";
|
|
33930
|
+
logger.info(
|
|
33931
|
+
`Startup complete. Mode: ${mode}, State: ${config2.COGENT_STATE_PATH}, Log level: ${config2.COGENT_LOG_LEVEL}`
|
|
33932
|
+
);
|
|
33933
|
+
}
|
|
33934
|
+
async function runPreflights() {
|
|
33935
|
+
const config2 = getConfig();
|
|
33907
33936
|
if (config2.COGENT_PLATFORM === "codex") {
|
|
33908
33937
|
await preflightCodex(config2.COGENT_CODEX_PATH);
|
|
33909
33938
|
const sandbox = await preflightCodexSandbox(config2);
|
|
@@ -33913,10 +33942,6 @@ async function runStartup() {
|
|
|
33913
33942
|
} else {
|
|
33914
33943
|
await preflightClaude(config2.COGENT_CLAUDE_PATH);
|
|
33915
33944
|
}
|
|
33916
|
-
const mode = isCloud ? "cloud" : "local";
|
|
33917
|
-
logger.info(
|
|
33918
|
-
`Startup complete. Mode: ${mode}, State: ${config2.COGENT_STATE_PATH}, Log level: ${config2.COGENT_LOG_LEVEL}`
|
|
33919
|
-
);
|
|
33920
33945
|
}
|
|
33921
33946
|
var UUID_V4_RE, execFileAsync3, PERSIST_PATH, legacyWarnEmitted, cloudWsClient, cloudInbox, cloudHttpClient, reRegistrationInProgress;
|
|
33922
33947
|
var init_startup = __esm({
|
|
@@ -33940,7 +33965,7 @@ var init_startup = __esm({
|
|
|
33940
33965
|
init_auto_relay();
|
|
33941
33966
|
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;
|
|
33942
33967
|
execFileAsync3 = promisify4(execFile5);
|
|
33943
|
-
PERSIST_PATH =
|
|
33968
|
+
PERSIST_PATH = path12.join(os6.homedir(), ".cogent-config.json");
|
|
33944
33969
|
legacyWarnEmitted = false;
|
|
33945
33970
|
cloudWsClient = null;
|
|
33946
33971
|
cloudInbox = null;
|
|
@@ -33952,11 +33977,11 @@ var init_startup = __esm({
|
|
|
33952
33977
|
// src/cloud/mail-credential-store.ts
|
|
33953
33978
|
import crypto3 from "node:crypto";
|
|
33954
33979
|
import fs12 from "node:fs/promises";
|
|
33955
|
-
import
|
|
33980
|
+
import path13 from "node:path";
|
|
33956
33981
|
import os7 from "node:os";
|
|
33957
33982
|
function defaultMailCredentialPath(cwd = process.cwd()) {
|
|
33958
|
-
const hash = crypto3.createHash("sha256").update(
|
|
33959
|
-
return
|
|
33983
|
+
const hash = crypto3.createHash("sha256").update(path13.resolve(cwd)).digest("hex").slice(0, 16);
|
|
33984
|
+
return path13.join(os7.homedir(), ".cogent", "mail-credentials", `${hash}.json`);
|
|
33960
33985
|
}
|
|
33961
33986
|
function resolveMailCredentialPath(credentialPath) {
|
|
33962
33987
|
if (credentialPath) return credentialPath;
|
|
@@ -33974,7 +33999,7 @@ async function loadMailCredentials(credentialPath) {
|
|
|
33974
33999
|
}
|
|
33975
34000
|
async function saveMailCredentials(creds, credentialPath) {
|
|
33976
34001
|
const filePath = resolveMailCredentialPath(credentialPath);
|
|
33977
|
-
await fs12.mkdir(
|
|
34002
|
+
await fs12.mkdir(path13.dirname(filePath), { recursive: true, mode: 448 });
|
|
33978
34003
|
await fs12.writeFile(filePath, JSON.stringify(creds, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
|
|
33979
34004
|
await fs12.chmod(filePath, 384);
|
|
33980
34005
|
}
|
|
@@ -34053,11 +34078,11 @@ function registerRegisterPeerTool(server) {
|
|
|
34053
34078
|
const hasCloudEndpointConfig = config2.COGENT_ENDPOINT && isCloudEndpoint(config2.COGENT_ENDPOINT);
|
|
34054
34079
|
let sessionCreated = false;
|
|
34055
34080
|
let cloudSessionId;
|
|
34081
|
+
let cloudToken;
|
|
34056
34082
|
let autoSecret;
|
|
34057
34083
|
if (hasCloudEndpointConfig && config2.COGENT_ENDPOINT) {
|
|
34058
34084
|
let endpoint = config2.COGENT_ENDPOINT;
|
|
34059
34085
|
cloudSessionId = config2.COGENT_SESSION_ID;
|
|
34060
|
-
let cloudToken;
|
|
34061
34086
|
if (!cloudSessionId) {
|
|
34062
34087
|
const trustPersisted = hasJoinedInProcess() || process.env.COGENT_TRUST_PERSISTED_CREDENTIALS === "1";
|
|
34063
34088
|
if (trustPersisted) {
|
|
@@ -34112,6 +34137,15 @@ function registerRegisterPeerTool(server) {
|
|
|
34112
34137
|
);
|
|
34113
34138
|
}
|
|
34114
34139
|
}
|
|
34140
|
+
if (!cloudToken && isCloudDeferred(config2.COGENT_ENDPOINT)) {
|
|
34141
|
+
return errorResult(
|
|
34142
|
+
new BridgeError(
|
|
34143
|
+
"JOIN_NOT_CONFIRMED" /* JOIN_NOT_CONFIRMED */,
|
|
34144
|
+
"Cloud endpoint + COGENT_SESSION_ID are configured but this bridge has not joined the channel (no session token \u2014 the relay was unreachable or the session label did not resolve at startup). cogent_register_peer will NOT register against local state (that would leave you on no channel while other local agents' data sits on this host).",
|
|
34145
|
+
"Call cogent_join_session with the channel name + secret (+ Org_ID for Team channels) and confirm it returns success, then retry cogent_register_peer."
|
|
34146
|
+
)
|
|
34147
|
+
);
|
|
34148
|
+
}
|
|
34115
34149
|
const backend = getBackend();
|
|
34116
34150
|
const existing = await backend.getPeer(peerId);
|
|
34117
34151
|
const channelSessionId = sessionId;
|
|
@@ -34297,6 +34331,13 @@ function registerSendMessageTool(server) {
|
|
|
34297
34331
|
},
|
|
34298
34332
|
async ({ fromPeerId, toPeerId, message }) => {
|
|
34299
34333
|
try {
|
|
34334
|
+
if (isCloudDeferred(getConfig().COGENT_ENDPOINT)) {
|
|
34335
|
+
return successResult({
|
|
34336
|
+
success: false,
|
|
34337
|
+
notJoined: true,
|
|
34338
|
+
error: "Not connected to a channel yet \u2014 call cogent_join_session (or cogent_register_peer) first. Message not sent (cross-channel send is blocked until you join)."
|
|
34339
|
+
});
|
|
34340
|
+
}
|
|
34300
34341
|
const backend = getBackend();
|
|
34301
34342
|
const from = await backend.getPeer(fromPeerId);
|
|
34302
34343
|
if (!from) {
|
|
@@ -35014,6 +35055,14 @@ function registerListPeersTool(server) {
|
|
|
35014
35055
|
},
|
|
35015
35056
|
async () => {
|
|
35016
35057
|
try {
|
|
35058
|
+
if (isCloudDeferred(getConfig().COGENT_ENDPOINT)) {
|
|
35059
|
+
return successResult({
|
|
35060
|
+
peers: [],
|
|
35061
|
+
count: 0,
|
|
35062
|
+
notJoined: true,
|
|
35063
|
+
hint: "Not connected to a channel yet \u2014 call cogent_join_session (or cogent_register_peer) first. Local peers are withheld to prevent cross-channel disclosure."
|
|
35064
|
+
});
|
|
35065
|
+
}
|
|
35017
35066
|
const backend = getBackend();
|
|
35018
35067
|
const peers = await backend.listPeers();
|
|
35019
35068
|
const staleTimeout = getConfig().COGENT_STALE_TIMEOUT_MS;
|
|
@@ -35078,6 +35127,16 @@ function registerGetHistoryTool(server) {
|
|
|
35078
35127
|
},
|
|
35079
35128
|
async ({ peerId, limit, includeRelayEchoes, metadataOnly, dedupInferredEchoes }) => {
|
|
35080
35129
|
try {
|
|
35130
|
+
if (isCloudDeferred(getConfig().COGENT_ENDPOINT)) {
|
|
35131
|
+
return successResult({
|
|
35132
|
+
messages: [],
|
|
35133
|
+
count: 0,
|
|
35134
|
+
truncated: false,
|
|
35135
|
+
totalAvailable: 0,
|
|
35136
|
+
notJoined: true,
|
|
35137
|
+
hint: "Not connected to a channel yet \u2014 call cogent_join_session (or cogent_register_peer) first. Local history is withheld to prevent cross-channel disclosure."
|
|
35138
|
+
});
|
|
35139
|
+
}
|
|
35081
35140
|
const backend = getBackend();
|
|
35082
35141
|
const messages = await backend.getHistory(
|
|
35083
35142
|
peerId,
|
|
@@ -35114,6 +35173,7 @@ var init_get_history = __esm({
|
|
|
35114
35173
|
"use strict";
|
|
35115
35174
|
import_zod12 = __toESM(require_zod(), 1);
|
|
35116
35175
|
init_backend();
|
|
35176
|
+
init_config();
|
|
35117
35177
|
init_errors4();
|
|
35118
35178
|
init_logger();
|
|
35119
35179
|
MAX_RESULT_CHARS = 5e4;
|
|
@@ -35139,6 +35199,14 @@ function registerDeregisterPeerTool(server) {
|
|
|
35139
35199
|
},
|
|
35140
35200
|
async ({ peerId }) => {
|
|
35141
35201
|
try {
|
|
35202
|
+
if (isCloudDeferred(getConfig().COGENT_ENDPOINT)) {
|
|
35203
|
+
return successResult({
|
|
35204
|
+
success: false,
|
|
35205
|
+
notJoined: true,
|
|
35206
|
+
removed: false,
|
|
35207
|
+
message: "Not connected to a channel yet \u2014 call cogent_join_session (or cogent_register_peer) first. Nothing to deregister."
|
|
35208
|
+
});
|
|
35209
|
+
}
|
|
35142
35210
|
const backend = getBackend();
|
|
35143
35211
|
const { removed, mailboxDeprovisioned } = await backend.deregisterPeer(peerId);
|
|
35144
35212
|
if (removed) heartbeat.stop();
|
|
@@ -35167,6 +35235,7 @@ var init_deregister_peer = __esm({
|
|
|
35167
35235
|
"use strict";
|
|
35168
35236
|
import_zod13 = __toESM(require_zod(), 1);
|
|
35169
35237
|
init_backend();
|
|
35238
|
+
init_config();
|
|
35170
35239
|
init_errors4();
|
|
35171
35240
|
init_logger();
|
|
35172
35241
|
init_heartbeat();
|
|
@@ -35640,12 +35709,12 @@ var init_setup_mail = __esm({
|
|
|
35640
35709
|
|
|
35641
35710
|
// src/mail/mail-sender.ts
|
|
35642
35711
|
import fs13 from "node:fs/promises";
|
|
35643
|
-
import
|
|
35712
|
+
import path14 from "node:path";
|
|
35644
35713
|
async function buildAndSendMail(sender, creds, params) {
|
|
35645
35714
|
const attachments = [];
|
|
35646
35715
|
for (const p of params.attachmentPaths ?? []) {
|
|
35647
35716
|
const content = await fs13.readFile(p);
|
|
35648
|
-
attachments.push({ filename:
|
|
35717
|
+
attachments.push({ filename: path14.basename(p), content });
|
|
35649
35718
|
}
|
|
35650
35719
|
const { messageId } = await sender.send({
|
|
35651
35720
|
from: creds.address,
|
|
@@ -35774,14 +35843,14 @@ var init_send_mail = __esm({
|
|
|
35774
35843
|
|
|
35775
35844
|
// src/mail/mail-fetcher.ts
|
|
35776
35845
|
import fs14 from "node:fs/promises";
|
|
35777
|
-
import
|
|
35846
|
+
import path15 from "node:path";
|
|
35778
35847
|
async function saveAttachments(attachments, downloadDir) {
|
|
35779
35848
|
if (attachments.length === 0) return [];
|
|
35780
35849
|
await fs14.mkdir(downloadDir, { recursive: true });
|
|
35781
35850
|
const saved = [];
|
|
35782
35851
|
for (const a of attachments) {
|
|
35783
|
-
const name =
|
|
35784
|
-
const savedPath =
|
|
35852
|
+
const name = path15.basename(a.filename) || "attachment";
|
|
35853
|
+
const savedPath = path15.join(downloadDir, name);
|
|
35785
35854
|
await fs14.writeFile(savedPath, a.content);
|
|
35786
35855
|
saved.push({ filename: name, savedPath, size: a.content.length });
|
|
35787
35856
|
}
|
|
@@ -35914,7 +35983,7 @@ var init_mail_fetcher = __esm({
|
|
|
35914
35983
|
|
|
35915
35984
|
// src/tools/fetch-mail.ts
|
|
35916
35985
|
import os8 from "node:os";
|
|
35917
|
-
import
|
|
35986
|
+
import path16 from "node:path";
|
|
35918
35987
|
function registerFetchMailTool(server, deps = {}) {
|
|
35919
35988
|
const makeFetcher = deps.fetcherFactory ?? ((creds) => new ImapflowMailFetcher(creds));
|
|
35920
35989
|
server.registerTool(
|
|
@@ -35976,7 +36045,7 @@ var init_fetch_mail = __esm({
|
|
|
35976
36045
|
init_mail_fetcher();
|
|
35977
36046
|
init_auth_error();
|
|
35978
36047
|
init_validate();
|
|
35979
|
-
DEFAULT_DOWNLOAD_DIR =
|
|
36048
|
+
DEFAULT_DOWNLOAD_DIR = path16.join(os8.homedir(), ".cogent", "mail-downloads");
|
|
35980
36049
|
}
|
|
35981
36050
|
});
|
|
35982
36051
|
|
|
@@ -36115,6 +36184,11 @@ async function main() {
|
|
|
36115
36184
|
`${SERVER_NAME} v${SERVER_VERSION} running on stdio
|
|
36116
36185
|
`
|
|
36117
36186
|
);
|
|
36187
|
+
void runPreflights().catch((err) => {
|
|
36188
|
+
logger.warn(
|
|
36189
|
+
`Preflight error (non-fatal): ${err instanceof Error ? err.message : String(err)}`
|
|
36190
|
+
);
|
|
36191
|
+
});
|
|
36118
36192
|
void checkForUpdate(SERVER_VERSION).then((nudge) => {
|
|
36119
36193
|
setLastNudge(nudge);
|
|
36120
36194
|
if (nudge) process.stderr.write(`${nudge}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@essentialai/cogent-plugin",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.19.0",
|
|
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",
|