tesseract-agent 0.10.7 → 0.10.9
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,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The mesh overlay data plane (Rust, via napi-rs).
|
|
3
|
+
*
|
|
4
|
+
* Everything here is CONTROL plane. Packets do not cross this boundary on desktop:
|
|
5
|
+
* the daemon runs the engine in `tun_mode: "internal"` so Rust owns the device
|
|
6
|
+
* directly — a per-packet call into JS would melt Electron's event loop.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** `"host:port"`. */
|
|
10
|
+
export type Endpoint = string;
|
|
11
|
+
|
|
12
|
+
export interface SendItem {
|
|
13
|
+
/** Where to send it. */
|
|
14
|
+
to: Endpoint;
|
|
15
|
+
bytes: Buffer;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface MeshEvent {
|
|
19
|
+
kind: 'peerEstablished' | 'pathChanged' | 'dropped' | 'handshakeFailed';
|
|
20
|
+
/** Present for peer-scoped events. */
|
|
21
|
+
staticPub?: Buffer;
|
|
22
|
+
/**
|
|
23
|
+
* For `dropped`: why. `source-not-allowed` means cryptokey routing refused the
|
|
24
|
+
* peer's source address (a spoofing attempt), which is a very different thing
|
|
25
|
+
* from `policy-denied` — do not merge them in telemetry.
|
|
26
|
+
* For `pathChanged`: `'direct'` or `'relay'`.
|
|
27
|
+
*/
|
|
28
|
+
detail?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export declare class MeshEngine {
|
|
32
|
+
constructor(
|
|
33
|
+
genesis: Buffer,
|
|
34
|
+
staticPrivate: Buffer,
|
|
35
|
+
cert: Buffer,
|
|
36
|
+
overlayV4: number,
|
|
37
|
+
overlayV6: Buffer,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Install a policy bundle.
|
|
42
|
+
*
|
|
43
|
+
* MUST already be verified (quorum, mesh binding, rollback, staleness). Handing
|
|
44
|
+
* this an unverified bundle puts the coordination relay back into the trust path.
|
|
45
|
+
*/
|
|
46
|
+
setBundle(bundle: Buffer): void;
|
|
47
|
+
|
|
48
|
+
addPeer(staticPub: Buffer, overlayV4: number, overlayV6: Buffer): void;
|
|
49
|
+
/** A relayed path always ranks below a direct one, regardless of RTT. */
|
|
50
|
+
addPath(staticPub: Buffer, endpoint: Endpoint, relayed: boolean): void;
|
|
51
|
+
startHandshake(staticPub: Buffer, nowMs: number): void;
|
|
52
|
+
|
|
53
|
+
injectTun(pkt: Buffer, nowMs: number): void;
|
|
54
|
+
injectUdp(pkt: Buffer, from: Endpoint, nowMs: number): void;
|
|
55
|
+
tick(nowMs: number): void;
|
|
56
|
+
|
|
57
|
+
pollSend(): SendItem | null;
|
|
58
|
+
pollTun(): Buffer | null;
|
|
59
|
+
pollEvent(): MeshEvent | null;
|
|
60
|
+
|
|
61
|
+
peerCount(): number;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Route all non-mesh traffic through this peer; `null` restores a split tunnel.
|
|
65
|
+
*
|
|
66
|
+
* SELECTION IS NOT AUTHORIZATION. It is one of three gates the engine checks per
|
|
67
|
+
* packet — the others are the CA marking the peer's certificate `isExitNode` and a
|
|
68
|
+
* `CAP_EXIT_NODE` grant in the signed bundle — so calling this before the peer's
|
|
69
|
+
* certificate arrives widens nothing.
|
|
70
|
+
*
|
|
71
|
+
* Declared late: the napi binding has had this since it was written
|
|
72
|
+
* (`crates/tsmesh-napi/src/lib.rs:293`), but it was missing from this file, so no
|
|
73
|
+
* TypeScript caller could reach it and a full tunnel was unreachable from the
|
|
74
|
+
* product even though the engine implemented it.
|
|
75
|
+
*/
|
|
76
|
+
setExitNode(staticPub: Buffer | null): void;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** True only when the prebuilt loaded AND its ABI matches. Check before use. */
|
|
80
|
+
export declare const available: boolean;
|
|
81
|
+
/** Why it did not load, for diagnostics. */
|
|
82
|
+
export declare const loadError: string | null;
|
|
83
|
+
export declare function abiVersion(): number | null;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Loader for the mesh data plane (Rust, napi-rs). Mirrors packages/mac-ax: return
|
|
3
|
+
// inert stubs when the prebuilt binary is absent so host-core can require this
|
|
4
|
+
// unconditionally and dev/build/typecheck stay green on any platform. Callers check
|
|
5
|
+
// `available` first.
|
|
6
|
+
//
|
|
7
|
+
// Deliberately NOT bundled by esbuild: add '@fineguide-tesseract/mesh-core' to the
|
|
8
|
+
// externals in apps/tesseract-cli/esbuild.mjs, and to asarUnpack in both
|
|
9
|
+
// electron-builder.yml files, exactly as the other native packages are.
|
|
10
|
+
//
|
|
11
|
+
// The prebuilt is FETCHED, not committed. Nine build targets at ~6-12 MB each, every
|
|
12
|
+
// release, would permanently bloat the repo — the same reason Chromium and
|
|
13
|
+
// Real-ESRGAN are fetched (apps/desktop/scripts/fetch-7zz.mjs is the pattern).
|
|
14
|
+
|
|
15
|
+
const path = require('node:path');
|
|
16
|
+
|
|
17
|
+
/** ABI the JS side expects. Must match ABI_VERSION in crates/tsmesh-napi. */
|
|
18
|
+
const EXPECTED_ABI = 8;
|
|
19
|
+
|
|
20
|
+
function platformDir() {
|
|
21
|
+
const arch = process.arch;
|
|
22
|
+
switch (process.platform) {
|
|
23
|
+
case 'darwin': return [`darwin-${arch}`, 'darwin-universal'];
|
|
24
|
+
case 'win32': return [`win32-${arch}`];
|
|
25
|
+
case 'linux': return [`linux-${arch}`];
|
|
26
|
+
default: return [];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let native = null;
|
|
31
|
+
let loadError = null;
|
|
32
|
+
|
|
33
|
+
for (const dir of platformDir()) {
|
|
34
|
+
try {
|
|
35
|
+
native = require(path.join(__dirname, 'prebuilds', dir, 'tsmesh.node'));
|
|
36
|
+
break;
|
|
37
|
+
} catch (e) {
|
|
38
|
+
loadError = e;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Developer build (`npm run rebuild -w packages/mesh-core`) lands here.
|
|
42
|
+
if (!native) {
|
|
43
|
+
try {
|
|
44
|
+
native = require(path.join(__dirname, 'build', 'tsmesh.node'));
|
|
45
|
+
} catch (e) {
|
|
46
|
+
loadError = loadError || e;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// A prebuilt from a different release can be present but incompatible. Refusing it
|
|
51
|
+
// here turns a confusing runtime failure deep in the data path into one clear
|
|
52
|
+
// disabled-feature check at load — the same discipline the Dart side needs against
|
|
53
|
+
// an over-the-air patch running on an older native.
|
|
54
|
+
let abiOk = false;
|
|
55
|
+
if (native) {
|
|
56
|
+
try {
|
|
57
|
+
abiOk = native.abiVersion() === EXPECTED_ABI;
|
|
58
|
+
if (!abiOk) {
|
|
59
|
+
loadError = new Error(
|
|
60
|
+
`mesh-core ABI mismatch: native ${native.abiVersion()}, expected ${EXPECTED_ABI}`,
|
|
61
|
+
);
|
|
62
|
+
native = null;
|
|
63
|
+
}
|
|
64
|
+
} catch (e) {
|
|
65
|
+
loadError = e;
|
|
66
|
+
native = null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const unavailable = () => {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`mesh-core native addon not available${loadError ? `: ${loadError.message}` : ''}`,
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
module.exports = {
|
|
77
|
+
/** True only when the prebuilt loaded AND its ABI matches. Check before use. */
|
|
78
|
+
available: !!native,
|
|
79
|
+
/** Why it did not load, for diagnostics and the support bundle. */
|
|
80
|
+
loadError: native ? null : loadError && loadError.message,
|
|
81
|
+
/** Native ABI version, or null when unavailable. */
|
|
82
|
+
abiVersion: native ? native.abiVersion : () => null,
|
|
83
|
+
/** The engine. Construct with (genesis, staticPrivate, cert, overlayV4, overlayV6). */
|
|
84
|
+
MeshEngine: native ? native.MeshEngine : unavailable,
|
|
85
|
+
};
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Materialise the mesh native payload for THIS platform from a GitHub release.
|
|
4
|
+
*
|
|
5
|
+
* TWO artifacts, not one: the data-plane engine (`tsmesh.node`) and the privileged
|
|
6
|
+
* helper (`tesseract-netd`). The engine decides what a packet may do; the helper opens
|
|
7
|
+
* the tun device and programs the routes. Without the second, a machine joins a network,
|
|
8
|
+
* holds a certificate, shows a roster — and carries no packets, which is exactly what a
|
|
9
|
+
* `npm i -g tesseract-agent` install did.
|
|
10
|
+
*
|
|
11
|
+
* The binary is not committed: nine build targets at ~6-12 MB each, every release,
|
|
12
|
+
* would permanently bloat a repo with no git-lfs — the same reasoning that makes 7-Zip,
|
|
13
|
+
* the on-device models and Chromium fetched rather than vendored
|
|
14
|
+
* (`apps/desktop/scripts/fetch-7zz.mjs` is the pattern this follows).
|
|
15
|
+
*
|
|
16
|
+
* ## Why a missing prebuilt is not an error
|
|
17
|
+
*
|
|
18
|
+
* `packages/mesh-core/index.js` returns inert stubs when no binary is present, so the
|
|
19
|
+
* overlay simply reports itself unavailable and nothing else breaks. This script honours
|
|
20
|
+
* that: it exits 0 when it cannot fetch, unless `--require` is passed. A release job
|
|
21
|
+
* uses `--require`; a developer's `npm ci` must not fail because a network blipped on an
|
|
22
|
+
* optional feature.
|
|
23
|
+
*
|
|
24
|
+
* ## Integrity
|
|
25
|
+
*
|
|
26
|
+
* Every download is checked against the `.sha256` published beside it. That file comes
|
|
27
|
+
* from the same release, so this is a consistency check against corruption and a
|
|
28
|
+
* truncated transfer — NOT a defence against a compromised release. The binary is
|
|
29
|
+
* loaded into the daemon, so treat the release as trusted infrastructure and rely on
|
|
30
|
+
* repository access control for that, exactly as the other fetched binaries do.
|
|
31
|
+
*/
|
|
32
|
+
import { chmodSync, createWriteStream, existsSync, mkdirSync, readFileSync, rmSync, statSync } from 'node:fs';
|
|
33
|
+
import { createHash } from 'node:crypto';
|
|
34
|
+
import { dirname, join } from 'node:path';
|
|
35
|
+
import { fileURLToPath } from 'node:url';
|
|
36
|
+
import { pipeline } from 'node:stream/promises';
|
|
37
|
+
|
|
38
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
39
|
+
const pkg = join(here, '..');
|
|
40
|
+
const REQUIRE = process.argv.includes('--require');
|
|
41
|
+
const REPO = process.env.TESSERACT_MESH_REPO || 'opentech-dev/tesseract-release';
|
|
42
|
+
const VERSION = process.env.TESSERACT_MESH_VERSION
|
|
43
|
+
|| JSON.parse(readFileSync(join(pkg, 'package.json'), 'utf8')).version;
|
|
44
|
+
|
|
45
|
+
/** Must match `platformDir()` in index.js — the loader looks only where this writes. */
|
|
46
|
+
function platformDir() {
|
|
47
|
+
const { platform, arch } = process;
|
|
48
|
+
if (platform === 'darwin') return `darwin-${arch}`;
|
|
49
|
+
if (platform === 'win32') return `win32-${arch}`;
|
|
50
|
+
if (platform === 'linux') return `linux-${arch}`;
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function done(msg) {
|
|
55
|
+
console.log(`[mesh-core] ${msg}`);
|
|
56
|
+
process.exit(0);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function fail(msg) {
|
|
60
|
+
console.error(`[mesh-core] ${msg}`);
|
|
61
|
+
// Exit 0 unless the caller demanded the binary: an optional feature must never be
|
|
62
|
+
// the reason an install fails.
|
|
63
|
+
process.exit(REQUIRE ? 1 : 0);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function fetchTo(url, dest) {
|
|
67
|
+
const res = await fetch(url, { redirect: 'follow' });
|
|
68
|
+
if (!res.ok) throw new Error(`${res.status} ${res.statusText} for ${url}`);
|
|
69
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
70
|
+
await pipeline(res.body, createWriteStream(dest));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function sha256(p) {
|
|
74
|
+
return createHash('sha256').update(readFileSync(p)).digest('hex');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const dir = platformDir();
|
|
78
|
+
if (!dir) done(`no prebuilt for ${process.platform}-${process.arch}; the overlay will report unavailable`);
|
|
79
|
+
|
|
80
|
+
const outDir = join(pkg, 'prebuilds', dir);
|
|
81
|
+
const out = join(outDir, 'tsmesh.node');
|
|
82
|
+
|
|
83
|
+
// Idempotent: a binary already present and non-empty is left alone, so repeated
|
|
84
|
+
// installs and offline rebuilds cost nothing.
|
|
85
|
+
if (existsSync(out) && statSync(out).size > 0) done(`already present for ${dir}`);
|
|
86
|
+
|
|
87
|
+
const tag = `mesh-core-v${VERSION}`;
|
|
88
|
+
const base = `https://github.com/${REPO}/releases/download/${tag}`;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Fetch one asset and check it against the `.sha256` published beside it.
|
|
92
|
+
*
|
|
93
|
+
* Throws. The caller decides whether a given asset is worth failing the install for,
|
|
94
|
+
* and the two are not the same: the engine and the helper fail independently, because a
|
|
95
|
+
* machine with an engine and no helper is still useful (it can enrol, hold a
|
|
96
|
+
* certificate and show a roster) while one with neither is not.
|
|
97
|
+
*/
|
|
98
|
+
async function fetchChecked(asset, dest) {
|
|
99
|
+
await fetchTo(`${base}/${asset}`, dest);
|
|
100
|
+
let expected = null;
|
|
101
|
+
try {
|
|
102
|
+
const sumPath = `${dest}.sha256`;
|
|
103
|
+
await fetchTo(`${base}/${asset}.sha256`, sumPath);
|
|
104
|
+
expected = readFileSync(sumPath, 'utf8').trim().split(/\s+/)[0];
|
|
105
|
+
rmSync(sumPath, { force: true });
|
|
106
|
+
} catch {
|
|
107
|
+
// A release without a checksum is older than this script. Refuse it under
|
|
108
|
+
// --require rather than install an unverified binary into a daemon.
|
|
109
|
+
if (REQUIRE) throw new Error('no .sha256 published beside the asset');
|
|
110
|
+
}
|
|
111
|
+
if (expected) {
|
|
112
|
+
const got = sha256(dest);
|
|
113
|
+
if (got !== expected) {
|
|
114
|
+
rmSync(dest, { force: true });
|
|
115
|
+
throw new Error(`checksum mismatch: expected ${expected}, got ${got}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return `${asset} (${statSync(dest).size} bytes)${expected ? ', checksum ok' : ''}`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const engineAsset = `tsmesh-${dir}.node`;
|
|
122
|
+
try {
|
|
123
|
+
console.log(`[mesh-core] fetched ${await fetchChecked(engineAsset, out)}`);
|
|
124
|
+
} catch (e) {
|
|
125
|
+
rmSync(out, { force: true });
|
|
126
|
+
fail(`could not fetch ${engineAsset} from ${tag}: ${e.message}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ── The privileged helper ────────────────────────────────────────────────────────────
|
|
130
|
+
//
|
|
131
|
+
// Lands in `prebuilds/<dir>/netd/`, which is where `netdBinaryPath()` looks — the same
|
|
132
|
+
// layout the desktop packages use, so one resolver serves both. Named for what it is on
|
|
133
|
+
// the machine it lands on (`tesseract-netd`), not for the crate.
|
|
134
|
+
//
|
|
135
|
+
// Its absence is NEVER fatal, not even under --require: a release older than this
|
|
136
|
+
// script has no netd asset for this platform, and failing there would break installs of
|
|
137
|
+
// the engine, which does work. The helper is installed by an explicit elevation the user
|
|
138
|
+
// accepts, and `ensureNetHelperInstalled` already says "this build did not ship the
|
|
139
|
+
// network helper" when it is not here.
|
|
140
|
+
const netdName = process.platform === 'win32' ? 'tesseract-netd.exe' : 'tesseract-netd';
|
|
141
|
+
const netdOut = join(outDir, 'netd', netdName);
|
|
142
|
+
const netdAsset = `tesseract-netd-${dir}${process.platform === 'win32' ? '.exe' : ''}`;
|
|
143
|
+
if (existsSync(netdOut) && statSync(netdOut).size > 0) {
|
|
144
|
+
console.log(`[mesh-core] network helper already present for ${dir}`);
|
|
145
|
+
} else {
|
|
146
|
+
try {
|
|
147
|
+
const what = await fetchChecked(netdAsset, netdOut);
|
|
148
|
+
// Executable, because the installer hands this exact path to systemd / launchd / the
|
|
149
|
+
// Windows SCM. A 0644 file there produces a service that fails to start with a
|
|
150
|
+
// permission error naming the binary, which reads as a corrupt download.
|
|
151
|
+
if (process.platform !== 'win32') chmodSync(netdOut, 0o755);
|
|
152
|
+
console.log(`[mesh-core] fetched ${what}`);
|
|
153
|
+
// WINTUN, and only on Windows. `tun-rs` does not link it — it LoadLibrary's `wintun.dll`
|
|
154
|
+
// from the helper's OWN directory at runtime — so a netd.exe without it fails with
|
|
155
|
+
// `open tun: LoadLibraryExW failed` and the machine carries no packets. It is fetched
|
|
156
|
+
// here rather than by a separate step because the two are one artifact in practice: a
|
|
157
|
+
// helper without its driver library is not a helper.
|
|
158
|
+
if (process.platform === 'win32') {
|
|
159
|
+
const dll = join(outDir, 'netd', 'wintun.dll');
|
|
160
|
+
console.log(`[mesh-core] fetched ${await fetchChecked(`wintun-${dir}.dll`, dll)}`);
|
|
161
|
+
}
|
|
162
|
+
} catch (e) {
|
|
163
|
+
rmSync(netdOut, { force: true });
|
|
164
|
+
// Remove the DLL too. Half a helper on disk is worse than none: `netdBinaryPath()` would
|
|
165
|
+
// find the exe and the installer would register a service that cannot open a device.
|
|
166
|
+
rmSync(join(outDir, 'netd', 'wintun.dll'), { force: true });
|
|
167
|
+
console.log(`[mesh-core] no network helper for ${dir} in ${tag} (${e.message}); routing will be unavailable`);
|
|
168
|
+
}
|
|
169
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tesseract-agent",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.9",
|
|
4
4
|
"license": "UNLICENSED",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"bundle": "node esbuild.mjs",
|
|
31
31
|
"dev": "tsc --watch",
|
|
32
32
|
"prepublishOnly": "npm run build",
|
|
33
|
-
"postinstall": "node -e \"process.env.PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD||process.env.CI_SKIP_PLAYWRIGHT?process.exit(0):process.exit(1)\" || playwright-core install chromium-headless-shell"
|
|
33
|
+
"postinstall": "node dist-bundle/mesh-core/scripts/fetch-prebuilt.mjs || echo \"[tesseract] mesh natives unavailable; the overlay will report itself disabled\" && node -e \"process.env.PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD||process.env.CI_SKIP_PLAYWRIGHT?process.exit(0):process.exit(1)\" || playwright-core install chromium-headless-shell"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@anthropic-ai/claude-agent-sdk": "0.3.257",
|