@cohortapp/agent-sdk 2.11.6 → 2.11.8
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cohortapp/agent-sdk",
|
|
3
|
-
"version": "2.11.
|
|
3
|
+
"version": "2.11.8",
|
|
4
4
|
"description": "Cohort Agent SDK — autonomous AI colleague runtime. Deploy senior AI colleagues on dedicated Mac minis, wired to the Cohort operating surface.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -84,7 +84,7 @@ const RUNG_MECHANISM = Object.fromEntries(RUNGS.map((r) => [r.id, r.mechanism]))
|
|
|
84
84
|
// work and land it in the org's shared, ACL'd store so the fleet's memory
|
|
85
85
|
// compounds. Fail-open + best-effort — never blocks or throws into the hot path;
|
|
86
86
|
// local SQLite/interaction logs remain the fail-open cache.
|
|
87
|
-
import { isEnabled as orgEnabled, loadOrgConfig, configFromAgent, electResponder } from "../../lib/org/client.mjs";
|
|
87
|
+
import { isEnabled as orgEnabled, loadOrgConfig, configFromAgent, electResponder, fetchSelfProfile } from "../../lib/org/client.mjs";
|
|
88
88
|
import { remember as orgRemember } from "../../lib/org/knowledge.mjs";
|
|
89
89
|
import { sweepSessionOutcomes, resultTextFromStdout } from "./session-outcomes.mjs";
|
|
90
90
|
import { recordWorkStep as orgRecordWorkStep, cohortChannelId, cohortMessageId } from "../../lib/org/work-ledger.mjs";
|
|
@@ -1085,8 +1085,83 @@ export function _resetLadderCaches() {
|
|
|
1085
1085
|
_ladderDisabledWarned = false;
|
|
1086
1086
|
}
|
|
1087
1087
|
|
|
1088
|
+
// The seat's own member id (cuid), resolved once. This is the `actor` on every
|
|
1089
|
+
// message/event the seat sends, so the own-echo guard needs it — NOT the slug
|
|
1090
|
+
// (e.g. "A016"), which never matches a cuid actor. COHORT_AGENT_ID is a slug, so
|
|
1091
|
+
// using it as `me` (the old behaviour) left own-echo permanently broken: the seat
|
|
1092
|
+
// re-ingested its own replies and held a conversation with itself.
|
|
1093
|
+
let _selfMemberId = null;
|
|
1094
|
+
|
|
1095
|
+
/** Read a previously-persisted memberId (cuid) from config/agent.json. */
|
|
1096
|
+
function persistedSelfMemberId() {
|
|
1097
|
+
try {
|
|
1098
|
+
const c = JSON.parse(readFileSync(join(AGENT_REPO_DIR, "config", "agent.json"), "utf-8"));
|
|
1099
|
+
const mid = c && typeof c.memberId === "string" ? c.memberId : "";
|
|
1100
|
+
// Guard against a slug ever being persisted here — the field MUST be a cuid.
|
|
1101
|
+
return mid && mid !== process.env.COHORT_AGENT_ID ? mid : "";
|
|
1102
|
+
} catch {
|
|
1103
|
+
return "";
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
/**
|
|
1108
|
+
* Resolve + cache the seat's own member cuid via /whoami (org/members/<slug>),
|
|
1109
|
+
* persisting it to config/agent.json so subsequent boots — and every own-echo
|
|
1110
|
+
* check — have it instantly. Best-effort and idempotent; safe to await on the
|
|
1111
|
+
* hot path (cached after the first success). Requires hq to expose `memberId`
|
|
1112
|
+
* on the profile DTO (org-api whoami/members).
|
|
1113
|
+
*/
|
|
1114
|
+
export async function resolveSelfMemberId(deps = {}) {
|
|
1115
|
+
if (_selfMemberId) return _selfMemberId;
|
|
1116
|
+
const fromDisk = persistedSelfMemberId();
|
|
1117
|
+
if (fromDisk) { _selfMemberId = fromDisk; return _selfMemberId; }
|
|
1118
|
+
try {
|
|
1119
|
+
const cfg = daemonOrgCfg();
|
|
1120
|
+
const conn = configFromAgent(cfg) || {};
|
|
1121
|
+
// configFromAgent returns the org key as `token` (NOT `apiKey`), and the
|
|
1122
|
+
// daemon's creds come from config/org.yaml, not process.env — so read `token`
|
|
1123
|
+
// first and only fall back to env. The slug lives at cfg.org.cohort.agentId
|
|
1124
|
+
// (same source ladderMemberId used); without it fetchSelfProfile can't hit
|
|
1125
|
+
// /members/<slug> and whoami is ambiguous in a multi-agent org.
|
|
1126
|
+
const apiKey = conn.token || conn.apiKey || process.env.COHORT_API_KEY || "";
|
|
1127
|
+
const orgId = conn.orgId || process.env.COHORT_ORG_ID || "";
|
|
1128
|
+
const agentId =
|
|
1129
|
+
conn.agentId ||
|
|
1130
|
+
(cfg && cfg.org && cfg.org.cohort && cfg.org.cohort.agentId) ||
|
|
1131
|
+
process.env.COHORT_AGENT_ID ||
|
|
1132
|
+
"";
|
|
1133
|
+
const base = conn.base || process.env.COHORT_BASE || "";
|
|
1134
|
+
if (!apiKey || !orgId) return _selfMemberId;
|
|
1135
|
+
const fetchSelf = deps.fetchSelfProfileImpl || fetchSelfProfile;
|
|
1136
|
+
const res = await fetchSelf({ base, apiKey, orgId, agentId });
|
|
1137
|
+
// fetchSelfProfile returns a res frame with the member under `result` (the
|
|
1138
|
+
// authed call() shape); tolerate `member` too for the raw directory route.
|
|
1139
|
+
const m = res && res.ok ? (res.result || res.member) : null;
|
|
1140
|
+
const mid = m && m.memberId ? String(m.memberId) : "";
|
|
1141
|
+
if (mid) {
|
|
1142
|
+
_selfMemberId = mid;
|
|
1143
|
+
try {
|
|
1144
|
+
const cfgPath = join(AGENT_REPO_DIR, "config", "agent.json");
|
|
1145
|
+
const c = JSON.parse(readFileSync(cfgPath, "utf-8"));
|
|
1146
|
+
c.memberId = mid;
|
|
1147
|
+
writeFileSync(cfgPath, JSON.stringify(c, null, 2) + "\n");
|
|
1148
|
+
} catch { /* best-effort persist — resolution still cached in-memory */ }
|
|
1149
|
+
}
|
|
1150
|
+
} catch { /* best-effort — falls back to slug until hq exposes memberId */ }
|
|
1151
|
+
return _selfMemberId;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
/** For tests: reset the cached self member id. */
|
|
1155
|
+
export function _resetSelfMemberId() { _selfMemberId = null; }
|
|
1156
|
+
|
|
1088
1157
|
/** The seat's org member id — the `me` every directedness join is relative to. */
|
|
1089
1158
|
function ladderMemberId() {
|
|
1159
|
+
// Prefer the resolved member cuid (the actor on events). Fall back to the slug
|
|
1160
|
+
// only until resolution completes — the slug can't power own-echo, but it is no
|
|
1161
|
+
// worse than the historical behaviour.
|
|
1162
|
+
if (_selfMemberId) return _selfMemberId;
|
|
1163
|
+
const disk = persistedSelfMemberId();
|
|
1164
|
+
if (disk) { _selfMemberId = disk; return disk; }
|
|
1090
1165
|
const cfg = daemonOrgCfg();
|
|
1091
1166
|
return (
|
|
1092
1167
|
process.env.COHORT_AGENT_ID ||
|
|
@@ -1315,6 +1390,10 @@ export async function runExecutionLadder(item, service, itemId, trace_id, deps =
|
|
|
1315
1390
|
|
|
1316
1391
|
let answered = null;
|
|
1317
1392
|
try {
|
|
1393
|
+
// Ensure the seat's own member cuid is resolved BEFORE directedness runs, so
|
|
1394
|
+
// the own-echo guard can recognize this seat's own messages (the actor on an
|
|
1395
|
+
// event is a cuid, not the slug). Cached after the first resolve.
|
|
1396
|
+
await resolveSelfMemberId(deps);
|
|
1318
1397
|
const me = ladderMemberId();
|
|
1319
1398
|
const obligations = await ladderObligations(log);
|
|
1320
1399
|
|
|
@@ -830,3 +830,35 @@ test("NO FALLTHROUGH: a TRANSIENT quick-reply failure still falls through to a f
|
|
|
830
830
|
assert.equal(dispatched, true, "a transient failure falls through to the full session, as before");
|
|
831
831
|
assert.equal(res.path, "session");
|
|
832
832
|
});
|
|
833
|
+
|
|
834
|
+
test("resolveSelfMemberId reads the member cuid from the res frame's `result` (own-echo keystone)", async () => {
|
|
835
|
+
daemon._resetSelfMemberId();
|
|
836
|
+
const pk = process.env.COHORT_API_KEY, po = process.env.COHORT_ORG_ID, pa = process.env.COHORT_AGENT_ID;
|
|
837
|
+
process.env.COHORT_API_KEY = "nlk_test";
|
|
838
|
+
process.env.COHORT_ORG_ID = "org_test";
|
|
839
|
+
process.env.COHORT_AGENT_ID = "A016";
|
|
840
|
+
try {
|
|
841
|
+
let calls = 0;
|
|
842
|
+
// fetchSelfProfile returns the authed call() res frame: the member is under
|
|
843
|
+
// `result`, and the org key is passed as apiKey. (Both were wrong in 2.11.7.)
|
|
844
|
+
const mid = await daemon.resolveSelfMemberId({
|
|
845
|
+
fetchSelfProfileImpl: async (o) => {
|
|
846
|
+
calls++;
|
|
847
|
+
assert.equal(o.agentId, "A016", "must pass the slug so /members/<slug> resolves");
|
|
848
|
+
return { ok: true, result: { memberId: "cmSELFcuid", slug: o.agentId } };
|
|
849
|
+
},
|
|
850
|
+
});
|
|
851
|
+
assert.equal(mid, "cmSELFcuid");
|
|
852
|
+
// cached: a second call must NOT refetch
|
|
853
|
+
const again = await daemon.resolveSelfMemberId({
|
|
854
|
+
fetchSelfProfileImpl: async () => { throw new Error("must not refetch a cached self id"); },
|
|
855
|
+
});
|
|
856
|
+
assert.equal(again, "cmSELFcuid");
|
|
857
|
+
assert.equal(calls, 1);
|
|
858
|
+
} finally {
|
|
859
|
+
daemon._resetSelfMemberId();
|
|
860
|
+
if (pk === undefined) delete process.env.COHORT_API_KEY; else process.env.COHORT_API_KEY = pk;
|
|
861
|
+
if (po === undefined) delete process.env.COHORT_ORG_ID; else process.env.COHORT_ORG_ID = po;
|
|
862
|
+
if (pa === undefined) delete process.env.COHORT_AGENT_ID; else process.env.COHORT_AGENT_ID = pa;
|
|
863
|
+
}
|
|
864
|
+
});
|