@mmmbuto/nexuscrew 0.8.57 → 0.9.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/CHANGELOG.md +164 -2
- package/README.md +1 -0
- package/frontend/dist/assets/index-0vuhL1YP.css +32 -0
- package/frontend/dist/assets/index-zjL6kZ7J.js +93 -0
- package/frontend/dist/index.html +2 -2
- package/frontend/dist/version.json +1 -1
- package/lib/audio/adapters.js +50 -6
- package/lib/cli/commands.js +40 -2
- package/lib/cli/doctor.js +95 -12
- package/lib/cli/init.js +25 -3
- package/lib/cli/path.js +43 -10
- package/lib/cli/pidfile.js +23 -2
- package/lib/config.js +15 -0
- package/lib/fleet/builtin.js +161 -19
- package/lib/fleet/catalogs/opencode-go.json +328 -0
- package/lib/fleet/cell-exec.js +87 -9
- package/lib/fleet/cell-lease-server.js +719 -0
- package/lib/fleet/cell-lease.js +112 -0
- package/lib/fleet/definitions.js +101 -7
- package/lib/fleet/launch-broker.js +115 -3
- package/lib/fleet/lease-client.js +191 -0
- package/lib/fleet/lease-routes.js +92 -0
- package/lib/fleet/lease-verifier.js +230 -0
- package/lib/fleet/managed.js +444 -55
- package/lib/fleet/prompt-delivery.js +50 -2
- package/lib/fleet/provider.js +1 -1
- package/lib/fleet/runtime.js +53 -6
- package/lib/live-host/bridge.js +369 -0
- package/lib/live-host/routes.js +184 -0
- package/lib/live-host/store.js +96 -0
- package/lib/mcp/tools.js +51 -0
- package/lib/nodes/commands.js +9 -2
- package/lib/nodes/store.js +14 -0
- package/lib/nodes/tunnel.js +4 -1
- package/lib/proxy/federation.js +106 -9
- package/lib/proxy/node-proxy.js +33 -0
- package/lib/proxy/panel-auth.js +307 -0
- package/lib/proxy/panel-proxy.js +305 -0
- package/lib/server.js +127 -4
- package/package.json +1 -1
- package/skills/alibaba-token-media/SKILL.md +19 -0
- package/skills/crew/SKILL.md +15 -0
- package/skills/fill-forms/SKILL.md +23 -0
- package/skills/mail-assistant/SKILL.md +15 -0
- package/skills/memory/SKILL.md +15 -0
- package/skills/nexuscrew-agent/SKILL.md +18 -0
- package/skills/vl-msa/SKILL.md +15 -0
- package/frontend/dist/assets/index-CYi_lhCg.css +0 -32
- package/frontend/dist/assets/index-_c-1_3iR.js +0 -93
package/lib/fleet/cell-exec.js
CHANGED
|
@@ -19,6 +19,11 @@ const DEFAULT_SUPERVISE = Object.freeze({
|
|
|
19
19
|
maxRapidRestarts: 8,
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
+
// F-B (audit 2a, correzione escalation): limite ESPLICITO fra SIGTERM e
|
|
23
|
+
// SIGKILL quando la lease e' persa (onLost, main()). Un figlio che ignora
|
|
24
|
+
// SIGTERM non deve restare appeso a tempo indeterminato.
|
|
25
|
+
const LEASE_LOST_KILL_ESCALATION_MS = 5000;
|
|
26
|
+
|
|
22
27
|
function parseArgs(argv) {
|
|
23
28
|
const out = {};
|
|
24
29
|
for (let i = 0; i < argv.length; i += 2) {
|
|
@@ -75,24 +80,40 @@ function validRestartPrompt(value) {
|
|
|
75
80
|
&& (value.readyWaitMs === undefined || validInteger(value.readyWaitMs, 0, 120000));
|
|
76
81
|
}
|
|
77
82
|
|
|
83
|
+
function validLease(value) {
|
|
84
|
+
if (value === undefined) return true;
|
|
85
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
|
|
86
|
+
// 2b (A2): la capability statica e' revocata e non esiste piu' nel payload.
|
|
87
|
+
// Il proof supervisore NON transita qui: arriva sul canale lease dal server.
|
|
88
|
+
if (Object.keys(value).some((k) => !['cellId', 'launchEpoch', 'stablePath'].includes(k))) return false;
|
|
89
|
+
return (value.cellId === undefined || (typeof value.cellId === 'string' && value.cellId.length > 0 && value.cellId.length <= 128))
|
|
90
|
+
&& typeof value.launchEpoch === 'string' && value.launchEpoch.length > 0 && value.launchEpoch.length <= 128
|
|
91
|
+
&& typeof value.stablePath === 'string' && value.stablePath.length > 0 && value.stablePath.length <= 4096;
|
|
92
|
+
}
|
|
93
|
+
|
|
78
94
|
function validPayload(payload) {
|
|
79
95
|
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return false;
|
|
80
|
-
if (Object.keys(payload).some((key) => !['command', 'args', 'env', 'supervise', 'restartPrompt'].includes(key))) return false;
|
|
96
|
+
if (Object.keys(payload).some((key) => !['command', 'args', 'env', 'supervise', 'restartPrompt', 'lease'].includes(key))) return false;
|
|
81
97
|
if (typeof payload.command !== 'string' || !payload.command || !Array.isArray(payload.args)) return false;
|
|
82
98
|
if (!payload.env || typeof payload.env !== 'object' || Array.isArray(payload.env)) return false;
|
|
83
99
|
return payload.args.every((v) => typeof v === 'string')
|
|
84
100
|
&& Object.entries(payload.env).every(([k, v]) => /^[A-Za-z_][A-Za-z0-9_]{0,63}$/.test(k) && typeof v === 'string')
|
|
85
101
|
&& validSupervise(payload.supervise)
|
|
86
|
-
&& validRestartPrompt(payload.restartPrompt)
|
|
102
|
+
&& validRestartPrompt(payload.restartPrompt)
|
|
103
|
+
&& validLease(payload.lease);
|
|
87
104
|
}
|
|
88
105
|
|
|
89
|
-
function receivePayload(socketPath, nonce, timeoutMs = 5000) {
|
|
106
|
+
function receivePayload(socketPath, nonce, timeoutMs = 5000, opts = {}) {
|
|
90
107
|
return new Promise((resolve, reject) => {
|
|
91
108
|
const socket = net.createConnection(socketPath);
|
|
92
109
|
let data = Buffer.alloc(0); let expected = null; let done = false;
|
|
93
110
|
const finish = (error, payload) => {
|
|
94
|
-
if (done) return; done = true;
|
|
95
|
-
if (error) reject(error);
|
|
111
|
+
if (done) return; done = true;
|
|
112
|
+
if (error) { socket.destroy(); reject(error); return; }
|
|
113
|
+
// R3.1.1 (opt-in): keepOpen restituisce il socket APERTO al caller (broker
|
|
114
|
+
// lease); il caller lo passa al lease-client. Default: destroy (one-shot).
|
|
115
|
+
if (opts.keepOpen) { resolve({ payload, socket }); return; }
|
|
116
|
+
socket.destroy(); resolve(payload);
|
|
96
117
|
};
|
|
97
118
|
socket.setTimeout(timeoutMs, () => finish(new Error('launch broker timed out')));
|
|
98
119
|
socket.once('connect', () => socket.write(`${JSON.stringify({ nonce })}\n`));
|
|
@@ -256,7 +277,14 @@ function startGenerationPrompt(config, generation, childState, seams = {}) {
|
|
|
256
277
|
async function main(argv = process.argv.slice(2), seams = {}) {
|
|
257
278
|
const parsed = parseArgs(argv);
|
|
258
279
|
if (!parsed) throw new Error('usage: cell-exec --socket <path> --nonce <hex>');
|
|
259
|
-
const
|
|
280
|
+
const received = await (seams.receivePayload || receivePayload)(parsed.socketPath, parsed.nonce, 5000, { keepOpen: true });
|
|
281
|
+
// Compat: il seam di test puo' restituire un payload direttamente; la forma
|
|
282
|
+
// produttiva restituisce { payload, socket } con socket APERTO (broker lease).
|
|
283
|
+
const payload = received && typeof received === 'object' && received.payload ? received.payload : received;
|
|
284
|
+
const leaseSocket = received && typeof received === 'object' && received.socket ? received.socket : null;
|
|
285
|
+
// Se il payload non porta lease (cella non-ospite), rilascia subito il socket
|
|
286
|
+
// broker: niente canale lease, e il supervisore non deve trattenere il loop.
|
|
287
|
+
if (!payload.lease && leaseSocket) { try { leaseSocket.destroy(); } catch (_) {} }
|
|
260
288
|
const supervise = normalizeSupervise(payload.supervise);
|
|
261
289
|
const spawnImpl = seams.spawn || spawn;
|
|
262
290
|
const now = seams.now || Date.now;
|
|
@@ -269,7 +297,58 @@ async function main(argv = process.argv.slice(2), seams = {}) {
|
|
|
269
297
|
if (process.env.TMUX) childEnv.TMUX = process.env.TMUX;
|
|
270
298
|
if (process.env.TMUX_PANE) childEnv.TMUX_PANE = process.env.TMUX_PANE;
|
|
271
299
|
|
|
300
|
+
// R3.1.2: nessun bearer di lease transita al child. childEnv deriva SOLO da
|
|
301
|
+
// payload.env (piu' TMUX); payload.lease (cellId/launchEpoch/stablePath) resta
|
|
302
|
+
// nel supervisore e alimenta il lease-client. spawnImpl passa env + stdio
|
|
303
|
+
// inherit. Il proof (2b) vive nel lease-client, MAI nell'env del child.
|
|
304
|
+
let generation = 0;
|
|
305
|
+
let leaseCtl = null;
|
|
306
|
+
// F-B (audit 2a): current/stopping vivono QUI (prima del lease-client) perche'
|
|
307
|
+
// la callback onLost qui sotto li riferisce dalla closure.
|
|
272
308
|
let current = null; let stopping = false;
|
|
309
|
+
if (payload.lease && leaseSocket && !leaseSocket.destroyed) {
|
|
310
|
+
const { startLeaseClient } = require('./lease-client.js');
|
|
311
|
+
leaseCtl = startLeaseClient(leaseSocket, {
|
|
312
|
+
stablePath: payload.lease.stablePath,
|
|
313
|
+
launchEpoch: payload.lease.launchEpoch,
|
|
314
|
+
// R3.3.4: la generation AVANZA coi restart del supervisore (loop sotto,
|
|
315
|
+
// generation += 1). Passiamo un getter cosicche' il reconnect presenti
|
|
316
|
+
// sempre la generation corrente, non 0 fisso (riconcilia :301 con :381).
|
|
317
|
+
generation: () => generation,
|
|
318
|
+
// F-B (audit 2a @ 142e272): lease persa per tutta la grace senza un
|
|
319
|
+
// reconnect riuscito. Prima il lease-client desisteva in silenzio dopo
|
|
320
|
+
// 60s e il child restava un orfano senza lease per tutta la sua vita.
|
|
321
|
+
// Ora il supervisore viene avvisato: ferma il child (stopping + SIGTERM)
|
|
322
|
+
// e il main loop termina con lui — mai un child vivo oltre la lease.
|
|
323
|
+
//
|
|
324
|
+
// Correzione (seconda riconsegna): un SOLO SIGTERM senza escalation
|
|
325
|
+
// lascia appeso un child che lo ignora (misurato sul percorso reale:
|
|
326
|
+
// {spawned:1, kills:['SIGTERM'], state:'pending'}) — l'orfano non NASCE
|
|
327
|
+
// piu' (F-B originale chiuso davvero), ma un figlio gia' vivo che non
|
|
328
|
+
// collabora non muore. LEASE_LOST_KILL_ESCALATION_MS e' il limite
|
|
329
|
+
// ESPLICITO, scritto qui: se il child non e' uscito entro questa
|
|
330
|
+
// finestra dal SIGTERM, si passa a SIGKILL. `target` fissa il processo
|
|
331
|
+
// di QUESTA generazione: se nel frattempo e' gia' uscito (current
|
|
332
|
+
// azzerato a null dal loop principale dopo waitChild), l'escalation e'
|
|
333
|
+
// no-op — mai un kill fantasma su un pid riusato.
|
|
334
|
+
onLost: () => {
|
|
335
|
+
writeError('nexuscrew cell supervisor stopped: lease lost (reconnect grace expired)\n');
|
|
336
|
+
stopping = true;
|
|
337
|
+
const target = current;
|
|
338
|
+
try { if (target) target.kill('SIGTERM'); } catch (_) {}
|
|
339
|
+
if (target) {
|
|
340
|
+
const escalate = seams.setTimeout || setTimeout;
|
|
341
|
+
const timer = escalate(() => {
|
|
342
|
+
if (current === target) {
|
|
343
|
+
try { target.kill('SIGKILL'); } catch (_) {}
|
|
344
|
+
}
|
|
345
|
+
}, LEASE_LOST_KILL_ESCALATION_MS);
|
|
346
|
+
if (timer && typeof timer.unref === 'function') timer.unref();
|
|
347
|
+
}
|
|
348
|
+
},
|
|
349
|
+
}, seams);
|
|
350
|
+
}
|
|
351
|
+
|
|
273
352
|
const handlers = new Map();
|
|
274
353
|
for (const signal of ['SIGTERM', 'SIGINT', 'SIGHUP']) {
|
|
275
354
|
const handler = () => {
|
|
@@ -283,7 +362,6 @@ async function main(argv = process.argv.slice(2), seams = {}) {
|
|
|
283
362
|
for (const [signal, handler] of handlers) proc.off?.(signal, handler);
|
|
284
363
|
};
|
|
285
364
|
|
|
286
|
-
let generation = 0;
|
|
287
365
|
let delayMs = supervise.restartDelayMs;
|
|
288
366
|
let rapid = [];
|
|
289
367
|
try {
|
|
@@ -343,7 +421,7 @@ async function main(argv = process.argv.slice(2), seams = {}) {
|
|
|
343
421
|
delayMs = Math.min(supervise.maxRestartDelayMs, Math.max(supervise.restartDelayMs, delayMs * 2));
|
|
344
422
|
generation += 1;
|
|
345
423
|
}
|
|
346
|
-
} finally { cleanup(); }
|
|
424
|
+
} finally { if (leaseCtl) { try { leaseCtl.stop(); } catch (_) {} } cleanup(); }
|
|
347
425
|
}
|
|
348
426
|
|
|
349
427
|
if (require.main === module) {
|
|
@@ -353,6 +431,6 @@ if (require.main === module) {
|
|
|
353
431
|
}
|
|
354
432
|
|
|
355
433
|
module.exports = {
|
|
356
|
-
DEFAULT_SUPERVISE, parseArgs, validSupervise, validRestartPrompt, validPayload,
|
|
434
|
+
DEFAULT_SUPERVISE, LEASE_LOST_KILL_ESCALATION_MS, parseArgs, validSupervise, validRestartPrompt, validPayload, validLease,
|
|
357
435
|
receivePayload, sanitizeSpawnError, normalizeSupervise, waitChild, startGenerationPrompt, main,
|
|
358
436
|
};
|