tesseract-agent 0.11.39 → 0.11.41

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.
@@ -0,0 +1,371 @@
1
+ import { createRequire as __cr } from 'module';
2
+ import { dirname as __dn } from 'path';
3
+ import { fileURLToPath as __fu } from 'url';
4
+ const require = __cr(import.meta.url);
5
+ const __dirname = __dn(__fu(import.meta.url));
6
+
7
+ // ../../packages/shared/dist/base-paths.js
8
+ import { homedir, tmpdir } from "node:os";
9
+ import { join, resolve, sep } from "node:path";
10
+ import { readFileSync } from "node:fs";
11
+ var STARTUP_CWD = resolve(process.cwd());
12
+ var USER_HOME = homedir();
13
+ var TEMP_DIR = tmpdir();
14
+ function readXdgUserDirs(home) {
15
+ const out = {};
16
+ try {
17
+ const cfg = process.env.XDG_CONFIG_HOME || join(home, ".config");
18
+ for (const line of readFileSync(join(cfg, "user-dirs.dirs"), "utf-8").split("\n")) {
19
+ const m = /^\s*(XDG_[A-Z]+_DIR)\s*=\s*"(.*)"\s*$/.exec(line);
20
+ if (!m)
21
+ continue;
22
+ out[m[1]] = m[2].replace(/^\$HOME\b/, home);
23
+ }
24
+ } catch {
25
+ }
26
+ return out;
27
+ }
28
+ function deriveSystemDirs(home) {
29
+ if (process.platform === "linux") {
30
+ const xdg = readXdgUserDirs(home);
31
+ return {
32
+ documents: xdg.XDG_DOCUMENTS_DIR || join(home, "Documents"),
33
+ downloads: xdg.XDG_DOWNLOAD_DIR || join(home, "Downloads"),
34
+ desktop: xdg.XDG_DESKTOP_DIR || join(home, "Desktop"),
35
+ pictures: xdg.XDG_PICTURES_DIR || join(home, "Pictures"),
36
+ videos: xdg.XDG_VIDEOS_DIR || join(home, "Videos"),
37
+ music: xdg.XDG_MUSIC_DIR || join(home, "Music")
38
+ };
39
+ }
40
+ return {
41
+ documents: join(home, "Documents"),
42
+ downloads: join(home, "Downloads"),
43
+ desktop: join(home, "Desktop"),
44
+ pictures: join(home, "Pictures"),
45
+ videos: process.platform === "win32" ? join(home, "Videos") : join(home, "Movies"),
46
+ music: join(home, "Music")
47
+ };
48
+ }
49
+ var systemDirs = deriveSystemDirs(USER_HOME);
50
+ function expandTilde(p) {
51
+ if (p === "~")
52
+ return USER_HOME;
53
+ if (p.startsWith("~/") || p.startsWith("~\\"))
54
+ return join(USER_HOME, p.slice(2));
55
+ return p;
56
+ }
57
+ function memoEnvRoot() {
58
+ const memo = /* @__PURE__ */ new Map();
59
+ return (envVars, fallback) => {
60
+ const names = Array.isArray(envVars) ? envVars : [envVars];
61
+ const memoKey = names.join("|");
62
+ const hit = memo.get(memoKey);
63
+ if (hit !== void 0)
64
+ return hit;
65
+ const raw = names.map((n) => process.env[n]).find((v) => v && v.trim());
66
+ const value = raw ? resolve(STARTUP_CWD, expandTilde(raw.trim())) : fallback();
67
+ if (!peeking)
68
+ memo.set(memoKey, value);
69
+ return value;
70
+ };
71
+ }
72
+ var envRoot = memoEnvRoot();
73
+ var peeking = false;
74
+ var TESSERACT_HOME_ENV_VARS = ["TESSERACT_HOMEDIR", "CCB_TESSERACT_DIR"];
75
+ function tesseractHome() {
76
+ return envRoot([...TESSERACT_HOME_ENV_VARS], () => join(USER_HOME, ".tesseract"));
77
+ }
78
+ function homeDaemonDir() {
79
+ return join(tesseractHome(), "daemon");
80
+ }
81
+ function daemonDir() {
82
+ return envRoot(["TESSERACT_DAEMON_DIR", "CCB_DAEMON_DIR"], homeDaemonDir);
83
+ }
84
+
85
+ // ../host-daemon/src/tesseract-mcp-proxy.ts
86
+ import { readFileSync as readFileSync2 } from "node:fs";
87
+ import { join as join2 } from "node:path";
88
+ import { createInterface } from "node:readline";
89
+ import { request as httpRequest } from "node:http";
90
+ import { request as httpsRequest } from "node:https";
91
+ var DEBUG = process.env.CCB_MCP_PROXY_DEBUG === "1";
92
+ function logErr(...a) {
93
+ try {
94
+ process.stderr.write("[tesseract-mcp-proxy] " + a.map((x) => x instanceof Error ? x.message : typeof x === "string" ? x : JSON.stringify(x)).join(" ") + "\n");
95
+ } catch {
96
+ }
97
+ }
98
+ function portFilePath() {
99
+ return process.env.TESSERACT_MCP_PORT_FILE?.trim() || join2(daemonDir(), "mcp-endpoint.json");
100
+ }
101
+ function loadEndpoint() {
102
+ const url = process.env.TESSERACT_MCP_URL?.trim();
103
+ if (url) return { url, token: null };
104
+ try {
105
+ const o = JSON.parse(readFileSync2(portFilePath(), "utf-8"));
106
+ return o.port && o.token ? { url: `http://127.0.0.1:${o.port}`, token: o.token } : null;
107
+ } catch {
108
+ return null;
109
+ }
110
+ }
111
+ var OWN_HEADERS = /* @__PURE__ */ new Set(["content-type", "accept", "content-length", "host", "connection", "transfer-encoding", "mcp-session-id", "mcp-protocol-version"]);
112
+ function extraHeaders() {
113
+ const raw = process.env.TESSERACT_MCP_HEADERS?.trim();
114
+ if (!raw) return {};
115
+ try {
116
+ const o = JSON.parse(raw);
117
+ if (!o || typeof o !== "object" || Array.isArray(o)) throw new Error("not a JSON object");
118
+ const out = {};
119
+ for (const [k, v] of Object.entries(o)) {
120
+ if (typeof v === "string" && !OWN_HEADERS.has(k.toLowerCase())) out[k.toLowerCase()] = v;
121
+ }
122
+ return out;
123
+ } catch (e) {
124
+ logErr("TESSERACT_MCP_HEADERS ignored \u2014", e);
125
+ return {};
126
+ }
127
+ }
128
+ var EXTRA_HEADERS = extraHeaders();
129
+ var CALL_DEADLINE_MS = 24 * 60 * 60 * 1e3;
130
+ function writeLine(obj) {
131
+ process.stdout.write(JSON.stringify(obj) + "\n");
132
+ }
133
+ function replyErrorTo(id, message) {
134
+ if (id !== void 0 && id !== null) writeLine({ jsonrpc: "2.0", id, error: { code: -32e3, message } });
135
+ }
136
+ var isRequest = (m) => typeof m.method === "string" && m.id !== void 0 && m.id !== null;
137
+ var idKey = (id) => JSON.stringify(id);
138
+ var sessionId = null;
139
+ var protocolVersion = null;
140
+ var initLine = null;
141
+ function send(url, line, bearer, signal, method = "POST") {
142
+ const sentSession = !!sessionId;
143
+ const headers = {
144
+ ...EXTRA_HEADERS,
145
+ ...bearer ? { authorization: `Bearer ${bearer}` } : {},
146
+ // `accept` lists both, as the Streamable HTTP spec asks of a client.
147
+ "content-type": "application/json",
148
+ accept: "application/json, text/event-stream",
149
+ ...sessionId ? { "mcp-session-id": sessionId } : {},
150
+ ...protocolVersion ? { "mcp-protocol-version": protocolVersion } : {}
151
+ };
152
+ const request = new URL(url).protocol === "https:" ? httpsRequest : httpRequest;
153
+ return new Promise((resolve2, reject) => {
154
+ const req = request(url, { method, headers, signal }, (res) => {
155
+ const status = res.statusCode ?? 0;
156
+ const ok = status >= 200 && status < 300;
157
+ const sid = res.headers["mcp-session-id"];
158
+ if (ok && typeof sid === "string" && sid.trim()) sessionId = sid.trim();
159
+ res.setEncoding("utf8");
160
+ resolve2({ status, ok, type: String(res.headers["content-type"] ?? ""), sentSession, body: res });
161
+ });
162
+ req.on("error", reject);
163
+ req.end(method === "POST" ? line : void 0);
164
+ });
165
+ }
166
+ function sseReader(emit) {
167
+ let buf = "";
168
+ let data = [];
169
+ const line = (l) => {
170
+ if (l === "") {
171
+ if (data.length) emit(data.join("\n"));
172
+ data = [];
173
+ return;
174
+ }
175
+ if (l.startsWith(":")) return;
176
+ const colon = l.indexOf(":");
177
+ const field = colon < 0 ? l : l.slice(0, colon);
178
+ let value = colon < 0 ? "" : l.slice(colon + 1);
179
+ if (value.startsWith(" ")) value = value.slice(1);
180
+ if (field === "data") data.push(value);
181
+ };
182
+ return {
183
+ push(chunk) {
184
+ buf += chunk;
185
+ for (; ; ) {
186
+ const m = /\r\n|\n|\r(?!$)/.exec(buf);
187
+ if (!m) break;
188
+ line(buf.slice(0, m.index));
189
+ buf = buf.slice(m.index + m[0].length);
190
+ }
191
+ },
192
+ // A stream that ends mid-event still delivers it: the answer is what the client is waiting for.
193
+ end() {
194
+ if (buf) line(buf.replace(/\r$/, ""));
195
+ buf = "";
196
+ line("");
197
+ }
198
+ };
199
+ }
200
+ async function relay(res, out) {
201
+ const answered = /* @__PURE__ */ new Set();
202
+ const each = (text2) => {
203
+ let parsed;
204
+ try {
205
+ parsed = JSON.parse(text2);
206
+ } catch {
207
+ if (text2.trim()) logErr("not JSON-RPC \u2014 dropped:", text2.slice(0, 200));
208
+ return;
209
+ }
210
+ for (const m of Array.isArray(parsed) ? parsed : [parsed]) {
211
+ if (!m || typeof m !== "object") continue;
212
+ const r = m;
213
+ if (r.method === void 0 && r.id !== void 0 && r.id !== null) answered.add(idKey(r.id));
214
+ out(r);
215
+ }
216
+ };
217
+ if (res.type.includes("text/event-stream")) {
218
+ const sse = sseReader(each);
219
+ for await (const chunk of res.body) sse.push(chunk);
220
+ sse.end();
221
+ return answered;
222
+ }
223
+ let text = "";
224
+ for await (const chunk of res.body) text += chunk;
225
+ if (text.trim()) each(text.trim());
226
+ return answered;
227
+ }
228
+ async function refusal(res) {
229
+ let text = "";
230
+ try {
231
+ for await (const chunk of res.body) {
232
+ text += chunk;
233
+ if (text.length > 4e3) break;
234
+ }
235
+ } catch {
236
+ }
237
+ res.body.resume();
238
+ let detail = "";
239
+ try {
240
+ const e = JSON.parse(text).error;
241
+ const said = typeof e === "string" ? e : e?.message;
242
+ if (typeof said === "string") detail = said;
243
+ } catch {
244
+ }
245
+ return `Tesseract tool endpoint returned ${res.status}${detail ? `: ${detail}` : "."}`;
246
+ }
247
+ var reopening = null;
248
+ function reopen(url, bearer, signal) {
249
+ reopening ??= (async () => {
250
+ if (!initLine) return false;
251
+ sessionId = null;
252
+ protocolVersion = null;
253
+ const init = await send(url, initLine, bearer, signal);
254
+ if (!init.ok) {
255
+ init.body.resume();
256
+ return false;
257
+ }
258
+ await relay(init, (m) => {
259
+ noteProtocol(m);
260
+ });
261
+ const note = await send(url, JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }), bearer, signal);
262
+ note.body.resume();
263
+ logErr("the server had forgotten this session \u2014 opened again");
264
+ return true;
265
+ })().catch(() => false).finally(() => {
266
+ reopening = null;
267
+ });
268
+ return reopening;
269
+ }
270
+ function noteProtocol(m) {
271
+ const v = m.result?.protocolVersion;
272
+ if (typeof v === "string" && v) protocolVersion = v;
273
+ }
274
+ async function forward(line) {
275
+ let parsed;
276
+ try {
277
+ parsed = JSON.parse(line);
278
+ } catch {
279
+ return;
280
+ }
281
+ const msgs = (Array.isArray(parsed) ? parsed : [parsed]).filter((m) => !!m && typeof m === "object");
282
+ const msg = Array.isArray(parsed) ? null : msgs[0] ?? null;
283
+ const replyAll = (message) => {
284
+ for (const m of msgs) if (isRequest(m)) replyErrorTo(m.id, message);
285
+ };
286
+ if (DEBUG) logErr("\u2192", msg?.method ?? "(batch)", "id", String(msg?.id));
287
+ const ep = loadEndpoint();
288
+ if (!ep) {
289
+ logErr(`endpoint not running (no ${portFilePath()}) \u2014 dropping`, msg?.method ?? "?");
290
+ replyAll("Tesseract is not running, so its tools are unavailable.");
291
+ return;
292
+ }
293
+ const initializing = msg?.method === "initialize" && isRequest(msg);
294
+ if (initializing) {
295
+ initLine = line;
296
+ sessionId = null;
297
+ protocolVersion = null;
298
+ }
299
+ const ac = new AbortController();
300
+ const timer = setTimeout(() => ac.abort(), CALL_DEADLINE_MS);
301
+ const own = process.env.TESSERACT_MCP_TOKEN;
302
+ const token = own || ep.token;
303
+ let bearer = token;
304
+ const post = (b) => send(ep.url, line, b, ac.signal);
305
+ try {
306
+ let res = await post(token);
307
+ if (res.status === 401 && own && ep.token && own !== ep.token) {
308
+ logErr("the conversation bearer was refused \u2014 continuing on the shared one");
309
+ res.body.resume();
310
+ bearer = ep.token;
311
+ res = await post(bearer);
312
+ }
313
+ if (res.status === 404 && res.sentSession && !initializing && initLine) {
314
+ res.body.resume();
315
+ if (await reopen(ep.url, bearer, ac.signal)) res = await post(bearer);
316
+ else {
317
+ replyAll("The server ended this session, and it could not be opened again.");
318
+ return;
319
+ }
320
+ }
321
+ if (DEBUG) logErr("\u2190", msg?.method ?? "?", "status", String(res.status), res.type || "(no body type)");
322
+ if (!res.ok) {
323
+ replyAll(await refusal(res));
324
+ return;
325
+ }
326
+ const answered = await relay(res, (m) => {
327
+ if (initializing && msg && m.method === void 0 && idKey(m.id) === idKey(msg.id)) noteProtocol(m);
328
+ writeLine(m);
329
+ });
330
+ for (const m of msgs) if (isRequest(m) && !answered.has(idKey(m.id))) replyErrorTo(m.id, "The tool server sent no answer.");
331
+ } catch (e) {
332
+ replyAll(ac.signal.aborted ? "Tesseract tool request timed out." : e instanceof Error ? e.message : String(e));
333
+ } finally {
334
+ clearTimeout(timer);
335
+ }
336
+ }
337
+ async function endSession() {
338
+ const ep = loadEndpoint();
339
+ if (!sessionId || !ep) return;
340
+ const ac = new AbortController();
341
+ const timer = setTimeout(() => ac.abort(), 3e3);
342
+ try {
343
+ (await send(ep.url, "", process.env.TESSERACT_MCP_TOKEN || ep.token, ac.signal, "DELETE")).body.resume();
344
+ } catch {
345
+ } finally {
346
+ clearTimeout(timer);
347
+ }
348
+ }
349
+ var requests = Promise.resolve();
350
+ var notes = Promise.resolve();
351
+ var rl = createInterface({ input: process.stdin });
352
+ rl.on("line", (line) => {
353
+ if (!line.trim()) return;
354
+ let call = false;
355
+ try {
356
+ const m = JSON.parse(line);
357
+ call = Array.isArray(m) ? m.some((x) => !!x && typeof x === "object" && isRequest(x)) : !!m && typeof m === "object" && isRequest(m);
358
+ } catch {
359
+ return;
360
+ }
361
+ const run = () => forward(line).catch((e) => {
362
+ logErr("dropped a message \u2014", e);
363
+ });
364
+ if (call) {
365
+ const before = notes;
366
+ requests = Promise.all([requests, before]).then(run);
367
+ } else notes = notes.then(run);
368
+ });
369
+ rl.on("close", () => {
370
+ void Promise.all([requests, notes]).then(endSession).finally(() => process.exit(0));
371
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tesseract-agent",
3
- "version": "0.11.39",
3
+ "version": "0.11.41",
4
4
  "license": "UNLICENSED",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -13,8 +13,8 @@
13
13
  "dist-bundle"
14
14
  ],
15
15
  "author": {
16
- "name": "Corneliu Maftuleac",
17
- "url": "https://x.com/cmaftuleac"
16
+ "name": "Eightcell Labs, Inc.",
17
+ "url": "https://tesseract.dev"
18
18
  },
19
19
  "contributors": [
20
20
  "Eightcell Labs, Inc. (https://tesseract.dev)"