@mmmbuto/nexuscrew 0.9.24 → 0.9.26
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 +75 -0
- package/frontend/dist/assets/{index-DzCo6cJc.js → index-Bdh6CVRY.js} +1 -1
- package/frontend/dist/index.html +1 -1
- package/frontend/dist/version.json +1 -1
- package/lib/cli/commands.js +33 -1
- package/lib/cli/doctor.js +87 -4
- package/lib/config.js +51 -1
- package/lib/fleet/builtin.js +13 -1
- package/lib/fleet/catalogs/opencode-go.json +200 -16
- package/lib/fleet/cell-exec.js +31 -5
- package/lib/fleet/identity-provision.js +142 -0
- package/lib/fleet/managed.js +140 -14
- package/lib/fleet/runtime.js +30 -1
- package/lib/mcp/identity-provider.js +133 -5
- package/lib/mcp/identity-schema.js +2 -0
- package/lib/mcp/server.js +50 -9
- package/lib/mcp/tools.js +15 -4
- package/package.json +1 -1
package/lib/mcp/server.js
CHANGED
|
@@ -32,6 +32,7 @@ const { TOOLS, IDENTITY_CODE, IDENTITY_REMEDIATION } = require('./tools.js');
|
|
|
32
32
|
const cells = require('./cells.js');
|
|
33
33
|
const {
|
|
34
34
|
normalizeIdentityContext, IDENTITY_CONTEXT_AUTHORITY_UNAVAILABLE,
|
|
35
|
+
IDENTITY_CONTEXT_VERIFIED_ENV_INVALID,
|
|
35
36
|
} = require('./identity-schema.js');
|
|
36
37
|
const { persistIdentityChannel } = require('./identity-provider.js');
|
|
37
38
|
|
|
@@ -99,18 +100,55 @@ function companionInstructions() {
|
|
|
99
100
|
// senza cambiare la precedenza e senza esporre valori/segreti. `resolveSession`
|
|
100
101
|
// resta il wrapper pubblico Promise<string|null> invariato (compatibilita').
|
|
101
102
|
const IDENTITY_REQUIRED_ENV_VARS = Object.freeze(['TMUX', 'TMUX_PANE', 'NEXUSCREW_MCP_SESSION']);
|
|
103
|
+
// i nomi dei metadati verified consegnati dal launcher VL (solo su
|
|
104
|
+
// sessione bound). Presence only: mai i valori.
|
|
105
|
+
const IDENTITY_VERIFIED_ENV_VARS = Object.freeze([
|
|
106
|
+
'NEXUSCREW_VERIFIED_ENV_VERSION',
|
|
107
|
+
'NEXUSCREW_VERIFIED_OWNER_INSTANCE_ID',
|
|
108
|
+
'NEXUSCREW_VERIFIED_CELL_ID',
|
|
109
|
+
'NEXUSCREW_VERIFIED_INCARNATION_ID',
|
|
110
|
+
'NEXUSCREW_VERIFIED_BINDING_ID',
|
|
111
|
+
'NEXUSCREW_VERIFIED_ORIGIN',
|
|
112
|
+
'NEXUSCREW_VERIFIED_THREAD_ID',
|
|
113
|
+
]);
|
|
114
|
+
const IDENTITY_ALL_REQUIRED_ENV_VARS = Object.freeze([
|
|
115
|
+
...IDENTITY_REQUIRED_ENV_VARS, ...IDENTITY_VERIFIED_ENV_VARS,
|
|
116
|
+
]);
|
|
102
117
|
|
|
103
118
|
function envPresenceOf(env) {
|
|
104
|
-
|
|
119
|
+
const presence = {
|
|
105
120
|
TMUX: !!env.TMUX,
|
|
106
121
|
TMUX_PANE: !!env.TMUX_PANE,
|
|
107
122
|
NEXUSCREW_MCP_SESSION: !!(typeof env.NEXUSCREW_MCP_SESSION === 'string' && env.NEXUSCREW_MCP_SESSION.trim()),
|
|
108
123
|
};
|
|
124
|
+
for (const name of IDENTITY_VERIFIED_ENV_VARS) {
|
|
125
|
+
presence[name] = !!(typeof env[name] === 'string' && env[name].trim());
|
|
126
|
+
}
|
|
127
|
+
return presence;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// la fonte verified esiste se ANY dei nomi verified e' presente.
|
|
131
|
+
// Presence parziale = fonte presente ma invalida (fail-closed), non assenza.
|
|
132
|
+
function verifiedEnvPresent(envPresence) {
|
|
133
|
+
return IDENTITY_VERIFIED_ENV_VARS.some((name) => envPresence[name] === true);
|
|
109
134
|
}
|
|
110
135
|
|
|
111
136
|
function resolveIdentity({ env, tmuxBin, execFileImpl }) {
|
|
112
137
|
const e = env || {};
|
|
113
138
|
const envPresence = envPresenceOf(e);
|
|
139
|
+
// se una qualunque variabile verified e' presente, il percorso
|
|
140
|
+
// legacy e' VETATO (non e' un fallback, e' un percorso alternativo usato
|
|
141
|
+
// solo in assenza totale della verified). La verifica completa (introspezione
|
|
142
|
+
// live/coerente) vive nel provider online; qui il chiamante senza provider
|
|
143
|
+
// riceve comunque un fail-closed nominato invece di un'identita' legacy
|
|
144
|
+
// fabbricata accanto a metadati verified di cui non si puo' garantire la
|
|
145
|
+
// coerenza.
|
|
146
|
+
if (verifiedEnvPresent(envPresence)) {
|
|
147
|
+
return {
|
|
148
|
+
session: null, source: 'verified-env', code: IDENTITY_CODE.VERIFIED_ENV_INVALID,
|
|
149
|
+
envPresence, requiredEnvVars: IDENTITY_ALL_REQUIRED_ENV_VARS, remediation: IDENTITY_REMEDIATION,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
114
152
|
const fallbackRaw = e.NEXUSCREW_MCP_SESSION;
|
|
115
153
|
const fallbackPresent = typeof fallbackRaw === 'string' && fallbackRaw.trim().length > 0;
|
|
116
154
|
const tmuxPresent = !!e.TMUX;
|
|
@@ -130,24 +168,24 @@ function resolveIdentity({ env, tmuxBin, execFileImpl }) {
|
|
|
130
168
|
|
|
131
169
|
const ok = (session, source) => ({
|
|
132
170
|
session, source, code: IDENTITY_CODE.OK,
|
|
133
|
-
envPresence, requiredEnvVars:
|
|
171
|
+
envPresence, requiredEnvVars: IDENTITY_ALL_REQUIRED_ENV_VARS, remediation: IDENTITY_REMEDIATION,
|
|
134
172
|
});
|
|
135
173
|
const missing = () => ({
|
|
136
174
|
session: null, source: 'missing', code: codeWhenMissing(),
|
|
137
|
-
envPresence, requiredEnvVars:
|
|
175
|
+
envPresence, requiredEnvVars: IDENTITY_ALL_REQUIRED_ENV_VARS, remediation: IDENTITY_REMEDIATION,
|
|
138
176
|
});
|
|
139
177
|
// P0: pane stantio o non verificabile -> NON attribuire. source 'stale-pane'
|
|
140
178
|
// nomina il problema; code STALE_PANE (tools.js).
|
|
141
179
|
const stalePane = () => ({
|
|
142
180
|
session: null, source: 'stale-pane', code: IDENTITY_CODE.STALE_PANE,
|
|
143
|
-
envPresence, requiredEnvVars:
|
|
181
|
+
envPresence, requiredEnvVars: IDENTITY_ALL_REQUIRED_ENV_VARS, remediation: IDENTITY_REMEDIATION,
|
|
144
182
|
});
|
|
145
183
|
// P0/R2: tmux e fallback env dicono sessioni DIVERSE entrambe valide:
|
|
146
184
|
// identità ambigua -> NON attribuire (nemmeno il fallback: è parte del
|
|
147
185
|
// conflitto). Il code nomina il mismatch, che INVALID non direbbe.
|
|
148
186
|
const sessionMismatch = () => ({
|
|
149
187
|
session: null, source: 'session-mismatch', code: IDENTITY_CODE.SESSION_MISMATCH,
|
|
150
|
-
envPresence, requiredEnvVars:
|
|
188
|
+
envPresence, requiredEnvVars: IDENTITY_ALL_REQUIRED_ENV_VARS, remediation: IDENTITY_REMEDIATION,
|
|
151
189
|
});
|
|
152
190
|
|
|
153
191
|
return new Promise((resolve) => {
|
|
@@ -273,7 +311,8 @@ function createMcpServer(opts = {}) {
|
|
|
273
311
|
try {
|
|
274
312
|
raw = await identityContextProvider({ tool });
|
|
275
313
|
} catch (e) {
|
|
276
|
-
if (e && e.code === IDENTITY_CONTEXT_AUTHORITY_UNAVAILABLE
|
|
314
|
+
if (e && (e.code === IDENTITY_CONTEXT_AUTHORITY_UNAVAILABLE
|
|
315
|
+
|| e.code === IDENTITY_CONTEXT_VERIFIED_ENV_INVALID)) throw e;
|
|
277
316
|
throw identityContextError(IDENTITY_CONTEXT_MISSING,
|
|
278
317
|
'contesto identita online non disponibile: binding condiviso rifiutato');
|
|
279
318
|
}
|
|
@@ -284,7 +323,7 @@ function createMcpServer(opts = {}) {
|
|
|
284
323
|
if (!identityContextProvider) return localIdentity();
|
|
285
324
|
return identityContext({ tool, sharedRequired: true }).then((context) => ({
|
|
286
325
|
session: context.tmuxSession,
|
|
287
|
-
source: 'online',
|
|
326
|
+
source: identityContextProvider.identitySource === 'verified-env' ? 'verified-env' : 'online',
|
|
288
327
|
code: IDENTITY_CODE.OK,
|
|
289
328
|
envPresence: envPresenceOf(env),
|
|
290
329
|
requiredEnvVars: IDENTITY_REQUIRED_ENV_VARS,
|
|
@@ -442,9 +481,11 @@ function createMcpServer(opts = {}) {
|
|
|
442
481
|
? identityContextProvider.currentProof() : null;
|
|
443
482
|
const sharedIdentity = sharedContext ? Object.freeze({
|
|
444
483
|
session: sharedContext.tmuxSession,
|
|
445
|
-
source: '
|
|
484
|
+
source: identityContextProvider.identitySource === 'verified-env'
|
|
485
|
+
? 'verified-env' : 'online',
|
|
486
|
+
code: IDENTITY_CODE.OK,
|
|
446
487
|
envPresence: envPresenceOf(env),
|
|
447
|
-
requiredEnvVars:
|
|
488
|
+
requiredEnvVars: IDENTITY_ALL_REQUIRED_ENV_VARS,
|
|
448
489
|
remediation: IDENTITY_REMEDIATION,
|
|
449
490
|
context: sharedContext,
|
|
450
491
|
...(sharedProof ? { proof: sharedProof } : {}),
|
package/lib/mcp/tools.js
CHANGED
|
@@ -44,15 +44,26 @@ const IDENTITY_CODE = Object.freeze({
|
|
|
44
44
|
// P0/R2: tmux e NEXUSCREW_MCP_SESSION sono entrambi validi ma dicono sessioni
|
|
45
45
|
// diverse: identità ambigua, non si attribuisce nessuna delle due.
|
|
46
46
|
SESSION_MISMATCH: 'NEXUSCREW_MCP_IDENTITY_SESSION_MISMATCH',
|
|
47
|
+
// una fonte verified (NEXUSCREW_VERIFIED_*) presente ma non
|
|
48
|
+
// coerente (versione, owner, cella, canale, introspezione) e' fail-closed:
|
|
49
|
+
// MAI fallback alle variabili legacy quando la fonte verified esiste.
|
|
50
|
+
VERIFIED_ENV_INVALID: 'NEXUSCREW_MCP_IDENTITY_VERIFIED_ENV_INVALID',
|
|
47
51
|
});
|
|
48
52
|
|
|
49
53
|
// Remediation senza segreti/valori: nomi soltanto, compatibile con
|
|
50
54
|
// `codex-vl mcp add --env-var` (allowlist di nomi, nessun valore persistito).
|
|
55
|
+
// la remediation NON consiglia piu' di allowlistare TMUX/TMUX_PANE/
|
|
56
|
+
// NEXUSCREW_MCP_SESSION in env_vars: sul percorso shared verified quel permesso
|
|
57
|
+
// fa fallire lo spawn (rmcp-client utils: nomi riservati rifiutati). Le strade
|
|
58
|
+
// sono due: sessione verified (codex-vl >= 0.154.0-vl.3 + NexusCrew >= 0.9.25
|
|
59
|
+
// con authority mode provisionato) oppure sessione legacy con le tre variabili
|
|
60
|
+
// ereditate dal client MCP.
|
|
51
61
|
const IDENTITY_REMEDIATION =
|
|
52
|
-
'
|
|
53
|
-
+ '
|
|
54
|
-
+ '
|
|
55
|
-
+ '
|
|
62
|
+
'Due percorsi validi: (1) sessione verified — serve codex-vl >= 0.154.0-vl.3 e NexusCrew >= 0.9.25 '
|
|
63
|
+
+ 'con authority mode provisionato (nexuscrew identity provision): il launcher consegna i metadati '
|
|
64
|
+
+ 'NEXUSCREW_VERIFIED_* al figlio MCP, non serve allowlistare nulla; (2) sessione legacy — il client '
|
|
65
|
+
+ 'MCP deve ereditare TMUX/TMUX_PANE/NEXUSCREW_MCP_SESSION (niente allowlist in env_vars del server '
|
|
66
|
+
+ 'MCP: con codex-vl >= 0.154.0-vl.3 i nomi sono riservati e lo spawn fallisce).';
|
|
56
67
|
|
|
57
68
|
// Perche' una ricerca fallita non e' una sola cosa.
|
|
58
69
|
//
|