@mmmbuto/nexuscrew 0.9.15 → 0.9.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/CHANGELOG.md +132 -3
- package/docs/ALIBABA_TOKEN_PLAN.md +70 -0
- package/docs/AUDIO_SHARE.md +119 -0
- package/docs/CELL_PANEL.md +156 -0
- package/docs/CONFIGURATION.md +122 -0
- package/docs/FLEET.md +222 -0
- package/docs/INSTALLATION.md +128 -0
- package/docs/LIVE_PROMPT.md +110 -0
- package/docs/MCP.md +151 -0
- package/docs/NODES.md +214 -0
- package/docs/NOTIFICATIONS.md +101 -0
- package/docs/OPERATIONS.md +114 -0
- package/docs/README.md +55 -0
- package/docs/SECURITY.md +185 -0
- package/docs/VL_MICRO_NODES.md +107 -0
- package/docs/img/fleet-deck-desktop.png +0 -0
- package/docs/img/session-mobile.png +0 -0
- package/docs/live-prompt-templates/LIVE_PROMPT.en.md +67 -0
- package/docs/live-prompt-templates/LIVE_PROMPT.es.md +70 -0
- package/docs/live-prompt-templates/LIVE_PROMPT.it.md +67 -0
- package/frontend/dist/assets/{index-ToJ5arRI.js → index-jBYBPXhB.js} +29 -29
- package/frontend/dist/index.html +1 -1
- package/frontend/dist/version.json +1 -1
- package/lib/audio/receipt.js +1 -1
- package/lib/auth/middleware.js +1 -1
- package/lib/cells/routes.js +26 -9
- package/lib/cells/scope-guard.js +2 -2
- package/lib/cli/commands.js +7 -7
- package/lib/cli/fleet-service.js +2 -2
- package/lib/cli/pidfile.js +15 -15
- package/lib/cli/service.js +2 -2
- package/lib/cli/stable-alias.js +1 -1
- package/lib/diagnostics/store.js +4 -1
- package/lib/files/routes.js +2 -2
- package/lib/fleet/builtin.js +59 -23
- package/lib/fleet/catalogs/opencode-go.json +3 -3
- package/lib/fleet/cell-exec.js +4 -4
- package/lib/fleet/cell-lease-server.js +17 -17
- package/lib/fleet/definitions.js +34 -15
- package/lib/fleet/launch-broker.js +3 -3
- package/lib/fleet/lease-client.js +1 -1
- package/lib/fleet/managed.js +97 -26
- package/lib/fleet/prompt-delivery.js +1 -1
- package/lib/fleet/runtime.js +3 -3
- package/lib/live-host/bridge.js +3 -3
- package/lib/live-host/routes.js +1 -1
- package/lib/mcp/server.js +7 -8
- package/lib/mcp/tools.js +1 -1
- package/lib/nodes/commands.js +2 -2
- package/lib/nodes/health.js +7 -11
- package/lib/nodes/identity.js +10 -13
- package/lib/nodes/store.js +2 -2
- package/lib/nodes/tunnel-supervisor.js +7 -7
- package/lib/nodes/tunnel.js +10 -11
- package/lib/notify/asks.js +4 -4
- package/lib/notify/persist.js +1 -1
- package/lib/notify/push.js +4 -4
- package/lib/notify/routes.js +12 -12
- package/lib/proxy/node-proxy.js +4 -4
- package/lib/proxy/panel-proxy.js +2 -2
- package/lib/pty/attach.js +1 -1
- package/lib/server.js +16 -4
- package/lib/settings/pairing-coordinator.js +1 -1
- package/lib/settings/routes.js +4 -4
- package/lib/tmux/lifecycle.js +4 -4
- package/lib/tmux/preview.js +1 -1
- package/lib/update/runner.js +9 -9
- package/lib/vl-nodes/broker.js +1 -1
- package/lib/ws/bridge.js +56 -3
- package/lib/ws/drop-counter.js +37 -0
- package/package.json +20 -2
- package/skills/nexuscrew/SKILL.md +1 -1
- package/frontend/index.html +0 -20
package/frontend/dist/index.html
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<meta name="apple-mobile-web-app-title" content="NexusCrew" />
|
|
12
12
|
<link rel="manifest" href="/manifest.json" />
|
|
13
13
|
<title>NexusCrew</title>
|
|
14
|
-
<script type="module" crossorigin src="/assets/index-
|
|
14
|
+
<script type="module" crossorigin src="/assets/index-jBYBPXhB.js"></script>
|
|
15
15
|
<link rel="stylesheet" crossorigin href="/assets/index-CprIQlC5.css">
|
|
16
16
|
</head>
|
|
17
17
|
<body>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"0.9.
|
|
1
|
+
{"version":"0.9.17"}
|
package/lib/audio/receipt.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// lib/audio/receipt.js — receipt degli enunciati, bounded e ridotto all'osso.
|
|
3
3
|
//
|
|
4
4
|
// Scope del chiamante = NODO + CELLA. Non la sola cella: due nodi possono avere
|
|
5
|
-
// celle omonime (`
|
|
5
|
+
// celle omonime (`Alpha` esiste su piu' installazioni), e una chiave basata solo
|
|
6
6
|
// sul nome permetterebbe a un nodo di leggere o sovrascrivere i receipt di una
|
|
7
7
|
// cella altrui con lo stesso nome. La chiave e' costruita qui, dal server, mai
|
|
8
8
|
// accettata dal client.
|
package/lib/auth/middleware.js
CHANGED
|
@@ -8,7 +8,7 @@ function bearerFrom(req) {
|
|
|
8
8
|
|
|
9
9
|
// 401 uniforme: nessun dettaglio su cosa è andato storto.
|
|
10
10
|
// `token` puo' essere una stringa (storico) o un holder live {get}: l'holder si
|
|
11
|
-
// rilegge ad OGNI richiesta, cosi' una rotazione (
|
|
11
|
+
// rilegge ad OGNI richiesta, cosi' una rotazione (da revisione di sicurezza) invalida il
|
|
12
12
|
// vecchio token senza restart. backward-compat: la stringa resta accettata.
|
|
13
13
|
function requireToken(token) {
|
|
14
14
|
const read = (token && typeof token === 'object' && typeof token.get === 'function')
|
package/lib/cells/routes.js
CHANGED
|
@@ -68,7 +68,7 @@ function validIdentity(value) {
|
|
|
68
68
|
&& isValidSession(value.tmuxSession);
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
function cellsRoutes({ fleetP, instanceId, submit, readonly = () => false, now = () => Date.now() }) {
|
|
71
|
+
function cellsRoutes({ fleetP, instanceId, submit, readonly = () => false, now = () => Date.now(), diagnostics = null }) {
|
|
72
72
|
const r = express.Router();
|
|
73
73
|
|
|
74
74
|
async function status() {
|
|
@@ -96,31 +96,47 @@ function cellsRoutes({ fleetP, instanceId, submit, readonly = () => false, now =
|
|
|
96
96
|
r.post('/send', express.json({ limit: '16kb' }), async (req, res) => {
|
|
97
97
|
if (readonly()) return res.status(403).json({ error: 'READONLY: invio cella bloccato' });
|
|
98
98
|
const body = req.body || {};
|
|
99
|
+
// Observability (2026-08-28): chi manda, a chi, con quale id e come finisce.
|
|
100
|
+
// SOLO metadati: il testo del messaggio non tocca mai la diagnostica.
|
|
101
|
+
const logSend = (level, event, reason) => {
|
|
102
|
+
if (!diagnostics) return;
|
|
103
|
+
try {
|
|
104
|
+
diagnostics.record(level, 'cell-msg', event, reason
|
|
105
|
+
? `Invio cella: ${reason}`
|
|
106
|
+
: 'Invio cella consegnato alla sessione target', {
|
|
107
|
+
fromCell: body.from && body.from.cell,
|
|
108
|
+
toCell: body.to && body.to.cell,
|
|
109
|
+
msgId: typeof body.id === 'string' ? body.id : undefined,
|
|
110
|
+
reason: reason ? String(reason).slice(0, 48) : undefined,
|
|
111
|
+
});
|
|
112
|
+
} catch (_) {}
|
|
113
|
+
};
|
|
114
|
+
const reject = (code, reason) => { logSend('warn', 'CELL_MESSAGE_REJECTED', reason); return res.status(code).json({ error: reason }); };
|
|
99
115
|
const keys = Object.keys(body);
|
|
100
116
|
if (keys.some((key) => !['id', 'from', 'to', 'message'].includes(key))
|
|
101
117
|
|| !MESSAGE_ID_RE.test(String(body.id || ''))
|
|
102
118
|
|| !validIdentity(body.from) || !validIdentity(body.to)
|
|
103
119
|
|| !submitTextOk(body.message)) {
|
|
104
|
-
return
|
|
120
|
+
return reject(400, 'messaggio cella non valido');
|
|
105
121
|
}
|
|
106
122
|
const localId = instanceId();
|
|
107
123
|
if (!NODE_ID_RE.test(String(localId || '')) || body.to.instanceId !== localId) {
|
|
108
|
-
return
|
|
124
|
+
return reject(409, 'destinazione non appartiene a questo nodo');
|
|
109
125
|
}
|
|
110
126
|
const visited = parseVisited(req);
|
|
111
127
|
if (visited === null || (visited.length && (visited.at(-1) !== localId
|
|
112
128
|
|| body.from.instanceId !== visited[0]))) {
|
|
113
|
-
return
|
|
129
|
+
return reject(403, 'identita mittente non verificata');
|
|
114
130
|
}
|
|
115
131
|
if (!visited.length && body.from.instanceId !== localId) {
|
|
116
|
-
return
|
|
132
|
+
return reject(403, 'mittente remoto senza route autenticata');
|
|
117
133
|
}
|
|
118
134
|
try {
|
|
119
135
|
const cells = publicCells(await status(), localId, now());
|
|
120
136
|
const target = cells.find((cell) => cell.cell === body.to.cell
|
|
121
137
|
&& cell.tmuxSession === body.to.tmuxSession);
|
|
122
|
-
if (!target) return
|
|
123
|
-
if (!target.canReceive) return
|
|
138
|
+
if (!target) return reject(404, 'cella destinataria sconosciuta');
|
|
139
|
+
if (!target.canReceive) return reject(409, 'cella destinataria non attiva');
|
|
124
140
|
const label = `${body.from.cell}@${body.from.instanceId.slice(0, 8)}`;
|
|
125
141
|
// End on printable text even when the source message ends in a newline:
|
|
126
142
|
// Pi may auto-submit a bracketed paste that ends with LF. NexusCrew owns
|
|
@@ -128,8 +144,9 @@ function cellsRoutes({ fleetP, instanceId, submit, readonly = () => false, now =
|
|
|
128
144
|
const envelope = `[NexusCrew message ${body.id} from ${label}]\n${body.message}\n[End NexusCrew message]`;
|
|
129
145
|
const outcome = await submit(target.tmuxSession, envelope, { engine: target.engine });
|
|
130
146
|
if (!outcome || outcome.submitted !== true) {
|
|
131
|
-
return
|
|
147
|
+
return reject(409, outcome?.reason || 'consegna non riuscita');
|
|
132
148
|
}
|
|
149
|
+
logSend('notice', 'CELL_MESSAGE_SENT');
|
|
133
150
|
const at = now();
|
|
134
151
|
return res.json({
|
|
135
152
|
id: body.id,
|
|
@@ -138,7 +155,7 @@ function cellsRoutes({ fleetP, instanceId, submit, readonly = () => false, now =
|
|
|
138
155
|
to: { instanceId: localId, cell: target.cell, tmuxSession: target.tmuxSession },
|
|
139
156
|
note: 'submitted conferma solo paste+Enter nel TUI, non elaborazione o completamento',
|
|
140
157
|
});
|
|
141
|
-
} catch (e) { return
|
|
158
|
+
} catch (e) { return reject(500, String(e.message || e).slice(0, 96)); }
|
|
142
159
|
});
|
|
143
160
|
|
|
144
161
|
r.use((err, _req, res, _next) => {
|
package/lib/cells/scope-guard.js
CHANGED
|
@@ -27,7 +27,7 @@ const SESSION_LIST_KEYS = ['sessions', 'unmanaged'];
|
|
|
27
27
|
// sessioni sotto quei nomi, ma TILE, e ogni tile porta il nome della sessione
|
|
28
28
|
// tmux. Filtrare tre chiavi su quattro lasciava passare a un peer ristretto
|
|
29
29
|
// l'esistenza e il nome di ogni cella dell'hub — non il contenuto, ma la mappa.
|
|
30
|
-
// Trovato
|
|
30
|
+
// Trovato da una revisione indipendente, non da questo codice.
|
|
31
31
|
function filterDecks(decks, scope) {
|
|
32
32
|
if (!Array.isArray(decks)) return decks;
|
|
33
33
|
const out = [];
|
|
@@ -86,7 +86,7 @@ function declaredTarget(req) {
|
|
|
86
86
|
// si aggiunge alla tabella — di un canale aperto che nessuno vede.
|
|
87
87
|
//
|
|
88
88
|
// La prima versione elencava sei chiavi esatte (`cell`, `session`,
|
|
89
|
-
// `tmuxSession`, `to.cell`, `to.tmuxSession`, `?session`) e
|
|
89
|
+
// `tmuxSession`, `to.cell`, `to.tmuxSession`, `?session`) e la revisione ha mostrato
|
|
90
90
|
// il limite: una route dimenticata che chiama il suo bersaglio `cellId` passava
|
|
91
91
|
// liscia. Si riconosce quindi la FORMA del nome — qualunque chiave che parli di
|
|
92
92
|
// celle o di sessioni — invece di un elenco che invecchia a ogni sinonimo.
|
package/lib/cli/commands.js
CHANGED
|
@@ -560,7 +560,7 @@ async function probeNexusCrew(port, token, opts = {}) {
|
|
|
560
560
|
// dice che il servizio E' IN PIEDI e che il token locale non vale — e chi deve
|
|
561
561
|
// spiegare un riavvio ha bisogno di distinguerle, altrimenti dichiara fallito
|
|
562
562
|
// un riavvio riuscito e manda a cercare un processo morto che e' vivo.
|
|
563
|
-
// Rilievo
|
|
563
|
+
// Rilievo della revisione: il caso token-assente era gia' coperto, questo no.
|
|
564
564
|
async function probeNexusCrewStatus(port, token, opts = {}) {
|
|
565
565
|
const fetchImpl = opts.fetchImpl || globalThis.fetch;
|
|
566
566
|
if (typeof fetchImpl !== 'function') return null;
|
|
@@ -932,7 +932,7 @@ function autoUpdateCommand(args, opts = {}) {
|
|
|
932
932
|
// stesso file: tmp nella stessa directory, 0600, rename. Un `writeFileSync`
|
|
933
933
|
// diretto puo' lasciare un config.json troncato se il processo muore a
|
|
934
934
|
// meta' — e un config.json illeggibile e' un nodo che non riparte, cioe'
|
|
935
|
-
// proprio il guasto che questo comando serve a evitare. Rilievo
|
|
935
|
+
// proprio il guasto che questo comando serve a evitare. Rilievo della revisione.
|
|
936
936
|
try {
|
|
937
937
|
if (fs.lstatSync(configPath).isSymbolicLink()) {
|
|
938
938
|
log('autoupdate: config.json e\' un symlink, non lo scrivo');
|
|
@@ -975,7 +975,7 @@ function autoUpdateCommand(args, opts = {}) {
|
|
|
975
975
|
|
|
976
976
|
// update: npm i -g @latest + sanazione definizioni di boot + restart se attivo.
|
|
977
977
|
// Fallimento npm -> messaggio chiaro, code 1.
|
|
978
|
-
//
|
|
978
|
+
// rimedio da revisione, difetto 1: la via manuale attraversa la STESSA
|
|
979
979
|
// sanazione del runner (healBootDefinitions: rigenera le definizioni sul
|
|
980
980
|
// node vivo e APPLICA l'attivazione differita). Prima update() reinstallava
|
|
981
981
|
// e riavviava soltanto: chi aggiornava a mano con un node appena cambiato
|
|
@@ -1177,7 +1177,7 @@ async function dispatchNodes(rest, flags, opts = {}) {
|
|
|
1177
1177
|
if (!cells.length) { log('nodes cells: elenco vuoto — usa `none` se intendi nessuna cella'); return { code: 1 }; }
|
|
1178
1178
|
return { code: nodesCmds.nodesEdit({ ...opts, log, ref, patch: { cellVisibility: 'selected', cells } }).code };
|
|
1179
1179
|
}
|
|
1180
|
-
// Accesso ai pannelli (
|
|
1180
|
+
// Accesso ai pannelli (da revisione): dietro un pannello c'e' un browser con sessioni
|
|
1181
1181
|
// gia' autenticate, e quell'accesso non si revoca cambiando una chiave. Il
|
|
1182
1182
|
// resto del modello tratta un peer pairato come l'operatore stesso; qui no:
|
|
1183
1183
|
// default negato, si concede un nodo per volta.
|
|
@@ -1407,7 +1407,7 @@ function dispatch(argv, opts = {}) {
|
|
|
1407
1407
|
// SENZA TOKEN NON SI PUO' DIRE NIENTE SULLA SALUTE, e soprattutto non si
|
|
1408
1408
|
// deve dire qualcosa di sbagliato: la sonda fallirebbe per autenticazione e
|
|
1409
1409
|
// il messaggio incolperebbe la porta o il processo, mandando a cercare dove
|
|
1410
|
-
// il problema non e'. Rilievo
|
|
1410
|
+
// il problema non e'. Rilievo della revisione. Il riavvio e' comunque partito,
|
|
1411
1411
|
// quindi non e' un fallimento: e' una verifica che non si e' potuta fare, e
|
|
1412
1412
|
// si dice cosi'.
|
|
1413
1413
|
if (!token) {
|
|
@@ -1460,8 +1460,8 @@ function dispatch(argv, opts = {}) {
|
|
|
1460
1460
|
// processi sulla stessa porta, e il nostro sopravvivrebbe allo stop del
|
|
1461
1461
|
// servizio. Li' si riferisce e basta.
|
|
1462
1462
|
//
|
|
1463
|
-
//
|
|
1464
|
-
//
|
|
1463
|
+
// Invariante: il ramo managed non chiama `startPortable`; un fallback
|
|
1464
|
+
// locale creerebbe un secondo processo fuori dal controllo del gestore.
|
|
1465
1465
|
if (result.runtimeOwner === 'managed') {
|
|
1466
1466
|
log(`restart: il servizio NON risponde su 127.0.0.1:${port} dopo il riavvio.`);
|
|
1467
1467
|
log(' Il runtime e\' gestito dal servizio di sistema: non avvio un processo accanto.');
|
package/lib/cli/fleet-service.js
CHANGED
|
@@ -238,7 +238,7 @@ function fleetInstallCommands(platform, target, ctx) {
|
|
|
238
238
|
// Install no-symlink + atomic rename (come service.js). execImpl iniettabile per test;
|
|
239
239
|
// default execFileSync (argv diretto, MAI shell string). Su activation fallita il file
|
|
240
240
|
// e' PRESERVATO e le failure raccolte per diagnosi (M1: non si ingoia, non si rollback).
|
|
241
|
-
//
|
|
241
|
+
// da revisione: `activate: false` (speculare a installService) scrive la definizione SENZA
|
|
242
242
|
// toccare il service manager — per chi rigenera a meta' aggiornamento: il companion
|
|
243
243
|
// e' RunAtLoad/oneshot, un bootstrap adesso eseguirebbe le celle boot:true mentre
|
|
244
244
|
// il servizio principale sta per essere riavviato. I comandi saltati sono dichiarati
|
|
@@ -313,7 +313,7 @@ function installFleetService(platform, content, ctx, {
|
|
|
313
313
|
|
|
314
314
|
// Gemello di isOurService (service.js): stessa ragione, stessa disciplina —
|
|
315
315
|
// l'ancora vive accanto al template della companion.
|
|
316
|
-
// Stessa disciplina di service.js (
|
|
316
|
+
// Stessa disciplina di service.js (da revisione): la riga identificativa e'
|
|
317
317
|
// definita una volta e usata SIA dal template SIA dall'ancora, e l'ancora e' la
|
|
318
318
|
// riga ESATTA — un prefisso lascia passare il servizio di terzi che comincia la
|
|
319
319
|
// propria Description col nostro nome.
|
package/lib/cli/pidfile.js
CHANGED
|
@@ -72,7 +72,7 @@ function readProcessStart(pid) {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
// Campi che SOLO writePidfile puo' scrivere: mai dal chiamante via `extra`.
|
|
75
|
-
// Misurato
|
|
75
|
+
// Misurato in revisione: con lo spread di extra per ultimo,
|
|
76
76
|
// writePidfile(f, pid, cmd, {processStart:'FINTO', attestation:'unsupported'})
|
|
77
77
|
// vinceva sul valore vero calcolato da probeProcessStart — un chiamante
|
|
78
78
|
// poteva scrivere un'attestazione INVENTATA. safeExtra la filtra qui, e i
|
|
@@ -105,7 +105,7 @@ const RESERVED_META_FIELDS = new Set(['pid', 'cmd', 'startTs', 'uid', 'processSt
|
|
|
105
105
|
// controlla quella variabile d'ambiente controlla anche la finestra — non
|
|
106
106
|
// un confine di sicurezza reale (chi setta l'ambiente del processo ha gia'
|
|
107
107
|
// vinto), ma va detto: il marker non e' una garanzia piu' forte di questa.
|
|
108
|
-
// 3.
|
|
108
|
+
// 3. da revisione: claimSchemaMarker (sotto) crea il marker al KILL, non solo
|
|
109
109
|
// al write — quindi un pidfile che sta per essere RIMOSSO puo' lasciare
|
|
110
110
|
// dietro un marker in una directory che magari non ospitera' mai piu' un
|
|
111
111
|
// runtime. E' INNOCUO: il marker non ha altro effetto che rendere PIU'
|
|
@@ -119,7 +119,7 @@ function schemaMarkerPath(pidfilePath) {
|
|
|
119
119
|
return path.join(path.dirname(pidfilePath), '.pidfile-schema-v2');
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
//
|
|
122
|
+
// da revisione interna: misurato in revisione
|
|
123
123
|
// sul frozen — se .pidfile-schema-v2 esiste come DIRECTORY (o symlink, o
|
|
124
124
|
// qualunque cosa non sia un file regolare), il vecchio hasSchemaMarker
|
|
125
125
|
// tornava `false` per costruzione (isFile() falso), e ensureSchemaMarker
|
|
@@ -192,7 +192,7 @@ function hasSchemaMarker(pidfilePath) {
|
|
|
192
192
|
// pidfile e' gia' stato scritto quando arriviamo qui, ed e' lui
|
|
193
193
|
// l'informazione che conta.
|
|
194
194
|
//
|
|
195
|
-
//
|
|
195
|
+
// da revisione interna: questa funzione NON
|
|
196
196
|
// garantisce piu' nulla sul futuro — prima il commento qui diceva "il
|
|
197
197
|
// prossimo killPidfile lo scoprira' da solo", ed era FALSO: se questa
|
|
198
198
|
// scrittura fallisce con ENOSPC/EROFS/quota/permesso transitorio, non resta
|
|
@@ -215,7 +215,7 @@ function ensureSchemaMarker(pidfilePath) {
|
|
|
215
215
|
}
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
-
//
|
|
218
|
+
// da revisione: LA decisione di sicurezza. checkSchemaMarker (sopra) guarda
|
|
219
219
|
// un EVENTO PASSATO — "il marker e' stato scritto" — che puo' fallire in
|
|
220
220
|
// silenzio senza lasciare traccia (vedi ensureSchemaMarker): il percorso
|
|
221
221
|
// resta ENOENT, indistinguibile da "mai tentato". claimSchemaMarker sposta
|
|
@@ -444,14 +444,14 @@ function killPidfile(p, signal = 'SIGTERM', impl = {}) {
|
|
|
444
444
|
// Prima di questo controllo (nascita, aggiunta dopo cmd) il segnale
|
|
445
445
|
// partiva sul solo cmd, senza verifica di identità, per QUALUNQUE pidfile
|
|
446
446
|
// senza processStart — vecchio o nuovo, piattaforma capace o no: era
|
|
447
|
-
// esattamente il buco piu' largo trovato
|
|
447
|
+
// esattamente il buco piu' largo trovato in revisione, perche'
|
|
448
448
|
// "nessuna nascita nel meta" copriva sia "questa macchina non sa attestare"
|
|
449
449
|
// sia "l'attestazione e' fallita alla creazione per un motivo che non
|
|
450
450
|
// conosciamo" con lo STESSO comportamento permissivo. Il ramo sotto separa
|
|
451
451
|
// i due casi guardando cosa writePidfile ha DICHIARATO al momento in cui
|
|
452
452
|
// contava (probeProcessStart, sopra), non ricalcolando una sonda adesso —
|
|
453
453
|
// rifarla qui confonderebbe di nuovo "non so attestare" con "non ci sono
|
|
454
|
-
// riuscito in QUESTO istante", l'errore di categoria che
|
|
454
|
+
// riuscito in QUESTO istante", l'errore di categoria che la revisione ha
|
|
455
455
|
// contestato nel primo disegno di questo fix.
|
|
456
456
|
let unverifiedBirth = false;
|
|
457
457
|
let schemaMarker = null; // calcolato pigro: solo se serve davvero (vedi sotto)
|
|
@@ -489,7 +489,7 @@ function killPidfile(p, signal = 'SIGTERM', impl = {}) {
|
|
|
489
489
|
// altro probe su PID/cmd (la stessa prova debole con un altro nome): serve
|
|
490
490
|
// una prova ALTERNATIVA del proprietario — per esempio un runId verificato
|
|
491
491
|
// contro un endpoint locale autenticato. Non e' implementata: e' un lavoro
|
|
492
|
-
// a se', con un suo giro di
|
|
492
|
+
// a se', con un suo giro di revisione dedicato (non eseguito).
|
|
493
493
|
unverifiedBirth = 'unsupported';
|
|
494
494
|
} else if (meta.attestation === 'indeterminate') {
|
|
495
495
|
// Il codice NUOVO (questo, o una versione successiva a questo fix) ha
|
|
@@ -497,7 +497,7 @@ function killPidfile(p, signal = 'SIGTERM', impl = {}) {
|
|
|
497
497
|
// che non sa classificare come proprieta' della piattaforma (vedi
|
|
498
498
|
// probeProcessStart sopra) — sa che avrebbe potuto, su questo pid,
|
|
499
499
|
// adesso, e non ce l'ha fatta. Trattarlo come "la piattaforma non sa
|
|
500
|
-
// attestare" sarebbe l'errore di categoria contestato
|
|
500
|
+
// attestare" sarebbe l'errore di categoria contestato in revisione:
|
|
501
501
|
// un'assenza non e' una proprieta' stabile solo perche' e' comoda da
|
|
502
502
|
// leggere cosi'. Si rifiuta: il cmd da solo matcha per inclusioni, e non
|
|
503
503
|
// basta a chi non sa nemmeno perche' la nascita manca QUI. Il pidfile
|
|
@@ -512,7 +512,7 @@ function killPidfile(p, signal = 'SIGTERM', impl = {}) {
|
|
|
512
512
|
// forma di meta la puo' produrre anche un chiamante non nostro): "nessun
|
|
513
513
|
// campo" da solo non prova la provenienza.
|
|
514
514
|
//
|
|
515
|
-
//
|
|
515
|
+
// da revisione: la domanda che decide non e' piu' "il marker e' gia'
|
|
516
516
|
// stato scritto?" (un evento passato che puo' fallire in silenzio —
|
|
517
517
|
// vedi ensureSchemaMarker/claimSchemaMarker sopra) ma "posso scriverlo
|
|
518
518
|
// ADESSO?" — claimSchemaMarker prova a crearlo in questo esatto istante:
|
|
@@ -534,9 +534,9 @@ function killPidfile(p, signal = 'SIGTERM', impl = {}) {
|
|
|
534
534
|
// scrivere qui, verificato nell'istante in cui
|
|
535
535
|
// conta, non dedotto da uno passato. Si rifiuta.
|
|
536
536
|
//
|
|
537
|
-
// MISURATO
|
|
537
|
+
// MISURATO IN REVISIONE: trattare OGNI meta senza attestazione come
|
|
538
538
|
// indeterminate rompe l'aggiornamento automatico per ogni nodo il cui
|
|
539
|
-
// runtime e' ancora precedente a quando processStart e' nato (
|
|
539
|
+
// runtime e' ancora precedente a quando processStart e' nato (introdotto in
|
|
540
540
|
// v0.9.0) — npm install sovrascrive pidfile.js PRIMA che il runner lo
|
|
541
541
|
// richieda, il runner legge il pidfile VECCHIO del runtime in esecuzione,
|
|
542
542
|
// killPidfile rifiuta, stopPortableRuntime torna killed:false,
|
|
@@ -555,9 +555,9 @@ function killPidfile(p, signal = 'SIGTERM', impl = {}) {
|
|
|
555
555
|
// morire (il difetto gia' visto sul campo che l'ingresso unico di
|
|
556
556
|
// riavvio doveva chiudere); un ramo "legacy" permanente basato sulla sola
|
|
557
557
|
// FORMA del meta (lasciava aperti per sempre restore da backup,
|
|
558
|
-
// corruzione e downgrade —
|
|
559
|
-
// controllato ma mai riprovato al kill (
|
|
560
|
-
//
|
|
558
|
+
// corruzione e downgrade — la revisione l'ha contestata); un marker
|
|
559
|
+
// controllato ma mai riprovato al kill (da revisione:
|
|
560
|
+
// una scrittura fallita in silenzio riapre
|
|
561
561
|
// lo stesso buco senza lasciare traccia).
|
|
562
562
|
schemaMarker = (impl.claimSchemaMarkerImpl || claimSchemaMarker)(p);
|
|
563
563
|
if (schemaMarker.state === 'created') {
|
package/lib/cli/service.js
CHANGED
|
@@ -407,7 +407,7 @@ function installCommands(platform, target, ctx) {
|
|
|
407
407
|
}
|
|
408
408
|
|
|
409
409
|
|
|
410
|
-
// --- Riconoscimento di PROPRIETA' (
|
|
410
|
+
// --- Riconoscimento di PROPRIETA' (da revisione) -------------------------
|
|
411
411
|
// La sanazione automatica riscrive una definizione solo se e' NOSTRA. La prima
|
|
412
412
|
// versione cercava /nexuscrew/i nel contenuto: un unit di terzi che ci nomina
|
|
413
413
|
// in un commento («Avviato dopo NexusCrew») veniva sovrascritto. La parola non
|
|
@@ -426,7 +426,7 @@ function installCommands(platform, target, ctx) {
|
|
|
426
426
|
// dall'ancora: cosi' non possono divergere per costruzione, e il test guardia
|
|
427
427
|
// diventa una seconda difesa invece dell'unica.
|
|
428
428
|
//
|
|
429
|
-
//
|
|
429
|
+
// da revisione: l'ancora linux era un PREFISSO
|
|
430
430
|
// (`/^Description=NexusCrew\b/`) e un servizio di terzi che comincia la sua
|
|
431
431
|
// Description con il nostro nome — «NexusCrew-compatible proxy», «NexusCrew
|
|
432
432
|
// fork by tizio» — veniva riconosciuto come nostro e sovrascritto. Il mac era
|
package/lib/cli/stable-alias.js
CHANGED
|
@@ -109,7 +109,7 @@ function resolveBootPaths({ nodeBin, entryPath, env = process.env, realpath } =
|
|
|
109
109
|
};
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
//
|
|
112
|
+
// da revisione: il runner dell'aggiornamento puo' girare con un process.execPath gia'
|
|
113
113
|
// MORTO su disco — l'upgrade di node (Homebrew) unlinka il Cellar vecchio
|
|
114
114
|
// mentre il processo resta in memoria, e npm reinstalla il pacchetto nel
|
|
115
115
|
// prefix del node NUOVO. In quel caso resolveBootPaths scriverebbe il path
|
package/lib/diagnostics/store.js
CHANGED
|
@@ -4,10 +4,13 @@ const DEFAULT_MAX_RECORDS = 500;
|
|
|
4
4
|
const DEFAULT_MAX_BYTES = 256 * 1024;
|
|
5
5
|
const DEFAULT_MAX_ENTRY_BYTES = 4096;
|
|
6
6
|
const ALLOWED_DURATIONS = new Set([300, 900, 1800, 3600]);
|
|
7
|
-
const LEVELS = new Set(['debug', 'info', 'warn', 'error']);
|
|
7
|
+
const LEVELS = new Set(['debug', 'info', 'notice', 'warn', 'error']);
|
|
8
8
|
const META_KEYS = new Set([
|
|
9
9
|
'port', 'platform', 'reason', 'durationSeconds', 'count', 'errno', 'client',
|
|
10
10
|
'cell', 'engine', 'action', 'state', 'transport', 'phase', 'version', 'status', 'node', 'code',
|
|
11
|
+
// Observability (2026-08-28): metadati di routing e chiusure — identita di
|
|
12
|
+
// cella e id di messaggio, MAI contenuto (DENIED_KEY resta la seconda rete).
|
|
13
|
+
'fromCell', 'toCell', 'msgId', 'sessionId', 'durationMs', 'gapMs', 'closeCode', 'exitCode', 'drops',
|
|
11
14
|
]);
|
|
12
15
|
const DENIED_KEY = /(authorization|cookie|token|secret|credential|password|prompt|terminal|argv|command|env|content|payload|file|path|endpoint|url)/i;
|
|
13
16
|
|
package/lib/files/routes.js
CHANGED
|
@@ -47,7 +47,7 @@ function filesRoutes({ cfg, sessionExists, paste, notifier, readonly = () => fal
|
|
|
47
47
|
// pasteToSession). Il nome upload e' sanitizzato: se scatta, la causa e'
|
|
48
48
|
// il filesRoot di configurazione (control char) o un path oltre i 4096
|
|
49
49
|
// char. Rifiuto noto PRIMA di tmux: la PTY non va neanche tentata.
|
|
50
|
-
// 500, non 502 (rilievo
|
|
50
|
+
// 500, non 502 (rilievo da revisione interna): qui il valle NON e' stato
|
|
51
51
|
// contattato — a rifiutare siamo noi, per configurazione nostra. 502
|
|
52
52
|
// affermerebbe «il servizio a monte non risponde» e sveglierebbe un
|
|
53
53
|
// allarme su un tmux che sta benissimo. La coppia di codici e'
|
|
@@ -75,7 +75,7 @@ function filesRoutes({ cfg, sessionExists, paste, notifier, readonly = () => fal
|
|
|
75
75
|
// nell'outbox della sessione mittente. Path sorgente fail-closed: stringa
|
|
76
76
|
// assoluta, realpath ESISTENTE sotto HOME (i symlink si risolvono PRIMA del
|
|
77
77
|
// check: un link che esce da HOME viene rifiutato), file regolare.
|
|
78
|
-
//
|
|
78
|
+
// da revisione: gated READONLY — la copia e' una scrittura su disco; il gate
|
|
79
79
|
// sta PRIMA di ogni altro check (nessun probe di sessione/path in READONLY).
|
|
80
80
|
const bridgeReadonly = () => (cfg.readonlyDefault === true || process.env.NEXUSCREW_READONLY === '1');
|
|
81
81
|
router.post('/outbox', express.json({ limit: '8kb' }), (req, res) => {
|
package/lib/fleet/builtin.js
CHANGED
|
@@ -84,23 +84,32 @@ function draftFrom(defs) {
|
|
|
84
84
|
};
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
function warnEngineCap(log, engineId, count) {
|
|
88
|
+
const emit = typeof log === 'function' ? log : console.warn;
|
|
89
|
+
emit(`WARN fleet backfill: engine ${engineId} non aggiunto: ${count} engine dichiarati, cap ${CAPS.MAX_ENGINES} raggiunto; riduci gli engine prima di riprovare`);
|
|
90
|
+
}
|
|
91
|
+
|
|
87
92
|
// Upgrade locale, idempotente e non distruttivo: installazioni gia' esistenti
|
|
88
93
|
// ricevono l'engine standard Shell senza riscrivere celle o sostituire un id
|
|
89
94
|
// scelto dall'utente. Se lo store e' pieno o la scrittura non e' possibile, il
|
|
90
95
|
// bootstrap resta utilizzabile con le definizioni precedenti.
|
|
91
96
|
function backfillShellEngine(defsPath, defs, log) {
|
|
92
97
|
if (!defs) return defs;
|
|
98
|
+
const emit = typeof log === 'function' ? log : console.warn;
|
|
93
99
|
// Le condizioni si valutano su cio' che si legge DENTRO il lock, non
|
|
94
100
|
// sullo stato che avevamo in mano: e' la differenza fra decidere sul
|
|
95
101
|
// presente e decidere su una fotografia.
|
|
96
102
|
const esito = aggiornaDefinizioni(defsPath, (dentro) => {
|
|
97
103
|
if (dentro.engines.some((engine) => engine.managed?.client === 'shell')) return null;
|
|
98
104
|
if (dentro.engines.some((engine) => engine.id === 'shell.local')) return null;
|
|
99
|
-
if (dentro.engines.length >= CAPS.MAX_ENGINES)
|
|
105
|
+
if (dentro.engines.length >= CAPS.MAX_ENGINES) {
|
|
106
|
+
warnEngineCap(emit, 'shell.local', dentro.engines.length);
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
100
109
|
const draft = draftFrom(dentro);
|
|
101
110
|
draft.engines.push(defaultShellEngine());
|
|
102
111
|
return draft;
|
|
103
|
-
}, { log });
|
|
112
|
+
}, { log: emit });
|
|
104
113
|
return esito || defs;
|
|
105
114
|
}
|
|
106
115
|
|
|
@@ -115,6 +124,7 @@ function backfillShellEngine(defsPath, defs, log) {
|
|
|
115
124
|
// avviene solo nel field gate esplicito del coordinator).
|
|
116
125
|
function backfillAgyEngine(defsPath, defs, cfg = {}) {
|
|
117
126
|
if (!defs) return defs;
|
|
127
|
+
const emit = typeof cfg.log === 'function' ? cfg.log : console.warn;
|
|
118
128
|
const platform = cfg.platform || process.platform;
|
|
119
129
|
const termux = platform === 'android'
|
|
120
130
|
|| termuxRuntimePaths(cfg.env || process.env, { platform, home: cfg.home }) !== null;
|
|
@@ -124,11 +134,14 @@ function backfillAgyEngine(defsPath, defs, cfg = {}) {
|
|
|
124
134
|
const esito = aggiornaDefinizioni(defsPath, (dentro) => {
|
|
125
135
|
if (dentro.engines.some((engine) => engine.managed?.client === 'agy')) return null;
|
|
126
136
|
if (dentro.engines.some((engine) => engine.id === 'agy.native')) return null;
|
|
127
|
-
if (dentro.engines.length >= CAPS.MAX_ENGINES)
|
|
137
|
+
if (dentro.engines.length >= CAPS.MAX_ENGINES) {
|
|
138
|
+
warnEngineCap(emit, 'agy.native', dentro.engines.length);
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
128
141
|
const draft = draftFrom(dentro);
|
|
129
142
|
draft.engines.push(defaultAgyEngine());
|
|
130
143
|
return draft;
|
|
131
|
-
}, { log:
|
|
144
|
+
}, { log: emit });
|
|
132
145
|
return esito || defs;
|
|
133
146
|
}
|
|
134
147
|
|
|
@@ -175,7 +188,7 @@ function risolviEseguibile(nome) {
|
|
|
175
188
|
function dirScrivibileDaTutti(file) {
|
|
176
189
|
// Tutta la CATENA, non il solo genitore: con `/a` scrivibile da chiunque,
|
|
177
190
|
// `/a/b/docker` resta sostituibile rinominando `b` — il binario e la sua
|
|
178
|
-
// directory immediata possono essere ineccepibili e il percorso no.
|
|
191
|
+
// directory immediata possono essere ineccepibili e il percorso no. Una revisione
|
|
179
192
|
// ha riprodotto esattamente questo caso su un controllo fermo al genitore.
|
|
180
193
|
//
|
|
181
194
|
// Lo sticky bit interrompe la risalita per quel livello: li' il kernel
|
|
@@ -238,16 +251,20 @@ function defaultDesktopEngine() {
|
|
|
238
251
|
// — e' il posto dove qualcuno sa gia' che il container esiste.
|
|
239
252
|
function backfillDesktopEngine(defsPath, defs, log) {
|
|
240
253
|
if (!defs) return defs;
|
|
254
|
+
const emit = typeof log === 'function' ? log : console.warn;
|
|
241
255
|
if (defs.engines.some((engine) => engine.id === 'desktop.local')) {
|
|
242
256
|
return riparaDesktopEngine(defsPath, defs, log);
|
|
243
257
|
}
|
|
244
258
|
const esito = aggiornaDefinizioni(defsPath, (dentro) => {
|
|
245
259
|
if (dentro.engines.some((engine) => engine.id === 'desktop.local')) return null;
|
|
246
|
-
if (dentro.engines.length >= CAPS.MAX_ENGINES)
|
|
260
|
+
if (dentro.engines.length >= CAPS.MAX_ENGINES) {
|
|
261
|
+
warnEngineCap(emit, 'desktop.local', dentro.engines.length);
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
247
264
|
const draft = draftFrom(dentro);
|
|
248
265
|
draft.engines.push(defaultDesktopEngine());
|
|
249
266
|
return draft;
|
|
250
|
-
}, { log });
|
|
267
|
+
}, { log: emit });
|
|
251
268
|
return esito || defs;
|
|
252
269
|
}
|
|
253
270
|
|
|
@@ -322,17 +339,21 @@ function eDaRiparare(defs) {
|
|
|
322
339
|
// invariato), rispetta il cap MAX_ENGINES. Non tocca CELLE.
|
|
323
340
|
function backfillKimiEngine(defsPath, defs, log) {
|
|
324
341
|
if (!defs) return defs;
|
|
342
|
+
const emit = typeof log === 'function' ? log : console.warn;
|
|
325
343
|
// Le condizioni si valutano su cio' che si legge DENTRO il lock, non
|
|
326
344
|
// sullo stato che avevamo in mano: e' la differenza fra decidere sul
|
|
327
345
|
// presente e decidere su una fotografia.
|
|
328
346
|
const esito = aggiornaDefinizioni(defsPath, (dentro) => {
|
|
329
347
|
if (dentro.engines.some((engine) => engine.managed?.client === 'kimi')) return null;
|
|
330
348
|
if (dentro.engines.some((engine) => engine.id === 'kimi.native')) return null;
|
|
331
|
-
if (dentro.engines.length >= CAPS.MAX_ENGINES)
|
|
349
|
+
if (dentro.engines.length >= CAPS.MAX_ENGINES) {
|
|
350
|
+
warnEngineCap(emit, 'kimi.native', dentro.engines.length);
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
332
353
|
const draft = draftFrom(dentro);
|
|
333
354
|
draft.engines.push(defaultKimiEngine());
|
|
334
355
|
return draft;
|
|
335
|
-
}, { log });
|
|
356
|
+
}, { log: emit });
|
|
336
357
|
return esito || defs;
|
|
337
358
|
}
|
|
338
359
|
|
|
@@ -345,6 +366,7 @@ function backfillKimiEngine(defsPath, defs, log) {
|
|
|
345
366
|
// MAX_ENGINES. Non tocca CELLE.
|
|
346
367
|
function backfillGrokEngine(defsPath, defs, cfg = {}) {
|
|
347
368
|
if (!defs) return defs;
|
|
369
|
+
const emit = typeof cfg.log === 'function' ? cfg.log : console.warn;
|
|
348
370
|
const platform = cfg.platform || process.platform;
|
|
349
371
|
const termux = platform === 'android'
|
|
350
372
|
|| termuxRuntimePaths(cfg.env || process.env, { platform, home: cfg.home }) !== null;
|
|
@@ -354,11 +376,14 @@ function backfillGrokEngine(defsPath, defs, cfg = {}) {
|
|
|
354
376
|
const esito = aggiornaDefinizioni(defsPath, (dentro) => {
|
|
355
377
|
if (dentro.engines.some((engine) => engine.managed?.client === 'grok')) return null;
|
|
356
378
|
if (dentro.engines.some((engine) => engine.id === 'grok.native')) return null;
|
|
357
|
-
if (dentro.engines.length >= CAPS.MAX_ENGINES)
|
|
379
|
+
if (dentro.engines.length >= CAPS.MAX_ENGINES) {
|
|
380
|
+
warnEngineCap(emit, 'grok.native', dentro.engines.length);
|
|
381
|
+
return null;
|
|
382
|
+
}
|
|
358
383
|
const draft = draftFrom(dentro);
|
|
359
384
|
draft.engines.push(defaultGrokEngine());
|
|
360
385
|
return draft;
|
|
361
|
-
}, { log:
|
|
386
|
+
}, { log: emit });
|
|
362
387
|
return esito || defs;
|
|
363
388
|
}
|
|
364
389
|
|
|
@@ -372,17 +397,21 @@ function backfillGrokEngine(defsPath, defs, cfg = {}) {
|
|
|
372
397
|
// MAX_ENGINES. Non tocca CELLE.
|
|
373
398
|
function backfillVlEngine(defsPath, defs, log) {
|
|
374
399
|
if (!defs) return defs;
|
|
400
|
+
const emit = typeof log === 'function' ? log : console.warn;
|
|
375
401
|
// Le condizioni si valutano su cio' che si legge DENTRO il lock, non
|
|
376
402
|
// sullo stato che avevamo in mano: e' la differenza fra decidere sul
|
|
377
403
|
// presente e decidere su una fotografia.
|
|
378
404
|
const esito = aggiornaDefinizioni(defsPath, (dentro) => {
|
|
379
405
|
if (dentro.engines.some((engine) => engine.managed?.client === 'vl')) return null;
|
|
380
406
|
if (dentro.engines.some((engine) => engine.id === 'vl.native')) return null;
|
|
381
|
-
if (dentro.engines.length >= CAPS.MAX_ENGINES)
|
|
407
|
+
if (dentro.engines.length >= CAPS.MAX_ENGINES) {
|
|
408
|
+
warnEngineCap(emit, 'vl.native', dentro.engines.length);
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
382
411
|
const draft = draftFrom(dentro);
|
|
383
412
|
draft.engines.push(defaultVlEngine());
|
|
384
413
|
return draft;
|
|
385
|
-
}, { log });
|
|
414
|
+
}, { log: emit });
|
|
386
415
|
return esito || defs;
|
|
387
416
|
}
|
|
388
417
|
|
|
@@ -557,11 +586,11 @@ async function createBuiltinFleet(cfg = {}) {
|
|
|
557
586
|
// passano il flag, quindi la suite esistente non vede endpoint lease aperti da up().
|
|
558
587
|
const cellLeaseEnabled = cfg.cellLeaseEnabled === true;
|
|
559
588
|
|
|
560
|
-
// ---
|
|
589
|
+
// --- da revisione interna: TUTTI i gate availability AVVENGONO PRIMA di creare
|
|
561
590
|
// leaseManager/launchBroker e di leaseManager.boot(). leaseManager non esiste finche'
|
|
562
591
|
// la Fleet non e' confermata disponibile, quindi NESSUN endpoint lease resta vivo su
|
|
563
592
|
// un early return (off/blocked) finche' ogni gate resta sopra questa boundary.
|
|
564
|
-
//
|
|
593
|
+
// da revisione interna: questo e' un fatto contingente del ref corrente, NON una
|
|
565
594
|
// garanzia anti-refactor. Un gate aggiunto DOPO la creazione del manager passerebbe
|
|
566
595
|
// inosservato senza un controllo statico/mutation-sensitive: il commento non impedisce
|
|
567
596
|
// un `return off` futuro oltre la boundary. La promessa vera e' limitata: oggi nessun
|
|
@@ -576,12 +605,19 @@ async function createBuiltinFleet(cfg = {}) {
|
|
|
576
605
|
// fallito, non che ci sia stata un'eccezione.
|
|
577
606
|
const bootStat = {};
|
|
578
607
|
let boot = loadDefinitions(defsPath, bootStat);
|
|
579
|
-
if (!boot)
|
|
580
|
-
|
|
581
|
-
|
|
608
|
+
if (!boot) {
|
|
609
|
+
if (bootStat.parseReason) {
|
|
610
|
+
const emit = typeof cfg.log === 'function' ? cfg.log : console.warn;
|
|
611
|
+
emit(`WARN fleet definitions: ${bootStat.parseReason}`);
|
|
612
|
+
return { ...off, reason: bootStat.parseReason };
|
|
613
|
+
}
|
|
614
|
+
return bootStat.lstatBlocked
|
|
615
|
+
? { ...off, reason: `fleet.json non verificabile (${bootStat.lstatBlocked}): fail-closed, non "assente" né "invalido"` }
|
|
616
|
+
: off;
|
|
617
|
+
}
|
|
582
618
|
|
|
583
619
|
if (!readonly()) {
|
|
584
|
-
//
|
|
620
|
+
// da revisione (rilievo 3): lo snapshot della BASE si prende QUI, alla lettura
|
|
585
621
|
// che ha prodotto `boot` — non con una rilettura dopo la migrazione. Il
|
|
586
622
|
// vecchio `primaDelloScrivere` era una rilettura: una scrittura arrivata
|
|
587
623
|
// DURANTE migrateLegacyTmuxSessions (una catena di chiamate tmux: finestra
|
|
@@ -658,8 +694,8 @@ async function createBuiltinFleet(cfg = {}) {
|
|
|
658
694
|
// il canale lease del supervisore.
|
|
659
695
|
...(leaseManager ? {
|
|
660
696
|
onLease: (socket, lease) => {
|
|
661
|
-
//
|
|
662
|
-
// persistenza del bind fallisce.
|
|
697
|
+
// da revisione interna: attachInitial restituisce false se la
|
|
698
|
+
// persistenza del bind fallisce. da revisione interna: ora il
|
|
663
699
|
// broker chiama onLease PRIMA di scrivere il payload. Il ritorno
|
|
664
700
|
// ESPLICITO false (o la socket distrutta qui) comunica al broker di NON
|
|
665
701
|
// consegnare il payload: il supervisore riceve la chiusura prima del
|
|
@@ -1014,8 +1050,8 @@ async function createBuiltinFleet(cfg = {}) {
|
|
|
1014
1050
|
throw httpError(400, 'modello senza id o engine');
|
|
1015
1051
|
}
|
|
1016
1052
|
}
|
|
1017
|
-
if (!Array.isArray(engines) || engines.length < 1 || engines.length >
|
|
1018
|
-
throw httpError(400,
|
|
1053
|
+
if (!Array.isArray(engines) || engines.length < 1 || engines.length > CAPS.MAX_ENGINES) {
|
|
1054
|
+
throw httpError(400, `engines deve contenere 1..${CAPS.MAX_ENGINES} definizioni`);
|
|
1019
1055
|
}
|
|
1020
1056
|
const allowed = new Set(['id', 'label', 'rc', 'managed', 'command', 'args', 'envKeys', 'model', 'promptMode', 'promptFlag']);
|
|
1021
1057
|
const seen = new Set();
|
|
@@ -140,9 +140,9 @@
|
|
|
140
140
|
"supports_search_tool": false
|
|
141
141
|
},
|
|
142
142
|
{
|
|
143
|
-
"slug": "grok-4.
|
|
144
|
-
"display_name": "grok-4.
|
|
145
|
-
"description": "OpenCode Go: grok-4.
|
|
143
|
+
"slug": "grok-4.6",
|
|
144
|
+
"display_name": "grok-4.6",
|
|
145
|
+
"description": "OpenCode Go: grok-4.6",
|
|
146
146
|
"default_reasoning_level": "medium",
|
|
147
147
|
"supported_reasoning_levels": [
|
|
148
148
|
{
|