@nac3/forge-cli 1.0.33 → 1.0.36
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/dist/chat/claude.d.ts.map +1 -1
- package/dist/chat/claude.js +15 -1
- package/dist/chat/claude.js.map +1 -1
- package/dist/chat/panel.d.ts.map +1 -1
- package/dist/chat/panel.js +260 -0
- package/dist/chat/panel.js.map +1 -1
- package/dist/chat/server.d.ts.map +1 -1
- package/dist/chat/server.js +223 -0
- package/dist/chat/server.js.map +1 -1
- package/dist/llm/cohere_client.d.ts +1 -0
- package/dist/llm/cohere_client.d.ts.map +1 -1
- package/dist/llm/cohere_client.js +5 -0
- package/dist/llm/cohere_client.js.map +1 -1
- package/dist/llm/deepseek_client.d.ts +1 -0
- package/dist/llm/deepseek_client.d.ts.map +1 -1
- package/dist/llm/deepseek_client.js +5 -0
- package/dist/llm/deepseek_client.js.map +1 -1
- package/dist/llm/federation_client.d.ts +3 -0
- package/dist/llm/federation_client.d.ts.map +1 -1
- package/dist/llm/federation_client.js +2 -0
- package/dist/llm/federation_client.js.map +1 -1
- package/dist/llm/federation_host.d.ts.map +1 -1
- package/dist/llm/federation_host.js +1 -0
- package/dist/llm/federation_host.js.map +1 -1
- package/dist/llm/gemini_client.d.ts +14 -0
- package/dist/llm/gemini_client.d.ts.map +1 -1
- package/dist/llm/gemini_client.js +0 -0
- package/dist/llm/gemini_client.js.map +1 -1
- package/dist/llm/managed_client.d.ts +1 -0
- package/dist/llm/managed_client.d.ts.map +1 -1
- package/dist/llm/managed_client.js +5 -1
- package/dist/llm/managed_client.js.map +1 -1
- package/dist/llm/mistral_client.d.ts +1 -0
- package/dist/llm/mistral_client.d.ts.map +1 -1
- package/dist/llm/mistral_client.js +5 -0
- package/dist/llm/mistral_client.js.map +1 -1
- package/dist/llm/multi_provider_client.d.ts +5 -0
- package/dist/llm/multi_provider_client.d.ts.map +1 -1
- package/dist/llm/multi_provider_client.js +6 -0
- package/dist/llm/multi_provider_client.js.map +1 -1
- package/dist/llm/ollama_client.d.ts +1 -0
- package/dist/llm/ollama_client.d.ts.map +1 -1
- package/dist/llm/ollama_client.js +5 -0
- package/dist/llm/ollama_client.js.map +1 -1
- package/dist/llm/openai_client.d.ts +1 -0
- package/dist/llm/openai_client.d.ts.map +1 -1
- package/dist/llm/openai_client.js +6 -0
- package/dist/llm/openai_client.js.map +1 -1
- package/dist/llm/single_step.d.ts +1 -0
- package/dist/llm/single_step.d.ts.map +1 -1
- package/dist/llm/single_step.js +23 -5
- package/dist/llm/single_step.js.map +1 -1
- package/dist/llm/xai_client.d.ts +1 -0
- package/dist/llm/xai_client.d.ts.map +1 -1
- package/dist/llm/xai_client.js +5 -0
- package/dist/llm/xai_client.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/chat/server.js
CHANGED
|
@@ -1918,6 +1918,28 @@ async function route(req, res, ctx) {
|
|
|
1918
1918
|
await handleBrainConfigSet(req, res, ctx);
|
|
1919
1919
|
return;
|
|
1920
1920
|
}
|
|
1921
|
+
/* F4 brain federation -- connect to another Forge's brain, mint
|
|
1922
|
+
authorize/route codes, host the answer loop, all from the panel. */
|
|
1923
|
+
if (req.method === 'GET' && url.pathname === '/api/forge/federation') {
|
|
1924
|
+
await handleFederationGet(req, res);
|
|
1925
|
+
return;
|
|
1926
|
+
}
|
|
1927
|
+
if (req.method === 'POST' && url.pathname === '/api/forge/federation/join') {
|
|
1928
|
+
await handleFederationJoin(req, res);
|
|
1929
|
+
return;
|
|
1930
|
+
}
|
|
1931
|
+
if (req.method === 'POST' && url.pathname === '/api/forge/federation/leave') {
|
|
1932
|
+
await handleFederationLeave(req, res);
|
|
1933
|
+
return;
|
|
1934
|
+
}
|
|
1935
|
+
if (req.method === 'POST' && url.pathname === '/api/forge/federation/invite') {
|
|
1936
|
+
await handleFederationInvite(req, res);
|
|
1937
|
+
return;
|
|
1938
|
+
}
|
|
1939
|
+
if (req.method === 'POST' && url.pathname === '/api/forge/federation/host') {
|
|
1940
|
+
await handleFederationHost(req, res);
|
|
1941
|
+
return;
|
|
1942
|
+
}
|
|
1921
1943
|
/* PND-069 -- modelos disponibles por provider (API en vivo con
|
|
1922
1944
|
fallback estatico). */
|
|
1923
1945
|
if (req.method === 'GET' && url.pathname === '/api/forge/brain-models') {
|
|
@@ -3176,6 +3198,21 @@ async function handleBrainConfigSet(req, res, ctx) {
|
|
|
3176
3198
|
if (model)
|
|
3177
3199
|
cfg[tier].model = model;
|
|
3178
3200
|
}
|
|
3201
|
+
/* F1 multimodal tier -- the dedicated brain for image-bearing turns.
|
|
3202
|
+
* Optional: clear reverts to auto vision routing. Mirrors the CLI
|
|
3203
|
+
* (yf brain config --multimodal-*) so the panel reaches parity. */
|
|
3204
|
+
if (body.multimodal_clear === true) {
|
|
3205
|
+
delete cfg.multimodal;
|
|
3206
|
+
}
|
|
3207
|
+
else if (body.multimodal && typeof body.multimodal === 'object') {
|
|
3208
|
+
const b = body.multimodal;
|
|
3209
|
+
const provRaw = typeof b.provider === 'string'
|
|
3210
|
+
&& ALL_BRAIN_PROVIDERS.includes(b.provider) ? b.provider : undefined;
|
|
3211
|
+
const modelRaw = typeof b.model === 'string' && b.model.trim() !== '' ? b.model.trim() : undefined;
|
|
3212
|
+
const provider = (provRaw ?? cfg.multimodal?.provider ?? cfg.frontier.provider);
|
|
3213
|
+
const model = modelRaw ?? cfg.multimodal?.model ?? cfg.frontier.model;
|
|
3214
|
+
cfg.multimodal = { provider, model };
|
|
3215
|
+
}
|
|
3179
3216
|
await writeBrainConfig(cfg);
|
|
3180
3217
|
/* Apply the brain mode LIVE: hot-swap the provider so flipping
|
|
3181
3218
|
* Plan/Console takes effect on the very next turn -- no panel restart.
|
|
@@ -3193,6 +3230,192 @@ async function handleBrainConfigSet(req, res, ctx) {
|
|
|
3193
3230
|
restart_needed_for: applied_live ? [] : ['anthropic_mode'],
|
|
3194
3231
|
});
|
|
3195
3232
|
}
|
|
3233
|
+
/* ============================================================
|
|
3234
|
+
* F4 brain federation -- settings-panel handlers (2026-06-21).
|
|
3235
|
+
*
|
|
3236
|
+
* The federation protocol (client/host/broker/CLI) already exists;
|
|
3237
|
+
* this exposes it in the panel so a non-terminal user can: connect
|
|
3238
|
+
* to another Forge's brain (child join), mint an authorize/route
|
|
3239
|
+
* code (parent invite), see status, leave, and toggle hosting. The
|
|
3240
|
+
* host answer-loop runs INSIDE the panel process while the toggle is
|
|
3241
|
+
* on; closing the panel (process exit) stops it.
|
|
3242
|
+
*
|
|
3243
|
+
* Routing is automatic: once federation.json is active, cost_router's
|
|
3244
|
+
* maybeApplyFederation() points every turn at the parent's brain, so
|
|
3245
|
+
* join only has to write the config + vault key (CLI parity).
|
|
3246
|
+
* ============================================================ */
|
|
3247
|
+
const federationHostRuntime = {
|
|
3248
|
+
running: false, controller: null,
|
|
3249
|
+
};
|
|
3250
|
+
/** Mask the child id in a fed handle for display: fed:<parent>:<id> -> fed:<parent>:<id6>... */
|
|
3251
|
+
function maskFedHandle(fedHandle) {
|
|
3252
|
+
const i = fedHandle.lastIndexOf(':');
|
|
3253
|
+
if (i < 0)
|
|
3254
|
+
return fedHandle;
|
|
3255
|
+
return fedHandle.slice(0, i + 1) + fedHandle.slice(i + 1, i + 7) + '...';
|
|
3256
|
+
}
|
|
3257
|
+
/** GET /api/forge/federation -- current child link, parent invite
|
|
3258
|
+
* capability, and host-loop state. */
|
|
3259
|
+
async function handleFederationGet(_req, res) {
|
|
3260
|
+
const { readFederationConfig, getFederationKey } = await import('../core/federation_config.js');
|
|
3261
|
+
const { resolveParentAuth } = await import('../llm/federation_host.js');
|
|
3262
|
+
const cfg = await readFederationConfig();
|
|
3263
|
+
const keyPresent = (await getFederationKey()) !== null;
|
|
3264
|
+
const auth = await resolveParentAuth();
|
|
3265
|
+
sendJson(res, 200, {
|
|
3266
|
+
ok: true,
|
|
3267
|
+
child: cfg ? {
|
|
3268
|
+
active: cfg.active,
|
|
3269
|
+
parent_handle: cfg.parent_handle,
|
|
3270
|
+
fed_handle: maskFedHandle(cfg.fed_handle),
|
|
3271
|
+
base_url: cfg.base_url,
|
|
3272
|
+
joined_at: cfg.joined_at,
|
|
3273
|
+
key_present: keyPresent,
|
|
3274
|
+
} : null,
|
|
3275
|
+
parent: { can_invite: !!auth, handle: auth ? auth.handle : null },
|
|
3276
|
+
host: { running: federationHostRuntime.running },
|
|
3277
|
+
});
|
|
3278
|
+
}
|
|
3279
|
+
/** POST /api/forge/federation/join { token } -- redeem a parent's
|
|
3280
|
+
* code; from now on every turn routes to the parent's brain. */
|
|
3281
|
+
async function handleFederationJoin(req, res) {
|
|
3282
|
+
const { setFederationKey, writeFederationConfig } = await import('../core/federation_config.js');
|
|
3283
|
+
const { HITO4_BASE_URL } = await import('../license/hito4_client.js');
|
|
3284
|
+
let body;
|
|
3285
|
+
try {
|
|
3286
|
+
body = JSON.parse(await readBody(req));
|
|
3287
|
+
}
|
|
3288
|
+
catch {
|
|
3289
|
+
sendJson(res, 400, { ok: false, error: 'invalid json body' });
|
|
3290
|
+
return;
|
|
3291
|
+
}
|
|
3292
|
+
const token = typeof body.token === 'string' ? body.token.trim() : '';
|
|
3293
|
+
if (!token) {
|
|
3294
|
+
sendJson(res, 400, { ok: false, error: 'falta el codigo de federacion' });
|
|
3295
|
+
return;
|
|
3296
|
+
}
|
|
3297
|
+
const baseUrl = (typeof body.base_url === 'string' && body.base_url ? body.base_url : HITO4_BASE_URL).replace(/\/+$/, '');
|
|
3298
|
+
let r;
|
|
3299
|
+
try {
|
|
3300
|
+
r = await fetch(baseUrl + '/v1/federation/redeem', {
|
|
3301
|
+
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
3302
|
+
body: JSON.stringify({ token }),
|
|
3303
|
+
});
|
|
3304
|
+
}
|
|
3305
|
+
catch (e) {
|
|
3306
|
+
sendJson(res, 502, { ok: false, error: 'no pude contactar el broker: ' + (e instanceof Error ? e.message : String(e)) });
|
|
3307
|
+
return;
|
|
3308
|
+
}
|
|
3309
|
+
const j = await r.json().catch(() => ({}));
|
|
3310
|
+
if (!r.ok || !j.ok || !j.fed_handle || !j.fed_key || !j.parent_handle) {
|
|
3311
|
+
sendJson(res, r.status === 400 ? 400 : 502, {
|
|
3312
|
+
ok: false,
|
|
3313
|
+
error: (j.error || ('HTTP ' + r.status)) + (r.status === 400 ? ' (codigo invalido o vencido)' : ''),
|
|
3314
|
+
});
|
|
3315
|
+
return;
|
|
3316
|
+
}
|
|
3317
|
+
await setFederationKey(j.fed_key);
|
|
3318
|
+
await writeFederationConfig({
|
|
3319
|
+
active: true,
|
|
3320
|
+
parent_handle: j.parent_handle,
|
|
3321
|
+
fed_handle: j.fed_handle,
|
|
3322
|
+
base_url: baseUrl,
|
|
3323
|
+
joined_at: new Date().toISOString(),
|
|
3324
|
+
});
|
|
3325
|
+
sendJson(res, 200, { ok: true, parent_handle: j.parent_handle });
|
|
3326
|
+
}
|
|
3327
|
+
/** POST /api/forge/federation/leave -- drop the link; route locally. */
|
|
3328
|
+
async function handleFederationLeave(_req, res) {
|
|
3329
|
+
const { readFederationConfig, clearFederationConfig } = await import('../core/federation_config.js');
|
|
3330
|
+
const cfg = await readFederationConfig();
|
|
3331
|
+
await clearFederationConfig();
|
|
3332
|
+
sendJson(res, 200, { ok: true, was_federated: !!cfg, parent_handle: cfg ? cfg.parent_handle : null });
|
|
3333
|
+
}
|
|
3334
|
+
/** POST /api/forge/federation/invite { ttl_seconds? } -- PARENT mints a
|
|
3335
|
+
* single-use authorize/route code to lend its brain to a child. */
|
|
3336
|
+
async function handleFederationInvite(req, res) {
|
|
3337
|
+
const { resolveParentAuth } = await import('../llm/federation_host.js');
|
|
3338
|
+
const { HITO4_BASE_URL, signBearer } = await import('../license/hito4_client.js');
|
|
3339
|
+
const auth = await resolveParentAuth();
|
|
3340
|
+
if (!auth) {
|
|
3341
|
+
sendJson(res, 400, { ok: false, error: 'Necesitas una licencia activa (handle provisto) para prestar tu brain.' });
|
|
3342
|
+
return;
|
|
3343
|
+
}
|
|
3344
|
+
let body = {};
|
|
3345
|
+
try {
|
|
3346
|
+
body = JSON.parse(await readBody(req));
|
|
3347
|
+
}
|
|
3348
|
+
catch { /* empty body ok */ }
|
|
3349
|
+
const baseUrl = (typeof body.base_url === 'string' && body.base_url ? body.base_url : HITO4_BASE_URL).replace(/\/+$/, '');
|
|
3350
|
+
const payload = {};
|
|
3351
|
+
if (Number.isFinite(Number(body.ttl_seconds)))
|
|
3352
|
+
payload.ttl_seconds = Number(body.ttl_seconds);
|
|
3353
|
+
let r;
|
|
3354
|
+
try {
|
|
3355
|
+
r = await fetch(baseUrl + '/v1/federation/invite', {
|
|
3356
|
+
method: 'POST',
|
|
3357
|
+
headers: {
|
|
3358
|
+
'authorization': 'Bearer ' + signBearer(auth.secret, auth.handle),
|
|
3359
|
+
'x-yf-user-handle': auth.handle,
|
|
3360
|
+
'content-type': 'application/json',
|
|
3361
|
+
},
|
|
3362
|
+
body: JSON.stringify(payload),
|
|
3363
|
+
});
|
|
3364
|
+
}
|
|
3365
|
+
catch (e) {
|
|
3366
|
+
sendJson(res, 502, { ok: false, error: 'no pude contactar el broker: ' + (e instanceof Error ? e.message : String(e)) });
|
|
3367
|
+
return;
|
|
3368
|
+
}
|
|
3369
|
+
const j = await r.json().catch(() => ({}));
|
|
3370
|
+
if (!r.ok || !j.ok || !j.token) {
|
|
3371
|
+
sendJson(res, 502, { ok: false, error: j.error || ('HTTP ' + r.status) });
|
|
3372
|
+
return;
|
|
3373
|
+
}
|
|
3374
|
+
sendJson(res, 200, { ok: true, token: j.token, expires_in: j.expires_in ?? 1800 });
|
|
3375
|
+
}
|
|
3376
|
+
/** POST /api/forge/federation/host { enabled } -- start/stop the answer
|
|
3377
|
+
* loop inside the panel process so children get this brain's replies. */
|
|
3378
|
+
async function handleFederationHost(req, res) {
|
|
3379
|
+
let body = {};
|
|
3380
|
+
try {
|
|
3381
|
+
body = JSON.parse(await readBody(req));
|
|
3382
|
+
}
|
|
3383
|
+
catch { /* default */ }
|
|
3384
|
+
const enable = body.enabled === true;
|
|
3385
|
+
if (enable) {
|
|
3386
|
+
if (federationHostRuntime.running) {
|
|
3387
|
+
sendJson(res, 200, { ok: true, running: true });
|
|
3388
|
+
return;
|
|
3389
|
+
}
|
|
3390
|
+
const { resolveParentAuth, runFederationHost } = await import('../llm/federation_host.js');
|
|
3391
|
+
const auth = await resolveParentAuth();
|
|
3392
|
+
if (!auth) {
|
|
3393
|
+
sendJson(res, 400, { ok: false, error: 'Necesitas una licencia activa para hospedar.' });
|
|
3394
|
+
return;
|
|
3395
|
+
}
|
|
3396
|
+
const controller = new AbortController();
|
|
3397
|
+
federationHostRuntime.controller = controller;
|
|
3398
|
+
federationHostRuntime.running = true;
|
|
3399
|
+
/* Fire-and-forget: the loop runs until aborted (panel close or toggle
|
|
3400
|
+
* off). Reset the flag if it ever returns/throws on its own. */
|
|
3401
|
+
void runFederationHost({ signal: controller.signal })
|
|
3402
|
+
.catch(() => undefined)
|
|
3403
|
+
.finally(() => {
|
|
3404
|
+
if (federationHostRuntime.controller === controller) {
|
|
3405
|
+
federationHostRuntime.running = false;
|
|
3406
|
+
federationHostRuntime.controller = null;
|
|
3407
|
+
}
|
|
3408
|
+
});
|
|
3409
|
+
sendJson(res, 200, { ok: true, running: true });
|
|
3410
|
+
return;
|
|
3411
|
+
}
|
|
3412
|
+
/* Disable. */
|
|
3413
|
+
if (federationHostRuntime.controller)
|
|
3414
|
+
federationHostRuntime.controller.abort();
|
|
3415
|
+
federationHostRuntime.running = false;
|
|
3416
|
+
federationHostRuntime.controller = null;
|
|
3417
|
+
sendJson(res, 200, { ok: true, running: false });
|
|
3418
|
+
}
|
|
3196
3419
|
/** PND-064 -- POST /api/forge/panel-invoke. Paridad total de tools
|
|
3197
3420
|
* en modo Plan: el MCP server local reenvia aca cada nac3_invoke
|
|
3198
3421
|
* del subproceso `claude -p`. Corre el MISMO dispatcher que el
|