jefrichat-mcp 0.48.14 → 0.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/http.js +54 -5
- package/dist/index.js +475 -154
- package/package.json +1 -1
package/dist/http.js
CHANGED
|
@@ -60904,6 +60904,22 @@ var JefriAuthError = class extends Error {
|
|
|
60904
60904
|
var JefriClient = class _JefriClient {
|
|
60905
60905
|
ws;
|
|
60906
60906
|
handlers = /* @__PURE__ */ new Map();
|
|
60907
|
+
// Round 2 of the sessions audit (finding #1): a message can arrive in the
|
|
60908
|
+
// milliseconds between `registered` and the caller attaching its handlers —
|
|
60909
|
+
// connect() itself awaits E2E setup after registration, and the connector
|
|
60910
|
+
// wires its inbox after connect() resolves. Events nobody is listening for
|
|
60911
|
+
// yet are BUFFERED (delivery-critical types only) and replayed, in order,
|
|
60912
|
+
// to the first subscriber. Live listeners bypass the buffer entirely.
|
|
60913
|
+
static REPLAYABLE = /* @__PURE__ */ new Set([
|
|
60914
|
+
"message_received",
|
|
60915
|
+
"file_received",
|
|
60916
|
+
"debate_turn",
|
|
60917
|
+
"debate_summary_request",
|
|
60918
|
+
"debate_cancel"
|
|
60919
|
+
]);
|
|
60920
|
+
static REPLAY_MAX = 500;
|
|
60921
|
+
replayBuffer = [];
|
|
60922
|
+
replayScheduled = false;
|
|
60907
60923
|
server;
|
|
60908
60924
|
token;
|
|
60909
60925
|
identity;
|
|
@@ -61160,14 +61176,33 @@ var JefriClient = class _JefriClient {
|
|
|
61160
61176
|
const set = this.handlers.get(event) ?? /* @__PURE__ */ new Set();
|
|
61161
61177
|
set.add(handler);
|
|
61162
61178
|
this.handlers.set(event, set);
|
|
61179
|
+
if (this.replayBuffer.length && _JefriClient.REPLAYABLE.has(event)) this.scheduleReplayDrain();
|
|
61163
61180
|
return this;
|
|
61164
61181
|
}
|
|
61165
61182
|
off(event, handler) {
|
|
61166
61183
|
this.handlers.get(event)?.delete(handler);
|
|
61167
61184
|
return this;
|
|
61168
61185
|
}
|
|
61169
|
-
|
|
61170
|
-
|
|
61186
|
+
scheduleReplayDrain() {
|
|
61187
|
+
if (this.replayScheduled) return;
|
|
61188
|
+
this.replayScheduled = true;
|
|
61189
|
+
queueMicrotask(() => {
|
|
61190
|
+
this.replayScheduled = false;
|
|
61191
|
+
const drain3 = this.replayBuffer;
|
|
61192
|
+
this.replayBuffer = [];
|
|
61193
|
+
const keep = [];
|
|
61194
|
+
for (const b of drain3) {
|
|
61195
|
+
const hs = this.handlers.get(b.event);
|
|
61196
|
+
if (hs?.size) this.dispatch(b.event, b.payload, hs);
|
|
61197
|
+
else keep.push(b);
|
|
61198
|
+
}
|
|
61199
|
+
this.replayBuffer = keep.concat(this.replayBuffer);
|
|
61200
|
+
});
|
|
61201
|
+
}
|
|
61202
|
+
/** emit()'s containment, shared with replay: a throwing handler must NEVER
|
|
61203
|
+
* propagate or starve the others; async rejections are contained too. */
|
|
61204
|
+
dispatch(event, payload, handlers) {
|
|
61205
|
+
for (const h of handlers) {
|
|
61171
61206
|
try {
|
|
61172
61207
|
const res = h(payload);
|
|
61173
61208
|
if (res instanceof Promise)
|
|
@@ -61177,6 +61212,16 @@ var JefriClient = class _JefriClient {
|
|
|
61177
61212
|
}
|
|
61178
61213
|
}
|
|
61179
61214
|
}
|
|
61215
|
+
emit(event, payload) {
|
|
61216
|
+
if (_JefriClient.REPLAYABLE.has(event) && !this.handlers.get(event)?.size) {
|
|
61217
|
+
if (this.replayBuffer.length >= _JefriClient.REPLAY_MAX) {
|
|
61218
|
+
this.replayBuffer.shift();
|
|
61219
|
+
console.error(`[jefri-sdk] replay buffer overflow for pre-subscription events \u2014 oldest dropped`);
|
|
61220
|
+
}
|
|
61221
|
+
this.replayBuffer.push({ event, payload });
|
|
61222
|
+
}
|
|
61223
|
+
this.dispatch(event, payload, this.handlers.get(event) ?? []);
|
|
61224
|
+
}
|
|
61180
61225
|
send(ev) {
|
|
61181
61226
|
if (!this.ws || this.ws.readyState !== this.ws.OPEN)
|
|
61182
61227
|
throw new Error("not connected to the Jefri Chat hub (reconnecting) \u2014 try again in a moment");
|
|
@@ -62535,9 +62580,13 @@ ${e?.message ?? e}`);
|
|
|
62535
62580
|
description: "Show this session's Jefri Chat identity (username, display name) and connection status.",
|
|
62536
62581
|
inputSchema: {}
|
|
62537
62582
|
},
|
|
62538
|
-
async () =>
|
|
62539
|
-
|
|
62540
|
-
|
|
62583
|
+
async () => {
|
|
62584
|
+
const unbound = await ctx.identityStatus?.();
|
|
62585
|
+
if (unbound) return ok3(unbound);
|
|
62586
|
+
return withClient(
|
|
62587
|
+
async (c) => ok3(`You are "${c.identity.displayName}" (@${c.identity.username}) on Jefri Chat at ${ctx.serverUrl}, status: online.`)
|
|
62588
|
+
);
|
|
62589
|
+
}
|
|
62541
62590
|
);
|
|
62542
62591
|
server2.registerTool(
|
|
62543
62592
|
"jefri_agents",
|
package/dist/index.js
CHANGED
|
@@ -3244,8 +3244,8 @@ var require_utils = __commonJS({
|
|
|
3244
3244
|
}
|
|
3245
3245
|
return ind;
|
|
3246
3246
|
}
|
|
3247
|
-
function removeDotSegments(
|
|
3248
|
-
let input2 =
|
|
3247
|
+
function removeDotSegments(path5) {
|
|
3248
|
+
let input2 = path5;
|
|
3249
3249
|
const output2 = [];
|
|
3250
3250
|
let nextSlash = -1;
|
|
3251
3251
|
let len = 0;
|
|
@@ -3497,8 +3497,8 @@ var require_schemes = __commonJS({
|
|
|
3497
3497
|
wsComponent.secure = void 0;
|
|
3498
3498
|
}
|
|
3499
3499
|
if (wsComponent.resourceName) {
|
|
3500
|
-
const [
|
|
3501
|
-
wsComponent.path =
|
|
3500
|
+
const [path5, query] = wsComponent.resourceName.split("?");
|
|
3501
|
+
wsComponent.path = path5 && path5 !== "/" ? path5 : void 0;
|
|
3502
3502
|
wsComponent.query = query;
|
|
3503
3503
|
wsComponent.resourceName = void 0;
|
|
3504
3504
|
}
|
|
@@ -6891,12 +6891,12 @@ var require_dist = __commonJS({
|
|
|
6891
6891
|
throw new Error(`Unknown format "${name}"`);
|
|
6892
6892
|
return f;
|
|
6893
6893
|
};
|
|
6894
|
-
function addFormats(ajv, list3,
|
|
6894
|
+
function addFormats(ajv, list3, fs13, exportName) {
|
|
6895
6895
|
var _a;
|
|
6896
6896
|
var _b;
|
|
6897
6897
|
(_a = (_b = ajv.opts.code).formats) !== null && _a !== void 0 ? _a : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
|
|
6898
6898
|
for (const f of list3)
|
|
6899
|
-
ajv.addFormat(f,
|
|
6899
|
+
ajv.addFormat(f, fs13[f]);
|
|
6900
6900
|
}
|
|
6901
6901
|
module.exports = exports = formatsPlugin;
|
|
6902
6902
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -10956,10 +10956,10 @@ function ensureDirs() {
|
|
|
10956
10956
|
}
|
|
10957
10957
|
}
|
|
10958
10958
|
function publishEndpoint(d, deps = {}) {
|
|
10959
|
-
const writeFile = deps.writeFileSync ?? ((
|
|
10959
|
+
const writeFile = deps.writeFileSync ?? ((path5, data, opts) => fs6.writeFileSync(path5, data, opts));
|
|
10960
10960
|
const rename = deps.renameSync ?? ((from, to) => fs6.renameSync(from, to));
|
|
10961
|
-
const unlink = deps.unlinkSync ?? ((
|
|
10962
|
-
const readFile = deps.readFileSync ?? ((
|
|
10961
|
+
const unlink = deps.unlinkSync ?? ((path5) => fs6.unlinkSync(path5));
|
|
10962
|
+
const readFile = deps.readFileSync ?? ((path5) => fs6.readFileSync(path5, "utf8"));
|
|
10963
10963
|
ensureDirs();
|
|
10964
10964
|
if (!d.procToken) d = { ...d, procToken: procToken(d.pid) };
|
|
10965
10965
|
if (!d.nonce) d = { ...d, nonce: INSTANCE_NONCE };
|
|
@@ -13136,8 +13136,8 @@ function getErrorMap() {
|
|
|
13136
13136
|
|
|
13137
13137
|
// ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
|
|
13138
13138
|
var makeIssue = (params) => {
|
|
13139
|
-
const { data, path:
|
|
13140
|
-
const fullPath = [...
|
|
13139
|
+
const { data, path: path5, errorMaps, issueData } = params;
|
|
13140
|
+
const fullPath = [...path5, ...issueData.path || []];
|
|
13141
13141
|
const fullIssue = {
|
|
13142
13142
|
...issueData,
|
|
13143
13143
|
path: fullPath
|
|
@@ -13253,11 +13253,11 @@ var errorUtil;
|
|
|
13253
13253
|
|
|
13254
13254
|
// ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js
|
|
13255
13255
|
var ParseInputLazyPath = class {
|
|
13256
|
-
constructor(parent, value,
|
|
13256
|
+
constructor(parent, value, path5, key) {
|
|
13257
13257
|
this._cachedPath = [];
|
|
13258
13258
|
this.parent = parent;
|
|
13259
13259
|
this.data = value;
|
|
13260
|
-
this._path =
|
|
13260
|
+
this._path = path5;
|
|
13261
13261
|
this._key = key;
|
|
13262
13262
|
}
|
|
13263
13263
|
get path() {
|
|
@@ -16894,10 +16894,10 @@ function assignProp(target, prop, value) {
|
|
|
16894
16894
|
configurable: true
|
|
16895
16895
|
});
|
|
16896
16896
|
}
|
|
16897
|
-
function getElementAtPath(obj,
|
|
16898
|
-
if (!
|
|
16897
|
+
function getElementAtPath(obj, path5) {
|
|
16898
|
+
if (!path5)
|
|
16899
16899
|
return obj;
|
|
16900
|
-
return
|
|
16900
|
+
return path5.reduce((acc, key) => acc?.[key], obj);
|
|
16901
16901
|
}
|
|
16902
16902
|
function promiseAllObject(promisesObj) {
|
|
16903
16903
|
const keys = Object.keys(promisesObj);
|
|
@@ -17217,11 +17217,11 @@ function aborted(x, startIndex = 0) {
|
|
|
17217
17217
|
}
|
|
17218
17218
|
return false;
|
|
17219
17219
|
}
|
|
17220
|
-
function prefixIssues(
|
|
17220
|
+
function prefixIssues(path5, issues) {
|
|
17221
17221
|
return issues.map((iss) => {
|
|
17222
17222
|
var _a;
|
|
17223
17223
|
(_a = iss).path ?? (_a.path = []);
|
|
17224
|
-
iss.path.unshift(
|
|
17224
|
+
iss.path.unshift(path5);
|
|
17225
17225
|
return iss;
|
|
17226
17226
|
});
|
|
17227
17227
|
}
|
|
@@ -26871,9 +26871,9 @@ var StdioServerTransport = class {
|
|
|
26871
26871
|
};
|
|
26872
26872
|
|
|
26873
26873
|
// src/index.ts
|
|
26874
|
-
import
|
|
26875
|
-
import
|
|
26876
|
-
import
|
|
26874
|
+
import fs12 from "node:fs";
|
|
26875
|
+
import os8 from "node:os";
|
|
26876
|
+
import path4 from "node:path";
|
|
26877
26877
|
|
|
26878
26878
|
// ../../node_modules/.pnpm/ws@8.21.0/node_modules/ws/wrapper.mjs
|
|
26879
26879
|
var import_stream = __toESM(require_stream(), 1);
|
|
@@ -27350,13 +27350,13 @@ var VFile = class {
|
|
|
27350
27350
|
* @returns {undefined}
|
|
27351
27351
|
* Nothing.
|
|
27352
27352
|
*/
|
|
27353
|
-
set path(
|
|
27354
|
-
if (isUrl(
|
|
27355
|
-
|
|
27353
|
+
set path(path5) {
|
|
27354
|
+
if (isUrl(path5)) {
|
|
27355
|
+
path5 = fileURLToPath(path5);
|
|
27356
27356
|
}
|
|
27357
|
-
assertNonEmpty(
|
|
27358
|
-
if (this.path !==
|
|
27359
|
-
this.history.push(
|
|
27357
|
+
assertNonEmpty(path5, "path");
|
|
27358
|
+
if (this.path !== path5) {
|
|
27359
|
+
this.history.push(path5);
|
|
27360
27360
|
}
|
|
27361
27361
|
}
|
|
27362
27362
|
/**
|
|
@@ -27623,8 +27623,8 @@ function assertNonEmpty(part, name) {
|
|
|
27623
27623
|
throw new Error("`" + name + "` cannot be empty");
|
|
27624
27624
|
}
|
|
27625
27625
|
}
|
|
27626
|
-
function assertPath(
|
|
27627
|
-
if (!
|
|
27626
|
+
function assertPath(path5, name) {
|
|
27627
|
+
if (!path5) {
|
|
27628
27628
|
throw new Error("Setting `" + name + "` requires `path` to be set too");
|
|
27629
27629
|
}
|
|
27630
27630
|
}
|
|
@@ -35610,7 +35610,7 @@ function transformGfmAutolinkLiterals(tree) {
|
|
|
35610
35610
|
{ ignore: ["link", "linkReference"] }
|
|
35611
35611
|
);
|
|
35612
35612
|
}
|
|
35613
|
-
function findUrl(_, protocol, domain2,
|
|
35613
|
+
function findUrl(_, protocol, domain2, path5, match) {
|
|
35614
35614
|
let prefix = "";
|
|
35615
35615
|
if (!previous2(match)) {
|
|
35616
35616
|
return false;
|
|
@@ -35623,7 +35623,7 @@ function findUrl(_, protocol, domain2, path4, match) {
|
|
|
35623
35623
|
if (!isCorrectDomain(domain2)) {
|
|
35624
35624
|
return false;
|
|
35625
35625
|
}
|
|
35626
|
-
const parts = splitUrl(domain2 +
|
|
35626
|
+
const parts = splitUrl(domain2 + path5);
|
|
35627
35627
|
if (!parts[0]) return false;
|
|
35628
35628
|
const result = {
|
|
35629
35629
|
type: "link",
|
|
@@ -38638,6 +38638,22 @@ var JefriAuthError = class extends Error {
|
|
|
38638
38638
|
var JefriClient = class _JefriClient {
|
|
38639
38639
|
ws;
|
|
38640
38640
|
handlers = /* @__PURE__ */ new Map();
|
|
38641
|
+
// Round 2 of the sessions audit (finding #1): a message can arrive in the
|
|
38642
|
+
// milliseconds between `registered` and the caller attaching its handlers —
|
|
38643
|
+
// connect() itself awaits E2E setup after registration, and the connector
|
|
38644
|
+
// wires its inbox after connect() resolves. Events nobody is listening for
|
|
38645
|
+
// yet are BUFFERED (delivery-critical types only) and replayed, in order,
|
|
38646
|
+
// to the first subscriber. Live listeners bypass the buffer entirely.
|
|
38647
|
+
static REPLAYABLE = /* @__PURE__ */ new Set([
|
|
38648
|
+
"message_received",
|
|
38649
|
+
"file_received",
|
|
38650
|
+
"debate_turn",
|
|
38651
|
+
"debate_summary_request",
|
|
38652
|
+
"debate_cancel"
|
|
38653
|
+
]);
|
|
38654
|
+
static REPLAY_MAX = 500;
|
|
38655
|
+
replayBuffer = [];
|
|
38656
|
+
replayScheduled = false;
|
|
38641
38657
|
server;
|
|
38642
38658
|
token;
|
|
38643
38659
|
identity;
|
|
@@ -38894,14 +38910,33 @@ var JefriClient = class _JefriClient {
|
|
|
38894
38910
|
const set = this.handlers.get(event) ?? /* @__PURE__ */ new Set();
|
|
38895
38911
|
set.add(handler);
|
|
38896
38912
|
this.handlers.set(event, set);
|
|
38913
|
+
if (this.replayBuffer.length && _JefriClient.REPLAYABLE.has(event)) this.scheduleReplayDrain();
|
|
38897
38914
|
return this;
|
|
38898
38915
|
}
|
|
38899
38916
|
off(event, handler) {
|
|
38900
38917
|
this.handlers.get(event)?.delete(handler);
|
|
38901
38918
|
return this;
|
|
38902
38919
|
}
|
|
38903
|
-
|
|
38904
|
-
|
|
38920
|
+
scheduleReplayDrain() {
|
|
38921
|
+
if (this.replayScheduled) return;
|
|
38922
|
+
this.replayScheduled = true;
|
|
38923
|
+
queueMicrotask(() => {
|
|
38924
|
+
this.replayScheduled = false;
|
|
38925
|
+
const drain2 = this.replayBuffer;
|
|
38926
|
+
this.replayBuffer = [];
|
|
38927
|
+
const keep = [];
|
|
38928
|
+
for (const b of drain2) {
|
|
38929
|
+
const hs = this.handlers.get(b.event);
|
|
38930
|
+
if (hs?.size) this.dispatch(b.event, b.payload, hs);
|
|
38931
|
+
else keep.push(b);
|
|
38932
|
+
}
|
|
38933
|
+
this.replayBuffer = keep.concat(this.replayBuffer);
|
|
38934
|
+
});
|
|
38935
|
+
}
|
|
38936
|
+
/** emit()'s containment, shared with replay: a throwing handler must NEVER
|
|
38937
|
+
* propagate or starve the others; async rejections are contained too. */
|
|
38938
|
+
dispatch(event, payload, handlers) {
|
|
38939
|
+
for (const h of handlers) {
|
|
38905
38940
|
try {
|
|
38906
38941
|
const res = h(payload);
|
|
38907
38942
|
if (res instanceof Promise)
|
|
@@ -38911,6 +38946,16 @@ var JefriClient = class _JefriClient {
|
|
|
38911
38946
|
}
|
|
38912
38947
|
}
|
|
38913
38948
|
}
|
|
38949
|
+
emit(event, payload) {
|
|
38950
|
+
if (_JefriClient.REPLAYABLE.has(event) && !this.handlers.get(event)?.size) {
|
|
38951
|
+
if (this.replayBuffer.length >= _JefriClient.REPLAY_MAX) {
|
|
38952
|
+
this.replayBuffer.shift();
|
|
38953
|
+
console.error(`[jefri-sdk] replay buffer overflow for pre-subscription events \u2014 oldest dropped`);
|
|
38954
|
+
}
|
|
38955
|
+
this.replayBuffer.push({ event, payload });
|
|
38956
|
+
}
|
|
38957
|
+
this.dispatch(event, payload, this.handlers.get(event) ?? []);
|
|
38958
|
+
}
|
|
38914
38959
|
send(ev) {
|
|
38915
38960
|
if (!this.ws || this.ws.readyState !== this.ws.OPEN)
|
|
38916
38961
|
throw new Error("not connected to the Jefri Chat hub (reconnecting) \u2014 try again in a moment");
|
|
@@ -40595,8 +40640,8 @@ var psPathArg = (p) => p.startsWith("~/") || p.startsWith("~\\") ? `($HOME + ${p
|
|
|
40595
40640
|
var isTextyFile = (mime, name) => /^text\/|json|xml|javascript|typescript|markdown|csv|ya?ml|x-sh|toml/i.test(mime) || /\.(md|txt|json|csv|ya?ml|log|ts|tsx|js|jsx|py|rb|go|rs|java|c|h|cpp|sh|sql|toml|env|cfg|ini)$/i.test(name);
|
|
40596
40641
|
var DATAURL_INLINE_MAX = 256 * 1024;
|
|
40597
40642
|
var dataUrlTooBig = (dataUrl) => dataUrl.length > DATAURL_INLINE_MAX * 1.4 ? `That dataUrl is ~${MB(dataUrl.length * 0.75)} MB decoded \u2014 far past the ${Math.round(DATAURL_INLINE_MAX / 1024)}KB inline limit. NEVER read a local file and base64 it through the model. Call this tool again with the file's PATH \u2014 you'll get a single-use upload command that sends it in seconds.` : null;
|
|
40598
|
-
async function remoteUploadCommand(c2, ctx, target,
|
|
40599
|
-
const fname = crossBasename(
|
|
40643
|
+
async function remoteUploadCommand(c2, ctx, target, path5, caption, where) {
|
|
40644
|
+
const fname = crossBasename(path5);
|
|
40600
40645
|
const fmime = mimeOf(fname);
|
|
40601
40646
|
const hub = ctx.serverUrl.replace(/\/$/, "");
|
|
40602
40647
|
try {
|
|
@@ -40611,10 +40656,10 @@ async function remoteUploadCommand(c2, ctx, target, path4, caption, where) {
|
|
|
40611
40656
|
return {
|
|
40612
40657
|
ok: `This cloud connector can't read your disk \u2014 but here is the fast path. Run this ONE command in the terminal (it uploads AND delivers the file to ${where}):
|
|
40613
40658
|
|
|
40614
|
-
curl -sS -X POST ${shq(uploadUrl)} -H ${shq(`Content-Type: ${fmime}`)} --data-binary @${shPathArg(
|
|
40659
|
+
curl -sS -X POST ${shq(uploadUrl)} -H ${shq(`Content-Type: ${fmime}`)} --data-binary @${shPathArg(path5)}
|
|
40615
40660
|
|
|
40616
40661
|
Windows PowerShell instead:
|
|
40617
|
-
Invoke-RestMethod -Uri ${psq(uploadUrl)} -Method Post -ContentType ${psq(fmime)} -InFile ${psPathArg(
|
|
40662
|
+
Invoke-RestMethod -Uri ${psq(uploadUrl)} -Method Post -ContentType ${psq(fmime)} -InFile ${psPathArg(path5)}
|
|
40618
40663
|
|
|
40619
40664
|
The link is single-use and expires in ${mins} min; the file name and caption are already attached to it. Run it now, then confirm delivery from its JSON response ({"ok":true,...}). Do NOT read the file and base64 it through dataUrl \u2014 that is minutes of model output and fails over ~3MB.`
|
|
40620
40665
|
};
|
|
@@ -40624,7 +40669,7 @@ The link is single-use and expires in ${mins} min; the file name and caption are
|
|
|
40624
40669
|
const web = hub.replace("acp-hub.", "acp-web.").replace(":4000", ":4321");
|
|
40625
40670
|
const dropLink = target.to ? `${web}/?to=${encodeURIComponent(target.to)}` : `${web}/?group=${encodeURIComponent(target.groupId)}`;
|
|
40626
40671
|
const q = (target.to ? `to=${encodeURIComponent(target.to)}` : `groupId=${encodeURIComponent(target.groupId)}`) + `&fileName=${encodeURIComponent(fname)}` + (caption ? `&caption=${encodeURIComponent(caption)}` : "");
|
|
40627
|
-
const cmd = `curl -s -X POST ${shq(`${hub}/api/files?${q}`)} -H "Authorization: Bearer $JEFRI_TOKEN" -H ${shq(`Content-Type: ${fmime}`)} --data-binary @${shPathArg(
|
|
40672
|
+
const cmd = `curl -s -X POST ${shq(`${hub}/api/files?${q}`)} -H "Authorization: Bearer $JEFRI_TOKEN" -H ${shq(`Content-Type: ${fmime}`)} --data-binary @${shPathArg(path5)}`;
|
|
40628
40673
|
return {
|
|
40629
40674
|
fail: `This is the cloud connector, so it can't read "${fname}" off your computer, and the upload-link service didn't answer. Options:
|
|
40630
40675
|
|
|
@@ -40998,9 +41043,13 @@ ${e?.message ?? e}`);
|
|
|
40998
41043
|
description: "Show this session's Jefri Chat identity (username, display name) and connection status.",
|
|
40999
41044
|
inputSchema: {}
|
|
41000
41045
|
},
|
|
41001
|
-
async () =>
|
|
41002
|
-
|
|
41003
|
-
|
|
41046
|
+
async () => {
|
|
41047
|
+
const unbound = await ctx.identityStatus?.();
|
|
41048
|
+
if (unbound) return ok3(unbound);
|
|
41049
|
+
return withClient(
|
|
41050
|
+
async (c2) => ok3(`You are "${c2.identity.displayName}" (@${c2.identity.username}) on Jefri Chat at ${ctx.serverUrl}, status: online.`)
|
|
41051
|
+
);
|
|
41052
|
+
}
|
|
41004
41053
|
);
|
|
41005
41054
|
server2.registerTool(
|
|
41006
41055
|
"jefri_agents",
|
|
@@ -41026,8 +41075,8 @@ ${e?.message ?? e}`);
|
|
|
41026
41075
|
})
|
|
41027
41076
|
);
|
|
41028
41077
|
const NOT_OWNER = "This only works when you run the Jefri connector as your OWN (human) account and own this agent. An agent session can't manage agents \u2014 use the web app or an owner session.";
|
|
41029
|
-
const ownerApi = async (c2,
|
|
41030
|
-
const res = await fetch(`${ctx.serverUrl}${
|
|
41078
|
+
const ownerApi = async (c2, path5, method) => {
|
|
41079
|
+
const res = await fetch(`${ctx.serverUrl}${path5}`, {
|
|
41031
41080
|
method,
|
|
41032
41081
|
headers: { Authorization: `Bearer ${c2.token}` }
|
|
41033
41082
|
});
|
|
@@ -41159,18 +41208,18 @@ ${fp}`);
|
|
|
41159
41208
|
caption: external_exports.string().optional().describe("optional private E2E caption sent as a separate encrypted message")
|
|
41160
41209
|
}
|
|
41161
41210
|
},
|
|
41162
|
-
async ({ to, path:
|
|
41211
|
+
async ({ to, path: path5, dataUrl, fileName, caption }) => withClient(async (c2) => {
|
|
41163
41212
|
let name, mime, url;
|
|
41164
|
-
if (
|
|
41213
|
+
if (path5) {
|
|
41165
41214
|
if (!ctx.local) {
|
|
41166
41215
|
return fail(
|
|
41167
41216
|
`I can't read local files from the cloud connector. Use a local/stdin Jefri Chat connector, or open the web chat and drop the file there.`
|
|
41168
41217
|
);
|
|
41169
41218
|
}
|
|
41170
|
-
const abs = resolveLocalFile(
|
|
41219
|
+
const abs = resolveLocalFile(path5);
|
|
41171
41220
|
if (!abs) {
|
|
41172
41221
|
return fail(
|
|
41173
|
-
`I can't find a file at ${
|
|
41222
|
+
`I can't find a file at ${path5} on this machine.`
|
|
41174
41223
|
);
|
|
41175
41224
|
}
|
|
41176
41225
|
const buf = fs5.readFileSync(abs);
|
|
@@ -41222,18 +41271,18 @@ ${fp}`);
|
|
|
41222
41271
|
caption: external_exports.string().optional().describe("optional private E2E group caption sent separately")
|
|
41223
41272
|
}
|
|
41224
41273
|
},
|
|
41225
|
-
async ({ groupId, path:
|
|
41274
|
+
async ({ groupId, path: path5, dataUrl, fileName, caption }) => withClient(async (c2) => {
|
|
41226
41275
|
let name, mime, url;
|
|
41227
|
-
if (
|
|
41276
|
+
if (path5) {
|
|
41228
41277
|
if (!ctx.local) {
|
|
41229
41278
|
return fail(
|
|
41230
41279
|
`I can't read local files from the cloud connector. Use a local/stdin Jefri Chat connector, or open the web chat and drop the file there.`
|
|
41231
41280
|
);
|
|
41232
41281
|
}
|
|
41233
|
-
const abs = resolveLocalFile(
|
|
41282
|
+
const abs = resolveLocalFile(path5);
|
|
41234
41283
|
if (!abs) {
|
|
41235
41284
|
return fail(
|
|
41236
|
-
`I can't find a file at ${
|
|
41285
|
+
`I can't find a file at ${path5} on this machine.`
|
|
41237
41286
|
);
|
|
41238
41287
|
}
|
|
41239
41288
|
const buf = fs5.readFileSync(abs);
|
|
@@ -41266,16 +41315,16 @@ ${fp}`);
|
|
|
41266
41315
|
fileName: external_exports.string().optional().describe("file name (used with dataUrl, or to name a fileUrl download)")
|
|
41267
41316
|
}
|
|
41268
41317
|
},
|
|
41269
|
-
async ({ to, path:
|
|
41318
|
+
async ({ to, path: path5, caption, dataUrl, fileUrl, fileName }, extra) => withClient(async (c2) => {
|
|
41270
41319
|
let name, mime, url;
|
|
41271
|
-
if (
|
|
41320
|
+
if (path5) {
|
|
41272
41321
|
if (!ctx.local) {
|
|
41273
|
-
const r = await remoteUploadCommand(c2, ctx, { to },
|
|
41322
|
+
const r = await remoteUploadCommand(c2, ctx, { to }, path5, caption, `@${to}`);
|
|
41274
41323
|
return "ok" in r ? ok3(r.ok) : fail(r.fail);
|
|
41275
41324
|
}
|
|
41276
|
-
const abs = resolveLocalFile(
|
|
41325
|
+
const abs = resolveLocalFile(path5);
|
|
41277
41326
|
if (!abs) {
|
|
41278
|
-
return fail(`I can't find a file at ${
|
|
41327
|
+
return fail(`I can't find a file at ${path5} on this machine.`);
|
|
41279
41328
|
}
|
|
41280
41329
|
const buf = fs5.readFileSync(abs);
|
|
41281
41330
|
name = np4.basename(abs);
|
|
@@ -41393,12 +41442,12 @@ ${fp}`);
|
|
|
41393
41442
|
caption: external_exports.string().optional().describe("optional text caption to send with it")
|
|
41394
41443
|
}
|
|
41395
41444
|
},
|
|
41396
|
-
async ({ to, path:
|
|
41445
|
+
async ({ to, path: path5, caption }) => withClient(async (c2) => {
|
|
41397
41446
|
if (!ctx.local)
|
|
41398
41447
|
return fail(
|
|
41399
41448
|
`Sending a folder reads files off disk, which this connector can't do \u2014 it runs in the cloud. Run jefri_send_folder from a LOCAL agent (Claude Code with the stdio connector) inside the project.`
|
|
41400
41449
|
);
|
|
41401
|
-
const rawDir =
|
|
41450
|
+
const rawDir = path5 && path5.trim() ? path5 : process.cwd();
|
|
41402
41451
|
const dir = rawDir.startsWith("~") ? np4.join(os4.homedir(), rawDir.slice(1)) : rawDir;
|
|
41403
41452
|
if (!fs5.existsSync(dir))
|
|
41404
41453
|
return fail(
|
|
@@ -41675,11 +41724,11 @@ ${cmd}
|
|
|
41675
41724
|
kind: external_exports.enum(["file", "contract", "skill", "note"]).optional()
|
|
41676
41725
|
}
|
|
41677
41726
|
},
|
|
41678
|
-
async ({ departmentId, path:
|
|
41679
|
-
const fname = np4.basename(
|
|
41727
|
+
async ({ departmentId, path: path5, kind }) => withClient(async (c2) => {
|
|
41728
|
+
const fname = np4.basename(path5.startsWith("~") ? path5.slice(1) : path5);
|
|
41680
41729
|
const mime = mimeOf(fname);
|
|
41681
41730
|
const q = `name=${encodeURIComponent(fname)}&kind=${kind ?? "file"}`;
|
|
41682
|
-
const cmd = `curl -s -X POST ${shq(`${hubBase()}/api/departments/${departmentId}/files?${q}`)} -H "Authorization: Bearer $JEFRI_TOKEN" -H "Content-Type: ${mime}" --data-binary @${shq(
|
|
41731
|
+
const cmd = `curl -s -X POST ${shq(`${hubBase()}/api/departments/${departmentId}/files?${q}`)} -H "Authorization: Bearer $JEFRI_TOKEN" -H "Content-Type: ${mime}" --data-binary @${shq(path5)}`;
|
|
41683
41732
|
return ok3(`To add ${fname} to the department, run in the terminal:
|
|
41684
41733
|
|
|
41685
41734
|
${cmd}
|
|
@@ -41761,10 +41810,10 @@ ${cmd}
|
|
|
41761
41810
|
description: "Add a local file to this agent's document memory. Memory is CURATED BY THE OWNER, so this returns a command that uses the OWNER token (the agent token can't add docs) \u2014 or the owner can upload it from the agent's page in the web app. Documents follow the agent's visibility (public \u2192 owner's connections can read; shared \u2192 chosen people; private \u2192 owner only). Warn before uploading anything sensitive to a public agent.",
|
|
41762
41811
|
inputSchema: { path: external_exports.string().describe("local file path to upload") }
|
|
41763
41812
|
},
|
|
41764
|
-
async ({ path:
|
|
41765
|
-
const fname = np4.basename(
|
|
41813
|
+
async ({ path: path5 }) => withClient(async (c2) => {
|
|
41814
|
+
const fname = np4.basename(path5.startsWith("~") ? path5.slice(1) : path5);
|
|
41766
41815
|
const mime = mimeOf(fname);
|
|
41767
|
-
const cmd = `curl -s -X POST ${shq(`${hubBase()}/api/agents/${encodeURIComponent(c2.identity.username)}/docs?name=${encodeURIComponent(fname)}`)} -H "Authorization: Bearer $JEFRI_OWNER_TOKEN" -H "Content-Type: ${mime}" --data-binary @${shq(
|
|
41816
|
+
const cmd = `curl -s -X POST ${shq(`${hubBase()}/api/agents/${encodeURIComponent(c2.identity.username)}/docs?name=${encodeURIComponent(fname)}`)} -H "Authorization: Bearer $JEFRI_OWNER_TOKEN" -H "Content-Type: ${mime}" --data-binary @${shq(path5)}`;
|
|
41768
41817
|
return ok3(
|
|
41769
41818
|
`Adding to agent memory is OWNER-only, so this uses your owner token. Set it first (export JEFRI_OWNER_TOKEN=\u2026 from the app), then run:
|
|
41770
41819
|
|
|
@@ -41942,15 +41991,15 @@ Or just upload ${fname} from the agent's page in the web app.`
|
|
|
41942
41991
|
fileName: external_exports.string().optional().describe("file name (used with dataUrl, or to name a fileUrl download)")
|
|
41943
41992
|
}
|
|
41944
41993
|
},
|
|
41945
|
-
async ({ groupId, path:
|
|
41994
|
+
async ({ groupId, path: path5, caption, dataUrl, fileUrl, fileName }, extra) => withClient(async (c2) => {
|
|
41946
41995
|
let name, mime, url;
|
|
41947
|
-
if (
|
|
41996
|
+
if (path5) {
|
|
41948
41997
|
if (!ctx.local) {
|
|
41949
|
-
const r = await remoteUploadCommand(c2, ctx, { groupId },
|
|
41998
|
+
const r = await remoteUploadCommand(c2, ctx, { groupId }, path5, caption, "the group");
|
|
41950
41999
|
return "ok" in r ? ok3(r.ok) : fail(r.fail);
|
|
41951
42000
|
}
|
|
41952
|
-
const abs = resolveLocalFile(
|
|
41953
|
-
if (!abs) return fail(`I can't find a file at ${
|
|
42001
|
+
const abs = resolveLocalFile(path5);
|
|
42002
|
+
if (!abs) return fail(`I can't find a file at ${path5} on this machine.`);
|
|
41954
42003
|
const buf = fs5.readFileSync(abs);
|
|
41955
42004
|
name = np4.basename(abs);
|
|
41956
42005
|
mime = mimeOf(name);
|
|
@@ -42883,10 +42932,147 @@ async function runDoctor() {
|
|
|
42883
42932
|
process.exitCode = problems ? 1 : 0;
|
|
42884
42933
|
}
|
|
42885
42934
|
|
|
42935
|
+
// src/identity.ts
|
|
42936
|
+
import fs11 from "node:fs";
|
|
42937
|
+
import os7 from "node:os";
|
|
42938
|
+
import path3 from "node:path";
|
|
42939
|
+
var IDENTITIES_FILE = path3.join(os7.homedir(), ".jefri", "identities.json");
|
|
42940
|
+
function loadIdentitiesFile(filePath = IDENTITIES_FILE) {
|
|
42941
|
+
let raw;
|
|
42942
|
+
let mode = null;
|
|
42943
|
+
try {
|
|
42944
|
+
const st = fs11.statSync(filePath);
|
|
42945
|
+
mode = st.mode & 511;
|
|
42946
|
+
raw = fs11.readFileSync(filePath, "utf8");
|
|
42947
|
+
} catch (e) {
|
|
42948
|
+
if (e?.code === "ENOENT") return null;
|
|
42949
|
+
const err = new Error(`cannot read ${filePath}: ${e?.message ?? e}`);
|
|
42950
|
+
err.code = "JEFRI_IDENTITIES_UNREADABLE";
|
|
42951
|
+
throw err;
|
|
42952
|
+
}
|
|
42953
|
+
if (process.platform !== "win32" && mode !== null && (mode & 63) !== 0) {
|
|
42954
|
+
const err = new Error(
|
|
42955
|
+
`${filePath} is readable by others (mode ${mode.toString(8)}) \u2014 it holds agent tokens. Run: chmod 600 ${filePath}`
|
|
42956
|
+
);
|
|
42957
|
+
err.code = "JEFRI_IDENTITIES_PERM";
|
|
42958
|
+
throw err;
|
|
42959
|
+
}
|
|
42960
|
+
const parsed = JSON.parse(raw);
|
|
42961
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
42962
|
+
return parsed;
|
|
42963
|
+
}
|
|
42964
|
+
var expandHome = (p) => p === "~" ? os7.homedir() : p.startsWith("~/") || p.startsWith("~\\") ? path3.join(os7.homedir(), p.slice(2)) : p;
|
|
42965
|
+
var nativeRealpath = (p) => (fs11.realpathSync.native ?? fs11.realpathSync)(p);
|
|
42966
|
+
function canonical(p, realpath) {
|
|
42967
|
+
let r = path3.resolve(expandHome(p));
|
|
42968
|
+
try {
|
|
42969
|
+
r = realpath(r);
|
|
42970
|
+
} catch {
|
|
42971
|
+
}
|
|
42972
|
+
return r;
|
|
42973
|
+
}
|
|
42974
|
+
function matchDirRule(byDir, cwd, realpath = nativeRealpath) {
|
|
42975
|
+
if (!byDir) return null;
|
|
42976
|
+
const c2 = canonical(cwd, realpath);
|
|
42977
|
+
let best = null;
|
|
42978
|
+
for (const [rulePath, value] of Object.entries(byDir)) {
|
|
42979
|
+
const r = canonical(rulePath, realpath);
|
|
42980
|
+
const isMatch = c2 === r || c2.startsWith(r) && (r.endsWith(path3.sep) || c2[r.length] === path3.sep);
|
|
42981
|
+
if (isMatch && (!best || r.length > best.len)) best = { len: r.length, value };
|
|
42982
|
+
}
|
|
42983
|
+
return best?.value ?? null;
|
|
42984
|
+
}
|
|
42985
|
+
function selectableNames(file) {
|
|
42986
|
+
return Object.entries(file?.identities ?? {}).filter(([, p]) => p?.modelSelectable === true).map(([name]) => name).sort();
|
|
42987
|
+
}
|
|
42988
|
+
var POOL_AUTO = "pool:auto";
|
|
42989
|
+
function resolveIdentity(opts = {}) {
|
|
42990
|
+
const env = opts.env ?? process.env;
|
|
42991
|
+
if (env.JEFRI_TOKEN) return { kind: "legacy-token", token: env.JEFRI_TOKEN };
|
|
42992
|
+
if (env.JEFRI_AS) return { kind: "legacy-as", username: env.JEFRI_AS };
|
|
42993
|
+
const file = opts.file ?? null;
|
|
42994
|
+
const identities = file?.identities ?? {};
|
|
42995
|
+
const selectable = () => selectableNames(file);
|
|
42996
|
+
const toProfile = (name, source) => {
|
|
42997
|
+
if (name === POOL_AUTO)
|
|
42998
|
+
return {
|
|
42999
|
+
kind: "error",
|
|
43000
|
+
message: "this rule resolves to pool:auto, but pool assignment is not available yet (SESSIONS-PLAN Phase 3) \u2014 name a specific identity instead"
|
|
43001
|
+
};
|
|
43002
|
+
const p = identities[name];
|
|
43003
|
+
if (!p?.token)
|
|
43004
|
+
return {
|
|
43005
|
+
kind: "error",
|
|
43006
|
+
message: `identity "${name}" (from the ${source} rule) is not defined in ~/.jefri/identities.json \u2014 add it, or fix the rule`
|
|
43007
|
+
};
|
|
43008
|
+
return { kind: "profile", name, token: p.token, source };
|
|
43009
|
+
};
|
|
43010
|
+
if (env.JEFRI_PROFILE) return toProfile(env.JEFRI_PROFILE, "env");
|
|
43011
|
+
const rules = file?.rules ?? {};
|
|
43012
|
+
const dirHit = matchDirRule(rules.byDir, opts.cwd ?? process.cwd(), opts.realpath);
|
|
43013
|
+
if (dirHit) return toProfile(dirHit, "dir");
|
|
43014
|
+
if (opts.clientName && rules.byClient) {
|
|
43015
|
+
const wanted = opts.clientName.trim().toLowerCase();
|
|
43016
|
+
for (const [client, name] of Object.entries(rules.byClient)) {
|
|
43017
|
+
if (client.trim().toLowerCase() === wanted) return toProfile(name, "client");
|
|
43018
|
+
}
|
|
43019
|
+
}
|
|
43020
|
+
if (rules.default) return toProfile(rules.default, "default");
|
|
43021
|
+
return { kind: "unassigned", selectable: selectable() };
|
|
43022
|
+
}
|
|
43023
|
+
function profileForBe(file, name) {
|
|
43024
|
+
const p = file?.identities?.[name];
|
|
43025
|
+
if (!p?.token)
|
|
43026
|
+
return { ok: false, message: `no identity named "${name}" in ~/.jefri/identities.json` };
|
|
43027
|
+
if (p.modelSelectable !== true)
|
|
43028
|
+
return {
|
|
43029
|
+
ok: false,
|
|
43030
|
+
message: `identity "${name}" is not marked modelSelectable \u2014 a human must set "modelSelectable": true in ~/.jefri/identities.json before the model may choose it`
|
|
43031
|
+
};
|
|
43032
|
+
return { ok: true, name, token: p.token };
|
|
43033
|
+
}
|
|
43034
|
+
|
|
43035
|
+
// src/bind.ts
|
|
43036
|
+
function createBinder() {
|
|
43037
|
+
let state = { phase: "unassigned" };
|
|
43038
|
+
let inflight = null;
|
|
43039
|
+
return {
|
|
43040
|
+
state: () => state,
|
|
43041
|
+
current: () => inflight,
|
|
43042
|
+
bind(profile, connect) {
|
|
43043
|
+
if (state.phase === "bound") {
|
|
43044
|
+
return state.profile === profile ? inflight : Promise.reject(
|
|
43045
|
+
new Error(`already bound as "${state.profile}" \u2014 one identity per session; restart the session to re-identify`)
|
|
43046
|
+
);
|
|
43047
|
+
}
|
|
43048
|
+
if (state.phase === "binding") {
|
|
43049
|
+
return state.profile === profile ? inflight : Promise.reject(
|
|
43050
|
+
new Error(`a bind to "${state.profile}" is already in flight \u2014 await it (it may still fail and free the session)`)
|
|
43051
|
+
);
|
|
43052
|
+
}
|
|
43053
|
+
state = { phase: "binding", profile };
|
|
43054
|
+
const p = connect().then(
|
|
43055
|
+
(c2) => {
|
|
43056
|
+
state = { phase: "bound", profile };
|
|
43057
|
+
return c2;
|
|
43058
|
+
},
|
|
43059
|
+
(e) => {
|
|
43060
|
+
state = { phase: "unassigned", lastError: String(e?.message ?? e) };
|
|
43061
|
+
if (inflight === p) inflight = null;
|
|
43062
|
+
throw e;
|
|
43063
|
+
}
|
|
43064
|
+
);
|
|
43065
|
+
inflight = p;
|
|
43066
|
+
return p;
|
|
43067
|
+
}
|
|
43068
|
+
};
|
|
43069
|
+
}
|
|
43070
|
+
|
|
42886
43071
|
// src/index.ts
|
|
42887
43072
|
init_version();
|
|
42888
43073
|
var SERVER = process.env.JEFRI_SERVER ?? "http://localhost:4000";
|
|
42889
43074
|
var TOKEN = process.env.JEFRI_TOKEN;
|
|
43075
|
+
var LEGACY_MODE = !!(process.env.JEFRI_TOKEN || process.env.JEFRI_AS);
|
|
42890
43076
|
var USERNAME = process.env.JEFRI_AS ?? "claude_agent";
|
|
42891
43077
|
var NAME = process.env.JEFRI_NAME ?? USERNAME;
|
|
42892
43078
|
var TAGS = (process.env.JEFRI_TAGS ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -42896,15 +43082,15 @@ var VERBOSE = process.env.JEFRI_DEBUG === "1" || process.env.JEFRI_VERBOSE === "
|
|
|
42896
43082
|
var note = (...a) => {
|
|
42897
43083
|
if (VERBOSE) log(...a);
|
|
42898
43084
|
};
|
|
42899
|
-
var TOKEN_CACHE =
|
|
42900
|
-
var LEGACY_TOKEN_CACHE =
|
|
43085
|
+
var TOKEN_CACHE = path4.join(os8.homedir(), ".jefri", "tokens.json");
|
|
43086
|
+
var LEGACY_TOKEN_CACHE = path4.join(os8.homedir(), ".acp", "tokens.json");
|
|
42901
43087
|
var cacheKey = `${SERVER}::${USERNAME}`;
|
|
42902
43088
|
function readTokenCache() {
|
|
42903
43089
|
try {
|
|
42904
|
-
return JSON.parse(
|
|
43090
|
+
return JSON.parse(fs12.readFileSync(TOKEN_CACHE, "utf8"));
|
|
42905
43091
|
} catch {
|
|
42906
43092
|
try {
|
|
42907
|
-
return JSON.parse(
|
|
43093
|
+
return JSON.parse(fs12.readFileSync(LEGACY_TOKEN_CACHE, "utf8"));
|
|
42908
43094
|
} catch {
|
|
42909
43095
|
return {};
|
|
42910
43096
|
}
|
|
@@ -42913,22 +43099,44 @@ function readTokenCache() {
|
|
|
42913
43099
|
function cacheToken(tok) {
|
|
42914
43100
|
const c2 = readTokenCache();
|
|
42915
43101
|
c2[cacheKey] = tok;
|
|
42916
|
-
|
|
42917
|
-
|
|
43102
|
+
fs12.mkdirSync(path4.dirname(TOKEN_CACHE), { recursive: true });
|
|
43103
|
+
fs12.writeFileSync(TOKEN_CACHE, JSON.stringify(c2, null, 2));
|
|
42918
43104
|
}
|
|
42919
43105
|
var clientPromise = null;
|
|
42920
43106
|
var inbox = [];
|
|
42921
43107
|
var control = null;
|
|
42922
43108
|
var detectedHost = null;
|
|
43109
|
+
var binder = createBinder();
|
|
43110
|
+
var resolution = null;
|
|
43111
|
+
var resolvedProfileToken = null;
|
|
43112
|
+
var knownSelectable = [];
|
|
43113
|
+
var unassignedGuidance = () => {
|
|
43114
|
+
const sel = knownSelectable.length ? `Model-selectable identities: ${knownSelectable.join(", ")} \u2014 call jefri_be("<name>") to become one.` : `No model-selectable identities are configured yet.`;
|
|
43115
|
+
const st = binder.state();
|
|
43116
|
+
const failed = st.phase === "unassigned" && st.lastError ? `
|
|
43117
|
+
Last bind attempt failed: ${st.lastError}` : "";
|
|
43118
|
+
return `This session has NO Jefri identity assigned. Three ways to get one:
|
|
43119
|
+
1. ${sel}
|
|
43120
|
+
2. Launch with JEFRI_PROFILE=<name> (or JEFRI_AS=<agent> / JEFRI_TOKEN=\u2026).
|
|
43121
|
+
3. Add a byDir/byClient/default rule to ~/.jefri/identities.json.` + failed;
|
|
43122
|
+
};
|
|
43123
|
+
var ensureClientGated = () => {
|
|
43124
|
+
if (LEGACY_MODE) return ensureClient();
|
|
43125
|
+
const cur = binder.current();
|
|
43126
|
+
if (cur) return cur;
|
|
43127
|
+
if (resolution?.kind === "error") return Promise.reject(new Error(resolution.message));
|
|
43128
|
+
return Promise.reject(new Error(unassignedGuidance()));
|
|
43129
|
+
};
|
|
42923
43130
|
function ensureClient() {
|
|
42924
43131
|
if (!clientPromise) {
|
|
42925
43132
|
clientPromise = (async () => {
|
|
42926
|
-
const known = TOKEN ?? readTokenCache()[cacheKey];
|
|
43133
|
+
const known = LEGACY_MODE ? TOKEN ?? readTokenCache()[cacheKey] : resolvedProfileToken;
|
|
43134
|
+
if (!LEGACY_MODE && !known) throw new Error(unassignedGuidance());
|
|
42927
43135
|
const clientInfo = {
|
|
42928
43136
|
name: "jefrichat-mcp",
|
|
42929
43137
|
version: connectorVersion(),
|
|
42930
43138
|
cwd: process.cwd(),
|
|
42931
|
-
machine:
|
|
43139
|
+
machine: os8.hostname()
|
|
42932
43140
|
};
|
|
42933
43141
|
const provision = () => JefriClient.connect({
|
|
42934
43142
|
server: SERVER,
|
|
@@ -42946,10 +43154,19 @@ function ensureClient() {
|
|
|
42946
43154
|
c2 = await JefriClient.connect({ server: SERVER, token: known, status: "online", clientInfo });
|
|
42947
43155
|
if (!c2.identity) {
|
|
42948
43156
|
if (TOKEN) throw new Error("JEFRI_TOKEN was rejected by the server");
|
|
43157
|
+
if (!LEGACY_MODE) throw new Error("this profile's token was rejected by the server \u2014 fix it in ~/.jefri/identities.json");
|
|
42949
43158
|
log("cached token rejected, re-provisioning by username");
|
|
42950
43159
|
c2 = await provision();
|
|
42951
43160
|
if (c2.identity) cacheToken(c2.token);
|
|
42952
43161
|
}
|
|
43162
|
+
if (!LEGACY_MODE && c2.identity && c2.identity.type !== "agent") {
|
|
43163
|
+
try {
|
|
43164
|
+
c2.close?.();
|
|
43165
|
+
c2.disconnect?.();
|
|
43166
|
+
} catch {
|
|
43167
|
+
}
|
|
43168
|
+
throw new Error(`the token for this profile resolves to a ${c2.identity.type} identity (@${c2.identity.username}) \u2014 sessions may only bind AGENT identities`);
|
|
43169
|
+
}
|
|
42953
43170
|
} else {
|
|
42954
43171
|
c2 = await provision();
|
|
42955
43172
|
if (c2.identity) cacheToken(c2.token);
|
|
@@ -42991,78 +43208,6 @@ function ensureClient() {
|
|
|
42991
43208
|
}, 3e3);
|
|
42992
43209
|
});
|
|
42993
43210
|
configureAuto({ self, owner: c2.identity?.owner, getHistory });
|
|
42994
|
-
const sendAs = (t, text5) => {
|
|
42995
|
-
if (t.groupId) c2.groupMessage(t.groupId, text5);
|
|
42996
|
-
else if (t.to) c2.message(t.to, text5);
|
|
42997
|
-
};
|
|
42998
|
-
try {
|
|
42999
|
-
control = await startControlServer({
|
|
43000
|
-
self,
|
|
43001
|
-
displayName: c2.identity?.displayName ?? self,
|
|
43002
|
-
owner: c2.identity?.owner,
|
|
43003
|
-
server: SERVER,
|
|
43004
|
-
workdir: getAuto().workdir,
|
|
43005
|
-
cwd: process.cwd(),
|
|
43006
|
-
machine: os7.hostname(),
|
|
43007
|
-
getHost: () => detectedHost,
|
|
43008
|
-
isOnline: () => c2.online,
|
|
43009
|
-
isAutonomous: () => getAuto().enabled,
|
|
43010
|
-
getBrain: () => {
|
|
43011
|
-
const b = brainStatus();
|
|
43012
|
-
return { label: b.resolved, ready: b.ready };
|
|
43013
|
-
},
|
|
43014
|
-
sendText: sendAs,
|
|
43015
|
-
// Read a conversation from the HUB, over the socket this process
|
|
43016
|
-
// already holds. The panel's own inbox only has what arrived while
|
|
43017
|
-
// this connector was running, so a freshly-started UI shows nothing
|
|
43018
|
-
// for a conversation that has been going for weeks — which reads as
|
|
43019
|
-
// broken rather than as empty.
|
|
43020
|
-
fetchHistory: async ({ with: other, groupId, limit }) => {
|
|
43021
|
-
const convId = groupId ? groupConversationId(groupId) : dmConversationId(self, other);
|
|
43022
|
-
const p = waitFor(c2, "history", (e) => e.conversationId === convId);
|
|
43023
|
-
c2.history(convId);
|
|
43024
|
-
const res = await p;
|
|
43025
|
-
const raw = (res?.messages ?? []).slice(-limit);
|
|
43026
|
-
return raw.map((m) => ({
|
|
43027
|
-
id: String(m.id ?? ""),
|
|
43028
|
-
// Raw username for routing, sanitized copy for display — the same
|
|
43029
|
-
// split the panel makes, for the same reason: a name can carry a
|
|
43030
|
-
// bidi override that would reorder the line around it.
|
|
43031
|
-
from: String(m.senderUsername ?? ""),
|
|
43032
|
-
fromDisplay: sanitizeForDisplay(String(m.senderUsername ?? "")),
|
|
43033
|
-
text: m.kind === "file" ? `\u{1F4CE} ${m.fileName ?? "file"}` : String(m.content ?? ""),
|
|
43034
|
-
at: String(m.createdAt ?? ""),
|
|
43035
|
-
mine: String(m.senderUsername ?? "") === self,
|
|
43036
|
-
kind: m.kind === "file" ? "file" : "text",
|
|
43037
|
-
fileName: m.fileName ?? null
|
|
43038
|
-
}));
|
|
43039
|
-
},
|
|
43040
|
-
handleWithBrain: async (m) => {
|
|
43041
|
-
const text5 = await runOnceForMessage({
|
|
43042
|
-
content: m.text,
|
|
43043
|
-
sender: m.from,
|
|
43044
|
-
isGroup: !!m.groupId,
|
|
43045
|
-
groupId: m.groupId,
|
|
43046
|
-
conversationId: m.conversationId,
|
|
43047
|
-
isMention: m.isMention,
|
|
43048
|
-
senderIsBot: agentUsers.has(m.from)
|
|
43049
|
-
});
|
|
43050
|
-
sendAs(m.groupId ? { groupId: m.groupId } : { to: m.from }, text5);
|
|
43051
|
-
return { ok: true, reply: text5 };
|
|
43052
|
-
},
|
|
43053
|
-
log
|
|
43054
|
-
});
|
|
43055
|
-
const popupCommand = `${selfCommand()} popup --agent ${shQuote(self)}`;
|
|
43056
|
-
setClickCommand(popupCommand);
|
|
43057
|
-
const app = installMacApp(`${selfCommand()} popup`, connectorVersion());
|
|
43058
|
-
if (app) log(`Jefri Chat.app ready at ${app} \u2014 drag it to your Dock to read and reply`);
|
|
43059
|
-
} catch (e) {
|
|
43060
|
-
log("control socket unavailable (panel disabled):", e?.message ?? e);
|
|
43061
|
-
}
|
|
43062
|
-
if (macNeedsTerminalNotifier)
|
|
43063
|
-
note("tip: for reliable desktop notifications on macOS, run: brew install terminal-notifier");
|
|
43064
|
-
if (getAuto().enabled)
|
|
43065
|
-
note(`autonomous mode is ON \u2014 incoming messages will be handled by "${getAuto().brain}" in ${getAuto().workdir}`);
|
|
43066
43211
|
const handleIncoming = (m) => {
|
|
43067
43212
|
const preview = m.kind === "file" ? `\u{1F4CE} ${m.fileName ?? "file"}` : m.content ?? "";
|
|
43068
43213
|
const isGroup = !!m.groupId;
|
|
@@ -43157,6 +43302,78 @@ function ensureClient() {
|
|
|
43157
43302
|
);
|
|
43158
43303
|
});
|
|
43159
43304
|
c2.on("debate_cancel", (ev) => cancelDebate(String(ev?.debateId ?? ""), Number(ev?.turnSeq ?? -999)));
|
|
43305
|
+
const sendAs = (t, text5) => {
|
|
43306
|
+
if (t.groupId) c2.groupMessage(t.groupId, text5);
|
|
43307
|
+
else if (t.to) c2.message(t.to, text5);
|
|
43308
|
+
};
|
|
43309
|
+
try {
|
|
43310
|
+
control = await startControlServer({
|
|
43311
|
+
self,
|
|
43312
|
+
displayName: c2.identity?.displayName ?? self,
|
|
43313
|
+
owner: c2.identity?.owner,
|
|
43314
|
+
server: SERVER,
|
|
43315
|
+
workdir: getAuto().workdir,
|
|
43316
|
+
cwd: process.cwd(),
|
|
43317
|
+
machine: os8.hostname(),
|
|
43318
|
+
getHost: () => detectedHost,
|
|
43319
|
+
isOnline: () => c2.online,
|
|
43320
|
+
isAutonomous: () => getAuto().enabled,
|
|
43321
|
+
getBrain: () => {
|
|
43322
|
+
const b = brainStatus();
|
|
43323
|
+
return { label: b.resolved, ready: b.ready };
|
|
43324
|
+
},
|
|
43325
|
+
sendText: sendAs,
|
|
43326
|
+
// Read a conversation from the HUB, over the socket this process
|
|
43327
|
+
// already holds. The panel's own inbox only has what arrived while
|
|
43328
|
+
// this connector was running, so a freshly-started UI shows nothing
|
|
43329
|
+
// for a conversation that has been going for weeks — which reads as
|
|
43330
|
+
// broken rather than as empty.
|
|
43331
|
+
fetchHistory: async ({ with: other, groupId, limit }) => {
|
|
43332
|
+
const convId = groupId ? groupConversationId(groupId) : dmConversationId(self, other);
|
|
43333
|
+
const p = waitFor(c2, "history", (e) => e.conversationId === convId);
|
|
43334
|
+
c2.history(convId);
|
|
43335
|
+
const res = await p;
|
|
43336
|
+
const raw = (res?.messages ?? []).slice(-limit);
|
|
43337
|
+
return raw.map((m) => ({
|
|
43338
|
+
id: String(m.id ?? ""),
|
|
43339
|
+
// Raw username for routing, sanitized copy for display — the same
|
|
43340
|
+
// split the panel makes, for the same reason: a name can carry a
|
|
43341
|
+
// bidi override that would reorder the line around it.
|
|
43342
|
+
from: String(m.senderUsername ?? ""),
|
|
43343
|
+
fromDisplay: sanitizeForDisplay(String(m.senderUsername ?? "")),
|
|
43344
|
+
text: m.kind === "file" ? `\u{1F4CE} ${m.fileName ?? "file"}` : String(m.content ?? ""),
|
|
43345
|
+
at: String(m.createdAt ?? ""),
|
|
43346
|
+
mine: String(m.senderUsername ?? "") === self,
|
|
43347
|
+
kind: m.kind === "file" ? "file" : "text",
|
|
43348
|
+
fileName: m.fileName ?? null
|
|
43349
|
+
}));
|
|
43350
|
+
},
|
|
43351
|
+
handleWithBrain: async (m) => {
|
|
43352
|
+
const text5 = await runOnceForMessage({
|
|
43353
|
+
content: m.text,
|
|
43354
|
+
sender: m.from,
|
|
43355
|
+
isGroup: !!m.groupId,
|
|
43356
|
+
groupId: m.groupId,
|
|
43357
|
+
conversationId: m.conversationId,
|
|
43358
|
+
isMention: m.isMention,
|
|
43359
|
+
senderIsBot: agentUsers.has(m.from)
|
|
43360
|
+
});
|
|
43361
|
+
sendAs(m.groupId ? { groupId: m.groupId } : { to: m.from }, text5);
|
|
43362
|
+
return { ok: true, reply: text5 };
|
|
43363
|
+
},
|
|
43364
|
+
log
|
|
43365
|
+
});
|
|
43366
|
+
const popupCommand = `${selfCommand()} popup --agent ${shQuote(self)}`;
|
|
43367
|
+
setClickCommand(popupCommand);
|
|
43368
|
+
const app = installMacApp(`${selfCommand()} popup`, connectorVersion());
|
|
43369
|
+
if (app) log(`Jefri Chat.app ready at ${app} \u2014 drag it to your Dock to read and reply`);
|
|
43370
|
+
} catch (e) {
|
|
43371
|
+
log("control socket unavailable (panel disabled):", e?.message ?? e);
|
|
43372
|
+
}
|
|
43373
|
+
if (macNeedsTerminalNotifier)
|
|
43374
|
+
note("tip: for reliable desktop notifications on macOS, run: brew install terminal-notifier");
|
|
43375
|
+
if (getAuto().enabled)
|
|
43376
|
+
note(`autonomous mode is ON \u2014 incoming messages will be handled by "${getAuto().brain}" in ${getAuto().workdir}`);
|
|
43160
43377
|
note(`connected to ${SERVER} as ${self}`);
|
|
43161
43378
|
return c2;
|
|
43162
43379
|
})().catch((e) => {
|
|
@@ -43167,7 +43384,75 @@ function ensureClient() {
|
|
|
43167
43384
|
return clientPromise;
|
|
43168
43385
|
}
|
|
43169
43386
|
var server = new McpServer({ name: "jefrichat", version: "0.1.0" });
|
|
43170
|
-
registerAcpTools(server, {
|
|
43387
|
+
registerAcpTools(server, {
|
|
43388
|
+
ensureClient: ensureClientGated,
|
|
43389
|
+
inbox,
|
|
43390
|
+
serverUrl: SERVER,
|
|
43391
|
+
local: true,
|
|
43392
|
+
// jefri_whoami in an UNBOUND new-mode session answers from here — listing
|
|
43393
|
+
// ONLY the model-selectable aliases (never tokens, paths, or rule details).
|
|
43394
|
+
identityStatus: async () => {
|
|
43395
|
+
if (LEGACY_MODE) return null;
|
|
43396
|
+
const inflight = binder.current();
|
|
43397
|
+
if (inflight) {
|
|
43398
|
+
try {
|
|
43399
|
+
await inflight;
|
|
43400
|
+
} catch {
|
|
43401
|
+
}
|
|
43402
|
+
}
|
|
43403
|
+
if (binder.state().phase === "bound") return null;
|
|
43404
|
+
if (resolution?.kind === "error") return `Identity configuration problem: ${resolution.message}`;
|
|
43405
|
+
return unassignedGuidance();
|
|
43406
|
+
}
|
|
43407
|
+
});
|
|
43408
|
+
if (!LEGACY_MODE) {
|
|
43409
|
+
server.registerTool(
|
|
43410
|
+
"jefri_be",
|
|
43411
|
+
{
|
|
43412
|
+
title: "Choose this session's Jefri identity",
|
|
43413
|
+
description: "Bind this UNASSIGNED session to one of the model-selectable identities from ~/.jefri/identities.json. Works once per session (one agent = one session); a session that is already someone must be restarted to re-identify.",
|
|
43414
|
+
inputSchema: { name: external_exports.string().describe("the identity's name, as listed by jefri_whoami") }
|
|
43415
|
+
},
|
|
43416
|
+
async ({ name }) => {
|
|
43417
|
+
const already = binder.state();
|
|
43418
|
+
if (already.phase === "bound")
|
|
43419
|
+
return { content: [{ type: "text", text: `Already bound as "${already.profile}" \u2014 one identity per session. Restart the session to re-identify.` }] };
|
|
43420
|
+
const inflight = binder.current();
|
|
43421
|
+
if (inflight) {
|
|
43422
|
+
try {
|
|
43423
|
+
await inflight;
|
|
43424
|
+
} catch {
|
|
43425
|
+
}
|
|
43426
|
+
const after = binder.state();
|
|
43427
|
+
if (after.phase === "bound")
|
|
43428
|
+
return { content: [{ type: "text", text: `Already bound as "${after.profile}" (the automatic rule won) \u2014 restart the session to re-identify.` }] };
|
|
43429
|
+
}
|
|
43430
|
+
let file = null;
|
|
43431
|
+
try {
|
|
43432
|
+
file = loadIdentitiesFile();
|
|
43433
|
+
} catch (e) {
|
|
43434
|
+
return { content: [{ type: "text", text: `Cannot read ~/.jefri/identities.json: ${e?.message ?? e}` }], isError: true };
|
|
43435
|
+
}
|
|
43436
|
+
knownSelectable = selectableNames(file);
|
|
43437
|
+
const fenced = profileForBe(file, name);
|
|
43438
|
+
if (!fenced.ok) return { content: [{ type: "text", text: fenced.message }], isError: true };
|
|
43439
|
+
try {
|
|
43440
|
+
const c2 = await binder.bind(fenced.name, () => {
|
|
43441
|
+
resolvedProfileToken = fenced.token;
|
|
43442
|
+
return ensureClient();
|
|
43443
|
+
});
|
|
43444
|
+
try {
|
|
43445
|
+
c2.announceHost(detectedHost ?? void 0);
|
|
43446
|
+
} catch {
|
|
43447
|
+
}
|
|
43448
|
+
return { content: [{ type: "text", text: `\u2705 You are now @${c2.identity.username} ("${c2.identity.displayName}") for the rest of this session.` }] };
|
|
43449
|
+
} catch (e) {
|
|
43450
|
+
return { content: [{ type: "text", text: `Could not bind "${name}": ${e?.message ?? e}
|
|
43451
|
+
The session is still unassigned \u2014 fix the cause and try again.` }], isError: true };
|
|
43452
|
+
}
|
|
43453
|
+
}
|
|
43454
|
+
);
|
|
43455
|
+
}
|
|
43171
43456
|
async function main() {
|
|
43172
43457
|
if (process.argv[2] === "doctor") {
|
|
43173
43458
|
await runDoctor();
|
|
@@ -43197,20 +43482,56 @@ async function main() {
|
|
|
43197
43482
|
setTimeout(() => bootConnect(attempt + 1), jittered).unref?.();
|
|
43198
43483
|
});
|
|
43199
43484
|
};
|
|
43200
|
-
bootConnect();
|
|
43485
|
+
if (LEGACY_MODE) bootConnect();
|
|
43201
43486
|
const transport = new StdioServerTransport();
|
|
43202
43487
|
server.server.oninitialized = () => {
|
|
43203
43488
|
try {
|
|
43204
43489
|
const rawHost = server.server.getClientVersion()?.name;
|
|
43205
43490
|
detectedHost = rawHost ?? null;
|
|
43206
43491
|
setAutoHost(rawHost);
|
|
43207
|
-
|
|
43208
|
-
|
|
43492
|
+
if (LEGACY_MODE) {
|
|
43493
|
+
void ensureClient().then((cl) => cl.announceHost(rawHost)).catch(() => {
|
|
43494
|
+
});
|
|
43495
|
+
return;
|
|
43496
|
+
}
|
|
43497
|
+
let file = null;
|
|
43498
|
+
try {
|
|
43499
|
+
file = loadIdentitiesFile();
|
|
43500
|
+
} catch (e) {
|
|
43501
|
+
resolution = { kind: "error", message: String(e?.message ?? e) };
|
|
43502
|
+
log(resolution.message);
|
|
43503
|
+
return;
|
|
43504
|
+
}
|
|
43505
|
+
knownSelectable = selectableNames(file);
|
|
43506
|
+
resolution = resolveIdentity({ clientName: rawHost ?? null, file });
|
|
43507
|
+
if (resolution.kind === "error") {
|
|
43508
|
+
log(resolution.message);
|
|
43509
|
+
return;
|
|
43510
|
+
}
|
|
43511
|
+
if (resolution.kind !== "profile") {
|
|
43512
|
+
note("no identity assigned for this session (unassigned mode) \u2014 jefri_whoami explains the options");
|
|
43513
|
+
return;
|
|
43514
|
+
}
|
|
43515
|
+
const prof = resolution;
|
|
43516
|
+
const autoBind = (attempt = 0) => {
|
|
43517
|
+
binder.bind(prof.name, () => {
|
|
43518
|
+
resolvedProfileToken = prof.token;
|
|
43519
|
+
return ensureClient();
|
|
43520
|
+
}).then((cl) => cl.announceHost(rawHost)).catch((e) => {
|
|
43521
|
+
const delay = Math.min(2e3 * 2 ** attempt, 3e4);
|
|
43522
|
+
const jittered = Math.floor(delay / 2 + Math.random() * (delay / 2));
|
|
43523
|
+
log(`bind to "${prof.name}" failed (retrying in ${Math.round(jittered / 1e3)}s):`, e?.message ?? e);
|
|
43524
|
+
setTimeout(() => {
|
|
43525
|
+
if (binder.state().phase === "unassigned") autoBind(attempt + 1);
|
|
43526
|
+
}, jittered).unref?.();
|
|
43527
|
+
});
|
|
43528
|
+
};
|
|
43529
|
+
autoBind();
|
|
43209
43530
|
} catch {
|
|
43210
43531
|
}
|
|
43211
43532
|
};
|
|
43212
43533
|
await server.connect(transport);
|
|
43213
|
-
note(`MCP server ready \u2014 identity @${USERNAME}, Jefri Chat server ${SERVER}`);
|
|
43534
|
+
note(LEGACY_MODE ? `MCP server ready \u2014 identity @${USERNAME}, Jefri Chat server ${SERVER}` : `MCP server ready \u2014 identity resolves after the MCP handshake (Jefri Chat server ${SERVER})`);
|
|
43214
43535
|
let shuttingDown = false;
|
|
43215
43536
|
const shutdownOnHostClose = () => {
|
|
43216
43537
|
if (shuttingDown) return;
|
package/package.json
CHANGED