@mmmbuto/nexuscrew 0.9.40 → 0.9.42
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 +21 -1
- package/bin/nc-activity-hook.js +55 -0
- package/frontend/dist/assets/index-CPO7bv6-.css +32 -0
- package/frontend/dist/assets/index-DpFbq_tt.js +169 -0
- package/frontend/dist/index.html +2 -2
- package/frontend/dist/version.json +1 -1
- package/lib/files/activity.js +407 -0
- package/lib/fleet/ai-desktop.js +123 -0
- package/lib/fleet/cell-exec.js +152 -4
- package/lib/fleet/codex-hooks.js +253 -0
- package/lib/fleet/managed.js +274 -30
- package/lib/fleet/mcp-cache-readiness.js +154 -0
- package/lib/fleet/prompt-delivery.js +65 -12
- package/lib/fleet/runtime.js +158 -10
- package/lib/notify/ask-relay.js +104 -19
- package/lib/proxy/panel-proxy.js +16 -2
- package/lib/server.js +41 -1
- package/lib/settings/routes.js +55 -3
- package/lib/tmux/list.js +19 -1
- package/lib/tmux/supervisor-pane.js +81 -0
- package/package.json +4 -3
- package/frontend/dist/assets/index-BJBurY16.js +0 -169
- package/frontend/dist/assets/index-Csz8VaFz.css +0 -32
package/lib/server.js
CHANGED
|
@@ -8,7 +8,8 @@ const express = require('express');
|
|
|
8
8
|
const { WebSocketServer } = require('ws');
|
|
9
9
|
const { defaults, loadConfig, assertLoopback, configJsonPath } = require('./config.js');
|
|
10
10
|
const { writeConfigAtomic } = require('./cli/init.js');
|
|
11
|
-
const { listSessions, attachedClients, setSessionVisibility } = require('./tmux/list.js');
|
|
11
|
+
const { listSessions, listPanes, attachedClients, setSessionVisibility } = require('./tmux/list.js');
|
|
12
|
+
const { statiSupervisore: statiSupervisoreDi, senzaSupervisore } = require('./tmux/supervisor-pane.js');
|
|
12
13
|
const { runAction, pasteToSession, submitToSession } = require('./tmux/actions.js');
|
|
13
14
|
const { createSession, killSession, isProtectedSession } = require('./tmux/lifecycle.js');
|
|
14
15
|
const { createPreviewSampler } = require('./tmux/preview.js');
|
|
@@ -20,6 +21,7 @@ const { requireToken, bearerFrom } = require('./auth/middleware.js');
|
|
|
20
21
|
const { filesRoutes } = require('./files/routes.js');
|
|
21
22
|
const { createOutboxWatcher } = require('./files/watcher.js');
|
|
22
23
|
const { leggiTelemetria } = require('./files/telemetry.js');
|
|
24
|
+
const { leggiAttivita, leggiGenerazione } = require('./files/activity.js');
|
|
23
25
|
const VERSION = require('../package.json').version;
|
|
24
26
|
const { transcribe } = require('./voice/transcribe.js');
|
|
25
27
|
const { selectProvider } = require('./fleet/provider.js');
|
|
@@ -975,6 +977,10 @@ function createServer(opts = {}) {
|
|
|
975
977
|
api.get('/sessions', async (_req, res) => {
|
|
976
978
|
try {
|
|
977
979
|
const sessions = await listSessions(cfg.tmuxBin);
|
|
980
|
+
// UNA chiamata per giro per sapere chi ha ancora il suo supervisore.
|
|
981
|
+
const statiSupervisore = sessions.length
|
|
982
|
+
? statiSupervisoreDi(await listPanes(cfg.tmuxBin))
|
|
983
|
+
: new Map();
|
|
978
984
|
const sum = watcher.getSummary();
|
|
979
985
|
const enriched = await Promise.all(sessions.map(async (s) => {
|
|
980
986
|
const sample = await previews.getState(s.name);
|
|
@@ -982,6 +988,7 @@ function createServer(opts = {}) {
|
|
|
982
988
|
// the capture fallback. This prevents a quoted "• Working (...)" line
|
|
983
989
|
// inside another agent's transcript from becoming a false positive.
|
|
984
990
|
const piTitle = /^(?:π|pi)(?:\s+-|$)/iu.test(s.paneTitle);
|
|
991
|
+
const statoDelSupervisore = statiSupervisore.get(s.name) || 'assente';
|
|
985
992
|
const working = s.working === true || (piTitle && sample?.working === true);
|
|
986
993
|
return {
|
|
987
994
|
...s,
|
|
@@ -993,6 +1000,33 @@ function createServer(opts = {}) {
|
|
|
993
1000
|
// Claude via statusline). Null per tutte le altre — assenza
|
|
994
1001
|
// legittima, non un trattino: la riga resta com'era.
|
|
995
1002
|
telemetry: leggiTelemetria(cfg.filesRoot, s.name),
|
|
1003
|
+
// Stato del turno pubblicato dagli hook della cella: lavora /
|
|
1004
|
+
// ferma / attesa. `null` NON e' «ferma»: e' «non verificato» —
|
|
1005
|
+
// nessun evento entro la finestra, oppure una cella che non ha questo
|
|
1006
|
+
// canale (codex-vl, pi, shell). La UI non deve dedurre «ferma» da un
|
|
1007
|
+
// silenzio: e' esattamente l'errore che il titolo del pane fa oggi.
|
|
1008
|
+
//
|
|
1009
|
+
// E SI LEGGE SOLO SE IL SUPERVISORE DELLA CELLA ESISTE ANCORA. Con
|
|
1010
|
+
// `remain-on-exit` (runtime.js) la sessione sopravvive al suo
|
|
1011
|
+
// processo: un supervisore ucciso (SIGKILL, OOM) lascia la sessione
|
|
1012
|
+
// «attiva», e lo `Stop` con `exit:1` scritto prima resterebbe «ferma»
|
|
1013
|
+
// per sempre — mentre non c'e' nessun client a cui quello stato si
|
|
1014
|
+
// riferisca. La verita' e' il pane che esegue cell-exec, marcato al
|
|
1015
|
+
// lancio: `#{pane_dead}` della sessione riguarda il pane ATTIVO, e con
|
|
1016
|
+
// una seconda finestra viva selezionata direbbe «vivo» a supervisore
|
|
1017
|
+
// morto (misurato). La decisione completa sta in
|
|
1018
|
+
// `lib/tmux/supervisor-pane.js`: qui si passa lo stato del pane e — per
|
|
1019
|
+
// il solo caso «nessun marcatore» — il segno `exit:1` del file, che
|
|
1020
|
+
// distingue un lancio nuovo con marcatura fallita (non si legge: il
|
|
1021
|
+
// suo `Stop` non scadrebbe) da un lancio VECCHIO (si legge, e il
|
|
1022
|
+
// lettore fa scadere tutto a 5 minuti). E' quello che evita di
|
|
1023
|
+
// mostrare «non verificato» su ogni cella viva di ogni nodo appena
|
|
1024
|
+
// aggiornato.
|
|
1025
|
+
attivita: statoDelSupervisore === 'vivo'
|
|
1026
|
+
? leggiAttivita(cfg.filesRoot, s.name)
|
|
1027
|
+
: (senzaSupervisore(statoDelSupervisore, leggiGenerazione(path.join(cfg.filesRoot, s.name)).uscitaGarantita)
|
|
1028
|
+
? null
|
|
1029
|
+
: leggiAttivita(cfg.filesRoot, s.name)),
|
|
996
1030
|
};
|
|
997
1031
|
}));
|
|
998
1032
|
res.json({ sessions: enriched });
|
|
@@ -1272,6 +1306,12 @@ function createServer(opts = {}) {
|
|
|
1272
1306
|
});
|
|
1273
1307
|
} catch (e) { res.status(500).json({ error: String(e.message || e) }); }
|
|
1274
1308
|
});
|
|
1309
|
+
// Riconciliazione dopo refresh: gli esiti incerti noti al relay (solo
|
|
1310
|
+
// contatori/ID, mai testo) per ricostruire lo stato «verifica» senza reinvii.
|
|
1311
|
+
api.get('/asks-relay/state', (_req, res) => {
|
|
1312
|
+
try { res.json({ attempts: askRelay.state() }); }
|
|
1313
|
+
catch (e) { res.status(500).json({ error: String(e.message || e) }); }
|
|
1314
|
+
});
|
|
1275
1315
|
// The client re-emits imported events into the local hub (deliverOnly: the
|
|
1276
1316
|
// notifier is bypassed on purpose). The alert relay hangs off that same call,
|
|
1277
1317
|
// so ONLY an imported notify can ring this device, and never through the
|
package/lib/settings/routes.js
CHANGED
|
@@ -68,9 +68,10 @@ const { scrubError } = require('../update/core.js');
|
|
|
68
68
|
// il transaction handler di /nodes/pair e la superficie pubblica di peering.
|
|
69
69
|
const { createPairHandler } = require('./pairing-coordinator.js');
|
|
70
70
|
const { publicPeeringRoutes } = require('./public-peering-routes.js');
|
|
71
|
+
const aiDesktop = require('../fleet/ai-desktop.js');
|
|
71
72
|
|
|
72
73
|
// Whitelist chiavi scrivibili di config.json via API (lista chiusa dal task).
|
|
73
|
-
const CONFIG_KEYS = new Set(['roles', 'port', 'wizardDone', 'autoUpdate', 'alternateScreen']);
|
|
74
|
+
const CONFIG_KEYS = new Set(['roles', 'port', 'wizardDone', 'autoUpdate', 'alternateScreen', 'aiDesktop']);
|
|
74
75
|
const ROLE_KEYS = new Set(['client', 'node']);
|
|
75
76
|
const ADD_KEYS = new Set(['name', 'ssh', 'sshPort', 'remotePort', 'localPort', 'keyPath', 'label']);
|
|
76
77
|
const EDIT_KEYS = new Set([
|
|
@@ -279,10 +280,10 @@ function settingsRoutes(deps = {}) {
|
|
|
279
280
|
return send(res, 400, { error: 'body deve essere un oggetto JSON' });
|
|
280
281
|
}
|
|
281
282
|
for (const k of Object.keys(b)) {
|
|
282
|
-
if (!CONFIG_KEYS.has(k)) return send(res, 400, { error: `chiave non ammessa: "${k}" (whitelist: roles, port, wizardDone, autoUpdate, alternateScreen)` });
|
|
283
|
+
if (!CONFIG_KEYS.has(k)) return send(res, 400, { error: `chiave non ammessa: "${k}" (whitelist: roles, port, wizardDone, autoUpdate, alternateScreen, aiDesktop)` });
|
|
283
284
|
}
|
|
284
285
|
if (Object.keys(b).length === 0) {
|
|
285
|
-
return send(res, 400, { error: 'nessuna chiave da scrivere (whitelist: roles, port, wizardDone, autoUpdate, alternateScreen)' });
|
|
286
|
+
return send(res, 400, { error: 'nessuna chiave da scrivere (whitelist: roles, port, wizardDone, autoUpdate, alternateScreen, aiDesktop)' });
|
|
286
287
|
}
|
|
287
288
|
if (b.roles !== undefined) {
|
|
288
289
|
if (!b.roles || typeof b.roles !== 'object' || Array.isArray(b.roles)) {
|
|
@@ -315,6 +316,9 @@ function settingsRoutes(deps = {}) {
|
|
|
315
316
|
if (b.alternateScreen !== undefined && typeof b.alternateScreen !== 'boolean') {
|
|
316
317
|
return send(res, 400, { error: 'alternateScreen deve essere boolean' });
|
|
317
318
|
}
|
|
319
|
+
if (b.aiDesktop !== undefined && typeof b.aiDesktop !== 'boolean') {
|
|
320
|
+
return send(res, 400, { error: 'aiDesktop deve essere boolean' });
|
|
321
|
+
}
|
|
318
322
|
|
|
319
323
|
// merge sul config esistente (preserva le chiavi non gestite qui)
|
|
320
324
|
const { cfg: current } = readConfigFile(configPath);
|
|
@@ -327,6 +331,7 @@ function settingsRoutes(deps = {}) {
|
|
|
327
331
|
if (b.wizardDone !== undefined) next.wizardDone = b.wizardDone;
|
|
328
332
|
if (b.autoUpdate !== undefined) next.autoUpdate = b.autoUpdate;
|
|
329
333
|
if (b.alternateScreen !== undefined) next.alternateScreen = b.alternateScreen;
|
|
334
|
+
if (b.aiDesktop !== undefined) next.aiDesktop = b.aiDesktop;
|
|
330
335
|
atomicWriteConfig(configPath, next);
|
|
331
336
|
if (b.autoUpdate !== undefined && updater && typeof updater.setEnabled === 'function') {
|
|
332
337
|
updater.setEnabled(b.autoUpdate);
|
|
@@ -353,6 +358,53 @@ function settingsRoutes(deps = {}) {
|
|
|
353
358
|
} catch (e) { send(res, 500, { error: String(e.message || e) }); }
|
|
354
359
|
});
|
|
355
360
|
|
|
361
|
+
// --- AIDesktop: la spunta che governa il container e il tasto --------------
|
|
362
|
+
// GET: la chiave esplicita comanda; se l'utente non l'ha mai toccata, il
|
|
363
|
+
// default si deriva dal container (in esecuzione → true). docker viene
|
|
364
|
+
// interrogato SOLO qui, su chiamata esplicita — mai nel percorso caldo del
|
|
365
|
+
// fleetStatus. Il binario passa dal seam per i test (finto binario).
|
|
366
|
+
r.get('/ai-desktop', async (req, res) => {
|
|
367
|
+
const { cfg: cfgNow } = readConfigFile(configPath);
|
|
368
|
+
const esplicita = aiDesktop.chiaveEsplicita(cfgNow);
|
|
369
|
+
const stato = await aiDesktop.containerRunning({ dockerBin: seams.aiDesktopDockerBin });
|
|
370
|
+
const desired = esplicita !== null ? esplicita : stato.running;
|
|
371
|
+
return send(res, 200, {
|
|
372
|
+
desired, running: stato.running, exists: stato.exists, explicit: esplicita !== null,
|
|
373
|
+
...(stato.error ? { error: stato.error } : {}),
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
// POST {enabled}: salva la chiave ED esegue start o stop, restituendo
|
|
378
|
+
// l'esito VERO del comando — nessun successo ottimistico. Se docker
|
|
379
|
+
// risponde male la risposta è 500 con la causa: la chiave resta salvata
|
|
380
|
+
// (la spunta dice COSA si vuole, l'esito dice SE il mondo ci è arrivato)
|
|
381
|
+
// e il badge della UI mostra il disallineamento invece di fingere.
|
|
382
|
+
r.post('/ai-desktop', mutGate, async (req, res) => {
|
|
383
|
+
const b = req.body;
|
|
384
|
+
if (!b || typeof b !== 'object' || Array.isArray(b) || typeof b.enabled !== 'boolean') {
|
|
385
|
+
return send(res, 400, { error: 'body deve essere {enabled: boolean}' });
|
|
386
|
+
}
|
|
387
|
+
const { cfg: cfgNow } = readConfigFile(configPath);
|
|
388
|
+
try {
|
|
389
|
+
atomicWriteConfig(configPath, { ...cfgNow, aiDesktop: b.enabled });
|
|
390
|
+
} catch (e) {
|
|
391
|
+
return send(res, 500, { error: `config non salvata: ${e.message}` });
|
|
392
|
+
}
|
|
393
|
+
const dockerOpts = { dockerBin: seams.aiDesktopDockerBin };
|
|
394
|
+
const esito = b.enabled
|
|
395
|
+
? await aiDesktop.avvia(dockerOpts)
|
|
396
|
+
: await aiDesktop.ferma(dockerOpts);
|
|
397
|
+
if (!esito.ok) {
|
|
398
|
+
return send(res, 500, {
|
|
399
|
+
error: esito.error || 'comando docker fallito',
|
|
400
|
+
desired: b.enabled,
|
|
401
|
+
running: esito.running ?? null,
|
|
402
|
+
timeout: !!esito.timeout,
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
return send(res, 200, { ok: true, desired: b.enabled, running: esito.running });
|
|
406
|
+
});
|
|
407
|
+
|
|
356
408
|
// Auto-update npm: check e apply sono autenticati, READONLY-gated e non
|
|
357
409
|
// accettano mai package/versione dal browser. Il manager usa esclusivamente
|
|
358
410
|
// @mmmbuto/nexuscrew@latest, confronta semver e installa la versione esatta.
|
package/lib/tmux/list.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
const { execFile, execFileSync } = require('node:child_process');
|
|
3
|
+
const { PANES_FMT } = require('./supervisor-pane.js');
|
|
3
4
|
|
|
4
5
|
const FMT = "#{session_name}\t#{session_attached}\t#{session_windows}\t#{session_created}\t#{session_activity}\t#{pane_current_command}\t#{@nexuscrew_visibility}\t#{pane_title}";
|
|
5
6
|
|
|
@@ -78,6 +79,23 @@ function setSessionVisibility(tmuxBin = 'tmux', name, technical = false) {
|
|
|
78
79
|
});
|
|
79
80
|
}
|
|
80
81
|
|
|
82
|
+
// I pani di TUTTE le sessioni, in UNA chiamata: da qui si ricava quale pane e'
|
|
83
|
+
// il supervisore di ogni cella e se e' vivo (lib/tmux/supervisor-pane.js).
|
|
84
|
+
// `#{pane_dead}` qui e' quello giusto: il pane del SUPERVISORE, non il pane
|
|
85
|
+
// attivo della sessione — che con una seconda finestra viva direbbe «vivo»
|
|
86
|
+
// anche con il supervisore morto.
|
|
87
|
+
function listPanes(tmuxBin = 'tmux') {
|
|
88
|
+
return new Promise((resolve, reject) => {
|
|
89
|
+
execFile(tmuxBin, ['list-panes', '-a', '-F', PANES_FMT], (err, stdout, stderr) => {
|
|
90
|
+
if (err) {
|
|
91
|
+
if (isNoTmuxServerError(stderr || err.message)) return resolve('');
|
|
92
|
+
return reject(new Error(`tmux list-panes failed: ${stderr || err.message}`));
|
|
93
|
+
}
|
|
94
|
+
resolve(stdout);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
81
99
|
function listSessions(tmuxBin = 'tmux') {
|
|
82
100
|
return new Promise((resolve, reject) => {
|
|
83
101
|
execFile(tmuxBin, ['list-sessions', '-F', FMT], (err, stdout, stderr) => {
|
|
@@ -104,5 +122,5 @@ function attachedClients(tmuxBin = 'tmux', session) {
|
|
|
104
122
|
}
|
|
105
123
|
|
|
106
124
|
module.exports = {
|
|
107
|
-
parsePaneTitle, parseSessions, isNoTmuxServerError, listSessions, attachedClients, setSessionVisibility, FMT,
|
|
125
|
+
parsePaneTitle, parseSessions, isNoTmuxServerError, listSessions, listPanes, attachedClients, setSessionVisibility, FMT,
|
|
108
126
|
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Chi e' il pane del SUPERVISORE di una cella, e se e' ancora vivo.
|
|
3
|
+
//
|
|
4
|
+
// PERCHE' NON BASTA `#{pane_dead}` DI list-sessions (misurato con tmux vero):
|
|
5
|
+
// quel campo riguarda il pane ATTIVO della sessione. Con una seconda finestra
|
|
6
|
+
// viva selezionata dice «vivo» anche quando il pane del supervisore e' morto, e
|
|
7
|
+
// allora lo `Stop` con `exit:1` scritto prima resta «ferma» per sempre — mentre
|
|
8
|
+
// la garanzia di quel segno e' che il supervisore DICHIARA l'uscita, e un
|
|
9
|
+
// supervisore ucciso non l'ha dichiarata.
|
|
10
|
+
//
|
|
11
|
+
// La verita' e' il pane che esegue `cell-exec`: la runtime lo marca al lancio
|
|
12
|
+
// con un'opzione di pane (`@nc_supervisor`), e il server lo cerca qui.
|
|
13
|
+
//
|
|
14
|
+
// UNA SOLA CHIAMATA PER GIRO: `list-panes -a -F <PANES_FMT>`, poi tutto si
|
|
15
|
+
// calcola in memoria. Nessuna chiamata tmux per singola sessione.
|
|
16
|
+
const NC_SUPERVISOR_OPT = '@nc_supervisor';
|
|
17
|
+
const PANES_FMT = `#{session_name}\t#{pane_id}\t#{pane_dead}\t#{${NC_SUPERVISOR_OPT}}`;
|
|
18
|
+
|
|
19
|
+
function parsePanes(raw) {
|
|
20
|
+
return String(raw)
|
|
21
|
+
.split('\n')
|
|
22
|
+
.filter((line) => line.trim() !== '')
|
|
23
|
+
.map((line) => {
|
|
24
|
+
const campi = line.split('\t');
|
|
25
|
+
return {
|
|
26
|
+
session: campi[0] || '',
|
|
27
|
+
paneId: campi[1] || '',
|
|
28
|
+
dead: campi[2] === '1',
|
|
29
|
+
marked: campi[3] === '1',
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Lo stato del supervisore di ogni sessione, dalle sole righe di list-panes:
|
|
36
|
+
* 'vivo' — il pane marcato c'e' ed e' vivo;
|
|
37
|
+
* 'morto' — il pane marcato c'e' ed e' morto (SIGKILL/OOM del supervisore,
|
|
38
|
+
* e la sessione e' sopravvissuta per `remain-on-exit`);
|
|
39
|
+
* 'assente' — nessun pane marcato: la cella e' stata lanciata PRIMA che il
|
|
40
|
+
* marcatore esistesse, oppure la marcatura non e' riuscita.
|
|
41
|
+
*/
|
|
42
|
+
function statiSupervisore(raw) {
|
|
43
|
+
const perSessione = new Map();
|
|
44
|
+
for (const pane of parsePanes(raw)) {
|
|
45
|
+
if (!pane.session) continue;
|
|
46
|
+
const stato = perSessione.get(pane.session) || { marcato: null };
|
|
47
|
+
// Un solo pane marcato per lancio: se ne comparissero due, l'ultimo vince.
|
|
48
|
+
if (pane.marked) stato.marcato = pane;
|
|
49
|
+
perSessione.set(pane.session, stato);
|
|
50
|
+
}
|
|
51
|
+
const out = new Map();
|
|
52
|
+
for (const [nome, stato] of perSessione) {
|
|
53
|
+
if (!stato.marcato) out.set(nome, 'assente');
|
|
54
|
+
else out.set(nome, stato.marcato.dead ? 'morto' : 'vivo');
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* La decisione: di questa cella non si puo' leggere lo stato?
|
|
61
|
+
*
|
|
62
|
+
* 'morto' → si': il supervisore non c'e' piu' e nessuno dichiarera' l'uscita.
|
|
63
|
+
*
|
|
64
|
+
* 'assente' → dipende dal FILE DI GENERAZIONE, e la differenza e' la ragione
|
|
65
|
+
* della regola: un lancio NUOVO in cui la marcatura del pane e' fallita ha il
|
|
66
|
+
* segno `exit:1`, quindi il suo `Stop` non scadrebbe mai e nessuno garantisce
|
|
67
|
+
* il supervisore → non si legge. Un lancio VECCHIO (file di una riga, senza
|
|
68
|
+
* segno) non ha nessuna non-scadenza da proteggere: vale il lettore, che fa
|
|
69
|
+
* scadere `Stop` e gli eventi di lavoro a cinque minuti. Il caso peggiore e'
|
|
70
|
+
* limitato a quei cinque minuti, invece di diventare «non verificato» per ogni
|
|
71
|
+
* cella viva su ogni nodo appena aggiornato.
|
|
72
|
+
*
|
|
73
|
+
* 'vivo' → no.
|
|
74
|
+
*/
|
|
75
|
+
function senzaSupervisore(stato, uscitaGarantita) {
|
|
76
|
+
if (stato === 'morto') return true;
|
|
77
|
+
if (stato === 'assente') return uscitaGarantita === true;
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = { NC_SUPERVISOR_OPT, PANES_FMT, parsePanes, statiSupervisore, senzaSupervisore };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mmmbuto/nexuscrew",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.42",
|
|
4
4
|
"description": "Faithful browser tmux client — attach to live sessions over a real PTY, localhost-only, mobile-easy",
|
|
5
5
|
"main": "lib/server.js",
|
|
6
6
|
"bin": {
|
|
@@ -45,15 +45,16 @@
|
|
|
45
45
|
"express": "^4.21.0",
|
|
46
46
|
"multer": "^2.2.0",
|
|
47
47
|
"qrcode-terminal": "^0.12.0",
|
|
48
|
+
"smol-toml": "1.9.0",
|
|
48
49
|
"web-push": "^3.6.7",
|
|
49
50
|
"ws": "^8.18.0"
|
|
50
51
|
},
|
|
51
52
|
"optionalDependencies": {
|
|
52
|
-
"@mmmbuto/node-pty-android-arm64": "^1.1.2",
|
|
53
53
|
"@lydell/node-pty-darwin-arm64": "^1.2.0-beta.14",
|
|
54
54
|
"@lydell/node-pty-darwin-x64": "^1.2.0-beta.14",
|
|
55
55
|
"@lydell/node-pty-linux-arm64": "^1.2.0-beta.12",
|
|
56
|
-
"@lydell/node-pty-linux-x64": "^1.2.0-beta.12"
|
|
56
|
+
"@lydell/node-pty-linux-x64": "^1.2.0-beta.12",
|
|
57
|
+
"@mmmbuto/node-pty-android-arm64": "^1.1.2"
|
|
57
58
|
},
|
|
58
59
|
"engines": {
|
|
59
60
|
"node": ">=18"
|