@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/fleet/managed.js
CHANGED
|
@@ -415,14 +415,14 @@ const OPENCODE_GO_PI_MODELS = Object.freeze(OPENCODE_GO_CHAT_MODELS.map((id) =>
|
|
|
415
415
|
|
|
416
416
|
const CUSTOM_KEYS = ['displayName', 'protocol', 'baseUrl', 'envKey', 'providerId'];
|
|
417
417
|
const MANAGED_KEYS = new Set(['client', 'provider', 'credentialProfile', 'model', 'permissionPolicy', 'credentialSourcePolicy', 'envPassthrough', ...CUSTOM_KEYS]);
|
|
418
|
-
//
|
|
418
|
+
// Massimo numero di nomi in `envPassthrough`. L'allowlist e' opt-in e per
|
|
419
419
|
// nome, mai un passthrough in blocco: un tetto basso ferma una lista incontrollata.
|
|
420
420
|
const MAX_ENV_PASSTHROUGH = 32;
|
|
421
421
|
const CODEX_APP_SERVER_IDENTITY_REQUIRED = 'CODEX_APP_SERVER_IDENTITY_REQUIRED';
|
|
422
422
|
const CODEX_APP_SERVER_IDENTITY_REQUIRED_DEFAULT = '1';
|
|
423
423
|
// Explicit credential source policy. Default 'auto' preserves the legacy
|
|
424
424
|
// resolution order (runtime -> store -> shell -> key files -> legacy) so a
|
|
425
|
-
//
|
|
425
|
+
// Pre- fleet.json migrates no-op: no existing cell changes resolution.
|
|
426
426
|
const CREDENTIAL_SOURCES = Object.freeze(['environment', 'nexuscrew-store', 'auto']);
|
|
427
427
|
// I valori che `credential().source` puo' assumere, e che escono verso la UI in
|
|
428
428
|
// `describeManaged().credentialSource` / `describeCatalogCredential()`. La UI li
|
|
@@ -517,7 +517,7 @@ const CATALOG = Object.freeze([
|
|
|
517
517
|
// VL/Vivling (repository `vl`): runtime TUI locale. Auth propria del runtime
|
|
518
518
|
// in OGNI variante (config.toml / VL_API_KEY): NexusCrew non legge ne' copia
|
|
519
519
|
// credenziali (auth 'none') — la chiave, dove serve, sale per NOME via
|
|
520
|
-
//
|
|
520
|
+
// EnvPassthrough, mai come valore, mai su argv. vl.native e' «usa la tua
|
|
521
521
|
// configurazione»: NESSUNA env provider/base_url, perche' i default interni del
|
|
522
522
|
// runtime sono gia' openai-compat + localhost:11434 e le VL_* ambientali
|
|
523
523
|
// sovrascrivono il config.toml (comporle qui cancellerebbe default_profile in
|
|
@@ -615,7 +615,7 @@ function profileFor(client, provider, credentialProfile) {
|
|
|
615
615
|
&& (p.credentialProfile || '') === (credentialProfile || '')) || null;
|
|
616
616
|
}
|
|
617
617
|
|
|
618
|
-
//
|
|
618
|
+
// Validazione unica dell'allowlist envPassthrough (nomi, mai valori): la
|
|
619
619
|
// usano sia la dichiarazione dell'operatore sia il default portato dal profilo
|
|
620
620
|
// del catalogo, cosi' i due percorsi non possono divergere sui vincoli.
|
|
621
621
|
function sanitizeEnvPassthrough(list) {
|
|
@@ -700,7 +700,7 @@ function normalizeManagedSpec(value, { extraModels = null, engineId = null, onIs
|
|
|
700
700
|
if (!displayName || displayName.length > 64 || /[\x00-\x1f\x7f]/.test(displayName)) return null;
|
|
701
701
|
if (!validBaseUrl(baseUrl)) return null;
|
|
702
702
|
// vl: la chiave NON viaggia come envKey copiata dallo store (pattern degli
|
|
703
|
-
//
|
|
703
|
+
// Altri client) ma per NOME via envPassthrough — un custom vl verso un
|
|
704
704
|
// endpoint senza auth (Ollama remoto) non dichiara nessuna chiave, quindi
|
|
705
705
|
// envKey vuota e' legittima solo per vl. Se dichiarata, resta un nome valido.
|
|
706
706
|
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)`);
|
|
@@ -708,7 +708,7 @@ function normalizeManagedSpec(value, { extraModels = null, engineId = null, onIs
|
|
|
708
708
|
if (!model || !(profile.protocols || [profile.protocol]).includes(protocol)) return issue('protocol', protocol, `protocollo non supportato dal profilo (ammessi: ${(profile.protocols || [profile.protocol]).join(', ')})`);
|
|
709
709
|
Object.assign(out, { displayName, baseUrl, ...(envKey ? { envKey } : {}), protocol, providerId });
|
|
710
710
|
}
|
|
711
|
-
//
|
|
711
|
+
// EnvPassthrough e' un'allowlist di NOMI di variabili d'ambiente che il
|
|
712
712
|
// child deve ricevere, risolti a runtime dalle credentialSources (dopo i rami
|
|
713
713
|
// provider in resolveManagedEngine). E' opt-in e per nome: MAI un passthrough
|
|
714
714
|
// in blocco dell'ambiente, solo i nomi elencati — ciascuno un nome env valido.
|
|
@@ -1227,9 +1227,9 @@ function describeManaged(spec, cfg = {}) {
|
|
|
1227
1227
|
const delegatedPiAuth = profile.client === 'pi' && profile.provider !== 'custom'
|
|
1228
1228
|
&& profile.delegatePiAuth !== false;
|
|
1229
1229
|
// vl: l'autenticazione e' del runtime in OGNI variante (auth 'none', o chiave
|
|
1230
|
-
//
|
|
1230
|
+
// Che sale per NOME via envPassthrough/ quando il profilo la dichiara). Il
|
|
1231
1231
|
// verdetto configured non puo' dipendere da una credenziale che NexusCrew non
|
|
1232
|
-
//
|
|
1232
|
+
// Possiede: il fail-closed giusto e' quello del, che NOMINA il nome mancante.
|
|
1233
1233
|
const authConfigured = delegatedPiAuth || profile.auth === 'login' || profile.auth === 'none'
|
|
1234
1234
|
|| profile.client === 'vl' || !!cred.value;
|
|
1235
1235
|
let configured = !!binary && authConfigured;
|
|
@@ -1267,7 +1267,17 @@ function describeManaged(spec, cfg = {}) {
|
|
|
1267
1267
|
// (source 'unreadable', authConfigured false) non viene collassata in
|
|
1268
1268
|
// 'missing' — il discriminante e' CHI ha fallito, non il esito grezzo.
|
|
1269
1269
|
credentialSource: cred.source,
|
|
1270
|
-
configured,
|
|
1270
|
+
configured,
|
|
1271
|
+
// Catalogo builtin del profilo PIU' i modelli dichiarati in
|
|
1272
|
+
// `definitions.models[]` per questo engine (o, legacy, per il profilo).
|
|
1273
|
+
// Senza l'unione un profilo custom — che catalogo non ha — elencava `[]`
|
|
1274
|
+
// anche con due modelli dichiarati: il boot li accettava (declaredFor) ma
|
|
1275
|
+
// la finestra, che legge da qui, non li mostrava.
|
|
1276
|
+
models: [...new Set([
|
|
1277
|
+
...(profile.models || []),
|
|
1278
|
+
...declaredModelsFor(extraModels, profile.id, cfg.engineId || null).map((m) => m.id),
|
|
1279
|
+
])],
|
|
1280
|
+
defaultModel: profile.model || '',
|
|
1271
1281
|
binary: binary || '', displayName: normalized.displayName || profile.label,
|
|
1272
1282
|
reason,
|
|
1273
1283
|
};
|
|
@@ -1564,11 +1574,11 @@ function writePiProviderExtension(spec, home, declaredModels) {
|
|
|
1564
1574
|
return target;
|
|
1565
1575
|
}
|
|
1566
1576
|
|
|
1567
|
-
//
|
|
1568
|
-
// VL_SYSTEM_APPEND_FILE
|
|
1569
|
-
//
|
|
1570
|
-
//
|
|
1571
|
-
//
|
|
1577
|
+
// Per-cell prompt for the vl runtime: since 0.3.1 vl reads
|
|
1578
|
+
// VL_SYSTEM_APPEND_FILE and composes the file on its own system (VL_SYSTEM
|
|
1579
|
+
// keeps its "replace" meaning and is untouched: a live wrapper depends on it).
|
|
1580
|
+
// The file carries the cell prompt and the MCP companion instructions: vl has
|
|
1581
|
+
// no MCP client, that is the only point where the text reaches it.
|
|
1572
1582
|
// forma e stesso posto di writePiProviderExtension: generato sotto
|
|
1573
1583
|
// ~/.nexuscrew, atomico (tmp+rename), mai credenziali — il contenuto e'
|
|
1574
1584
|
// cell.prompt piu' testo statico, nessun valore letto dalle sorgenti
|
|
@@ -1775,9 +1785,9 @@ function resolveManagedEngine(engine, cell, cfg = {}) {
|
|
|
1775
1785
|
// divergere. Divergono, e una cella standalone muore sull'handshake.
|
|
1776
1786
|
let identityChannel = null;
|
|
1777
1787
|
let identityAuthorityUnavailable = '';
|
|
1778
|
-
//
|
|
1779
|
-
//
|
|
1780
|
-
//
|
|
1788
|
+
// Why the vl per-cell prompt was NOT composed (runtime version that cannot
|
|
1789
|
+
// hold it, or a file that cannot be written safely).
|
|
1790
|
+
// Empty string = delivered. Surfaces with the engine resolved as vlPromptDegraded.
|
|
1781
1791
|
let vlPromptDegraded = '';
|
|
1782
1792
|
// I due profili Claude con configurazione privata: perdono gli MCP del file
|
|
1783
1793
|
// principale e vanno ricollegati (vedi `sharedMcpArgs`).
|
|
@@ -2042,12 +2052,12 @@ function resolveManagedEngine(engine, cell, cfg = {}) {
|
|
|
2042
2052
|
// TUI interattivo nella cwd della cella.
|
|
2043
2053
|
if (model) args.push('--model', model);
|
|
2044
2054
|
} else if (spec.client === 'vl') {
|
|
2045
|
-
// VL/Vivling runtime TUI: auth
|
|
2046
|
-
// (config.toml / VL_API_KEY
|
|
2047
|
-
//
|
|
2048
|
-
//
|
|
2049
|
-
// (VL_SYSTEM_APPEND_FILE, gate
|
|
2050
|
-
//
|
|
2055
|
+
// VL/Vivling runtime TUI: auth is handled by the runtime in every variant
|
|
2056
|
+
// (config.toml / VL_API_KEY by name via envPassthrough), never
|
|
2057
|
+
// credentials on argv. The model chosen in the UI travels via VL_MODEL
|
|
2058
|
+
// and the per-cell prompt via a file on the runtime system
|
|
2059
|
+
// (VL_SYSTEM_APPEND_FILE, gate below); `vl --profile` remains
|
|
2060
|
+
// the operator's choice. No --model on argv: the TUI starts without arguments.
|
|
2051
2061
|
if (model) env.VL_MODEL = model;
|
|
2052
2062
|
// vl.native e' «usa la tua configurazione»: NESSUNA env provider/base_url.
|
|
2053
2063
|
// I default interni del runtime sono gia' openai-compat + localhost:11434,
|
|
@@ -2079,12 +2089,12 @@ function resolveManagedEngine(engine, cell, cfg = {}) {
|
|
|
2079
2089
|
env.VL_PROVIDER = profile.vlProvider;
|
|
2080
2090
|
env.VL_BASE_URL = profile.endpoint;
|
|
2081
2091
|
}
|
|
2082
|
-
//
|
|
2083
|
-
// (VL_SYSTEM_APPEND_FILE).
|
|
2084
|
-
//
|
|
2085
|
-
//
|
|
2086
|
-
//
|
|
2087
|
-
//
|
|
2092
|
+
// Per-cell prompt: file composed on the runtime system
|
|
2093
|
+
// (VL_SYSTEM_APPEND_FILE). The version gate lives in code, not in hope:
|
|
2094
|
+
// below 0.3.1 the variable is silently ignored, and a cell starting without
|
|
2095
|
+
// its own prompt without saying so is the defect this branch closes,
|
|
2096
|
+
// not the remedy. Degrading = NOT composing the env (never a promise the
|
|
2097
|
+
// runtime discards) + declaring the reason.
|
|
2088
2098
|
const gate = vlSystemAppendGate(info.binary, cfg);
|
|
2089
2099
|
if (gate.ok) {
|
|
2090
2100
|
try {
|
|
@@ -2190,17 +2200,17 @@ function resolveManagedEngine(engine, cell, cfg = {}) {
|
|
|
2190
2200
|
// (finding separato, fuori da questa patch).
|
|
2191
2201
|
const promptViaDelivery = spec.client === 'kimi'
|
|
2192
2202
|
|| (spec.client === 'claude' && spec.provider === 'kimi-code');
|
|
2193
|
-
// vl
|
|
2194
|
-
//
|
|
2195
|
-
// per-
|
|
2196
|
-
//
|
|
2197
|
-
// vl
|
|
2203
|
+
// vl is a TUI without a prompt surface (`vl [OPTIONS]`, no prompt flag):
|
|
2204
|
+
// it NEVER receives the per-cell prompt on argv — it travels in the
|
|
2205
|
+
// per-cell file composed by the vl branch (VL_SYSTEM_APPEND_FILE).
|
|
2206
|
+
// send-keys does not apply: the deliverBootstrapPrompt classifier covers
|
|
2207
|
+
// only kimi/claude, and vl starts immediately without waits.
|
|
2198
2208
|
if (spec.client !== 'shell' && spec.client !== 'vl' && !promptViaDelivery && cell?.prompt) args.push(cell.prompt);
|
|
2199
2209
|
// nexuscrew-store source: neutralize the profile's env set in the composed
|
|
2200
2210
|
// child env (unset, never empty), so the runtime cannot leak credentials that
|
|
2201
2211
|
// the local store is meant to own.
|
|
2202
2212
|
applyStoreNeutralization(env, spec, profile);
|
|
2203
|
-
//
|
|
2213
|
+
// Resolve envPassthrough AFTER the provider branches. Each declared name is
|
|
2204
2214
|
// read from the credentialSources (same order as credential() 'auto': runtime
|
|
2205
2215
|
// -> store -> shell -> key files -> legacy) and injected into the child env.
|
|
2206
2216
|
// Per nome, mai in blocco. A name that is declared but absent from every source
|
|
@@ -2244,7 +2254,7 @@ function publicCatalog() {
|
|
|
2244
2254
|
auth: p.auth, endpoint: p.endpoint || '', model: p.model || '', models: [...(p.models || [])],
|
|
2245
2255
|
protocols: [...(p.protocols || [p.protocol])], supportsUnsafe: !['pi', 'shell', 'vl'].includes(p.client), requiresModel: !!p.requiresModel || !!p.custom,
|
|
2246
2256
|
permissionPolicyDefault: p.client === 'claude' ? 'unsafe' : 'standard',
|
|
2247
|
-
//
|
|
2257
|
+
// Solo il client claude riceve MCP gestito da NexusCrew (cellMcpArgs
|
|
2248
2258
|
// sharedMcpArgs nel ramo claude di resolveManagedEngine). Per ogni altro
|
|
2249
2259
|
// client (codex, vl, kimi, pi, agy, grok) `cell.mcp` e' INERTE: la cella lo
|
|
2250
2260
|
// accetta ma non ha effetto, perche' i server MCP li registra il client nel
|
|
@@ -28,7 +28,7 @@ const PANE_STATES = Object.freeze([
|
|
|
28
28
|
'ready', 'busy', 'not-ready-auth', 'not-ready-consent', 'not-ready-onboarding', 'unknown',
|
|
29
29
|
]);
|
|
30
30
|
|
|
31
|
-
// Esiti bounded della consegna (prompt.reason in API: closed enum
|
|
31
|
+
// Esiti bounded della consegna (prompt.reason in API: closed enum).
|
|
32
32
|
const DELIVERY_STATES = Object.freeze([
|
|
33
33
|
'submitted', // paste ok + pane riverificato + Enter ok
|
|
34
34
|
'staged-not-submitted', // paste ok, Enter fallito: testo forse nel composer
|
|
@@ -36,16 +36,16 @@ const DELIVERY_STATES = Object.freeze([
|
|
|
36
36
|
'failed-pre-paste', // resolve/load certamente falliti (anche dopo 1 retry)
|
|
37
37
|
'skipped-not-ready', // classifier not-ready a fine attesa bounded (kind in notReady)
|
|
38
38
|
'skipped-unknown', // classifier mai ready entro il timeout
|
|
39
|
-
'prompt-rejected', //
|
|
40
|
-
'cancelled', //
|
|
39
|
+
'prompt-rejected', // control bytes found in the prompt (policy check)
|
|
40
|
+
'cancelled', // Generazione terminata durante l'attesa/consegna
|
|
41
41
|
'report-timeout', // up() non ha ricevuto l'esito del launcher entro il bound
|
|
42
42
|
]);
|
|
43
43
|
|
|
44
|
-
// Codici bounded actionRequired
|
|
44
|
+
// Codici bounded actionRequired. Nessuna inferenza "rifiutato": il marker
|
|
45
45
|
// TUI prova solo che serve un'azione nel terminale della cella.
|
|
46
46
|
const ACTION_CODES = Object.freeze(['KIMI_AUTH_ACTION_REQUIRED', 'CLIENT_INTERACTION_REQUIRED']);
|
|
47
47
|
|
|
48
|
-
// Slug recovery bounded
|
|
48
|
+
// Slug recovery bounded: l'API trasporta SOLO {code, recovery}; il testo
|
|
49
49
|
// e' mappato localmente dalla PWA via i18n (fleet-recovery-<slug>), mai inviato
|
|
50
50
|
// dal server (un nodo remoto federato non deve poter iniettare testo libero).
|
|
51
51
|
const RECOVERY_SLUGS = Object.freeze([
|
|
@@ -65,7 +65,7 @@ const MODAL_NOT_READY = Object.freeze([
|
|
|
65
65
|
|
|
66
66
|
// Marker ancorati alla CODA visibile (status bar / righe finali): lo stesso
|
|
67
67
|
// testo in scrollback, nel corpo conversazione o in forma diversa non deve
|
|
68
|
-
//
|
|
68
|
+
// Classificare (il marker auth e' la stringa ESATTA di status bar, non un
|
|
69
69
|
// "Not logged in" qualunque). Live-verificati: claude 2.1.220 (status bar
|
|
70
70
|
// "Not logged in · Run /login"), kimi 0.31.1 (welcome "Run /login or
|
|
71
71
|
// /provider", "Model: not set").
|
|
@@ -99,7 +99,7 @@ function classifyPane(captured, client) {
|
|
|
99
99
|
for (const [state, re] of TAIL_NOT_READY) if (re.test(tail)) return state;
|
|
100
100
|
if (BUSY_TAIL.test(tail)) return 'busy';
|
|
101
101
|
if (client === 'kimi') {
|
|
102
|
-
// READY positivo Kimi: box di input corrente E modello configurato
|
|
102
|
+
// READY positivo Kimi: box di input corrente E modello configurato.
|
|
103
103
|
// Il solo "Model:" non basta; la sola box non basta (presente anche da
|
|
104
104
|
// logged-out). Il welcome logged-out e' gia' intercettato dai marker
|
|
105
105
|
// not-ready qui sopra; se scrolla via senza login resta 'unknown' (safe).
|
|
@@ -132,7 +132,7 @@ function classifyPane(captured, client) {
|
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
// vlPaneReadiness(tmuxBin, target, opts) -> { ready, degraded }
|
|
135
|
-
//
|
|
135
|
+
// Content-readiness di una cella vl via pane — il marcatore [o] + coda
|
|
136
136
|
// `esc quit · ^y yield` riconosciuto da classifyPane(text,'vl'). E' il PUNTO DI
|
|
137
137
|
// INNESTO della readiness vl: quando arrivera' l'adapter di runtime, la
|
|
138
138
|
// readiness si misurera' sul socket di controllo (vl-core/src/transport.rs) e
|
|
@@ -174,7 +174,7 @@ function clampInt(value, dflt, min, max) {
|
|
|
174
174
|
// paneTarget (%N opzionale), readyWaitMs, pollMs, settleMs, tmpdir,
|
|
175
175
|
// tmuxExecImpl, captureImpl, sleepImpl, nowImpl, fsImpl, isCancelled }
|
|
176
176
|
// reason e' SEMPRE lo state (closed enum); notReady e' il kind bounded o ''.
|
|
177
|
-
//
|
|
177
|
+
// IsCancelled: valutato ad OGNI poll, prima del paste e prima dell'
|
|
178
178
|
// Enter; se true la consegna si ferma subito ('cancelled') senza paste/Enter —
|
|
179
179
|
// cell-exec cancella la delivery quando la generazione termina, cosi' un
|
|
180
180
|
// polling in volo non puo' mai iniettare nella generazione successiva.
|
|
@@ -219,7 +219,7 @@ async function deliverBootstrapPrompt(opts = {}) {
|
|
|
219
219
|
|
|
220
220
|
// (2) Consegna AT-MOST-ONCE: un solo paste per generazione. Il retry copre
|
|
221
221
|
// SOLO fallimenti certi pre-paste (resolve/load); dopo qualsiasi tentativo
|
|
222
|
-
//
|
|
222
|
+
// Di paste-buffer non esiste retry automatico.
|
|
223
223
|
let attempts = 0;
|
|
224
224
|
for (;;) {
|
|
225
225
|
if (cancelled()) return done(false, 'cancelled', '', attempts);
|
|
@@ -240,7 +240,7 @@ async function deliverBootstrapPrompt(opts = {}) {
|
|
|
240
240
|
if (cancelled()) return done(false, 'delivery-unknown', '', attempts);
|
|
241
241
|
const enter = await exec(tmuxBin, ['send-keys', '-t', stage.paneId, 'Enter'], { env });
|
|
242
242
|
if (enter.err) return done(false, 'staged-not-submitted', '', attempts);
|
|
243
|
-
//
|
|
243
|
+
// Cancel con Enter appena partito -> la generazione e' morta con un
|
|
244
244
|
// submit in volo: esito incerto (residuo PTY possibile), mai 'submitted'.
|
|
245
245
|
if (cancelled()) return done(false, 'delivery-unknown', '', attempts);
|
|
246
246
|
return done(true, 'submitted', '', attempts);
|
|
@@ -259,7 +259,7 @@ async function deliverBootstrapPrompt(opts = {}) {
|
|
|
259
259
|
}
|
|
260
260
|
}
|
|
261
261
|
|
|
262
|
-
//
|
|
262
|
+
// WaitDeliveryReport(tmuxBin, target, opts) -> delivery-like null.
|
|
263
263
|
// Il launcher supervisionato (cell-exec) e' l'UNICO owner della consegna per
|
|
264
264
|
// TUTTE le generazioni degli engine Kimi e pubblica l'esito bounded sull'
|
|
265
265
|
// opzione tmux di pane @nc_delivery ('<state>' o '<state>:<notReady>', solo
|
|
@@ -294,7 +294,7 @@ async function waitDeliveryReport(tmuxBin, target, { env, exec, sleepImpl, nowIm
|
|
|
294
294
|
// actionRequiredFor(client, provider, delivery) -> null | { code, recovery }
|
|
295
295
|
// Mappa bounded delivery -> azione operatore. Solo skip per not-ready/unknown
|
|
296
296
|
// producono actionRequired; i fallimenti di trasporto restano nel prompt.state.
|
|
297
|
-
//
|
|
297
|
+
// SOLO {code, recovery} closed enum/slug — il testo e' i18n locale PWA.
|
|
298
298
|
function actionRequiredFor(client, provider, delivery) {
|
|
299
299
|
if (!delivery || delivery.delivered) return null;
|
|
300
300
|
if (delivery.state !== 'skipped-not-ready' && delivery.state !== 'skipped-unknown') return null;
|
package/lib/fleet/routes.js
CHANGED
|
@@ -10,9 +10,9 @@ function capList(fleet) {
|
|
|
10
10
|
return typeof fleet.capabilities === 'function' ? fleet.capabilities() : DEFAULT_CAPS;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
//
|
|
14
|
-
// (
|
|
15
|
-
//
|
|
13
|
+
// A route for a method unsupported by the provider returns 501
|
|
14
|
+
// (never ambiguous 404/502). The builtin native errors (400/403/409) pass
|
|
15
|
+
// through the guard with their own status.
|
|
16
16
|
function requireCap(fleet, cap) {
|
|
17
17
|
if (!capList(fleet).includes(cap)) {
|
|
18
18
|
const e = new Error('not supported by this fleet provider');
|
|
@@ -53,6 +53,11 @@ function fleetRoutes(fleetP, cfg = {}) {
|
|
|
53
53
|
if (opts.action) emit('info', 'FLEET_ACTION_COMPLETED', 'Fleet action completed', {
|
|
54
54
|
action: opts.action, cell: safeCell(body), state: opts.action === 'down' ? 'stopped' : 'ready',
|
|
55
55
|
});
|
|
56
|
+
if (opts.action && typeof cfg.onFleetStateChange === 'function') {
|
|
57
|
+
try {
|
|
58
|
+
cfg.onFleetStateChange({ cell: safeCell(body), active: opts.action !== 'down', action: opts.action });
|
|
59
|
+
} catch (_) { /* observability never breaks the action */ }
|
|
60
|
+
}
|
|
56
61
|
res.json(result);
|
|
57
62
|
} catch (e) {
|
|
58
63
|
if (opts.action) {
|
|
@@ -63,7 +68,7 @@ function fleetRoutes(fleetP, cfg = {}) {
|
|
|
63
68
|
else emit('warn', 'FLEET_ACTION_FAILED', 'Fleet action failed', {
|
|
64
69
|
action: opts.action, cell: safeCell(req.body), state: 'failed',
|
|
65
70
|
status: e.status || 500,
|
|
66
|
-
// Cause-preserving
|
|
71
|
+
// Cause-preserving: bounded enum only (lib/fleet/causes.js). The
|
|
67
72
|
// cause triple is {status, code, phase}; no free message, cwd/path,
|
|
68
73
|
// argv, env, prompt, token or credential ever enters here. Untagged /
|
|
69
74
|
// legacy errors degrade to the bounded UNKNOWN fallback.
|
|
@@ -81,8 +86,8 @@ function fleetRoutes(fleetP, cfg = {}) {
|
|
|
81
86
|
}
|
|
82
87
|
};
|
|
83
88
|
|
|
84
|
-
// /status
|
|
85
|
-
//
|
|
89
|
+
// /status also exposes `provider` and `capabilities`, besides the
|
|
90
|
+
// historical fields (available/cells/engines).
|
|
86
91
|
r.get('/status', async (_req, res) => {
|
|
87
92
|
try {
|
|
88
93
|
const fleet = await fleetP;
|
|
@@ -90,10 +95,10 @@ function fleetRoutes(fleetP, cfg = {}) {
|
|
|
90
95
|
return res.json({
|
|
91
96
|
available: false,
|
|
92
97
|
provider: fleet.provider || 'disabled',
|
|
93
|
-
bootOwner: 'none', //
|
|
98
|
+
bootOwner: 'none', // provider unavailable -> no boot owner
|
|
94
99
|
capabilities: capList(fleet),
|
|
95
100
|
...(fleet.reason ? { reason: fleet.reason } : {}),
|
|
96
|
-
//
|
|
101
|
+
// Il codice macchina del boot bloccato viaggia fino al client.
|
|
97
102
|
// La policy di lettura decide su QUESTO, non sulla prosa del reason
|
|
98
103
|
// (la prosa resta ripiego per i reason che un codice non ce l'hanno).
|
|
99
104
|
// Senza campo dal provider, la risposta semplicemente non lo ha.
|
|
@@ -143,8 +148,8 @@ function fleetRoutes(fleetP, cfg = {}) {
|
|
|
143
148
|
return f.boot(String(b.cell || ''), b.enabled === true);
|
|
144
149
|
}, { mutate: true }));
|
|
145
150
|
|
|
146
|
-
// ---
|
|
147
|
-
//
|
|
151
|
+
// --- Extension: schema + define/edit/remove (engine and cell) ---
|
|
152
|
+
// Every route negotiates the provider capability: missing -> 501.
|
|
148
153
|
r.get('/schema', guard((f) => { requireCap(f, 'schema'); return f.schema(); }));
|
|
149
154
|
r.get('/definitions', guard((f) => { requireCap(f, 'definitions'); return f.definitions(); }));
|
|
150
155
|
r.get('/credentials/status', guard((f) => {
|
package/lib/fleet/runtime.js
CHANGED
|
@@ -175,7 +175,7 @@ function createBuiltinRuntime(ctx) {
|
|
|
175
175
|
needsPi ? discoverPiModels({ ...cfg, home }) : {},
|
|
176
176
|
]);
|
|
177
177
|
const engines = cache.defs.engines.map((e) => {
|
|
178
|
-
const managed = e.managed ? describeManaged(e.managed, { ...cfg, home, extraModels: extraModelsFrom(cache.defs) }) : null;
|
|
178
|
+
const managed = e.managed ? describeManaged(e.managed, { ...cfg, home, extraModels: extraModelsFrom(cache.defs), engineId: e.id }) : null;
|
|
179
179
|
return {
|
|
180
180
|
id: e.id, label: e.label, rc: !!e.rc,
|
|
181
181
|
...(managed ? {
|
|
@@ -184,7 +184,7 @@ function createBuiltinRuntime(ctx) {
|
|
|
184
184
|
models: managed.provider === 'ollama-cloud' ? ollamaModels
|
|
185
185
|
: (managed.client === 'pi'
|
|
186
186
|
? (e.managed.provider === 'custom'
|
|
187
|
-
? [e.managed.model]
|
|
187
|
+
? [...new Set([e.managed.model, ...managed.models].filter(Boolean))]
|
|
188
188
|
: (e.managed.provider === 'native'
|
|
189
189
|
? [...new Set(Object.values(piModels).flat())]
|
|
190
190
|
: (piModels[e.managed.provider] || [])))
|
|
@@ -200,7 +200,7 @@ function createBuiltinRuntime(ctx) {
|
|
|
200
200
|
return cache.defs.cells.some((c) => c.tmuxSession === String(name));
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
-
// up —
|
|
203
|
+
// up — mandatory order.
|
|
204
204
|
// Le override {engine,boot} del contratto route sono ignorate: il builtin e'
|
|
205
205
|
// definitions-driven (l'engine della cella e' quello dichiarato; boot e' uno
|
|
206
206
|
// stato persistente gestito da boot()). Lancia SENZA shell.
|
|
@@ -220,12 +220,12 @@ function createBuiltinRuntime(ctx) {
|
|
|
220
220
|
throw httpError(400, `engine managed non configurato (${engine.id}): ${resolved.reason}`, null, { phase: 'preflight', code });
|
|
221
221
|
}
|
|
222
222
|
launchEngine = resolved.engine;
|
|
223
|
-
//
|
|
224
|
-
// (VL_SYSTEM_APPEND_FILE, 0.3.1+)
|
|
225
|
-
// readinessDegraded
|
|
226
|
-
//
|
|
227
|
-
//
|
|
228
|
-
//
|
|
223
|
+
// A vl cell whose runtime cannot hold the per-cell prompt
|
|
224
|
+
// (VL_SYSTEM_APPEND_FILE, 0.3.1+) DEGRADES by declaring it — same
|
|
225
|
+
// shape as readinessDegraded: the cell starts without its own identity
|
|
226
|
+
// and the operator sees it (log here + flag in the result, consumed by the UI).
|
|
227
|
+
// Never in silence: a silent start without the prompt is the defect
|
|
228
|
+
// this closes.
|
|
229
229
|
if (launchEngine.vlPromptDegraded) {
|
|
230
230
|
const log = typeof cfg.log === 'function' ? cfg.log : console.warn;
|
|
231
231
|
log(`fleet vl ${cell.id}: ${launchEngine.vlPromptDegraded}`);
|
|
@@ -311,7 +311,7 @@ function createBuiltinRuntime(ctx) {
|
|
|
311
311
|
readyMs: Math.max(0, Math.min(30000, Number(cfg.sendKeysReadyMs) || readyMs)),
|
|
312
312
|
// Solo gli engine managed Kimi (kimi.native / claude.kimi-code) hanno
|
|
313
313
|
// la delivery classificata at-most-once ai restart; gli engine custom
|
|
314
|
-
//
|
|
314
|
+
// Send-keys conservano il reinject legacy senza Enter (0.8.47).
|
|
315
315
|
client: engine.managed && engine.managed.client === 'kimi' ? 'kimi'
|
|
316
316
|
: (engine.managed && engine.managed.provider === 'kimi-code' ? 'claude' : ''),
|
|
317
317
|
readyWaitMs: Math.max(0, Math.min(120000, Number(cfg.bootstrapReadyWaitMs) || 15000)),
|
|
@@ -337,11 +337,11 @@ function createBuiltinRuntime(ctx) {
|
|
|
337
337
|
};
|
|
338
338
|
const tmuxChild = composeClientInvocation(tmuxLaunchEngine, cell);
|
|
339
339
|
|
|
340
|
-
//
|
|
341
|
-
// (cell-hold.js),
|
|
342
|
-
//
|
|
343
|
-
// child
|
|
344
|
-
//
|
|
340
|
+
// Staged start: creates the pane with a trusted inert placeholder
|
|
341
|
+
// (cell-hold.js), arms window-local remain-on-exit on the @N, then
|
|
342
|
+
// respawn-pane -k to cell-exec on the exact %N. So remain-on-exit is already
|
|
343
|
+
// ON when the real child can terminate: no vanished window, no
|
|
344
|
+
// NEW_SESSION_FAILED masking the real exit.
|
|
345
345
|
// per id puntati, definitions.js); gli step critici usano gli ID restituiti
|
|
346
346
|
// da tmux ($N/@N/%N). respawn-pane -k preserva il pane ID: il %N catturato
|
|
347
347
|
// qui resta valido per readiness e prompt. Nessuna shell string, nessun
|
|
@@ -357,7 +357,7 @@ function createBuiltinRuntime(ctx) {
|
|
|
357
357
|
// new-session fallita: nessuna sessione creata. Revoca il ticket (il child
|
|
358
358
|
// non partira' mai) prima di propagare l'errore.
|
|
359
359
|
try { await launchBroker.revoke?.(ticket.nonce); } catch (_) { /* best-effort */ }
|
|
360
|
-
//
|
|
360
|
+
// Redaction: the tmux stderr can echo argv/env of the launched command.
|
|
361
361
|
const dup = /duplicate session/i.test(create.stderr);
|
|
362
362
|
const why = dup
|
|
363
363
|
? 'sessione già in esecuzione'
|
|
@@ -434,8 +434,8 @@ function createBuiltinRuntime(ctx) {
|
|
|
434
434
|
['respawn-pane', '-k', '-c', realCwd, '-t', paneId, tmuxChild.command, ...tmuxChild.args],
|
|
435
435
|
{ env: minimalEnv() });
|
|
436
436
|
if (respawn.err) {
|
|
437
|
-
// respawn
|
|
438
|
-
//
|
|
437
|
+
// respawn failed after issue(): the nonce must be revoked/consumed first
|
|
438
|
+
// before surfacing the error, then cleanup of the panel session.
|
|
439
439
|
await cleanupLaunch();
|
|
440
440
|
throw httpError(500,
|
|
441
441
|
`tmux respawn-pane failed: ${redactSecrets(respawn.stderr.trim() || respawn.err.message, launchEngine, cell)}`,
|
|
@@ -478,7 +478,7 @@ function createBuiltinRuntime(ctx) {
|
|
|
478
478
|
`comando Shell terminato subito${status}: ${diagnostic || 'verifica command, PATH e configurazione della shell'}`,
|
|
479
479
|
null, { phase: 'readiness', code: 'SHELL_COMMAND_FAILED' });
|
|
480
480
|
}
|
|
481
|
-
// Cause-preserving
|
|
481
|
+
// Cause-preserving: distinguish a cell-client spawn failure (the
|
|
482
482
|
// captured pane carries the stable 'cell spawn failed:' marker produced by
|
|
483
483
|
// cell-exec.js) from a generic early exit. Both stay on the readiness
|
|
484
484
|
// surface; the spawn branch keeps CELL_SPAWN_FAILED sanitized downstream.
|
|
@@ -491,7 +491,7 @@ function createBuiltinRuntime(ctx) {
|
|
|
491
491
|
['set-option', '-w', '-t', readiness.target, 'remain-on-exit', 'off'], { env: minimalEnv(), timeoutMs: 2000 });
|
|
492
492
|
}
|
|
493
493
|
|
|
494
|
-
//
|
|
494
|
+
// Content-readiness vl (marcatore [o] nel pane). MAI fail-closed: se
|
|
495
495
|
// entro il timeout il marcatore non compare DEGRADA e procede come se pronta
|
|
496
496
|
// (una cella che parte oggi deve partire anche domani), lasciando traccia
|
|
497
497
|
// nel risultato. vlPaneReadiness e' il punto di innesto sostituibile quando
|
|
@@ -514,14 +514,14 @@ function createBuiltinRuntime(ctx) {
|
|
|
514
514
|
};
|
|
515
515
|
}
|
|
516
516
|
|
|
517
|
-
// (6) prompt:
|
|
517
|
+
// (6) prompt: two distinct paths (0.8.47, single-owner).
|
|
518
518
|
// - managed kimi.native / claude.kimi-code: la consegna e' posseduta SOLO
|
|
519
519
|
// dal supervisore (cell-exec) per TUTTE le generazioni; qui si legge
|
|
520
520
|
// l'esito bounded (@nc_delivery sul pane) con attesa bounded. Mai paste/
|
|
521
521
|
// Enter dal runtime: niente doppia delivery se gen0 muore durante
|
|
522
522
|
// l'attesa e gen1 parte sotto il supervisore.
|
|
523
523
|
// - engine custom promptMode 'send-keys': injectPrompt legacy (bracketed
|
|
524
|
-
//
|
|
524
|
+
// Paste SENZA Enter, contratto invariato —), target %N esatto.
|
|
525
525
|
let prompt = null;
|
|
526
526
|
let actionRequired = null;
|
|
527
527
|
if (launchEngine.promptMode === 'send-keys' && cell.prompt) {
|
|
@@ -551,7 +551,7 @@ function createBuiltinRuntime(ctx) {
|
|
|
551
551
|
env: minimalEnv(),
|
|
552
552
|
readyMs: cfg.sendKeysReadyMs != null ? cfg.sendKeysReadyMs : readyMs,
|
|
553
553
|
target,
|
|
554
|
-
engine: launchEngine, cell, //
|
|
554
|
+
engine: launchEngine, cell, // to redact the reason if the paste-buffer fails
|
|
555
555
|
});
|
|
556
556
|
}
|
|
557
557
|
}
|
|
@@ -560,9 +560,9 @@ function createBuiltinRuntime(ctx) {
|
|
|
560
560
|
ok: true, cell: cellId, session: cell.tmuxSession, prompt,
|
|
561
561
|
...(actionRequired ? { actionRequired } : {}),
|
|
562
562
|
...(readinessDegraded ? { readinessDegraded: true } : {}),
|
|
563
|
-
//
|
|
564
|
-
// file
|
|
565
|
-
// payload
|
|
563
|
+
// Per-cell prompt not delivered to a vl cell (old runtime or
|
|
564
|
+
// unwritable file). Strict boolean like readinessDegraded: the
|
|
565
|
+
// payload may come from a federated node and the text is local i18n.
|
|
566
566
|
...(launchEngine.vlPromptDegraded ? { vlPromptDegraded: true } : {}),
|
|
567
567
|
};
|
|
568
568
|
}
|