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