@webority/ensemble 0.5.15 → 0.5.17
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/bin/ensemble.js +1 -1
- package/lib/core.js +82 -2
- package/package.json +6 -6
package/bin/ensemble.js
CHANGED
|
@@ -9,7 +9,7 @@ const core = require('../lib/core');
|
|
|
9
9
|
// Bus subcommands are handled by the internal `ensemble-runtime` binary; the Node CLI forwards argv
|
|
10
10
|
// through to it (inherited stdio, same exit code) so users type only one command: `ensemble <sub>`.
|
|
11
11
|
const BUS_SUBCOMMANDS = new Set([
|
|
12
|
-
'send', 'who', 'read', 'reply', 'label', 'hook', 'mcp', 'register', 'detach', 'ask',
|
|
12
|
+
'send', 'who', 'read', 'watch', 'reply', 'label', 'hook', 'mcp', 'register', 'detach', 'ask',
|
|
13
13
|
'rooms', 'room-create', 'room-send', 'room-messages', 'room-add', 'room-remove',
|
|
14
14
|
]);
|
|
15
15
|
|
package/lib/core.js
CHANGED
|
@@ -4,6 +4,7 @@ const os = require('os');
|
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const https = require('https');
|
|
7
|
+
const crypto = require('crypto');
|
|
7
8
|
const { spawn, spawnSync } = require('child_process');
|
|
8
9
|
|
|
9
10
|
const HOME = os.homedir();
|
|
@@ -69,6 +70,47 @@ function downloadTo(url, dest) {
|
|
|
69
70
|
});
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
// SHA-256 of a file on disk (hex). Used to verify CDN / package payloads against manifest.json.
|
|
74
|
+
function sha256File(filePath) {
|
|
75
|
+
const hash = crypto.createHash('sha256');
|
|
76
|
+
hash.update(fs.readFileSync(filePath));
|
|
77
|
+
return hash.digest('hex');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Fetch the release manifest from the same origin as binaries (DL_BASE/manifest.json).
|
|
81
|
+
// Shape: { "version": "x.y.z", "files": { "ensemble-runtime-win-x64.exe": "<sha256hex>", ... } }
|
|
82
|
+
// ENSEMBLE_DL_BASE is treated as trusted-only: CDN installs fail closed without a matching entry.
|
|
83
|
+
async function fetchReleaseManifest() {
|
|
84
|
+
const url = DL_BASE.replace(/\/$/, '') + '/manifest.json';
|
|
85
|
+
try {
|
|
86
|
+
const res = await request('GET', url, {}, null);
|
|
87
|
+
if (res.status !== 200) {
|
|
88
|
+
return { ok: false, reason: 'HTTP ' + res.status + ' for ' + url };
|
|
89
|
+
}
|
|
90
|
+
const body = JSON.parse(res.body);
|
|
91
|
+
if (!body || typeof body !== 'object' || !body.files || typeof body.files !== 'object') {
|
|
92
|
+
return { ok: false, reason: 'manifest.json missing files map' };
|
|
93
|
+
}
|
|
94
|
+
return { ok: true, manifest: body };
|
|
95
|
+
} catch (e) {
|
|
96
|
+
return { ok: false, reason: e && e.message ? e.message : String(e) };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Verify tmp against the expected hex from the manifest. Throws on mismatch / missing entry.
|
|
101
|
+
function verifyBinarySha256(tmpPath, expectedHex, logicalName) {
|
|
102
|
+
if (!expectedHex || typeof expectedHex !== 'string') {
|
|
103
|
+
throw new Error('no SHA-256 in manifest for ' + logicalName +
|
|
104
|
+
' — refuse to place an unverified binary from ' + DL_BASE);
|
|
105
|
+
}
|
|
106
|
+
const actual = sha256File(tmpPath);
|
|
107
|
+
if (actual.toLowerCase() !== expectedHex.trim().toLowerCase()) {
|
|
108
|
+
throw new Error('SHA-256 mismatch for ' + logicalName +
|
|
109
|
+
' (expected ' + expectedHex.trim().toLowerCase() + ', got ' + actual +
|
|
110
|
+
'). Refusing to place — CDN/package may be compromised or stale.');
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
72
114
|
// Throws (not die) so the npm postinstall can catch and not fail the whole install on an
|
|
73
115
|
// unsupported OS; the CLI dispatcher catches the throw and prints it cleanly.
|
|
74
116
|
function assertSupported() {
|
|
@@ -300,24 +342,62 @@ async function ensureBinaries(opts) {
|
|
|
300
342
|
{ src: pkgDir && path.join(pkgDir, 'ensemble-runtime' + pf.exe), cdn: 'ensemble-runtime-' + pf.key + pf.exe, dest: runtimePath },
|
|
301
343
|
{ src: pkgDir && path.join(pkgDir, 'Ensemble.Runner' + pf.exe), cdn: 'ensemble-runner-' + pf.key + pf.exe, dest: runnerPath },
|
|
302
344
|
];
|
|
345
|
+
|
|
346
|
+
// CDN path requires a SHA-256 manifest (fail closed). npm package copies also verify when the
|
|
347
|
+
// manifest lists the file (npm integrity is not a substitute for a published release hash).
|
|
348
|
+
// ENSEMBLE_DL_BASE is trusted-only as the origin of both binaries and manifest.json.
|
|
349
|
+
let manifestFiles = null;
|
|
350
|
+
const needsCdn = targets.some((t) => shouldRefreshBinary(t.dest, t.src, force) && !(t.src && isUsableBinary(t.src)));
|
|
351
|
+
const needsAny = targets.some((t) => shouldRefreshBinary(t.dest, t.src, force));
|
|
352
|
+
if (needsAny) {
|
|
353
|
+
const fetched = await fetchReleaseManifest();
|
|
354
|
+
if (fetched.ok) {
|
|
355
|
+
manifestFiles = fetched.manifest.files;
|
|
356
|
+
} else if (needsCdn) {
|
|
357
|
+
throw new Error('CDN install refused: could not load SHA-256 manifest from ' +
|
|
358
|
+
DL_BASE.replace(/\/$/, '') + '/manifest.json (' + fetched.reason +
|
|
359
|
+
'). Publish manifest.json next to the binaries, or install via npm platform packages.');
|
|
360
|
+
} else {
|
|
361
|
+
warn('manifest.json unavailable (' + fetched.reason +
|
|
362
|
+
') — placing npm package binaries without SHA-256 verify. Publish a release manifest for full integrity.');
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
303
366
|
let runnerRefreshed = false;
|
|
304
367
|
for (const t of targets) {
|
|
305
368
|
if (!shouldRefreshBinary(t.dest, t.src, force)) continue;
|
|
306
369
|
// Write to a temp then atomically place onto dest — placeBinary tolerates a Windows lock on the
|
|
307
370
|
// running runner (rename-aside), so an upgrade never fails just because the daemon is up.
|
|
308
371
|
const tmp = t.dest + '.new';
|
|
309
|
-
|
|
372
|
+
const fromNpm = !!(t.src && isUsableBinary(t.src));
|
|
373
|
+
if (fromNpm) {
|
|
310
374
|
log(' installing ' + path.basename(t.dest) + ' from npm package …');
|
|
311
375
|
fs.copyFileSync(t.src, tmp);
|
|
312
376
|
} else {
|
|
313
377
|
log(' downloading ' + t.cdn + ' …');
|
|
314
|
-
await downloadTo(DL_BASE + '/' + t.cdn, tmp);
|
|
378
|
+
await downloadTo(DL_BASE.replace(/\/$/, '') + '/' + t.cdn, tmp);
|
|
315
379
|
}
|
|
316
380
|
if (!isUsableBinary(tmp)) {
|
|
317
381
|
try { fs.unlinkSync(tmp); } catch (_) { /* ignore */ }
|
|
318
382
|
throw new Error('installed binary looks corrupt or too small: ' + t.dest +
|
|
319
383
|
' (expected ≥ ' + MIN_BINARY_BYTES + ' bytes). Check CDN / platform package.');
|
|
320
384
|
}
|
|
385
|
+
// Integrity: CDN always requires a matching hash. npm package verifies when the release
|
|
386
|
+
// manifest lists this cdn key (same bytes as the published artifact).
|
|
387
|
+
if (manifestFiles) {
|
|
388
|
+
const expected = manifestFiles[t.cdn];
|
|
389
|
+
if (!fromNpm || expected) {
|
|
390
|
+
try {
|
|
391
|
+
verifyBinarySha256(tmp, expected, t.cdn);
|
|
392
|
+
} catch (e) {
|
|
393
|
+
try { fs.unlinkSync(tmp); } catch (_) { /* ignore */ }
|
|
394
|
+
throw e;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
} else if (!fromNpm) {
|
|
398
|
+
try { fs.unlinkSync(tmp); } catch (_) { /* ignore */ }
|
|
399
|
+
throw new Error('CDN install refused: no SHA-256 for ' + t.cdn);
|
|
400
|
+
}
|
|
321
401
|
placeBinary(tmp, t.dest);
|
|
322
402
|
if (t.dest === runnerPath) runnerRefreshed = true;
|
|
323
403
|
if (pf.os !== 'windows') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webority/ensemble",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.17",
|
|
4
4
|
"description": "Connect this machine to Ensemble — runs the local agent runner and wires the ensemble session bus so your coding sessions talk (per-org, isolated).",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ensemble": "bin/ensemble.js"
|
|
@@ -12,11 +12,11 @@
|
|
|
12
12
|
"node": ">=18"
|
|
13
13
|
},
|
|
14
14
|
"optionalDependencies": {
|
|
15
|
-
"@webority/ensemble-darwin-arm64": "0.5.
|
|
16
|
-
"@webority/ensemble-darwin-x64": "0.5.
|
|
17
|
-
"@webority/ensemble-linux-arm64": "0.5.
|
|
18
|
-
"@webority/ensemble-linux-x64": "0.5.
|
|
19
|
-
"@webority/ensemble-win-x64": "0.5.
|
|
15
|
+
"@webority/ensemble-darwin-arm64": "0.5.17",
|
|
16
|
+
"@webority/ensemble-darwin-x64": "0.5.17",
|
|
17
|
+
"@webority/ensemble-linux-arm64": "0.5.17",
|
|
18
|
+
"@webority/ensemble-linux-x64": "0.5.17",
|
|
19
|
+
"@webority/ensemble-win-x64": "0.5.17"
|
|
20
20
|
},
|
|
21
21
|
"files": [
|
|
22
22
|
"bin",
|