@mmmbuto/nexuscrew 0.9.16 → 0.9.18
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 +157 -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-CwsGpQwK.js → index-DmpQoCsc.js} +1 -1
- package/frontend/dist/index.html +1 -1
- package/frontend/dist/version.json +1 -1
- package/lib/auth/middleware.js +1 -1
- 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/files/routes.js +2 -2
- package/lib/fleet/builtin.js +6 -6
- package/lib/fleet/catalogs/zai-responses.json +82 -0
- package/lib/fleet/cell-exec.js +4 -4
- package/lib/fleet/cell-lease-server.js +17 -17
- package/lib/fleet/definitions.js +28 -17
- package/lib/fleet/launch-broker.js +3 -3
- package/lib/fleet/lease-client.js +1 -1
- package/lib/fleet/managed.js +86 -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 +1 -1
- 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 +3 -3
- 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 +1 -1
- package/package.json +20 -2
- package/frontend/index.html +0 -20
package/lib/fleet/definitions.js
CHANGED
|
@@ -205,7 +205,7 @@ function parseModel(m) {
|
|
|
205
205
|
// parseDefinitions(raw) -> {schemaVersion, engines, cells, models} | null
|
|
206
206
|
// Accetta stringa JSON o oggetto gia' parsato. Strict + fail-closed.
|
|
207
207
|
// ---------------------------------------------------------------------------
|
|
208
|
-
function parseDefinitions(raw, { allowLegacyTmuxNames = true, onReject = null } = {}) {
|
|
208
|
+
function parseDefinitions(raw, { allowLegacyTmuxNames = true, onReject = null, onIssue = null } = {}) {
|
|
209
209
|
const reject = (message) => {
|
|
210
210
|
if (typeof onReject === 'function') onReject(message);
|
|
211
211
|
return null;
|
|
@@ -233,7 +233,7 @@ function parseDefinitions(raw, { allowLegacyTmuxNames = true, onReject = null }
|
|
|
233
233
|
// realta' c'e'. `models` e' opzionale: una configurazione che non lo ha
|
|
234
234
|
// resta valida esattamente com'era.
|
|
235
235
|
//
|
|
236
|
-
//
|
|
236
|
+
// da revisione (pacchetto): extraModels porta il DESCRITTORE completo (id,
|
|
237
237
|
// engine, contextWindow, maxTokens, reasoning), non solo l'id. Prima era
|
|
238
238
|
// Map<engine, Set<id>>: bastava per il gate "questo id e' ammesso?"
|
|
239
239
|
// (declaredFor), ma contextWindow/maxTokens/reasoning sparivano — e
|
|
@@ -262,7 +262,7 @@ function parseDefinitions(raw, { allowLegacyTmuxNames = true, onReject = null }
|
|
|
262
262
|
const engineMap = new Map();
|
|
263
263
|
const engines = [];
|
|
264
264
|
for (const e of d.engines) {
|
|
265
|
-
const eng = parseEngine(e, { extraModels });
|
|
265
|
+
const eng = parseEngine(e, { extraModels, onIssue });
|
|
266
266
|
if (!eng) return null;
|
|
267
267
|
if (engineIds.has(eng.id)) return null; // id engine univoco
|
|
268
268
|
engineIds.add(eng.id);
|
|
@@ -276,7 +276,9 @@ function parseDefinitions(raw, { allowLegacyTmuxNames = true, onReject = null }
|
|
|
276
276
|
const cellIds = new Set();
|
|
277
277
|
const cells = [];
|
|
278
278
|
for (const c of d.cells) {
|
|
279
|
-
const cell = parseCell(c, engineIds, engineMap, {
|
|
279
|
+
const cell = parseCell(c, engineIds, engineMap, {
|
|
280
|
+
allowLegacyTmuxNames, extraModels, onReject: reject, onIssue,
|
|
281
|
+
});
|
|
280
282
|
if (!cell) return null;
|
|
281
283
|
if (cellIds.has(cell.id)) return null; // id cell univoco
|
|
282
284
|
cellIds.add(cell.id);
|
|
@@ -306,7 +308,7 @@ function parseDefinitions(raw, { allowLegacyTmuxNames = true, onReject = null }
|
|
|
306
308
|
}
|
|
307
309
|
}
|
|
308
310
|
|
|
309
|
-
function parseEngine(e, { extraModels = null } = {}) {
|
|
311
|
+
function parseEngine(e, { extraModels = null, onIssue = null } = {}) {
|
|
310
312
|
if (!e || typeof e !== 'object' || Array.isArray(e)) return null;
|
|
311
313
|
|
|
312
314
|
// id
|
|
@@ -330,7 +332,7 @@ function parseEngine(e, { extraModels = null } = {}) {
|
|
|
330
332
|
// Managed: NexusCrew conosce client/provider e compone internamente il
|
|
331
333
|
// processo. Nessun command/env/argv o segreto vive nella definizione.
|
|
332
334
|
if (e.managed !== undefined) {
|
|
333
|
-
const managed = normalizeManagedSpec(e.managed, { extraModels });
|
|
335
|
+
const managed = normalizeManagedSpec(e.managed, { extraModels, engineId: e.id, onIssue });
|
|
334
336
|
if (!managed) return null;
|
|
335
337
|
for (const key of ['command', 'args', 'env', 'promptMode', 'promptFlag', 'model']) {
|
|
336
338
|
if (e[key] !== undefined) return null;
|
|
@@ -340,7 +342,7 @@ function parseEngine(e, { extraModels = null } = {}) {
|
|
|
340
342
|
// pannello, e il fallback engine->cella di cellStatus vale per entrambi.
|
|
341
343
|
// Prima di questo blocco il campo veniva scartato in silenzio: un valore
|
|
342
344
|
// valido spariva e uno invalido veniva accettato (fail-open) — rilievo 1
|
|
343
|
-
//
|
|
345
|
+
// della revisione.
|
|
344
346
|
let managedPanelUrl;
|
|
345
347
|
if (e.panelUrl !== undefined) {
|
|
346
348
|
if (!validPanelUrl(e.panelUrl)) return null;
|
|
@@ -426,18 +428,20 @@ function parseEngine(e, { extraModels = null } = {}) {
|
|
|
426
428
|
// e' nell'allowlist): un peer poteva mettere un id arbitrario in una cella di
|
|
427
429
|
// questa installazione, e il boot lo usava senza ricontrollarlo.
|
|
428
430
|
//
|
|
429
|
-
// Rilievo
|
|
431
|
+
// Rilievo della revisione indipendente, provato con probe su due profili distinti.
|
|
430
432
|
// Il controllo e' quello vero, non una copia: si passa dalla stessa
|
|
431
433
|
// `normalizeManagedSpec` con le stesse dichiarazioni.
|
|
432
|
-
function cellModelAllowed(engineId, model, engineMap, extraModels) {
|
|
434
|
+
function cellModelAllowed(engineId, model, engineMap, extraModels, onIssue) {
|
|
433
435
|
const engine = engineMap.get(engineId);
|
|
434
436
|
// Engine custom (non gestito): il modello e' un argomento del comando, non un
|
|
435
437
|
// id di catalogo, e qui non c'e' nulla da validare.
|
|
436
438
|
if (!engine || !engine.managed) return true;
|
|
437
|
-
return !!normalizeManagedSpec({ ...engine.managed, model }, { extraModels });
|
|
439
|
+
return !!normalizeManagedSpec({ ...engine.managed, model }, { extraModels, engineId, onIssue });
|
|
438
440
|
}
|
|
439
441
|
|
|
440
|
-
function parseCell(c, engineIds, engineMap = new Map(), {
|
|
442
|
+
function parseCell(c, engineIds, engineMap = new Map(), {
|
|
443
|
+
allowLegacyTmuxNames = true, extraModels = null, onReject = null, onIssue = null,
|
|
444
|
+
} = {}) {
|
|
441
445
|
const reject = (message) => {
|
|
442
446
|
if (typeof onReject === 'function') onReject(message);
|
|
443
447
|
return null;
|
|
@@ -476,7 +480,7 @@ function parseCell(c, engineIds, engineMap = new Map(), { allowLegacyTmuxNames =
|
|
|
476
480
|
let model;
|
|
477
481
|
if (c.model !== undefined) {
|
|
478
482
|
if (typeof c.model !== 'string' || c.model.length > MAX_MODEL_VAL_LEN) return null;
|
|
479
|
-
if (!cellModelAllowed(c.engine, c.model, engineMap, extraModels)) return null;
|
|
483
|
+
if (!cellModelAllowed(c.engine, c.model, engineMap, extraModels, onIssue)) return null;
|
|
480
484
|
model = c.model;
|
|
481
485
|
}
|
|
482
486
|
|
|
@@ -493,7 +497,7 @@ function parseCell(c, engineIds, engineMap = new Map(), { allowLegacyTmuxNames =
|
|
|
493
497
|
if (!engineIds.has(engineId) || typeof value !== 'string' || !value || value.length > MAX_MODEL_VAL_LEN) return null;
|
|
494
498
|
// Anche la memoria per-engine passa dal gate: e' un valore che tornera'
|
|
495
499
|
// in uso appena si ritorna su quell'engine.
|
|
496
|
-
if (!cellModelAllowed(engineId, value, engineMap, extraModels)) return null;
|
|
500
|
+
if (!cellModelAllowed(engineId, value, engineMap, extraModels, onIssue)) return null;
|
|
497
501
|
models[engineId] = value;
|
|
498
502
|
}
|
|
499
503
|
}
|
|
@@ -658,7 +662,7 @@ function parseCell(c, engineIds, engineMap = new Map(), { allowLegacyTmuxNames =
|
|
|
658
662
|
//
|
|
659
663
|
// Leggi-modifica-scrivi SOTTO LOCK. `atomicWrite` garantisce che il file non
|
|
660
664
|
// resti a meta' — non che nessuno lo abbia cambiato mentre lo tenevi in mano:
|
|
661
|
-
// sono due proprieta' diverse, e la prima non implica la seconda.
|
|
665
|
+
// sono due proprieta' diverse, e la prima non implica la seconda. Una revisione ha
|
|
662
666
|
// riprodotto la perdita su SETTE percorsi diversi (i backfill di avvio, la
|
|
663
667
|
// riparazione desktop e la persistenza della migrazione): ognuno salvava,
|
|
664
668
|
// osservava sul disco il valore scritto da un altro, e lo perdeva scrivendo un
|
|
@@ -888,7 +892,7 @@ function validateCommandTrust(command) {
|
|
|
888
892
|
if (!st.isFile()) return { ok: false, reason: 'non e\' un file regolare (symlink o speciale)' }; // lstat: symlink -> isFile()=false
|
|
889
893
|
if (!(st.mode & 0o100)) return { ok: false, reason: 'non eseguibile dall\'owner' };
|
|
890
894
|
if (st.mode & 0o002) return { ok: false, reason: 'world-writable' };
|
|
891
|
-
// Owner check (design §9a,
|
|
895
|
+
// Owner check (design §9a, da revisione): il command deve appartenere
|
|
892
896
|
// all'utente del service o a root — un owner terzo potrebbe sostituire
|
|
893
897
|
// l'eseguibile mantenendo il path "trusted".
|
|
894
898
|
if (typeof process.getuid === 'function') {
|
|
@@ -1034,10 +1038,17 @@ function atomicWrite(p, data) {
|
|
|
1034
1038
|
|
|
1035
1039
|
// Le letture devono accettare store legacy per poterli migrare; le scritture
|
|
1036
1040
|
// invece sono sempre tmux-safe e rifiutano qualunque nuovo nome con punto.
|
|
1037
|
-
|
|
1041
|
+
// La causa viene raccolta DURANTE la validazione e riportata nel
|
|
1042
|
+
// messaggio finale (campo, valore, regola). Senza cause nominate il
|
|
1043
|
+
// messaggio resta quello storico, generico.
|
|
1044
|
+
const issues = [];
|
|
1045
|
+
const parsed = parseDefinitions(data, { allowLegacyTmuxNames: false, onIssue: (i) => issues.push(i) });
|
|
1038
1046
|
if (!parsed) {
|
|
1039
1047
|
backupPredecessor(p); // conserva il precedente per recovery/forensics
|
|
1040
|
-
|
|
1048
|
+
const detail = issues.length
|
|
1049
|
+
? ' — ' + issues.map((i) => `${i.field} ${JSON.stringify(i.value ?? '')}: ${i.rule}`).join('; ')
|
|
1050
|
+
: ' — validazione fallita';
|
|
1051
|
+
throw new Error(`definizioni fleet non valide${detail}`);
|
|
1041
1052
|
}
|
|
1042
1053
|
|
|
1043
1054
|
const dir = path.dirname(p);
|
|
@@ -103,8 +103,8 @@ function createLaunchBroker(cfg = {}) {
|
|
|
103
103
|
let raw = '';
|
|
104
104
|
socket.setEncoding('utf8');
|
|
105
105
|
socket.setTimeout(3000, () => socket.destroy());
|
|
106
|
-
// Correzione (
|
|
107
|
-
// il broker scrive su questo socket (il payload, o dopo
|
|
106
|
+
// Correzione (da revisione, stesso buco trovato in cell-lease-server.js):
|
|
107
|
+
// il broker scrive su questo socket (il payload, o dopo la chiamata onLease
|
|
108
108
|
// prima). Un peer che muore mentre quella write e' in volo emette
|
|
109
109
|
// 'error' senza listener -> throw fatale, INTERO processo giu'.
|
|
110
110
|
socket.once('error', () => { try { socket.destroy(); } catch (_) {} });
|
|
@@ -130,7 +130,7 @@ function createLaunchBroker(cfg = {}) {
|
|
|
130
130
|
// piu' il timeout idle da 3s pensato per il one-shot.
|
|
131
131
|
socket.setTimeout(0);
|
|
132
132
|
socket.removeAllListeners('data');
|
|
133
|
-
//
|
|
133
|
+
// da revisione interna: la lease PRIMA del payload. onLease
|
|
134
134
|
// associa la connessione al lease manager (attachInitial); solo se
|
|
135
135
|
// committa il payload viene scritto. Se onLease ritorna false, o ha
|
|
136
136
|
// gia' distrutto la socket, il payload NON parte: il client riceve
|
|
@@ -67,7 +67,7 @@ function startLeaseClient(initialSocket, info, seams = {}) {
|
|
|
67
67
|
clearTimer(reconnectTimer);
|
|
68
68
|
// R3.2: oltre la grace non si ritenta (deny o server muto non vanno in loop).
|
|
69
69
|
if (reconnectDeadline != null && now() >= reconnectDeadline) {
|
|
70
|
-
//
|
|
70
|
+
// da revisione interna: la grace e' scaduta senza un reconnect
|
|
71
71
|
// riuscito: la lease e' PERSA per questo supervisore. Prima si desisteva
|
|
72
72
|
// in silenzio e il child restava un orfano senza lease per tutta la sua
|
|
73
73
|
// vita, senza che nessuno lo sapesse. Ora il supervisore viene avvisato
|
package/lib/fleet/managed.js
CHANGED
|
@@ -27,6 +27,13 @@ const OLLAMA_CLOUD_MODELS = Object.freeze([
|
|
|
27
27
|
// glm-5.3-flash 1M corroborato 1000000 da 7 provider su models.dev);
|
|
28
28
|
// altrimenti convenzione binaria del repo (deepseek 1M -> 1048576;
|
|
29
29
|
// minimax 512K -> 524288, valore pre-esistente ripristinato).
|
|
30
|
+
// La wire Responses nativa di Z.AI (https://api.z.ai/api/v1). Gli id e le
|
|
31
|
+
// finestre sono misurati sul listing reale del server (catalogo Codex-style,
|
|
32
|
+
// 2026-09-04): glm-5.3 e glm-5.3-flash a 1M. In questa catena non c'e'
|
|
33
|
+
// AnthMorph e i tool restano mcp__<server>__* (namespace_tools=false era una
|
|
34
|
+
// necessita' del proxy, non del backend).
|
|
35
|
+
const ZAI_RESPONSES_MODELS = Object.freeze(['glm-5.3', 'glm-5.3-flash']);
|
|
36
|
+
const ZAI_RESPONSES_CONTEXT = 1048576;
|
|
30
37
|
const OLLAMA_CONTEXT = Object.freeze({
|
|
31
38
|
// glm-5.2: scheda «976K context» (era 1M: over-declare di 24k).
|
|
32
39
|
'glm-5.2': 976000, 'kimi-k2.7-code': 262144,
|
|
@@ -92,10 +99,20 @@ const MODEL_ALIASES = Object.freeze({
|
|
|
92
99
|
// dichiarazione e' per profilo e non per fornitore: gli elenchi di Alibaba per
|
|
93
100
|
// Claude e per Codex differiscono davvero, e generalizzare creerebbe
|
|
94
101
|
// combinazioni che non esistono.
|
|
95
|
-
|
|
102
|
+
// `extraModels` e' indicizzata per ENGINE ID (e' la chiave con cui
|
|
103
|
+
// definitions.js e `extraModelsFrom` la costruiscono), non per profilo: per un
|
|
104
|
+
// engine custom `profile.id` e' la chiave condivisa (`codex-vl.custom`) e con
|
|
105
|
+
// quella sola un modello dichiarato per QUELL'engine non viene mai visto.
|
|
106
|
+
// La ricerca prova prima l'ID dell'engine e ricade sul profilo: un engine che
|
|
107
|
+
// dichiara i propri modelli li trova, e una dichiarazione legacy fatta sul
|
|
108
|
+
// profilo continua a valere.
|
|
109
|
+
function declaredFor(extraModels, profileId, model, engineId = null) {
|
|
96
110
|
if (!extraModels || typeof extraModels.get !== 'function') return false;
|
|
97
|
-
const
|
|
98
|
-
return
|
|
111
|
+
const keys = engineId && engineId !== profileId ? [engineId, profileId] : [profileId];
|
|
112
|
+
return keys.some((k) => {
|
|
113
|
+
const set = extraModels.get(k);
|
|
114
|
+
return !!(set && typeof set.has === 'function' && set.has(model));
|
|
115
|
+
});
|
|
99
116
|
}
|
|
100
117
|
|
|
101
118
|
// Dalle definizioni alla mappa che il gate si aspetta. Sta qui e non nei
|
|
@@ -103,7 +120,7 @@ function declaredFor(extraModels, profileId, model) {
|
|
|
103
120
|
// uno la dimentichi perche' un modello dichiarato smetta di essere valido
|
|
104
121
|
// proprio nel punto che conta — l'avvio.
|
|
105
122
|
//
|
|
106
|
-
//
|
|
123
|
+
// da revisione: Map<engine, Map<id, model>> — porta il descrittore intero, non solo
|
|
107
124
|
// l'id (stesso motivo del commento gemello in definitions.js: extraModels e'
|
|
108
125
|
// costruita due volte, una dentro parseDefinitions per il parsing, una qui per
|
|
109
126
|
// il runtime che rilegge le definizioni gia' salvate; le due DEVONO restare
|
|
@@ -123,10 +140,23 @@ function extraModelsFrom(defs) {
|
|
|
123
140
|
// modelli (customCatalogFor, writePiProviderExtension) chiama questa; chi ha
|
|
124
141
|
// bisogno solo di validare un id (declaredFor) continua a usare extraModels
|
|
125
142
|
// direttamente — due bisogni diversi sulla STESSA struttura, non due copie.
|
|
126
|
-
|
|
143
|
+
// Stessa chiave di `declaredFor` — prima l'ID dell'engine, poi il
|
|
144
|
+
// profilo. L'unione deduplica per `id`: se lo stesso id compaia sia sotto
|
|
145
|
+
// l'engine sia sotto il profilo e' lo stesso modello, e il catalogo non deve
|
|
146
|
+
// contenerlo due volte.
|
|
147
|
+
function declaredModelsFor(extraModels, profileId, engineId = null) {
|
|
127
148
|
if (!extraModels || typeof extraModels.get !== 'function') return [];
|
|
128
|
-
const
|
|
129
|
-
|
|
149
|
+
const keys = engineId && engineId !== profileId ? [engineId, profileId] : [profileId];
|
|
150
|
+
const out = [];
|
|
151
|
+
const seen = new Set();
|
|
152
|
+
for (const k of keys) {
|
|
153
|
+
const byId = extraModels.get(k);
|
|
154
|
+
if (!byId || typeof byId.values !== 'function') continue;
|
|
155
|
+
for (const m of byId.values()) {
|
|
156
|
+
if (m && m.id != null && !seen.has(m.id)) { seen.add(m.id); out.push(m); }
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return out;
|
|
130
160
|
}
|
|
131
161
|
|
|
132
162
|
function canonicalModel(model) {
|
|
@@ -367,6 +397,8 @@ const CATALOG = Object.freeze([
|
|
|
367
397
|
{ id: 'codex-vl.opencode-go', client: 'codex-vl', provider: 'opencode-go', label: 'OpenCode Go', auth: 'OPENCODE_API_KEY', endpoint: OPENCODE_GO_API_BASE, protocol: 'openai_responses', model: 'deepseek-v4-flash', models: OPENCODE_GO_RESPONSES_MODELS, strictModels: true, core: true },
|
|
368
398
|
{ id: 'codex-vl.openrouter', client: 'codex-vl', provider: 'openrouter', label: 'OpenRouter', auth: 'OPENROUTER_API_KEY', endpoint: 'https://openrouter.ai/api/v1', protocol: 'openai_responses', requiresModel: true, core: true, notice: 'codex-openrouter' },
|
|
369
399
|
{ id: 'codex-vl.ollama-cloud', client: 'codex-vl', provider: 'ollama-cloud', label: 'Ollama Cloud', auth: 'OLLAMA_API_KEY', endpoint: 'https://ollama.com/v1', protocol: 'openai_responses', model: 'glm-5.2', models: OLLAMA_CLOUD_MODELS, legacySecrets: true, core: true },
|
|
400
|
+
{ id: 'codex-vl.zai-a', client: 'codex-vl', provider: 'zai-a', label: 'Z.AI A (Responses nativo)', auth: 'ZAI_API_KEY_A', endpoint: 'https://api.z.ai/api/v1', protocol: 'openai_responses', model: 'glm-5.3', models: ZAI_RESPONSES_MODELS, strictModels: true, core: true },
|
|
401
|
+
{ id: 'codex-vl.zai-p', client: 'codex-vl', provider: 'zai-p', label: 'Z.AI P (Responses nativo)', auth: 'ZAI_API_KEY_P', endpoint: 'https://api.z.ai/api/v1', protocol: 'openai_responses', model: 'glm-5.3', models: ZAI_RESPONSES_MODELS, strictModels: true, core: true },
|
|
370
402
|
{ id: 'codex-vl.openai-api', client: 'codex-vl', provider: 'openai-api', label: 'OpenAI API', auth: 'OPENAI_API_KEY', endpoint: 'https://api.openai.com/v1', protocol: 'openai_responses', core: true },
|
|
371
403
|
{ id: 'codex-vl.ollama', client: 'codex-vl', provider: 'ollama', label: 'Ollama local', auth: 'none', endpoint: 'local provider', protocol: 'openai_responses', localProvider: 'ollama', core: true },
|
|
372
404
|
{ id: 'codex-vl.lmstudio', client: 'codex-vl', provider: 'lmstudio', label: 'LM Studio', auth: 'none', endpoint: 'local provider', protocol: 'openai_responses', localProvider: 'lmstudio', core: true },
|
|
@@ -508,7 +540,15 @@ function sanitizeEnvPassthrough(list) {
|
|
|
508
540
|
return names;
|
|
509
541
|
}
|
|
510
542
|
|
|
511
|
-
function normalizeManagedSpec(value, { extraModels = null } = {}) {
|
|
543
|
+
function normalizeManagedSpec(value, { extraModels = null, engineId = null, onIssue = null } = {}) {
|
|
544
|
+
// Un rifiuto senza causa produce «validazione fallita» generica e
|
|
545
|
+
// l'operatore cerca a vuoto. Se il chiamante passa `onIssue`, i rifiuti
|
|
546
|
+
// NOMINATI riportano campo, valore e regola prima di tornare null; senza
|
|
547
|
+
// sink il comportamento resta identico (null, nessun messaggio).
|
|
548
|
+
const issue = (field, valueIssue, rule) => {
|
|
549
|
+
if (typeof onIssue === 'function') onIssue({ field, value: valueIssue, rule });
|
|
550
|
+
return null;
|
|
551
|
+
};
|
|
512
552
|
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
|
|
513
553
|
if (Object.keys(value).some((k) => !MANAGED_KEYS.has(k))) return null;
|
|
514
554
|
if (!CLIENT_LABELS[value.client]) return null;
|
|
@@ -538,7 +578,7 @@ function normalizeManagedSpec(value, { extraModels = null } = {}) {
|
|
|
538
578
|
// resta rifiutato, ed e' cio' che impedisce di far partire una cella con un
|
|
539
579
|
// modello inesistente e scoprirlo dal fallimento.
|
|
540
580
|
if (profile.strictModels && !(profile.models || []).includes(model)
|
|
541
|
-
&& !declaredFor(extraModels, profile.id, model)) return
|
|
581
|
+
&& !declaredFor(extraModels, profile.id, model, engineId)) return issue('model', model, 'modello non dichiarato: non e\' nel catalogo del profilo e non risulta nell\'array "models" della definizione');
|
|
542
582
|
const permissionPolicy = value.permissionPolicy === undefined ? (profile.client === 'claude' ? 'unsafe' : 'standard') : value.permissionPolicy;
|
|
543
583
|
if (permissionPolicy !== 'standard' && permissionPolicy !== 'unsafe') return null;
|
|
544
584
|
// vl e' standard-only: il CLI Vivling non ha flag di approvazione, quindi
|
|
@@ -556,7 +596,7 @@ function normalizeManagedSpec(value, { extraModels = null } = {}) {
|
|
|
556
596
|
const envKey = typeof value.envKey === 'string' && value.envKey.trim()
|
|
557
597
|
? value.envKey.trim()
|
|
558
598
|
: profile.defaultEnvKey;
|
|
559
|
-
if (!ENV_KEY_RE.test(envKey || '')) return
|
|
599
|
+
if (!ENV_KEY_RE.test(envKey || '')) return issue('envKey', envKey, `consentito ${ENV_KEY_RE}`);
|
|
560
600
|
out.envKey = envKey;
|
|
561
601
|
}
|
|
562
602
|
if (profile.custom) {
|
|
@@ -571,9 +611,9 @@ function normalizeManagedSpec(value, { extraModels = null } = {}) {
|
|
|
571
611
|
// altri client) ma per NOME via envPassthrough (D3) — un custom vl verso un
|
|
572
612
|
// endpoint senza auth (Ollama remoto) non dichiara nessuna chiave, quindi
|
|
573
613
|
// envKey vuota e' legittima solo per vl. Se dichiarata, resta un nome valido.
|
|
574
|
-
if (profile.client === 'vl' ? (envKey !== '' && !ENV_KEY_RE.test(envKey)) : !ENV_KEY_RE.test(envKey)) return
|
|
575
|
-
if (!PROVIDER_ID_RE.test(providerId)) return
|
|
576
|
-
if (!model || !(profile.protocols || [profile.protocol]).includes(protocol)) return
|
|
614
|
+
if (profile.client === 'vl' ? (envKey !== '' && !ENV_KEY_RE.test(envKey)) : !ENV_KEY_RE.test(envKey)) return issue('envKey', envKey, `consentito ${ENV_KEY_RE} (obbligatorio; vuoto ammesso solo per il client vl)`);
|
|
615
|
+
if (!PROVIDER_ID_RE.test(providerId)) return issue('providerId', providerId, `consentito ${PROVIDER_ID_RE}`);
|
|
616
|
+
if (!model || !(profile.protocols || [profile.protocol]).includes(protocol)) return issue('protocol', protocol, `protocollo non supportato dal profilo (ammessi: ${(profile.protocols || [profile.protocol]).join(', ')})`);
|
|
577
617
|
Object.assign(out, { displayName, baseUrl, ...(envKey ? { envKey } : {}), protocol, providerId });
|
|
578
618
|
}
|
|
579
619
|
// D3: envPassthrough e' un'allowlist di NOMI di variabili d'ambiente che il
|
|
@@ -767,7 +807,7 @@ function binaryCandidates(client, home) {
|
|
|
767
807
|
// (non possiamo dichiararlo "trovato"); ma chi costruisce il messaggio
|
|
768
808
|
// (describeManaged) puo' ora distinguere "client non trovato" da "non ho potuto
|
|
769
809
|
// verificare un candidato" — il discriminante e' CHI ha fallito, non che ci sia
|
|
770
|
-
// stata un'eccezione. Stesso principio gia' applicato in checkTermuxExec
|
|
810
|
+
// stata un'eccezione. Stesso principio gia' applicato in checkTermuxExec.
|
|
771
811
|
function findBinary(client, home, out) {
|
|
772
812
|
const blocked = Array.isArray(out && out.blocked) ? out.blocked : null;
|
|
773
813
|
for (const candidate of binaryCandidates(client, home)) {
|
|
@@ -1061,7 +1101,7 @@ function describeManaged(spec, cfg = {}) {
|
|
|
1061
1101
|
const extraModels = cfg.extraModels || null;
|
|
1062
1102
|
const normalized = normalizeManagedSpec(spec, { extraModels });
|
|
1063
1103
|
if (!normalized) {
|
|
1064
|
-
//
|
|
1104
|
+
// da revisione: il rifiuto resta (i descrittori NON appartengono al profilo di una
|
|
1065
1105
|
// cella: vengono dalla definizione dell'ENGINE, in `d.models` — due
|
|
1066
1106
|
// soggetti diversi, mescolarli renderebbe ambiguo chi dichiara cosa). Ma
|
|
1067
1107
|
// se la causa e' proprio questa, il messaggio generico "invalid managed
|
|
@@ -1238,7 +1278,7 @@ function sharedMcpArgs(home) {
|
|
|
1238
1278
|
// indicizzato sul percorso assoluto — ed e' reale: su questa installazione nove
|
|
1239
1279
|
// progetti ne hanno, fra cui la directory di lavoro delle celle. Dimenticarlo
|
|
1240
1280
|
// significava che un elenco parziale non negava server che la sessione carica
|
|
1241
|
-
// davvero, e l'operatore avrebbe creduto di averli esclusi. Trovato
|
|
1281
|
+
// davvero, e l'operatore avrebbe creduto di averli esclusi. Trovato da una revisione.
|
|
1242
1282
|
//
|
|
1243
1283
|
// RESTANO FUORI, e non sono enumerabili da qui: plugin, connettori claude.ai e
|
|
1244
1284
|
// la configurazione gestita di sistema. Per quelli l'unica esclusione certa e'
|
|
@@ -1356,7 +1396,7 @@ function ensurePrivateClaudeConfig(home, profileId, label, penguinMode) {
|
|
|
1356
1396
|
return configDir;
|
|
1357
1397
|
}
|
|
1358
1398
|
|
|
1359
|
-
//
|
|
1399
|
+
// da revisione: il file .ts generato NON e' il consumatore — Pi lo E'. Un test che
|
|
1360
1400
|
// legge solo il file resta verde su un'estensione che Pi rifiuta a runtime.
|
|
1361
1401
|
// CONTRATTO REALE che Pi (@earendil-works/pi-coding-agent 0.80.10) impone a
|
|
1362
1402
|
// ogni modello di `pi.registerProvider(id, {models: [...]})` — fonte:
|
|
@@ -1382,13 +1422,13 @@ function toPiModelConfig(m) {
|
|
|
1382
1422
|
};
|
|
1383
1423
|
}
|
|
1384
1424
|
|
|
1385
|
-
//
|
|
1425
|
+
// da revisione: `declaredModels` (opzionale) sono i descrittori dichiarati per l'ENGINE
|
|
1386
1426
|
// (via declaredModelsFor), per il ramo Pi CUSTOM — stesso motivo/stessa fonte
|
|
1387
1427
|
// di customCatalogFor, mai letti da `spec.models`. Il ramo Pi NON-custom
|
|
1388
1428
|
// (profile.piExtension) continua a passare `models` dentro l'oggetto spec-like
|
|
1389
1429
|
// come sempre: e' un catalogo STATICO cablato nel codice (es. alibaba-token-
|
|
1390
1430
|
// plan), GIA' nella forma completa che Pi si aspetta — un caso diverso, non
|
|
1391
|
-
// toccato dal difetto
|
|
1431
|
+
// toccato dal difetto da revisione. declaredModels, quando presente, ha priorita', ed e'
|
|
1392
1432
|
// sempre passato per toPiModelConfig (mai i descrittori grezzi cosi' come sono).
|
|
1393
1433
|
function writePiProviderExtension(spec, home, declaredModels) {
|
|
1394
1434
|
const dir = path.join(home, '.nexuscrew', 'pi-providers');
|
|
@@ -1524,7 +1564,7 @@ function vlSystemAppendGate(binary, cfg) {
|
|
|
1524
1564
|
return { ok: true };
|
|
1525
1565
|
}
|
|
1526
1566
|
|
|
1527
|
-
//
|
|
1567
|
+
// (fix definitivo — una revisione del pacchetto aveva bocciato la prima versione:
|
|
1528
1568
|
// il test costruiva `spec.models` a mano, e in produzione quel campo non
|
|
1529
1569
|
// esiste mai). `declaredModels` NON viene da `spec`: i descrittori sono
|
|
1530
1570
|
// proprieta' della definizione dell'ENGINE (`d.models` del documento), non del
|
|
@@ -1614,19 +1654,21 @@ function resolveManagedEngine(engine, cell, cfg = {}) {
|
|
|
1614
1654
|
// non basta: la normalizzazione che decide se la cella PARTE e' questa, e
|
|
1615
1655
|
// senza l'elenco dichiarato un modello legittimo diventa «invalid managed
|
|
1616
1656
|
// profile» — cioe' una cella che non si avvia per una ragione invisibile.
|
|
1617
|
-
// E' il difetto che
|
|
1657
|
+
// E' il difetto che la revisione ha ripreso due volte: la vista diceva di si',
|
|
1618
1658
|
// il boot diceva di no, e nessun test guardava il boot.
|
|
1619
|
-
const spec = normalizeManagedSpec(engine.managed, { extraModels: cfg.extraModels || null });
|
|
1659
|
+
const spec = normalizeManagedSpec(engine.managed, { extraModels: cfg.extraModels || null, engineId: engine.id });
|
|
1620
1660
|
const info = describeManaged(spec, cfg);
|
|
1621
1661
|
if (!spec || !info.configured) return { ok: false, reason: info.reason, info };
|
|
1622
1662
|
const home = cfg.home || require('node:os').homedir();
|
|
1623
1663
|
const profile = profileFor(spec.client, spec.provider, spec.credentialProfile || '');
|
|
1624
|
-
//
|
|
1664
|
+
// da revisione: i descrittori dichiarati per QUESTO profilo (client.provider — la
|
|
1625
1665
|
// stessa chiave che declaredFor usa dentro normalizeManagedSpec per
|
|
1626
1666
|
// validare gli id). Vengono dalla definizione dell'ENGINE (extraModels),
|
|
1627
1667
|
// MAI da spec: e' il ponte che customCatalogFor/writePiProviderExtension
|
|
1628
1668
|
// (rami custom) usano per emettere catalogo e finestra di contesto.
|
|
1629
|
-
|
|
1669
|
+
// Prima i modelli dichiarati per QUESTO engine (per un custom e' la
|
|
1670
|
+
// chiave che conta: 'codex-vl.custom' e' condivisa), poi il fallback profilo.
|
|
1671
|
+
const declaredModels = declaredModelsFor(cfg.extraModels, profile.id, engine.id);
|
|
1630
1672
|
const cred = credential(profile, spec, cfg, home);
|
|
1631
1673
|
// L'override PER-CELLA va canonicalizzato come lo spec: `normalizeManagedSpec`
|
|
1632
1674
|
// applica l'alias a `spec.model`, ma `cell.model` lo scavalca DOPO e senza
|
|
@@ -1842,10 +1884,28 @@ function resolveManagedEngine(engine, cell, cfg = {}) {
|
|
|
1842
1884
|
args.push('-c', `model_catalog_json=${JSON.stringify(localCatalog)}`);
|
|
1843
1885
|
args.push('-c', `model_context_window=${opencodeContext}`);
|
|
1844
1886
|
}
|
|
1887
|
+
} else if (spec.provider === 'zai-a' || spec.provider === 'zai-p') {
|
|
1888
|
+
// Wire Responses nativa di Z.AI — niente AnthMorph e nessuna
|
|
1889
|
+
// traduzione: previous_response_id e streaming restano semantica nativa.
|
|
1890
|
+
// namespace_tools=false NON serve (era del proxy); i parametri di
|
|
1891
|
+
// robustezza sono quelli degli altri wrapper Responses misurati, e il
|
|
1892
|
+
// catalogo porta i metadata reali del server (context window 1M).
|
|
1893
|
+
const zaiId = `zai_${spec.provider.slice(-1)}`;
|
|
1894
|
+
const zaiEnvKey = spec.provider === 'zai-a' ? 'ZAI_API_KEY_A' : 'ZAI_API_KEY_P';
|
|
1895
|
+
env[zaiEnvKey] = cred.value;
|
|
1896
|
+
args.push(...codexProviderArgs(zaiId, 'Z.AI (Responses nativo)', profile.endpoint, zaiEnvKey));
|
|
1897
|
+
args.push('-c', `model_providers.${zaiId}.request_max_retries=4`);
|
|
1898
|
+
args.push('-c', `model_providers.${zaiId}.stream_max_retries=10`);
|
|
1899
|
+
args.push('-c', `model_providers.${zaiId}.stream_idle_timeout_ms=300000`);
|
|
1900
|
+
const zaiCatalog = path.join(__dirname, 'catalogs', 'zai-responses.json');
|
|
1901
|
+
if (fs.existsSync(zaiCatalog)) {
|
|
1902
|
+
args.push('-c', `model_catalog_json=${JSON.stringify(zaiCatalog)}`);
|
|
1903
|
+
args.push('-c', `model_context_window=${ZAI_RESPONSES_CONTEXT}`);
|
|
1904
|
+
}
|
|
1845
1905
|
} else if (spec.provider === 'custom') {
|
|
1846
1906
|
env[spec.envKey] = cred.value;
|
|
1847
1907
|
args.push(...codexProviderArgs(spec.providerId, spec.displayName, spec.baseUrl, spec.envKey));
|
|
1848
|
-
//
|
|
1908
|
+
// da revisione: onora `models` (engine definition, già validato) come gli altri
|
|
1849
1909
|
// provider — deriva model_catalog_json e model_context_window, così
|
|
1850
1910
|
// Codex-VL non ricade sul fallback 272K. Senza `models` -> null (no regressione).
|
|
1851
1911
|
const customMeta = customCatalogFor(spec, model, declaredModels, home);
|
|
@@ -2029,7 +2089,7 @@ module.exports = {
|
|
|
2029
2089
|
defaultDefinitions, defaultShellEngine, defaultAgyEngine, defaultKimiEngine, defaultGrokEngine, defaultVlEngine, describeManaged, describeCatalogCredential, discoverOllamaModels, resolveManagedEngine, needsExplicitNode,
|
|
2030
2090
|
discoverPiModels, EXTERNAL_DISCOVERY_TIMEOUT_MS, parseEnvFile, parseProviderShellFile, findBinary, publicCatalog, writePiProviderExtension, customCatalogFor,
|
|
2031
2091
|
writeVlCellPrompt, vlSystemAppendGate,
|
|
2032
|
-
extraModelsFrom, declaredModelsFor,
|
|
2092
|
+
extraModelsFrom, declaredModelsFor, declaredFor,
|
|
2033
2093
|
providerKeyPaths, parseProviderKeyFiles, credentialSources, credential, CREDENTIAL_SOURCE_VALUES,
|
|
2034
2094
|
credentialEnvNeutralizeSet, applyStoreNeutralization,
|
|
2035
2095
|
ensureKimiClaudeConfig, ensureAlibabaClaudeConfig, resolveInteractiveShell,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// managed conservano il prompt su argv (finding separato) e i custom send-keys
|
|
5
5
|
// conservano injectPrompt legacy senza Enter.
|
|
6
6
|
//
|
|
7
|
-
// Contratto (design
|
|
7
|
+
// Contratto (design della revisione 0.8.47):
|
|
8
8
|
// - Readiness REALE prima della consegna: il pane viene classificato sul SOLO
|
|
9
9
|
// viewport visibile (capture-pane -p, mai scrollback -S: su generation>0 lo
|
|
10
10
|
// scrollback puo' contenere vecchi prompt e marker stale). Auth, consenso
|
package/lib/fleet/runtime.js
CHANGED
|
@@ -80,7 +80,7 @@ function createBuiltinRuntime(ctx) {
|
|
|
80
80
|
// solo — il ponte Live, che la chiede esplicitamente e in-processo — quindi
|
|
81
81
|
// e' lui a doverla chiedere, non tutti gli altri a doversene ricordare.
|
|
82
82
|
// Il backup vieta gia' le cwd assolute per la stessa ragione: sono specifiche
|
|
83
|
-
// del dispositivo. Rilievo di
|
|
83
|
+
// del dispositivo. Rilievo di una revisione indipendente.
|
|
84
84
|
async function cellStatus({ includeCwd = false } = {}) {
|
|
85
85
|
if (Date.now() - cache.at > STATUS_TTL_MS) {
|
|
86
86
|
reloadDefs(); // pick-up di edit esterne/file
|
|
@@ -100,7 +100,7 @@ function createBuiltinRuntime(ctx) {
|
|
|
100
100
|
const effectivePolicy = ['pi', 'shell'].includes(engineDef?.managed?.client)
|
|
101
101
|
? 'standard'
|
|
102
102
|
: (remembered || engineDefault || '');
|
|
103
|
-
//
|
|
103
|
+
// da revisione: panelUrl per-cella vince su quello precompilato dall'engine (es.
|
|
104
104
|
// desktop.local), che a sua volta e' il default. Stessa forma di
|
|
105
105
|
// engineDef gia' usata sopra per la permission policy: un valore
|
|
106
106
|
// presente qui e' gia' passato da validPanelUrl a monte (parseEngine/
|
|
@@ -222,7 +222,7 @@ function createBuiltinRuntime(ctx) {
|
|
|
222
222
|
// broker-backed supervisor: credentials never enter tmux state/argv and a
|
|
223
223
|
// client that exits after readiness is restarted with bounded backoff.
|
|
224
224
|
// '-P -F #{pane_id}': tmux stampa il pane id della sessione appena creata,
|
|
225
|
-
// cosi' l'iniezione del prompt bersaglia ESATTAMENTE quel pane (
|
|
225
|
+
// cosi' l'iniezione del prompt bersaglia ESATTAMENTE quel pane (da revisione impl
|
|
226
226
|
// #5: elimina la race di riuso del nome sessione tra waitAlive e paste).
|
|
227
227
|
const readyMs = cfg.launchReadyMs != null ? cfg.launchReadyMs : 500;
|
|
228
228
|
const child = composeClientInvocation(launchEngine, cell);
|
package/lib/live-host/bridge.js
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
// emendata da rev5 MC2). La riga che decide è in codex-rs
|
|
23
23
|
// core/src/config/mod.rs: `developer_instructions.or(cfg.developer_
|
|
24
24
|
// instructions)` — l'override Some scarta il valore di config. R2
|
|
25
|
-
// (
|
|
25
|
+
// (verso corretto dopo revisione pre-release): l'identità della
|
|
26
26
|
// cella designata viaggia SEMPRE come intestazione anteposta al campo,
|
|
27
27
|
// anche senza prompt. Il campo NON è additivo: una cella senza
|
|
28
28
|
// LIVE_PROMPT.md, che prima non passava nulla e riceveva le developer
|
|
@@ -227,7 +227,7 @@ function readCellPrompt(filesRoot, tmuxSession) {
|
|
|
227
227
|
// il ponte: il nome esatto della sessione, che il server MCP accetta da
|
|
228
228
|
// NEXUSCREW_MCP_SESSION via stdio. Il valore NON si mette in systemd
|
|
229
229
|
// Environment= (una identità statica condivisa = impersonare una cella
|
|
230
|
-
// fissa, demolito in
|
|
230
|
+
// fissa, demolito in revisione v1): lo dice l'intestazione, per-cella.
|
|
231
231
|
//
|
|
232
232
|
// Il fatto, niente di più: quale cella (id Fleet) e, se il roster la dichiara,
|
|
233
233
|
// la sessione tmux esatta — quella con cui la voce raggiunge i canonici della
|
|
@@ -497,7 +497,7 @@ function createLiveBridge({
|
|
|
497
497
|
// Tre condizioni diverse, tre nomi: la designazione dichiarata non
|
|
498
498
|
// eleggibile dall'hub, la cella che non esiste piu' nel roster, e la cella
|
|
499
499
|
// che c'e' ma e' spenta. Un nome solo mandava a guardare l'hub anche quando
|
|
500
|
-
// il problema era una sessione chiusa. Rilievo di
|
|
500
|
+
// il problema era una sessione chiusa. Rilievo di una revisione indipendente.
|
|
501
501
|
if (snap.eligible !== true) return none('host-ineligible');
|
|
502
502
|
|
|
503
503
|
let cell;
|
package/lib/live-host/routes.js
CHANGED
|
@@ -39,7 +39,7 @@ function isActive(cell) {
|
|
|
39
39
|
return !!(cell && cell.active === true && cell.tmux !== false);
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
// --- Seam lease↔designazione (
|
|
42
|
+
// --- Seam lease↔designazione (decisione presa in revisione: grace = false) -----
|
|
43
43
|
//
|
|
44
44
|
// L'idoneita' dell'host designato non e' piu' solo «sessione tmux viva»: con
|
|
45
45
|
// remain-on-exit la sessione sopravvive alla morte del supervisore, e la
|
package/lib/mcp/server.js
CHANGED
|
@@ -162,7 +162,7 @@ function resolveIdentity({ env, tmuxBin, execFileImpl }) {
|
|
|
162
162
|
return settle(missing);
|
|
163
163
|
}
|
|
164
164
|
const name = String(stdout || '').trim();
|
|
165
|
-
// tmux 3.4, misura
|
|
165
|
+
// tmux 3.4, misura della revisione (probe doppia): pane morto con -t ->
|
|
166
166
|
// rc=0 e stdout VUOTO. Il vuoto è il segnale dello stantio.
|
|
167
167
|
if (!name) return settle(stalePane);
|
|
168
168
|
if (isValidSession(name)) {
|
|
@@ -288,12 +288,11 @@ function createMcpServer(opts = {}) {
|
|
|
288
288
|
return j;
|
|
289
289
|
}
|
|
290
290
|
|
|
291
|
-
// NC-R. Aggiornare NexusCrew NON
|
|
292
|
-
//
|
|
293
|
-
// riavvio della cella.
|
|
294
|
-
//
|
|
295
|
-
//
|
|
296
|
-
// messo un giro intero a capirlo.
|
|
291
|
+
// NC-R. Aggiornare NexusCrew NON cambia il codice gia' caricato dai bridge
|
|
292
|
+
// MCP delle celle attive: ogni processo resta sulla versione con cui e' stato
|
|
293
|
+
// avviato fino al riavvio della cella. Dopo una correzione installata, un
|
|
294
|
+
// bridge ancora attivo puo' quindi restituire l'errore precedente; prima di
|
|
295
|
+
// valutare la correzione vanno confrontate le versioni di hub e bridge.
|
|
297
296
|
//
|
|
298
297
|
// Il momento in cui serve saperlo e' esattamente quello in cui qualcosa
|
|
299
298
|
// fallisce, quindi la verifica sta SOLO sul ramo d'errore: a regime non costa
|
|
@@ -367,7 +366,7 @@ function createMcpServer(opts = {}) {
|
|
|
367
366
|
return replyError(null, INVALID_REQUEST, 'richiesta non valida (atteso oggetto JSON-RPC)');
|
|
368
367
|
}
|
|
369
368
|
const id = (typeof msg.id === 'string' || typeof msg.id === 'number') ? msg.id : undefined;
|
|
370
|
-
//
|
|
369
|
+
// da revisione: SOLO JSON-RPC 2.0 — versione assente/errata -> -32600 anche
|
|
371
370
|
// per i messaggi senza id (il no-op vale solo per notification 2.0 valide).
|
|
372
371
|
if (msg.jsonrpc !== '2.0') {
|
|
373
372
|
return replyError(id !== undefined ? id : null, INVALID_REQUEST, 'jsonrpc "2.0" richiesto');
|
package/lib/mcp/tools.js
CHANGED
|
@@ -345,7 +345,7 @@ const TOOLS = [
|
|
|
345
345
|
// Restituirlo cosi' evita di far leggere un successo dove c'e' un rifiuto.
|
|
346
346
|
// Nessun conteggio: il dispatcher non lo propaga, e un ramo che non puo'
|
|
347
347
|
// mai essere vero e' codice morto travestito da informazione
|
|
348
|
-
// (rilievo
|
|
348
|
+
// (rilievo di una revisione indipendente). Se un giorno servira' il dettaglio,
|
|
349
349
|
// va fatto propagare da forward(), non dedotto qui.
|
|
350
350
|
if (target) return { status: j.status, ...(j.reason ? { reason: j.reason } : {}) };
|
|
351
351
|
return { delivered: j.delivered };
|
package/lib/nodes/commands.js
CHANGED
|
@@ -204,7 +204,7 @@ function nodesAdd(opts) {
|
|
|
204
204
|
|
|
205
205
|
log(`nodes add: nodo "${name}" aggiunto (ssh ${ssh}${sshPort ? `:${sshPort}` : ''}, nexus remoto ${remotePort} -> locale ${localPort})`);
|
|
206
206
|
log('Incolla nel ~/.ssh/authorized_keys del NODO (lato forward, chiave dedicata):');
|
|
207
|
-
//
|
|
207
|
+
// da revisione: permitopen con le destinazioni EXPLICITE dei -L. Qui si conosce solo
|
|
208
208
|
// la porta nexus: la porta pannello del peer la annuncia il JOIN, non
|
|
209
209
|
// esiste ancora — quando apparirà, la riga completa (due destinazioni) la
|
|
210
210
|
// emette il pairing. Mai un permesso generico: è ciò che impedisce a questa
|
|
@@ -392,7 +392,7 @@ function nodesRemove(opts) {
|
|
|
392
392
|
let next;
|
|
393
393
|
try { next = store.removeNode(st, name); }
|
|
394
394
|
catch (e) { log(`nodes remove: ${e.message}`); return { code: 1 }; }
|
|
395
|
-
// Ferma un eventuale forward tunnel ATTIVO prima di togliere la config (
|
|
395
|
+
// Ferma un eventuale forward tunnel ATTIVO prima di togliere la config (da revisione):
|
|
396
396
|
// senza questo il ssh resterebbe orfano — porta locale aperta verso un nodo che non
|
|
397
397
|
// e' piu' in config, irraggiungibile e senza piu' uno stop pulito via CLI/API.
|
|
398
398
|
let stopped = false;
|
package/lib/nodes/health.js
CHANGED
|
@@ -51,7 +51,7 @@ function applyProbeHysteresis(key, probed, previous, now, threshold) {
|
|
|
51
51
|
// now is simply down — reporting 'unknown' would keep a declared-dead peer
|
|
52
52
|
// out of the down state forever (one probe per process, never reaching the
|
|
53
53
|
// threshold unless someone keeps probing). The hysteresis only exists to
|
|
54
|
-
// protect a measured up state from jitter, per
|
|
54
|
+
// protect a measured up state from jitter, per una revisione precedente.
|
|
55
55
|
return { ...probed, consecutiveFailures: failures };
|
|
56
56
|
}
|
|
57
57
|
|