@mmmbuto/nexuscrew 0.9.29 → 0.9.31

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 (78) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/docs/LIVE_PROMPT.md +1 -1
  3. package/docs/NOTIFICATIONS.md +37 -0
  4. package/frontend/dist/assets/index-DaAL7P-m.js +167 -0
  5. package/frontend/dist/assets/{index-CywSos2e.css → index-DqCfZFGi.css} +1 -1
  6. package/frontend/dist/index.html +2 -2
  7. package/frontend/dist/sw.js +91 -10
  8. package/frontend/dist/version.json +1 -1
  9. package/lib/audio/groups.js +1 -1
  10. package/lib/auth/token.js +4 -4
  11. package/lib/cli/commands.js +59 -12
  12. package/lib/cli/doctor.js +36 -7
  13. package/lib/cli/fleet-service.js +7 -7
  14. package/lib/cli/init.js +27 -27
  15. package/lib/cli/pidfile.js +1 -1
  16. package/lib/cli/service.js +6 -6
  17. package/lib/cli/stable-alias.js +4 -4
  18. package/lib/cli/url.js +1 -1
  19. package/lib/config.js +36 -6
  20. package/lib/decks/routes.js +11 -1
  21. package/lib/files/routes.js +1 -1
  22. package/lib/fleet/boot.js +2 -2
  23. package/lib/fleet/builtin.js +28 -19
  24. package/lib/fleet/causes.js +1 -1
  25. package/lib/fleet/cell-exec.js +23 -23
  26. package/lib/fleet/cell-hold.js +6 -6
  27. package/lib/fleet/cell-lease-server.js +106 -106
  28. package/lib/fleet/cell-lease.js +21 -21
  29. package/lib/fleet/definitions.js +21 -13
  30. package/lib/fleet/identity-authority.js +1 -1
  31. package/lib/fleet/launch-broker.js +13 -13
  32. package/lib/fleet/launch.js +15 -15
  33. package/lib/fleet/lease-client.js +23 -23
  34. package/lib/fleet/lease-routes.js +3 -3
  35. package/lib/fleet/lease-verifier.js +22 -22
  36. package/lib/fleet/managed.js +46 -36
  37. package/lib/fleet/prompt-delivery.js +13 -13
  38. package/lib/fleet/routes.js +15 -10
  39. package/lib/fleet/runtime.js +26 -26
  40. package/lib/live-host/bridge.js +36 -36
  41. package/lib/live-host/routes.js +2 -2
  42. package/lib/live-host/store.js +4 -4
  43. package/lib/mcp/server.js +14 -14
  44. package/lib/mcp/tools.js +5 -5
  45. package/lib/nodes/access-presets.js +258 -0
  46. package/lib/nodes/commands.js +198 -4
  47. package/lib/nodes/store.js +152 -9
  48. package/lib/nodes/tunnel.js +7 -7
  49. package/lib/notify/ask-answer-service.js +184 -0
  50. package/lib/notify/ask-receipts.js +197 -0
  51. package/lib/notify/ask-relay.js +152 -0
  52. package/lib/notify/asks.js +28 -3
  53. package/lib/notify/event-feed-acl.js +95 -0
  54. package/lib/notify/event-feed-asks-routes.js +144 -0
  55. package/lib/notify/event-feed-client.js +434 -0
  56. package/lib/notify/event-feed-history.js +115 -0
  57. package/lib/notify/event-feed-producers.js +175 -0
  58. package/lib/notify/event-feed-routes.js +254 -0
  59. package/lib/notify/event-feed.js +328 -0
  60. package/lib/notify/events.js +1 -1
  61. package/lib/notify/notifier.js +3 -3
  62. package/lib/notify/push-relay.js +240 -0
  63. package/lib/notify/push.js +8 -8
  64. package/lib/notify/routes.js +62 -23
  65. package/lib/proxy/federation.js +74 -3
  66. package/lib/proxy/node-proxy.js +13 -13
  67. package/lib/proxy/panel-auth.js +4 -4
  68. package/lib/proxy/panel-proxy.js +2 -2
  69. package/lib/proxy/resource-acl.js +145 -0
  70. package/lib/server.js +372 -39
  71. package/lib/settings/pairing-coordinator.js +1 -1
  72. package/lib/settings/routes.js +45 -32
  73. package/lib/update/manager.js +1 -1
  74. package/lib/update/runner.js +1 -1
  75. package/lib/voice/transcribe.js +1 -1
  76. package/lib/ws/bridge.js +3 -3
  77. package/package.json +1 -1
  78. package/frontend/dist/assets/index-B_THfiL5.js +0 -167
package/lib/config.js CHANGED
@@ -17,7 +17,7 @@ function baseDefaults() {
17
17
  return {
18
18
  bind: '127.0.0.1',
19
19
  port: 41820,
20
- // Seconda porta loopback, SOLO per /panel/* (P0 sicurezza 2026-08-16):
20
+ // Seconda porta loopback, SOLO per /panel/* (sicurezza 2026-08-16):
21
21
  // un pannello vive su un'origin diversa dal control plane — la porta fa
22
22
  // parte dell'origin, quindi un iframe cross-porta non legge il
23
23
  // localStorage del padre, qualunque JS esegua il container. Nessuna API
@@ -57,14 +57,14 @@ function baseDefaults() {
57
57
  // Il manager aggiorna solo verso una semver superiore: mai downgrade.
58
58
  autoUpdate: true,
59
59
  sessionPresets: {},
60
- // Ponte Live (fetta 3, rev5 MC0): isolabile — a false il ponte non si
60
+ // Ponte Live (fetta 3, rev5): isolabile — a false il ponte non si
61
61
  // connette mai e ogni avvio di Live resta sul comportamento standard.
62
62
  liveBridgeEnabled: true,
63
63
  // Socket di controllo dell'app-server (misurato 2026-08-15:
64
64
  // $CODEX_HOME/app-server-control/app-server-control.sock, WebSocket sopra
65
65
  // unix socket). Non cablare mai una porta al posto di questo path.
66
66
  liveBridgeSocketPath: path.join(os.homedir(), '.codex', 'app-server-control', 'app-server-control.sock'),
67
- // MC1.5: limite dichiarato per OGNI fase del ponte (GET designazione e
67
+ // Limite dichiarato per OGNI fase del ponte (GET designazione e
68
68
  // sessione sul socket). Oltre questo la Live parte senza puntamento.
69
69
  liveBridgeTimeoutMs: 1500,
70
70
  // PTY grace is finite and configurable for mobile handovers. The caps keep
@@ -82,7 +82,37 @@ function configJsonPath() {
82
82
  return process.env.NEXUSCREW_CONFIG_FILE || path.join(os.homedir(), '.nexuscrew', 'config.json');
83
83
  }
84
84
 
85
- // Legge ~/.nexuscrew/config.json se esiste (nuovo, B2). {} se assente/malformato.
85
+ // Legge ~/.nexuscrew/config.json se esiste (nuovo). {} se assente/malformato.
86
+ // federation.events.enabled — the node-level kill switch for the event feed.
87
+ // STRICT boolean: only the literal false disables. A truthy string or any
88
+ // other type never enables anything beyond the default, and never disables by
89
+ // accident: "false" (string) is not a decision, it is noise, and the default
90
+ // holds. Without explicit grants nothing is enabled either way — the switch
91
+ // only stops what grants already allow.
92
+ function eventsEnabledFlag(raw, log = console.warn) {
93
+ // Fail-closed strictness: only the literal true/false is a decision. A
94
+ // missing value keeps the default (enabled); ANY other type — "false" as a
95
+ // string, 0, null, an object — is invalid config, is treated as DISABLED and
96
+ // is reported: silently enabling on garbage is how switches lie.
97
+ if (raw === undefined) return true;
98
+ if (raw === true) return true;
99
+ if (raw === false) return false;
100
+ try { log('config: federation.events.enabled must be a boolean; invalid value treated as disabled'); } catch (_) {}
101
+ return false;
102
+ }
103
+
104
+ function readFederationEventsEnabled(opts = {}) {
105
+ if (typeof opts.federationEventsEnabled === 'boolean') return opts.federationEventsEnabled;
106
+ let cfg = {};
107
+ try {
108
+ const raw = fs.readFileSync(opts.configJsonPath || configJsonPath(), 'utf8');
109
+ cfg = JSON.parse(raw) || {};
110
+ } catch (_) { cfg = {}; }
111
+ const fed = cfg && typeof cfg.federation === 'object' && cfg.federation ? cfg.federation : null;
112
+ const events = fed && typeof fed.events === 'object' && fed.events ? fed.events : null;
113
+ return eventsEnabledFlag(events ? events.enabled : undefined);
114
+ }
115
+
86
116
  function readConfigJson() {
87
117
  try {
88
118
  const raw = fs.readFileSync(configJsonPath(), 'utf8');
@@ -134,7 +164,7 @@ function envOverrides() {
134
164
  return e;
135
165
  }
136
166
 
137
- // Precedence: baseDefaults < config.json < env < opts. [B2]
167
+ // Precedence: baseDefaults < config.json < env < opts.
138
168
  // Le credenziali authority vivono in due file 0600 nella dir
139
169
  // dell'authority (default ~/.nexuscrew/identity-authority). Se esistono vengono
140
170
  // caricate qui, senza MAI loggare i valori: builtin costruisce l'authority
@@ -195,4 +225,4 @@ function defaults() {
195
225
  return { ...baseDefaults(), ...envOverrides() };
196
226
  }
197
227
 
198
- module.exports = { assertLoopback, baseDefaults, readConfigJson, loadConfig, defaults, LOOPBACK, configJsonPath };
228
+ module.exports = { assertLoopback, baseDefaults, readConfigJson, loadConfig, defaults, LOOPBACK, configJsonPath, eventsEnabledFlag, readFederationEventsEnabled };
@@ -29,7 +29,17 @@ function decksRoutes({ cfg = {}, decksPath }) {
29
29
  };
30
30
  const find = (st, name) => st.decks.find((d) => d.name === name) || null;
31
31
  const checkRevision = (deck, rev) => {
32
- if (deck.revision !== rev) throw httpError(409, 'deck modificato da un’altra finestra', { current: deck });
32
+ if (deck.revision !== rev) {
33
+ // Traccia il conflitto di revisione con i campi che servono a
34
+ // capire chi lo ha causato (nessun merge lato server in questo ciclo).
35
+ console.log(JSON.stringify({
36
+ event: 'deck-revision-conflict',
37
+ name: deck.name,
38
+ expectedRevision: rev,
39
+ currentRevision: deck.revision,
40
+ }));
41
+ throw httpError(409, 'deck modificato da un’altra finestra', { current: deck });
42
+ }
33
43
  };
34
44
 
35
45
  r.get('/', (_req, res) => {
@@ -55,7 +55,7 @@ function filesRoutes({
55
55
  // paste=false (tasto allegati del composer): il client appende il path al
56
56
  // testo del composer — niente scrittura PTY. Default = incolla (FilesPanel).
57
57
  const wantPaste = String((req.body && req.body.paste) || '') !== 'false';
58
- // R31-A2: paste RICHIESTO ma non riuscito non e' un 200 con pasted:false —
58
+ // : paste RICHIESTO ma non riuscito non e' un 200 con pasted:false —
59
59
  // tre cause collassavano in quell'unica etichetta muta (non richiesto /
60
60
  // tmux non ha ricevuto / testo respinto) e nessuno, risposta o UI, poteva
61
61
  // accorgersene. Il vocabolario e' quello del ramo answer degli ask
package/lib/fleet/boot.js CHANGED
@@ -1,9 +1,9 @@
1
1
  'use strict';
2
- // B4.3 — Service companion boot. bootCells(fleet, {log}) avvia tutte le celle
2
+ // Fleet boot companion: bootCells(fleet, {log}) starts the boot:true cells
3
3
  // boot:true di un fleet provider (builtin) chiamandone up(). Una cella gia' in
4
4
  // esecuzione (up -> httpError 409 / "duplicate session") = SKIP non fatale;
5
5
  // ogni altro errore viene raccolto in failed[] e NON ferma le altre celle.
6
- // Nessun side-effect all'import. Design §4c / §9b.
6
+ // No side effects on import.
7
7
  //
8
8
  // Agnostico: usa solo il contratto fleet {available, status(), up()} — non legge
9
9
  // internals delle definizioni. La lista delle celle boot:true arriva da status()
@@ -2,7 +2,7 @@
2
2
  // Fleet built-in. FACADE: espone available/status/up/down/engine/boot e
3
3
  // isCellSession leggendo le definizioni da ~/.nexuscrew/fleet.json tramite
4
4
  // lib/fleet/definitions.js, insieme a define*/edit*/remove* +
5
- // schema + capabilities (design §4b/§9c).
5
+ // schema + capabilities.
6
6
  //
7
7
  // La responsabilita' runtime/launch (status/up/down/restart + launch/readiness
8
8
  // helpers) e' stata estratta in modo behavior-preserving in:
@@ -16,16 +16,16 @@
16
16
  // Agnostico: non conosce "claude"/"glm"/"codex" — lancia solo command+args
17
17
  // dichiarati.
18
18
  //
19
- // Sicurezza (design §6 / §9a / §9d / §9e) — invariata, vedi launch.js/runtime.js:
19
+ // Security model — unchanged, see launch.js/runtime.js:
20
20
  // - up esegue validateCommandTrust(command) e resolveCwd(cwd) PRIMA di lanciare;
21
21
  // una cella/engine mancante o non trusted -> httpError(400), NON lancia nulla.
22
22
  // - command/args/env NON passano per una shell: execFile + argv diretto.
23
23
  // - env: il builtin lancia con un env MINIMALE controllato dal service; engine.env
24
24
  // raggiunge il client solo tramite un broker AF_UNIX privato e monouso.
25
- // - READONLY (cfg.readonlyDefault===true | NEXUSCREW_READONLY=1) blocca ogni
26
- // mutazione fleet e ogni up (§9d): passano solo status/schema/capabilities.
27
- // - promptMode 'send-keys' inietta via bracketed paste; se il command e' gia'
28
- // uscito (sessione morta) NON digita (§9e).
25
+ // - READONLY (cfg.readonlyDefault===true | NEXUSCREW_READONLY=1) blocks every
26
+ // fleet mutation and every up: only status/schema/capabilities pass.
27
+ // - promptMode 'send-keys' injects via bracketed paste; when the command already
28
+ // exited (dead session) it does not type.
29
29
  const fs = require('node:fs');
30
30
  const os = require('node:os');
31
31
  const path = require('node:path');
@@ -37,7 +37,7 @@ const {
37
37
  } = require('./definitions.js');
38
38
  const {
39
39
  publicCatalog, describeManaged, describeCatalogCredential, defaultShellEngine, defaultAgyEngine, defaultKimiEngine, defaultGrokEngine, defaultVlEngine,
40
- CATALOG, credential: resolveProfileCredential, extraModelsFrom, knownMcpServerNames,
40
+ CATALOG, credential: resolveProfileCredential, extraModelsFrom, knownMcpServerNames, profileFor,
41
41
  } = require('./managed.js');
42
42
  const { probeModel } = require('./model-probe.js');
43
43
  const { validEnvKey } = require('./env-key.js');
@@ -125,7 +125,7 @@ function backfillShellEngine(defsPath, defs, log) {
125
125
  return esito || defs;
126
126
  }
127
127
 
128
- // Backfill platform-aware dell'engine Agy primario (design §4.2): installazioni
128
+ // Platform-aware backfill of the primary agy engine (idempotent, non-destructive).
129
129
  // esistenti su Linux/macOS non-Termux ricevono agy.native in modo idempotente e
130
130
  // non distruttivo. Su altre piattaforme (Termux/Windows) e' un no-op: agy non e'
131
131
  // un client primario supportato li' e l'utente puo comunque usarlo via shell.local
@@ -453,7 +453,7 @@ function applyCellTransition(target, engineId, { model, hasModel, policy, hasPol
453
453
  }
454
454
 
455
455
  // ---------------------------------------------------------------------------
456
- // cwd portabile (design §4.3). resolveCellCwd riconcilia cwd (assoluta) e
456
+ // Portable cwd: resolveCellCwd reconciles the absolute cwd and cwdRel before every write.
457
457
  // cwdRel (home-relative) in una COPPIA COERENTE prima di ogni scrittura:
458
458
  // - solo cwd (assoluta valida) -> deriva cwdRel
459
459
  // - solo cwdRel (valida) -> calcola la cwd assoluta target
@@ -582,7 +582,7 @@ async function createBuiltinFleet(cfg = {}) {
582
582
  const off = {
583
583
  available: false, provider: 'builtin',
584
584
  isCellSession: () => false, capabilities: () => [],
585
- // B4: close() sempre sicura su qualunque Fleet restituita, anche unavailable:
585
+ // Close sempre sicura su qualunque Fleet restituita, anche unavailable:
586
586
  // il caller non deve distinguere `off` dalla Fleet reale.
587
587
  close: async () => {},
588
588
  };
@@ -609,7 +609,7 @@ async function createBuiltinFleet(cfg = {}) {
609
609
  // early return segue la creazione del manager, e la creazione avviene dopo tutti i
610
610
  // gate noti. Manternerla richiede che ogni nuovo gate resti qui sopra.
611
611
 
612
- // Bootstrap: fleet.json valido? garbage -> unavailable (fail-closed, design §7).
612
+ // Bootstrap: a valid fleet.json? Garbage -> unavailable (fail-closed).
613
613
  // ENOENT (missing) o parse invalido -> off (unavailable legittimo, fail-closed);
614
614
  // EACCES/ELOOP sul lstat (file presente ma non verificabile) -> off CON reason,
615
615
  // perche' "non ho potuto guardare" e' un problema diverso da "non c'e'" o "e'
@@ -733,8 +733,8 @@ async function createBuiltinFleet(cfg = {}) {
733
733
  identityAuthority,
734
734
  identityDaemonCredential: cfg.identityDaemonCredential,
735
735
  identityLauncherCredential: cfg.identityLauncherCredential,
736
- // R3.1.1: la connessione broker one-shot resta APERTA dopo il payload e diviene
737
- // il canale lease del supervisore.
736
+ // The one-shot broker connection stays OPEN after the payload and becomes
737
+ // the supervisor lease channel.
738
738
  ...(leaseManager ? {
739
739
  onLease: (socket, lease) => {
740
740
  // da revisione interna: attachInitial restituisce false se la
@@ -874,9 +874,18 @@ async function createBuiltinFleet(cfg = {}) {
874
874
  }
875
875
  // Se un engine sta USANDO questo modello, toglierlo lo renderebbe invalido
876
876
  // al prossimo avvio — e il messaggio arriverebbe a chi non sa cosa e'
877
- // cambiato. Meglio rifiutare adesso, dicendo chi lo usa.
877
+ // cambiato. Meglio rifiutare adesso, dicendo chi lo usa. Un engine risolve
878
+ // il modello dalla dichiarazione fatta sul SUO id o sul suo profilo
879
+ // (declaredFor cerca in [engineId, profileId]): il ritiro di
880
+ // {wantedId, wantedEngine} lo rompe solo se wantedEngine e' una di quelle
881
+ // chiavi — un altro engine con la propria dichiarazione non c'entra.
878
882
  const usato = (defs.engines || [])
879
- .filter((e) => e.managed && e.managed.model === wantedId)
883
+ .filter((e) => {
884
+ if (!e.managed || e.managed.model !== wantedId) return false;
885
+ if (e.id === wantedEngine) return true;
886
+ const profile = profileFor(e.managed.client, e.managed.provider, e.managed.credentialProfile || '');
887
+ return !!(profile && profile.id === wantedEngine);
888
+ })
880
889
  .map((e) => e.id);
881
890
  if (usato.length) {
882
891
  throw httpError(409, `modello in uso: ${usato.join(', ')}`);
@@ -1264,7 +1273,7 @@ async function createBuiltinFleet(cfg = {}) {
1264
1273
  engines: defs.engines.map((e) => {
1265
1274
  const out = { ...e, envKeys: Object.keys(e.env || {}).sort() };
1266
1275
  delete out.env;
1267
- if (e.managed) out.managedInfo = describeManaged(e.managed, { ...cfg, home, extraModels: extraModelsFrom(defs) });
1276
+ if (e.managed) out.managedInfo = describeManaged(e.managed, { ...cfg, home, extraModels: extraModelsFrom(defs), engineId: e.id });
1268
1277
  return out;
1269
1278
  }),
1270
1279
  cells: defs.cells.map((c) => {
@@ -1363,7 +1372,7 @@ async function createBuiltinFleet(cfg = {}) {
1363
1372
  return credentialStatus();
1364
1373
  }
1365
1374
 
1366
- // schema() — descrittore campi per editor UI schema-driven (design §5/§9f).
1375
+ // schema() — field descriptor for the schema-driven UI editor.
1367
1376
  function schema() {
1368
1377
  return {
1369
1378
  schemaVersion: 1,
@@ -1482,8 +1491,8 @@ async function createBuiltinFleet(cfg = {}) {
1482
1491
  schema, definitions, capabilities,
1483
1492
  credentialStatus, setLocalCredential, removeLocalCredential, testModel,
1484
1493
  defineModel, removeModel, close,
1485
- // Fetta 2b (D3): il lease Live esposto alle route /api/lease, che il bridge
1486
- // MCP delle celle consuma per register/refresh/recovery (B5). Assente se il
1494
+ // Fetta 2b: il lease Live esposto alle route /api/lease, che il bridge
1495
+ // MCP delle celle consuma per register/refresh/recovery. Assente se il
1487
1496
  // lease e' disattivato: le route rispondono 501, non 500.
1488
1497
  ...(leaseManager ? { lease: leaseManager } : {}),
1489
1498
  ...(identityAuthority ? { identityAuthority } : {}),
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- // Cause-preserving diagnostics for Fleet up() failures (T4).
3
+ // Cause-preserving diagnostics for Fleet up failures.
4
4
  //
5
5
  // The up() path of the built-in fleet crosses five boundaries:
6
6
  // 1. preflight gates (command trust, cwd, managed-engine config)
@@ -101,7 +101,7 @@ function validIdentity(value) {
101
101
  function validLease(value) {
102
102
  if (value === undefined) return true;
103
103
  if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
104
- // 2b (A2): la capability statica e' revocata e non esiste piu' nel payload.
104
+ // 2b: la capability statica e' revocata e non esiste piu' nel payload.
105
105
  // Il proof supervisore NON transita qui: arriva sul canale lease dal server.
106
106
  if (Object.keys(value).some((k) => !['cellId', 'launchEpoch', 'stablePath'].includes(k))) return false;
107
107
  return (value.cellId === undefined || (typeof value.cellId === 'string' && value.cellId.length > 0 && value.cellId.length <= 128))
@@ -134,8 +134,8 @@ function receivePayload(socketPath, nonce, timeoutMs = 5000, opts = {}) {
134
134
  const finish = (error, payload) => {
135
135
  if (done) return; done = true;
136
136
  if (error) { socket.destroy(); reject(error); return; }
137
- // R3.1.1 (opt-in): keepOpen restituisce il socket APERTO al caller (broker
138
- // lease); il caller lo passa al lease-client. Default: destroy (one-shot).
137
+ // Opt-in keepOpen: the caller receives the OPEN socket (lease broker)
138
+ // and passes it to the lease-client. Default: destroy (one-shot).
139
139
  if (opts.keepOpen) { resolve({ payload, socket }); return; }
140
140
  socket.destroy(); resolve(payload);
141
141
  };
@@ -197,7 +197,7 @@ function validIdentityRequest(msg) {
197
197
  }
198
198
 
199
199
  // schema chiuso del metodo verify (v1). Il proof viene VERIFICATO
200
- // online dall'authority (C2): il daemon non valuta nulla da solo.
200
+ // Online dall'authority: il daemon non valuta nulla da solo.
201
201
  function validVerifyRequest(msg, channelGeneration) {
202
202
  if (msg.method !== 'nexuscrew/identity/verify') return false;
203
203
  const params = msg.params;
@@ -272,7 +272,7 @@ function createIdentityChannel({
272
272
  const reason = outcome && typeof outcome.reason === 'string' ? outcome.reason : 'identity-unverified';
273
273
  const code = identityErrorCode(reason);
274
274
  sendError(current.rpcId, code, reason);
275
- // C7-bis: il canale e' terminato su timeout OLTRE che su revoke — dopo la
275
+ // Bis: il canale e' terminato su timeout OLTRE che su revoke — dopo la
276
276
  // risposta di errore fd4 va in EOF (la TUI vede il canale chiuso).
277
277
  if (code === 'REVOKED' || reason === 'timeout') close(code, reason, current.rpcId, false);
278
278
  };
@@ -397,7 +397,7 @@ function waitChild(child) {
397
397
  });
398
398
  }
399
399
 
400
- // Delivery del bootstrap prompt per generazione (0.8.47, R2/R3).
400
+ // Delivery del bootstrap prompt per generazione (0.8.47, /).
401
401
  //
402
402
  // OWNER UNICO: per gli engine managed Kimi (client 'kimi'/'claude') il
403
403
  // supervisore consegna il prompt per TUTTE le generazioni, gen0 compresa —
@@ -408,7 +408,7 @@ function waitChild(child) {
408
408
  // massimo una nuova consegna. Engine custom send-keys: contratto legacy
409
409
  // invariato (gen0 = runtime.injectPrompt; qui solo gen>0, paste senza Enter).
410
410
  //
411
- // CANCELLAZIONE (R3): cancel() ferma il timer E il polling in volo tramite
411
+ // CANCELLAZIONE: cancel ferma il timer E il polling in volo tramite
412
412
  // isCancelled valutato dentro deliverBootstrapPrompt (ogni poll, pre-paste,
413
413
  // pre-Enter); il chiamante fa cancel() + await settled prima dello spawn
414
414
  // della generazione successiva: zero paste/Enter da un task della generazione
@@ -418,15 +418,15 @@ function waitChild(child) {
418
418
  // (solo enum chiusi; l'opzione muore col pane, nessuno state file). Best
419
419
  // effort: un set-option fallito lascia up() in report-timeout (onesto), la
420
420
  // consegna resta fatta.
421
- // CANCELLAZIONE (R3+R8): cancel() ferma il timer E il polling in volo tramite
421
+ // CANCELLAZIONE (+): cancel ferma il timer E il polling in volo tramite
422
422
  // isCancelled valutato dentro deliverBootstrapPrompt (ogni poll, pre-paste,
423
423
  // pre-Enter, post-Enter); il chiamante fa cancel() + await settled prima dello
424
424
  // spawn della generazione successiva. cancel() con timer ANCORA PENDENTE
425
425
  // (child uscito prima di readyMs, caso early-exit) risolve settled SUBITO:
426
- // mai deadlock (R8). settled risolve con l'esito della delivery: null se
426
+ // Mai deadlock. settled risolve con l'esito della delivery: null se
427
427
  // nessun paste incerto, {state} se la generazione e' terminata con un
428
428
  // post-paste incerto (delivery-unknown / staged-not-submitted) — il main loop
429
- // NON auto-restarta in quel caso (R9: byte potenzialmente residui nel PTY del
429
+ // NON auto-restarta in quel caso (leftover bytes potenzialmente residui nel PTY del
430
430
  // pane riusato; fermo bounded + restart operatore).
431
431
  function startGenerationPrompt(config, generation, childState, seams = {}) {
432
432
  if (!config) return null;
@@ -474,7 +474,7 @@ function startGenerationPrompt(config, generation, childState, seams = {}) {
474
474
  const state = result && typeof result.state === 'string' ? result.state : '';
475
475
  const uncertain = state === 'delivery-unknown' || state === 'staged-not-submitted';
476
476
  if (isCancelled()) {
477
- // R9: la generazione e' finita. Propago SOLO gli esiti post-paste
477
+ // La generazione e' finita. Propago SOLO gli esiti post-paste
478
478
  // incerti (residuo PTY possibile); cancelled pre-paste e' pulito.
479
479
  return uncertain ? { state } : null;
480
480
  }
@@ -482,7 +482,7 @@ function startGenerationPrompt(config, generation, childState, seams = {}) {
482
482
  const kind = state === 'skipped-not-ready' && result.notReady ? `:${result.notReady}` : '';
483
483
  await markDelivery(`${state}${kind}`);
484
484
  }
485
- // R12: l'esito post-paste incerto va conservato ANCHE se il child
485
+ // L'esito post-paste incerto va conservato ANCHE se il child
486
486
  // era vivo al ritorno di deliver: i byte possono restare nel PTY del
487
487
  // pane e sommarsi al bootstrap della generazione successiva. Al
488
488
  // prossimo child exit il main ferma il supervisor (no auto-restart).
@@ -501,7 +501,7 @@ function startGenerationPrompt(config, generation, childState, seams = {}) {
501
501
  timer.unref?.();
502
502
  return {
503
503
  settled,
504
- // R8: timer ancora pendente -> settle IMMEDIATO (idempotente): il main
504
+ // Timer ancora pendente -> settle IMMEDIATO (idempotente): il main
505
505
  // loop non resta mai appeso su un child uscito prima di readyMs.
506
506
  cancel() {
507
507
  cancelled = true;
@@ -542,10 +542,10 @@ async function main(argv = process.argv.slice(2), seams = {}) {
542
542
  if (process.env.TMUX) childEnv.TMUX = process.env.TMUX;
543
543
  if (process.env.TMUX_PANE) childEnv.TMUX_PANE = process.env.TMUX_PANE;
544
544
 
545
- // R3.1.2: nessun bearer di lease transita al child. childEnv deriva SOLO da
546
- // payload.env (piu' TMUX); payload.lease (cellId/launchEpoch/stablePath) resta
547
- // nel supervisore e alimenta il lease-client. spawnImpl passa env + stdio
548
- // inherit. Il proof (2b) vive nel lease-client, MAI nell'env del child.
545
+ // No lease bearer ever reaches the child. childEnv derives ONLY from
546
+ // payload.env (plus TMUX); payload.lease (cellId/launchEpoch/stablePath) stays
547
+ // in the supervisor and feeds the lease-client. spawnImpl passes env + stdio
548
+ // inherit. The proof lives in the lease-client, NEVER in the child env.
549
549
  let generation = 0;
550
550
  let leaseCtl = null;
551
551
  // da revisione interna: current/stopping vivono QUI (prima del lease-client) perche'
@@ -556,9 +556,9 @@ async function main(argv = process.argv.slice(2), seams = {}) {
556
556
  leaseCtl = startLeaseClient(leaseSocket, {
557
557
  stablePath: payload.lease.stablePath,
558
558
  launchEpoch: payload.lease.launchEpoch,
559
- // R3.3.4: la generation AVANZA coi restart del supervisore (loop sotto,
560
- // generation += 1). Passiamo un getter cosicche' il reconnect presenti
561
- // sempre la generation corrente, non 0 fisso (riconcilia :301 con :381).
559
+ // The generation ADVANCES with supervisor restarts (loop below,
560
+ // generation += 1). We pass a getter so the reconnect always presents
561
+ // the current generation, not a fixed 0 (reconciles :301 with :381).
562
562
  generation: () => generation,
563
563
  onIdentityDown: () => closeIdentityChannel('REVOKED', 'lease unavailable'),
564
564
  // da revisione interna: lease persa per tutta la grace senza un
@@ -647,7 +647,7 @@ async function main(argv = process.argv.slice(2), seams = {}) {
647
647
  resolve({ code: 1, signal: null, error });
648
648
  });
649
649
  });
650
- // R1: transizione di generazione comunicata al lease PRIMA di aprire il
650
+ // Transizione di generazione comunicata al lease PRIMA di aprire il
651
651
  // canale identita' della generazione nuova. L'exit del figlio ha la
652
652
  // precedenza sull'annuncio; FAIL-CLOSED (R1b): solo un annuncio
653
653
  // RISOLTO con ok === true E generazione corrispondente apre il canale —
@@ -719,8 +719,8 @@ async function main(argv = process.argv.slice(2), seams = {}) {
719
719
  childState.exited = true;
720
720
  closeIdentityChannel();
721
721
  current = null;
722
- // R2/R3: la generazione e' finita. Cancella la delivery in volo e ATTESA
723
- // del suo termine PRIMA di qualunque nuovo spawn. R9: se l'esito e' un
722
+ // : la generazione e' finita. Cancella la delivery in volo e ATTESA
723
+ // Del suo termine PRIMA di qualunque nuovo spawn.: se l'esito e' un
724
724
  // post-paste incerto (delivery-unknown / staged-not-submitted) i byte del
725
725
  // prompt possono essere residui nel PTY del pane riusato: NIENTE auto-
726
726
  // restart — fermo bounded del supervisor, restart esplicito operatore.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
  // lib/fleet/cell-hold.js — placeholder inerte per l'avvio staged del pane Fleet.
4
- // (NexusCrew 0.8.31 — design piano §3.3: identita tmux sicura)
4
+ // (tmux-safe identity rules: no interactive shell)
5
5
  //
6
6
  // Resta in attesa finche' `respawn-pane -k` non lo sostituisce con il vero client
7
7
  // (cell-exec). Crea il pane e la finestra in modo deterministico cosi' il runtime
@@ -9,11 +9,11 @@
9
9
  // cio' impedisce a un child rapido di chiudere la sessione durante il setup. La
10
10
  // normalizzazione dei punti nei nomi tmux e gestita separatamente dal mapping v2.
11
11
  //
12
- // Vincoli (piano §3.3): NESSUNA shell interattiva, NESSUN rc/alias/plugin utente,
13
- // NESSUN dato sensibile, NESSUN argomento interpretato. argv diretto
14
- // (process.execPath + path assoluto di questo file), puramente bloccante. Non e'
15
- // il comando reale della cella: command/env/prompt/payload broker non appaiono
16
- // mai nell'argv tmux.
12
+ // Constraints: NO interactive shell, NO user rc/alias/plugin sourcing,
13
+ // NO sensitive data, NO interpreted arguments. Direct argv
14
+ // (process.execPath + absolute path of this file), purely blocking. It is not
15
+ // the real cell command: command/env/prompt/payload broker never appear
16
+ // in the tmux argv.
17
17
  //
18
18
  // Uscita silenziosa sul segnale di terminazione inviato da respawn-pane -k.
19
19