@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.
Files changed (49) hide show
  1. package/CHANGELOG.md +164 -2
  2. package/README.md +1 -0
  3. package/frontend/dist/assets/index-0vuhL1YP.css +32 -0
  4. package/frontend/dist/assets/index-zjL6kZ7J.js +93 -0
  5. package/frontend/dist/index.html +2 -2
  6. package/frontend/dist/version.json +1 -1
  7. package/lib/audio/adapters.js +50 -6
  8. package/lib/cli/commands.js +40 -2
  9. package/lib/cli/doctor.js +95 -12
  10. package/lib/cli/init.js +25 -3
  11. package/lib/cli/path.js +43 -10
  12. package/lib/cli/pidfile.js +23 -2
  13. package/lib/config.js +15 -0
  14. package/lib/fleet/builtin.js +161 -19
  15. package/lib/fleet/catalogs/opencode-go.json +328 -0
  16. package/lib/fleet/cell-exec.js +87 -9
  17. package/lib/fleet/cell-lease-server.js +719 -0
  18. package/lib/fleet/cell-lease.js +112 -0
  19. package/lib/fleet/definitions.js +101 -7
  20. package/lib/fleet/launch-broker.js +115 -3
  21. package/lib/fleet/lease-client.js +191 -0
  22. package/lib/fleet/lease-routes.js +92 -0
  23. package/lib/fleet/lease-verifier.js +230 -0
  24. package/lib/fleet/managed.js +444 -55
  25. package/lib/fleet/prompt-delivery.js +50 -2
  26. package/lib/fleet/provider.js +1 -1
  27. package/lib/fleet/runtime.js +53 -6
  28. package/lib/live-host/bridge.js +369 -0
  29. package/lib/live-host/routes.js +184 -0
  30. package/lib/live-host/store.js +96 -0
  31. package/lib/mcp/tools.js +51 -0
  32. package/lib/nodes/commands.js +9 -2
  33. package/lib/nodes/store.js +14 -0
  34. package/lib/nodes/tunnel.js +4 -1
  35. package/lib/proxy/federation.js +106 -9
  36. package/lib/proxy/node-proxy.js +33 -0
  37. package/lib/proxy/panel-auth.js +307 -0
  38. package/lib/proxy/panel-proxy.js +305 -0
  39. package/lib/server.js +127 -4
  40. package/package.json +1 -1
  41. package/skills/alibaba-token-media/SKILL.md +19 -0
  42. package/skills/crew/SKILL.md +15 -0
  43. package/skills/fill-forms/SKILL.md +23 -0
  44. package/skills/mail-assistant/SKILL.md +15 -0
  45. package/skills/memory/SKILL.md +15 -0
  46. package/skills/nexuscrew-agent/SKILL.md +18 -0
  47. package/skills/vl-msa/SKILL.md +15 -0
  48. package/frontend/dist/assets/index-CYi_lhCg.css +0 -32
  49. package/frontend/dist/assets/index-_c-1_3iR.js +0 -93
@@ -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; socket.destroy();
95
- if (error) reject(error); else resolve(payload);
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 payload = await (seams.receivePayload || receivePayload)(parsed.socketPath, parsed.nonce);
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
  };