@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/cli/commands.js
CHANGED
|
@@ -86,7 +86,7 @@ Lifecycle semantics:
|
|
|
86
86
|
|
|
87
87
|
const CLI_VALUE_FLAGS = new Set([
|
|
88
88
|
'label', 'ssh', 'ssh-port', 'autostart', 'visibility', 'selected',
|
|
89
|
-
'name', 'local-name', 'local-label', 'identity-file', 'port',
|
|
89
|
+
'name', 'local-name', 'local-label', 'identity-file', 'port', 'dir',
|
|
90
90
|
]);
|
|
91
91
|
|
|
92
92
|
// valueFlags (Set|array opzionale): flag che consumano il token successivo nella
|
|
@@ -1508,6 +1508,38 @@ function dispatch(argv, opts = {}) {
|
|
|
1508
1508
|
serve({ ...opts, pidfile: flags.pidfile, serverStart: opts.serverStart });
|
|
1509
1509
|
return { code: 0, keepAlive: true }; // server.listen tiene il processo vivo; non exit
|
|
1510
1510
|
}
|
|
1511
|
+
if (cmd === 'identity') {
|
|
1512
|
+
const sub = rest[1];
|
|
1513
|
+
if (sub !== 'provision') {
|
|
1514
|
+
log('usage: nexuscrew identity provision [--force] [--no-write-config] [--dir <path>]');
|
|
1515
|
+
return { code: sub === undefined ? 0 : 1 };
|
|
1516
|
+
}
|
|
1517
|
+
const provision = require('../fleet/identity-provision.js').provisionIdentityAuthority;
|
|
1518
|
+
const home = opts.home || require('node:os').homedir();
|
|
1519
|
+
const result = provision({
|
|
1520
|
+
home,
|
|
1521
|
+
dir: flags.dir,
|
|
1522
|
+
force: flags.force === true,
|
|
1523
|
+
writeConfig: flags['no-write-config'] !== true,
|
|
1524
|
+
});
|
|
1525
|
+
if (!result.ok) {
|
|
1526
|
+
log(`identity provision: ${result.message}`);
|
|
1527
|
+
log(` existing credential files:`);
|
|
1528
|
+
for (const filePath of result.existing) log(` ${filePath}`);
|
|
1529
|
+
return { code: 1 };
|
|
1530
|
+
}
|
|
1531
|
+
log(`identity provision: credentials written under ${result.dir} (0600, dir 0700)`);
|
|
1532
|
+
log('identity provision: fleet.identity.mode = "authority"');
|
|
1533
|
+
if (result.configUpdated) {
|
|
1534
|
+
log(`identity provision: config updated (${result.mode})`);
|
|
1535
|
+
} else {
|
|
1536
|
+
log('identity provision: config NOT written (--no-write-config). Apply manually:');
|
|
1537
|
+
log(' fleet.identity.mode = "authority"');
|
|
1538
|
+
log(` keep both credential files in place under ${result.dir}`);
|
|
1539
|
+
}
|
|
1540
|
+
log('identity provision: restart the NexusCrew service to activate authority mode');
|
|
1541
|
+
return { code: 0 };
|
|
1542
|
+
}
|
|
1511
1543
|
if (cmd === 'doctor') {
|
|
1512
1544
|
if (flags.peers === true) return dispatchNodes(['nodes', 'doctor'], flags, opts);
|
|
1513
1545
|
const r = doctor({ ...opts, log });
|
package/lib/cli/doctor.js
CHANGED
|
@@ -462,6 +462,24 @@ function checkMcpIdentity(env) {
|
|
|
462
462
|
const e = env || process.env;
|
|
463
463
|
const hasTmux = !!(typeof e.TMUX === 'string' && e.TMUX);
|
|
464
464
|
const hasSession = !!(typeof e.NEXUSCREW_MCP_SESSION === 'string' && e.NEXUSCREW_MCP_SESSION.trim());
|
|
465
|
+
const verifiedNames = [
|
|
466
|
+
'NEXUSCREW_VERIFIED_ENV_VERSION',
|
|
467
|
+
'NEXUSCREW_VERIFIED_OWNER_INSTANCE_ID',
|
|
468
|
+
'NEXUSCREW_VERIFIED_CELL_ID',
|
|
469
|
+
'NEXUSCREW_VERIFIED_INCARNATION_ID',
|
|
470
|
+
'NEXUSCREW_VERIFIED_BINDING_ID',
|
|
471
|
+
'NEXUSCREW_VERIFIED_ORIGIN',
|
|
472
|
+
'NEXUSCREW_VERIFIED_THREAD_ID',
|
|
473
|
+
];
|
|
474
|
+
const verifiedPresent = verifiedNames.filter((name) => typeof e[name] === 'string' && e[name]);
|
|
475
|
+
if (verifiedPresent.length) {
|
|
476
|
+
return {
|
|
477
|
+
name: 'MCP identity env',
|
|
478
|
+
ok: true,
|
|
479
|
+
detail: `metadati verified osservabili (${verifiedPresent.length}/7 nomi NEXUSCREW_VERIFIED_*): `
|
|
480
|
+
+ 'identita attribuita solo dopo introspezione authority riuscita',
|
|
481
|
+
};
|
|
482
|
+
}
|
|
465
483
|
if (hasTmux || hasSession) {
|
|
466
484
|
const src = [];
|
|
467
485
|
if (hasTmux) src.push('TMUX');
|
|
@@ -469,16 +487,80 @@ function checkMcpIdentity(env) {
|
|
|
469
487
|
return {
|
|
470
488
|
name: 'MCP identity env',
|
|
471
489
|
ok: true,
|
|
472
|
-
detail: `identita osservabile (${src.join('+')})`,
|
|
490
|
+
detail: `identita legacy osservabile (${src.join('+')})`,
|
|
473
491
|
};
|
|
474
492
|
}
|
|
475
493
|
return {
|
|
476
494
|
name: 'MCP identity env',
|
|
477
495
|
ok: true, // mai FAIL: PWA-only senza MCP configurato non deve rompere il doctor
|
|
478
496
|
warn: true,
|
|
479
|
-
detail: 'nessuna identita MCP osservabile (
|
|
480
|
-
+ '
|
|
497
|
+
detail: 'nessuna identita MCP osservabile (nessun NEXUSCREW_VERIFIED_* e nessuna variabile legacy '
|
|
498
|
+
+ 'nel processo doctor). Percorsi validi: sessione verified (codex-vl >= 0.154.0-vl.3 + NexusCrew '
|
|
499
|
+
+ '>= 0.9.25 con authority mode provisionato) oppure sessione legacy con le tre variabili ereditate '
|
|
500
|
+
+ 'dal client MCP. NON allowlistare i nomi legacy in env_vars del server MCP: con vl.3 lo spawn '
|
|
501
|
+
+ 'verified fallisce — PWA-only: ignorabile',
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Stato dell'authority mode (registro flotta). Mostra mode e costruibilita' dell'
|
|
506
|
+
// authority (file presenti, permessi) SENZA mai esporre valori.
|
|
507
|
+
function checkIdentityAuthorityMode({ home, configPath, env } = {}) {
|
|
508
|
+
const e = env || process.env;
|
|
509
|
+
const fs = require('node:fs');
|
|
510
|
+
const path = require('node:path');
|
|
511
|
+
const cfgPath = configPath
|
|
512
|
+
|| process.env.NEXUSCREW_CONFIG_FILE
|
|
513
|
+
|| path.join(home || os.homedir(), '.nexuscrew', 'config.json');
|
|
514
|
+
let cfg = {};
|
|
515
|
+
try { cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8')) || {}; } catch (_) { cfg = {}; }
|
|
516
|
+
const mode = cfg.fleetIdentityMode
|
|
517
|
+
|| (cfg.fleet && cfg.fleet.identity && cfg.fleet.identity.mode)
|
|
518
|
+
|| 'legacy';
|
|
519
|
+
const dir = cfg.identityAuthorityDir || path.join(home || os.homedir(), '.nexuscrew', 'identity-authority');
|
|
520
|
+
const files = ['daemon.credential', 'launcher.credential'];
|
|
521
|
+
const present = [];
|
|
522
|
+
const values = {};
|
|
523
|
+
const problems = [];
|
|
524
|
+
for (const name of files) {
|
|
525
|
+
const filePath = path.join(dir, name);
|
|
526
|
+
try {
|
|
527
|
+
const st = fs.lstatSync(filePath);
|
|
528
|
+
if (!st.isFile()) { problems.push(`${name}: non regolare`); continue; }
|
|
529
|
+
if ((st.mode & 0o777) !== 0o600) { problems.push(`${name}: permessi non 0600`); continue; }
|
|
530
|
+
const value = fs.readFileSync(filePath, 'utf8').trim();
|
|
531
|
+
if (!value) { problems.push(`${name}: vuoto`); continue; }
|
|
532
|
+
present.push(name);
|
|
533
|
+
values[name] = value;
|
|
534
|
+
} catch (_) { problems.push(`${name}: assente`); }
|
|
535
|
+
}
|
|
536
|
+
// The two credentials must be DISTINCT (constant-time comparison):
|
|
537
|
+
// identical means the authority is not constructible, fail-closed.
|
|
538
|
+
let distinctLine = 'credentials distinct: n/a (una o entrambe assenti)';
|
|
539
|
+
if (present.length === 2) {
|
|
540
|
+
const distinct = Buffer.from(values['daemon.credential'])
|
|
541
|
+
.compare(Buffer.from(values['launcher.credential'])) !== 0;
|
|
542
|
+
distinctLine = `credentials distinct: ${distinct ? 'yes' : 'no'}`;
|
|
543
|
+
if (!distinct) problems.push('credenziali daemon e launcher identiche');
|
|
544
|
+
}
|
|
545
|
+
const constructible = present.length === 2 && problems.length === 0;
|
|
546
|
+
const ok = mode !== 'authority' || constructible;
|
|
547
|
+
const out = {
|
|
548
|
+
name: 'Fleet identity authority mode',
|
|
549
|
+
ok,
|
|
550
|
+
detail: `mode=${mode}; credenziali: ${present.length === 2 ? 'entrambe presenti (0600)' : present.join('+') || 'nessuna'} `
|
|
551
|
+
+ `(${dir}); ${distinctLine}${problems.length ? '; problemi: ' + problems.join(', ') : ''}`,
|
|
481
552
|
};
|
|
553
|
+
if (mode !== 'authority') {
|
|
554
|
+
out.warn = true;
|
|
555
|
+
out.detail += ' — il launcher espone CODEX_APP_SERVER_IDENTITY_REQUIRED=0: provisiona con '
|
|
556
|
+
+ '`nexuscrew identity provision` per attivare l\'authority mode';
|
|
557
|
+
} else if (!constructible) {
|
|
558
|
+
out.detail += ' — authority: configured but unavailable: '
|
|
559
|
+
+ (problems.length ? problems.join(', ') : 'credentials not constructible')
|
|
560
|
+
+ ' — le celle protette (codex-vl) non partono [IDENTITY_AUTHORITY_UNAVAILABLE]';
|
|
561
|
+
}
|
|
562
|
+
void e;
|
|
563
|
+
return out;
|
|
482
564
|
}
|
|
483
565
|
|
|
484
566
|
function checkFleetDefinitions(home, fleetDefsPath, enabled = true) {
|
|
@@ -578,6 +660,7 @@ function doctor(opts = {}) {
|
|
|
578
660
|
checkAutossh(existsImpl),
|
|
579
661
|
checkSshPermitlisten(opts.sshVersion),
|
|
580
662
|
checkMcpIdentity(opts.env),
|
|
663
|
+
checkIdentityAuthorityMode({ home: opts.home, env: opts.env }),
|
|
581
664
|
checkTmuxServerCwd(platform, execImpl, { tmuxBin: opts.tmuxBin, procCwdImpl: opts.procCwdImpl }),
|
|
582
665
|
checkTmuxServerTermuxPreload(opts.env, execImpl, { platform, home, tmuxBin: opts.tmuxBin }),
|
|
583
666
|
];
|
|
@@ -601,6 +684,6 @@ module.exports = {
|
|
|
601
684
|
checkFleetServiceWorkingDirectory,
|
|
602
685
|
checkTmuxSurvival, checkUserLinger,
|
|
603
686
|
checkSshClient, checkAutossh, checkSshPermitlisten,
|
|
604
|
-
checkMcpIdentity, checkTermuxExec,
|
|
687
|
+
checkMcpIdentity, checkIdentityAuthorityMode, checkTermuxExec,
|
|
605
688
|
checkTmuxServerCwd, checkTmuxServerTermuxPreload,
|
|
606
689
|
};
|
package/lib/config.js
CHANGED
|
@@ -135,8 +135,58 @@ function envOverrides() {
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
// Precedence: baseDefaults < config.json < env < opts. [B2]
|
|
138
|
+
// Le credenziali authority vivono in due file 0600 nella dir
|
|
139
|
+
// dell'authority (default ~/.nexuscrew/identity-authority). Se esistono vengono
|
|
140
|
+
// caricate qui, senza MAI loggare i valori: builtin costruisce l'authority
|
|
141
|
+
// solo quando ENTRAMBE sono presenti e distinte.
|
|
142
|
+
function readIdentityAuthorityCredentials(cfg = {}) {
|
|
143
|
+
const fs = require('node:fs');
|
|
144
|
+
const crypto = require('node:crypto');
|
|
145
|
+
const dir = cfg.identityAuthorityDir
|
|
146
|
+
|| path.join(cfg.home || os.homedir(), '.nexuscrew', 'identity-authority');
|
|
147
|
+
// Enforced AT LOAD. Identical credentials or wrong permissions
|
|
148
|
+
// (files != 0600, dir != 0700) => the credentials are NOT loaded: the
|
|
149
|
+
// authority is not constructed and the protected launch is refused instead.
|
|
150
|
+
// The reason travels in identityAuthorityFault, and never carries a value.
|
|
151
|
+
const faults = [];
|
|
152
|
+
let dirMode = null;
|
|
153
|
+
try { dirMode = fs.lstatSync(dir).mode & 0o777; } catch (_) { faults.push('authority dir missing'); }
|
|
154
|
+
if (dirMode !== null && dirMode !== 0o700) faults.push('authority dir mode is not 0700');
|
|
155
|
+
const readOne = (name) => {
|
|
156
|
+
try {
|
|
157
|
+
const st = fs.lstatSync(path.join(dir, name));
|
|
158
|
+
if (!st.isFile()) { faults.push(`${name}: not a regular file`); return undefined; }
|
|
159
|
+
if ((st.mode & 0o777) !== 0o600) { faults.push(`${name}: mode is not 0600`); return undefined; }
|
|
160
|
+
const value = fs.readFileSync(path.join(dir, name), 'utf8').trim();
|
|
161
|
+
if (!value) { faults.push(`${name}: empty`); return undefined; }
|
|
162
|
+
return value;
|
|
163
|
+
} catch (_) { faults.push(`${name}: missing`); return undefined; }
|
|
164
|
+
};
|
|
165
|
+
const daemon = readOne('daemon.credential');
|
|
166
|
+
const launcher = readOne('launcher.credential');
|
|
167
|
+
if (daemon && launcher) {
|
|
168
|
+
let distinct;
|
|
169
|
+
try {
|
|
170
|
+
distinct = !crypto.timingSafeEqual(Buffer.from(daemon), Buffer.from(launcher));
|
|
171
|
+
} catch (_) { distinct = false; }
|
|
172
|
+
if (!distinct) faults.push('daemon and launcher credentials are identical');
|
|
173
|
+
}
|
|
174
|
+
const out = {};
|
|
175
|
+
if (faults.length) {
|
|
176
|
+
out.identityAuthorityFault = faults.join('; ');
|
|
177
|
+
return out;
|
|
178
|
+
}
|
|
179
|
+
if (daemon) out.identityDaemonCredential = daemon;
|
|
180
|
+
if (launcher) out.identityLauncherCredential = launcher;
|
|
181
|
+
return out;
|
|
182
|
+
}
|
|
183
|
+
|
|
138
184
|
function loadConfig(opts = {}) {
|
|
139
|
-
|
|
185
|
+
const merged = { ...baseDefaults(), ...readConfigJson(), ...envOverrides(), ...opts };
|
|
186
|
+
// I file di credenziale sono fallback: non sovrascrivono mai valori espliciti
|
|
187
|
+
// di config.json/env/opts (precedence invariata).
|
|
188
|
+
const creds = readIdentityAuthorityCredentials(merged);
|
|
189
|
+
return { ...merged, ...creds };
|
|
140
190
|
}
|
|
141
191
|
|
|
142
192
|
// Retrocompat: defaults() = baseDefaults + env (NO config.json — per test isolati
|
package/lib/fleet/builtin.js
CHANGED
|
@@ -700,11 +700,23 @@ async function createBuiltinFleet(cfg = {}) {
|
|
|
700
700
|
// leaseManager e launchBroker. L'authority resta server-side: nessuna
|
|
701
701
|
// credenziale o chiave HMAC attraversa il payload consegnato alla cella.
|
|
702
702
|
const identityMode = cfg.fleetIdentityMode || cfg.fleet?.identity?.mode || 'legacy';
|
|
703
|
+
// Identical credentials => the authority is NOT constructed (consistent with
|
|
704
|
+
// identity-authority, which would reject at runtime anyway), and the reason
|
|
705
|
+
// goes to the log.
|
|
706
|
+
const identityCredentialsDistinct = (typeof cfg.identityDaemonCredential === 'string'
|
|
707
|
+
&& typeof cfg.identityLauncherCredential === 'string')
|
|
708
|
+
? Buffer.from(cfg.identityDaemonCredential).compare(Buffer.from(cfg.identityLauncherCredential)) !== 0
|
|
709
|
+
: !!(cfg.identityDaemonCredential && cfg.identityLauncherCredential);
|
|
710
|
+
if (identityMode === 'authority' && !identityCredentialsDistinct && cfg.log) {
|
|
711
|
+
cfg.log('fleet identity: authority mode requested but daemon and launcher credentials '
|
|
712
|
+
+ 'are identical or missing; identity enforcement stays off (REQUIRED=0)');
|
|
713
|
+
}
|
|
703
714
|
const identityOwnerInstanceId = typeof cfg.identityOwnerInstanceId === 'function'
|
|
704
715
|
? cfg.identityOwnerInstanceId()
|
|
705
716
|
: (cfg.identityOwnerInstanceId || null);
|
|
706
717
|
const identityAuthority = cfg.identityAuthority || (
|
|
707
|
-
|
|
718
|
+
identityCredentialsDistinct
|
|
719
|
+
&& cfg.identityDaemonCredential && cfg.identityLauncherCredential
|
|
708
720
|
? createIdentityAuthority({
|
|
709
721
|
dir: cfg.identityAuthorityDir || path.join(home, '.nexuscrew', 'identity-authority'),
|
|
710
722
|
daemonCredential: cfg.identityDaemonCredential,
|
|
@@ -279,9 +279,102 @@
|
|
|
279
279
|
"supports_search_tool": false
|
|
280
280
|
},
|
|
281
281
|
{
|
|
282
|
-
"slug": "
|
|
283
|
-
"display_name": "
|
|
284
|
-
"description": "OpenCode Go:
|
|
282
|
+
"slug": "deepseek-v4-flash-vision-exp",
|
|
283
|
+
"display_name": "deepseek-v4-flash-vision-exp",
|
|
284
|
+
"description": "OpenCode Go: deepseek-v4-flash-vision-exp (vision)",
|
|
285
|
+
"default_reasoning_level": "high",
|
|
286
|
+
"supported_reasoning_levels": [
|
|
287
|
+
{
|
|
288
|
+
"effort": "low",
|
|
289
|
+
"description": "Fast responses with lighter reasoning"
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
"effort": "high",
|
|
293
|
+
"description": "Greater reasoning depth for complex problems"
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
"effort": "max",
|
|
297
|
+
"description": "Maximum reasoning depth"
|
|
298
|
+
}
|
|
299
|
+
],
|
|
300
|
+
"shell_type": "default",
|
|
301
|
+
"visibility": "list",
|
|
302
|
+
"supported_in_api": true,
|
|
303
|
+
"priority": 57,
|
|
304
|
+
"availability_nux": null,
|
|
305
|
+
"upgrade": null,
|
|
306
|
+
"base_instructions": "",
|
|
307
|
+
"supports_reasoning_summaries": true,
|
|
308
|
+
"default_reasoning_summary": "none",
|
|
309
|
+
"support_verbosity": false,
|
|
310
|
+
"default_verbosity": null,
|
|
311
|
+
"apply_patch_tool_type": null,
|
|
312
|
+
"web_search_tool_type": "text",
|
|
313
|
+
"truncation_policy": {
|
|
314
|
+
"mode": "tokens",
|
|
315
|
+
"limit": 1000000
|
|
316
|
+
},
|
|
317
|
+
"supports_parallel_tool_calls": false,
|
|
318
|
+
"supports_image_detail_original": true,
|
|
319
|
+
"context_window": 1000000,
|
|
320
|
+
"effective_context_window_percent": 95,
|
|
321
|
+
"experimental_supported_tools": [],
|
|
322
|
+
"input_modalities": [
|
|
323
|
+
"text",
|
|
324
|
+
"image"
|
|
325
|
+
],
|
|
326
|
+
"supports_search_tool": false
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
"slug": "deepseek-v4.1-flash",
|
|
330
|
+
"display_name": "deepseek-v4.1-flash",
|
|
331
|
+
"description": "OpenCode Go: deepseek-v4.1-flash",
|
|
332
|
+
"default_reasoning_level": "high",
|
|
333
|
+
"supported_reasoning_levels": [
|
|
334
|
+
{
|
|
335
|
+
"effort": "low",
|
|
336
|
+
"description": "Fast responses with lighter reasoning"
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
"effort": "high",
|
|
340
|
+
"description": "Greater reasoning depth for complex problems"
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
"effort": "max",
|
|
344
|
+
"description": "Maximum reasoning depth"
|
|
345
|
+
}
|
|
346
|
+
],
|
|
347
|
+
"shell_type": "default",
|
|
348
|
+
"visibility": "list",
|
|
349
|
+
"supported_in_api": true,
|
|
350
|
+
"priority": 58,
|
|
351
|
+
"availability_nux": null,
|
|
352
|
+
"upgrade": null,
|
|
353
|
+
"base_instructions": "",
|
|
354
|
+
"supports_reasoning_summaries": true,
|
|
355
|
+
"default_reasoning_summary": "none",
|
|
356
|
+
"support_verbosity": false,
|
|
357
|
+
"default_verbosity": null,
|
|
358
|
+
"apply_patch_tool_type": null,
|
|
359
|
+
"web_search_tool_type": "text",
|
|
360
|
+
"truncation_policy": {
|
|
361
|
+
"mode": "tokens",
|
|
362
|
+
"limit": 1000000
|
|
363
|
+
},
|
|
364
|
+
"supports_parallel_tool_calls": false,
|
|
365
|
+
"supports_image_detail_original": false,
|
|
366
|
+
"context_window": 1000000,
|
|
367
|
+
"effective_context_window_percent": 95,
|
|
368
|
+
"experimental_supported_tools": [],
|
|
369
|
+
"input_modalities": [
|
|
370
|
+
"text"
|
|
371
|
+
],
|
|
372
|
+
"supports_search_tool": false
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
"slug": "glm-5.3",
|
|
376
|
+
"display_name": "glm-5.3",
|
|
377
|
+
"description": "OpenCode Go: glm-5.3",
|
|
285
378
|
"default_reasoning_level": "medium",
|
|
286
379
|
"supported_reasoning_levels": [
|
|
287
380
|
{
|
|
@@ -300,7 +393,7 @@
|
|
|
300
393
|
"shell_type": "default",
|
|
301
394
|
"visibility": "list",
|
|
302
395
|
"supported_in_api": true,
|
|
303
|
-
"priority":
|
|
396
|
+
"priority": 59,
|
|
304
397
|
"availability_nux": null,
|
|
305
398
|
"upgrade": null,
|
|
306
399
|
"base_instructions": "",
|
|
@@ -312,11 +405,11 @@
|
|
|
312
405
|
"web_search_tool_type": "text",
|
|
313
406
|
"truncation_policy": {
|
|
314
407
|
"mode": "tokens",
|
|
315
|
-
"limit":
|
|
408
|
+
"limit": 1000000
|
|
316
409
|
},
|
|
317
410
|
"supports_parallel_tool_calls": false,
|
|
318
411
|
"supports_image_detail_original": false,
|
|
319
|
-
"context_window":
|
|
412
|
+
"context_window": 1000000,
|
|
320
413
|
"effective_context_window_percent": 95,
|
|
321
414
|
"experimental_supported_tools": [],
|
|
322
415
|
"input_modalities": [
|
|
@@ -325,28 +418,74 @@
|
|
|
325
418
|
"supports_search_tool": false
|
|
326
419
|
},
|
|
327
420
|
{
|
|
328
|
-
"slug": "
|
|
329
|
-
"display_name": "
|
|
330
|
-
"description": "OpenCode Go:
|
|
331
|
-
"default_reasoning_level": "
|
|
421
|
+
"slug": "glm-5.3-flash",
|
|
422
|
+
"display_name": "glm-5.3-flash",
|
|
423
|
+
"description": "OpenCode Go: glm-5.3-flash",
|
|
424
|
+
"default_reasoning_level": "medium",
|
|
332
425
|
"supported_reasoning_levels": [
|
|
333
426
|
{
|
|
334
427
|
"effort": "low",
|
|
335
428
|
"description": "Fast responses with lighter reasoning"
|
|
336
429
|
},
|
|
430
|
+
{
|
|
431
|
+
"effort": "medium",
|
|
432
|
+
"description": "Balanced reasoning depth"
|
|
433
|
+
},
|
|
337
434
|
{
|
|
338
435
|
"effort": "high",
|
|
339
436
|
"description": "Greater reasoning depth for complex problems"
|
|
437
|
+
}
|
|
438
|
+
],
|
|
439
|
+
"shell_type": "default",
|
|
440
|
+
"visibility": "list",
|
|
441
|
+
"supported_in_api": true,
|
|
442
|
+
"priority": 60,
|
|
443
|
+
"availability_nux": null,
|
|
444
|
+
"upgrade": null,
|
|
445
|
+
"base_instructions": "",
|
|
446
|
+
"supports_reasoning_summaries": true,
|
|
447
|
+
"default_reasoning_summary": "none",
|
|
448
|
+
"support_verbosity": false,
|
|
449
|
+
"default_verbosity": null,
|
|
450
|
+
"apply_patch_tool_type": null,
|
|
451
|
+
"web_search_tool_type": "text",
|
|
452
|
+
"truncation_policy": {
|
|
453
|
+
"mode": "tokens",
|
|
454
|
+
"limit": 1000000
|
|
455
|
+
},
|
|
456
|
+
"supports_parallel_tool_calls": false,
|
|
457
|
+
"supports_image_detail_original": false,
|
|
458
|
+
"context_window": 1000000,
|
|
459
|
+
"effective_context_window_percent": 95,
|
|
460
|
+
"experimental_supported_tools": [],
|
|
461
|
+
"input_modalities": [
|
|
462
|
+
"text"
|
|
463
|
+
],
|
|
464
|
+
"supports_search_tool": false
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
"slug": "longcat-2.0",
|
|
468
|
+
"display_name": "longcat-2.0",
|
|
469
|
+
"description": "OpenCode Go: longcat-2.0",
|
|
470
|
+
"default_reasoning_level": "medium",
|
|
471
|
+
"supported_reasoning_levels": [
|
|
472
|
+
{
|
|
473
|
+
"effort": "low",
|
|
474
|
+
"description": "Fast responses with lighter reasoning"
|
|
340
475
|
},
|
|
341
476
|
{
|
|
342
|
-
"effort": "
|
|
343
|
-
"description": "
|
|
477
|
+
"effort": "medium",
|
|
478
|
+
"description": "Balanced reasoning depth"
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
"effort": "high",
|
|
482
|
+
"description": "Greater reasoning depth for complex problems"
|
|
344
483
|
}
|
|
345
484
|
],
|
|
346
485
|
"shell_type": "default",
|
|
347
486
|
"visibility": "list",
|
|
348
487
|
"supported_in_api": true,
|
|
349
|
-
"priority":
|
|
488
|
+
"priority": 61,
|
|
350
489
|
"availability_nux": null,
|
|
351
490
|
"upgrade": null,
|
|
352
491
|
"base_instructions": "",
|
|
@@ -361,13 +500,58 @@
|
|
|
361
500
|
"limit": 1000000
|
|
362
501
|
},
|
|
363
502
|
"supports_parallel_tool_calls": false,
|
|
364
|
-
"supports_image_detail_original":
|
|
503
|
+
"supports_image_detail_original": false,
|
|
365
504
|
"context_window": 1000000,
|
|
366
505
|
"effective_context_window_percent": 95,
|
|
367
506
|
"experimental_supported_tools": [],
|
|
368
507
|
"input_modalities": [
|
|
369
|
-
"text"
|
|
370
|
-
|
|
508
|
+
"text"
|
|
509
|
+
],
|
|
510
|
+
"supports_search_tool": false
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
"slug": "hy4-preview",
|
|
514
|
+
"display_name": "hy4-preview",
|
|
515
|
+
"description": "OpenCode Go: hy4-preview",
|
|
516
|
+
"default_reasoning_level": "medium",
|
|
517
|
+
"supported_reasoning_levels": [
|
|
518
|
+
{
|
|
519
|
+
"effort": "low",
|
|
520
|
+
"description": "Fast responses with lighter reasoning"
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
"effort": "medium",
|
|
524
|
+
"description": "Balanced reasoning depth"
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
"effort": "high",
|
|
528
|
+
"description": "Greater reasoning depth for complex problems"
|
|
529
|
+
}
|
|
530
|
+
],
|
|
531
|
+
"shell_type": "default",
|
|
532
|
+
"visibility": "list",
|
|
533
|
+
"supported_in_api": true,
|
|
534
|
+
"priority": 62,
|
|
535
|
+
"availability_nux": null,
|
|
536
|
+
"upgrade": null,
|
|
537
|
+
"base_instructions": "",
|
|
538
|
+
"supports_reasoning_summaries": true,
|
|
539
|
+
"default_reasoning_summary": "none",
|
|
540
|
+
"support_verbosity": false,
|
|
541
|
+
"default_verbosity": null,
|
|
542
|
+
"apply_patch_tool_type": null,
|
|
543
|
+
"web_search_tool_type": "text",
|
|
544
|
+
"truncation_policy": {
|
|
545
|
+
"mode": "tokens",
|
|
546
|
+
"limit": 1024000
|
|
547
|
+
},
|
|
548
|
+
"supports_parallel_tool_calls": false,
|
|
549
|
+
"supports_image_detail_original": false,
|
|
550
|
+
"context_window": 1024000,
|
|
551
|
+
"effective_context_window_percent": 95,
|
|
552
|
+
"experimental_supported_tools": [],
|
|
553
|
+
"input_modalities": [
|
|
554
|
+
"text"
|
|
371
555
|
],
|
|
372
556
|
"supports_search_tool": false
|
|
373
557
|
}
|
package/lib/fleet/cell-exec.js
CHANGED
|
@@ -111,9 +111,14 @@ function validLease(value) {
|
|
|
111
111
|
|
|
112
112
|
function validPayload(payload) {
|
|
113
113
|
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) return false;
|
|
114
|
-
if (Object.keys(payload).some((key) => !['command', 'args', 'env', 'supervise', 'restartPrompt', 'lease', 'identity'].includes(key))) return false;
|
|
114
|
+
if (Object.keys(payload).some((key) => !['command', 'args', 'env', 'supervise', 'restartPrompt', 'lease', 'identity', 'identityChannel'].includes(key))) return false;
|
|
115
115
|
if (typeof payload.command !== 'string' || !payload.command || !Array.isArray(payload.args)) return false;
|
|
116
116
|
if (!payload.env || typeof payload.env !== 'object' || Array.isArray(payload.env)) return false;
|
|
117
|
+
// Il launcher decide se il canale identita' esiste (managed.js) e lo dichiara
|
|
118
|
+
// nel payload con `identityChannel`: il supervisore non lo ricalcola. La chiave
|
|
119
|
+
// va quindi ammessa qui, e SOLO come booleano: un valore di altro tipo e' un
|
|
120
|
+
// payload malformato, non un "canale assente" (assente = caller precedente).
|
|
121
|
+
if (payload.identityChannel !== undefined && typeof payload.identityChannel !== 'boolean') return false;
|
|
117
122
|
return payload.args.every((v) => typeof v === 'string')
|
|
118
123
|
&& Object.entries(payload.env).every(([k, v]) => /^[A-Za-z_][A-Za-z0-9_]{0,63}$/.test(k) && typeof v === 'string')
|
|
119
124
|
&& validSupervise(payload.supervise)
|
|
@@ -522,7 +527,16 @@ async function main(argv = process.argv.slice(2), seams = {}) {
|
|
|
522
527
|
const sleep = seams.sleep || ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
523
528
|
const proc = seams.process || process;
|
|
524
529
|
const writeError = seams.writeError || seams.stderrWrite || ((message) => process.stderr.write(message));
|
|
525
|
-
|
|
530
|
+
// Il canale identita' nasce SOLO se il launcher lo ha deciso: la stessa
|
|
531
|
+
// risoluzione che decide CODEX_APP_SERVER_IDENTITY_REQUIRED in managed.js
|
|
532
|
+
// viaggia nel payload come `identityChannel`. Derivarlo qui (aggiungere
|
|
533
|
+
// sempre fd 3:4) permetteva ai due di divergere: una cella standalone
|
|
534
|
+
// riceveva il canale, avviava l'handshake contro un lease server legacy che
|
|
535
|
+
// rispondeva 'revoked', e moriva. Un payload senza il campo (caller
|
|
536
|
+
// precedenti) conserva il comportamento di prima.
|
|
537
|
+
const identityChannel = payload.identityChannel !== false;
|
|
538
|
+
const childEnv = { ...payload.env };
|
|
539
|
+
if (identityChannel) childEnv.NEXUSCREW_IDENTITY_FD = '3:4';
|
|
526
540
|
// tmux injects these only after the broker ticket was created. Preserve them
|
|
527
541
|
// for the actual TUI and bind NexusCrew MCP callbacks to the owning session.
|
|
528
542
|
if (process.env.TMUX) childEnv.TMUX = process.env.TMUX;
|
|
@@ -612,7 +626,11 @@ async function main(argv = process.argv.slice(2), seams = {}) {
|
|
|
612
626
|
const promptCtl = startGenerationPrompt(payload.restartPrompt, generation, childState, seams);
|
|
613
627
|
current = spawnImpl(payload.command, payload.args, {
|
|
614
628
|
env: childEnv,
|
|
615
|
-
|
|
629
|
+
// Senza canale non si creano nemmeno le pipe: fd 3 e 4 chiusi nel
|
|
630
|
+
// child (non pipe aperte e inutilizzate).
|
|
631
|
+
stdio: identityChannel
|
|
632
|
+
? ['inherit', 'inherit', 'inherit', 'pipe', 'pipe']
|
|
633
|
+
: ['inherit', 'inherit', 'inherit'],
|
|
616
634
|
});
|
|
617
635
|
// R1a (fix5): exit/error del figlio contano SUBITO, prima di qualunque
|
|
618
636
|
// await — se il figlio esce durante l'handshake di generazione, l'evento
|
|
@@ -649,7 +667,15 @@ async function main(argv = process.argv.slice(2), seams = {}) {
|
|
|
649
667
|
),
|
|
650
668
|
childExit.then(() => 'exited'),
|
|
651
669
|
]);
|
|
652
|
-
if (
|
|
670
|
+
if (!identityChannel) {
|
|
671
|
+
// Nessun canale da aprire: la transizione di generazione resta
|
|
672
|
+
// annunciata al lease (bookkeeping), ma l'identita' non esiste e
|
|
673
|
+
// non c'e' nessun fd da chiudere. L'uscita del figlio resta
|
|
674
|
+
// diagnostica.
|
|
675
|
+
if (raced === 'exited') {
|
|
676
|
+
writeError(`nexuscrew cell: figlio uscito durante l'handshake di generazione (gen ${generation})\n`);
|
|
677
|
+
}
|
|
678
|
+
} else if (raced === 'ok' && !childSettled) {
|
|
653
679
|
identityCtl = createIdentityChannel({
|
|
654
680
|
request: current && current.stdio && current.stdio[3],
|
|
655
681
|
response: current && current.stdio && current.stdio[4],
|
|
@@ -671,7 +697,7 @@ async function main(argv = process.argv.slice(2), seams = {}) {
|
|
|
671
697
|
} else {
|
|
672
698
|
writeError(`nexuscrew cell: generazione ${generation} senza identita' (${raced})\n`);
|
|
673
699
|
}
|
|
674
|
-
} else {
|
|
700
|
+
} else if (identityChannel) {
|
|
675
701
|
identityCtl = createIdentityChannel({
|
|
676
702
|
request: current && current.stdio && current.stdio[3],
|
|
677
703
|
response: current && current.stdio && current.stdio[4],
|