agent-nuvira 3.3.4 → 3.3.6
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/agent-sdk/src/scaffold.d.ts +1 -1
- package/dist/agent-sdk/src/scaffold.js +1 -1
- package/dist/cli/doctor.js +16 -16
- package/dist/cli/doctor.js.map +1 -1
- package/dist/config/process-env.d.ts +136 -0
- package/dist/config/process-env.d.ts.map +1 -0
- package/dist/config/process-env.js +217 -0
- package/dist/config/process-env.js.map +1 -0
- package/dist/learning/model-reachability.d.ts +95 -0
- package/dist/learning/model-reachability.d.ts.map +1 -0
- package/dist/learning/model-reachability.js +111 -0
- package/dist/learning/model-reachability.js.map +1 -0
- package/dist/learning/model-registry.d.ts +22 -0
- package/dist/learning/model-registry.d.ts.map +1 -1
- package/dist/learning/model-registry.js +24 -0
- package/dist/learning/model-registry.js.map +1 -1
- package/dist/learning/model-verify-job.d.ts +131 -0
- package/dist/learning/model-verify-job.d.ts.map +1 -0
- package/dist/learning/model-verify-job.js +221 -0
- package/dist/learning/model-verify-job.js.map +1 -0
- package/dist/web-dashboard/process-env-inventory.d.ts +57 -0
- package/dist/web-dashboard/process-env-inventory.d.ts.map +1 -0
- package/dist/web-dashboard/process-env-inventory.js +96 -0
- package/dist/web-dashboard/process-env-inventory.js.map +1 -0
- package/dist/web-dashboard/server.d.ts.map +1 -1
- package/dist/web-dashboard/server.js +246 -17
- package/dist/web-dashboard/server.js.map +1 -1
- package/dist/web-dashboard/src/types.d.ts +65 -0
- package/dist/web-dashboard/src/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/web-dashboard/public/assets/{index-YWF9FpwQ.js → index-BFquuwq9.js} +83 -83
- package/src/web-dashboard/public/assets/index-BFquuwq9.js.map +1 -0
- package/src/web-dashboard/public/assets/{index-XJjj2cBX.css → index-CIJ6FHHZ.css} +1 -1
- package/src/web-dashboard/public/index.html +2 -2
- package/src/web-dashboard/public/assets/index-YWF9FpwQ.js.map +0 -1
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The data behind the dashboard's Process Environment page.
|
|
3
|
+
*
|
|
4
|
+
* Two values are reported per variable, and keeping them apart is the whole
|
|
5
|
+
* point of this module:
|
|
6
|
+
*
|
|
7
|
+
* - **the file value** — the line in the credential `.env`. This is what the
|
|
8
|
+
* page writes, what persists, and what the NEXT `nuvira` run reads.
|
|
9
|
+
* - **the process value** — what `process.env` holds right now, which is what
|
|
10
|
+
* the RUNNING dashboard would actually obey.
|
|
11
|
+
*
|
|
12
|
+
* They disagree for one reason worth surfacing: `loadEnv()` never overrides a
|
|
13
|
+
* variable that is already in the environment (`Do NOT override existing
|
|
14
|
+
* process.env values — system env vars take priority`). So a value exported in
|
|
15
|
+
* the operator's shell, or a systemd unit's `Environment=`, silently outranks
|
|
16
|
+
* anything written here — for the dashboard AND for every CLI run in that shell.
|
|
17
|
+
* A page that showed only the file would let a user write `NUVIRA_OTEL=0`, watch
|
|
18
|
+
* the row turn off, and still export spans. `shadowed` is that fact, stated.
|
|
19
|
+
*
|
|
20
|
+
* The file is the page's own target, so `state` is computed from it; the process
|
|
21
|
+
* value is reported beside it, never folded in.
|
|
22
|
+
*/
|
|
23
|
+
import { loadEnvFile } from '../skills/secret-capture.js';
|
|
24
|
+
import { PROCESS_ENV_VARS, processEnvFlagIsOn, } from '../config/process-env.js';
|
|
25
|
+
/** Build the row set. Never throws — an unreadable `.env` yields every row unset. */
|
|
26
|
+
export function readProcessEnvInventory() {
|
|
27
|
+
let file = {};
|
|
28
|
+
try {
|
|
29
|
+
file = loadEnvFile();
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// Best-effort: a broken inventory must not blank the page, and every row
|
|
33
|
+
// still renders as "unset" rather than as absent.
|
|
34
|
+
}
|
|
35
|
+
const rows = PROCESS_ENV_VARS.map((spec) => toRow(spec, file));
|
|
36
|
+
addOtelWarning(rows, file);
|
|
37
|
+
return rows;
|
|
38
|
+
}
|
|
39
|
+
function toRow(spec, file) {
|
|
40
|
+
const fileValue = Object.prototype.hasOwnProperty.call(file, spec.name) ? file[spec.name] : null;
|
|
41
|
+
const rawEnv = process.env[spec.name];
|
|
42
|
+
const processValue = rawEnv === undefined ? null : rawEnv;
|
|
43
|
+
const state = fileValue === null
|
|
44
|
+
? 'unset'
|
|
45
|
+
: spec.kind === 'text'
|
|
46
|
+
? 'set'
|
|
47
|
+
: processEnvFlagIsOn(spec.rule, fileValue)
|
|
48
|
+
? 'on'
|
|
49
|
+
: 'off';
|
|
50
|
+
return {
|
|
51
|
+
name: spec.name,
|
|
52
|
+
label: spec.label,
|
|
53
|
+
group: spec.group,
|
|
54
|
+
kind: spec.kind,
|
|
55
|
+
rule: spec.rule,
|
|
56
|
+
acceptsValue: spec.acceptsValue,
|
|
57
|
+
valueLabel: spec.valueLabel,
|
|
58
|
+
placeholder: spec.placeholder,
|
|
59
|
+
description: spec.description,
|
|
60
|
+
unsetMeans: spec.unsetMeans,
|
|
61
|
+
cliEquivalent: spec.cliEquivalent,
|
|
62
|
+
fileValue,
|
|
63
|
+
processValue,
|
|
64
|
+
state,
|
|
65
|
+
// A blank stored line and an unset variable are different rows but the same
|
|
66
|
+
// effective silence, so only a real disagreement counts as shadowed.
|
|
67
|
+
shadowed: processValue !== null && processValue !== fileValue,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Say out loud that enabling export without an endpoint exports nothing.
|
|
72
|
+
*
|
|
73
|
+
* MEASURED against the OTLP resolution in `otel.ts`: with no
|
|
74
|
+
* `OTEL_EXPORTER_OTLP_ENDPOINT` and no `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`,
|
|
75
|
+
* `otelEndpoint()` returns null — the spans are built and then dropped. That is
|
|
76
|
+
* a run paying for a tracer and shipping nothing, and the page can see it.
|
|
77
|
+
*
|
|
78
|
+
* Both endpoint names are read straight from the file map: they are standard
|
|
79
|
+
* OpenTelemetry names rather than ours, so they are deliberately NOT on the
|
|
80
|
+
* allowlist and this module never writes them.
|
|
81
|
+
*/
|
|
82
|
+
function addOtelWarning(rows, file) {
|
|
83
|
+
const otel = rows.find((r) => r.name === 'NUVIRA_OTEL');
|
|
84
|
+
if (!otel)
|
|
85
|
+
return;
|
|
86
|
+
const effective = otel.processValue ?? otel.fileValue;
|
|
87
|
+
if (!processEnvFlagIsOn('truthy', effective))
|
|
88
|
+
return;
|
|
89
|
+
const endpoint = (name) => (process.env[name] ?? (Object.prototype.hasOwnProperty.call(file, name) ? file[name] : '')).trim();
|
|
90
|
+
if (!endpoint('OTEL_EXPORTER_OTLP_TRACES_ENDPOINT') && !endpoint('OTEL_EXPORTER_OTLP_ENDPOINT')) {
|
|
91
|
+
otel.warning =
|
|
92
|
+
'Export is on but no OTLP endpoint is set, so spans are built and then dropped. ' +
|
|
93
|
+
'Set OTEL_EXPORTER_OTLP_ENDPOINT to actually ship them.';
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=process-env-inventory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"process-env-inventory.js","sourceRoot":"","sources":["../../src/web-dashboard/process-env-inventory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC1D,OAAO,EACL,gBAAgB,EAChB,kBAAkB,GAEnB,MAAM,0BAA0B,CAAC;AAkClC,qFAAqF;AACrF,MAAM,UAAU,uBAAuB;IACrC,IAAI,IAAI,GAA2B,EAAE,CAAC;IACtC,IAAI,CAAC;QACH,IAAI,GAAG,WAAW,EAAE,CAAC;IACvB,CAAC;IAAC,MAAM,CAAC;QACP,yEAAyE;QACzE,kDAAkD;IACpD,CAAC;IAED,MAAM,IAAI,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;IAC/D,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC3B,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,KAAK,CAAC,IAAuB,EAAE,IAA4B;IAClE,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACjG,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtC,MAAM,YAAY,GAAG,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;IAE1D,MAAM,KAAK,GACT,SAAS,KAAK,IAAI;QAChB,CAAC,CAAC,OAAO;QACT,CAAC,CAAC,IAAI,CAAC,IAAI,KAAK,MAAM;YACpB,CAAC,CAAC,KAAK;YACP,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC;gBACxC,CAAC,CAAC,IAAI;gBACN,CAAC,CAAC,KAAK,CAAC;IAEhB,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,UAAU,EAAE,IAAI,CAAC,UAAU;QAC3B,aAAa,EAAE,IAAI,CAAC,aAAa;QACjC,SAAS;QACT,YAAY;QACZ,KAAK;QACL,4EAA4E;QAC5E,qEAAqE;QACrE,QAAQ,EAAE,YAAY,KAAK,IAAI,IAAI,YAAY,KAAK,SAAS;KAC9D,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAS,cAAc,CAAC,IAAqB,EAAE,IAA4B;IACzE,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,aAAa,CAAC,CAAC;IACxD,IAAI,CAAC,IAAI;QAAE,OAAO;IAElB,MAAM,SAAS,GAAG,IAAI,CAAC,YAAY,IAAI,IAAI,CAAC,SAAS,CAAC;IACtD,IAAI,CAAC,kBAAkB,CAAC,QAAQ,EAAE,SAAS,CAAC;QAAE,OAAO;IAErD,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAU,EAAE,CACxC,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAErG,IAAI,CAAC,QAAQ,CAAC,oCAAoC,CAAC,IAAI,CAAC,QAAQ,CAAC,6BAA6B,CAAC,EAAE,CAAC;QAChG,IAAI,CAAC,OAAO;YACV,iFAAiF;gBACjF,wDAAwD,CAAC;IAC7D,CAAC;AACH,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/web-dashboard/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,YAAY,EAAmC,MAAM,WAAW,CAAC;AAc1E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AA2B3D,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,WAAW,EAA4C,MAAM,mBAAmB,CAAC;AAC1F,OAAO,EAAmB,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/web-dashboard/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -43,6 +43,9 @@ import { clearModelListCache } from '../inference/model-validator.js';
|
|
|
43
43
|
import { probeProviderList } from '../inference/model-probe.js';
|
|
44
44
|
import { CATALOG_PROVIDER_IDS, getCatalogProvider } from '../inference/provider-catalog.js';
|
|
45
45
|
import { readHubData } from './hub-data.js';
|
|
46
|
+
import { isCuratedProcessEnvVar, normalizeProcessEnvValue, } from '../config/process-env.js';
|
|
47
|
+
import { FRESH_DAYS, FRESHNESS_COPY, REACHABILITY_COPY, classifyFreshness, classifyReachability, } from '../learning/model-reachability.js';
|
|
48
|
+
import { VERIFY_BACKLOG_DEFAULT_PER_RUN, VERIFY_BACKLOG_MAX_PER_RUN, getVerifyBacklogState, startVerifyBacklogRun, } from '../learning/model-verify-job.js';
|
|
46
49
|
import { setToolsetEnabled } from '../tools/toolsets.js';
|
|
47
50
|
import { setSkillEnabled } from '../learning/hub-skill-catalog.js';
|
|
48
51
|
// I11 — dashboard channel send-test: the same registry/adapters `nuvira gateway
|
|
@@ -2766,6 +2769,32 @@ function writeJson(res, status, data) {
|
|
|
2766
2769
|
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
2767
2770
|
res.end(JSON.stringify(data));
|
|
2768
2771
|
}
|
|
2772
|
+
/**
|
|
2773
|
+
* Turn a refused process-environment value into something a user can act on.
|
|
2774
|
+
*
|
|
2775
|
+
* UI copy on purpose: the validator sends a stable machine reason, and the
|
|
2776
|
+
* message is composed here. The two that matter most are the ones a user would
|
|
2777
|
+
* otherwise read as success — a name off the allowlist (the endpoint is curated,
|
|
2778
|
+
* and saying so is friendlier than a bare 400) and a value that is not a flag
|
|
2779
|
+
* (`NUVIRA_STRICT_MODEL=true` reads as OFF, so accepting it would store a
|
|
2780
|
+
* setting that does not do what the page says it does).
|
|
2781
|
+
*/
|
|
2782
|
+
function describeProcessEnvRefusal(name, reason) {
|
|
2783
|
+
switch (reason) {
|
|
2784
|
+
case 'unknown-name':
|
|
2785
|
+
return `${name || 'That name'} is not one of the curated process switches. Skill secrets live on the Environment page, and provider credentials in provider setup.`;
|
|
2786
|
+
case 'empty':
|
|
2787
|
+
return `${name} cannot be set to an empty value — use Unset to remove it from the env file.`;
|
|
2788
|
+
case 'multiline':
|
|
2789
|
+
return `${name} was not saved: a value may not contain a newline, or it would add a second variable to the env file.`;
|
|
2790
|
+
case 'too-long':
|
|
2791
|
+
return `${name} was not saved: the value is too long (limit 512 characters).`;
|
|
2792
|
+
case 'not-a-flag':
|
|
2793
|
+
return `${name} was not saved: it is a switch, so its value must be on (1, true, yes, on) or off (0, false, off, no).`;
|
|
2794
|
+
default:
|
|
2795
|
+
return `${name} was not saved.`;
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2769
2798
|
// ─── Request Handler ────────────────────────────────────────────────────────
|
|
2770
2799
|
function handleRequest(req, res) {
|
|
2771
2800
|
const url = new URL(req.url || '/', `http://${req.headers.host || HOST}`);
|
|
@@ -3531,6 +3560,140 @@ function handleRequest(req, res) {
|
|
|
3531
3560
|
})();
|
|
3532
3561
|
return;
|
|
3533
3562
|
}
|
|
3563
|
+
// ── Process environment — the curated switches, not a general env editor ─
|
|
3564
|
+
//
|
|
3565
|
+
// `/env` edits SKILL secrets: any well-formed NAME a skill might declare. The
|
|
3566
|
+
// switches that change how a RUN behaves (isolation, resume, the debug log,
|
|
3567
|
+
// OTLP export, tool hooks) are a different thing, and the useful thing to
|
|
3568
|
+
// expose is not "write a variable" but "here is what this switch is currently
|
|
3569
|
+
// set to, what unset means, and whether something outranks it".
|
|
3570
|
+
//
|
|
3571
|
+
// The allowlist is enforced HERE rather than in the UI, so a hand-made request
|
|
3572
|
+
// cannot use this endpoint to append an arbitrary name to the credential
|
|
3573
|
+
// `.env` — the page is curated because the server is. Reads need a session;
|
|
3574
|
+
// writing needs routing.operate (admin or operator), matching every other
|
|
3575
|
+
// route that mutates persisted configuration.
|
|
3576
|
+
if (pathname === '/api/process-env' && req.method === 'GET') {
|
|
3577
|
+
void (async () => {
|
|
3578
|
+
const session = adminSessions.validate(bearerToken(req));
|
|
3579
|
+
if (!session) {
|
|
3580
|
+
writeJson(res, 401, { ok: false, error: 'Not authenticated — log in first.' });
|
|
3581
|
+
return;
|
|
3582
|
+
}
|
|
3583
|
+
try {
|
|
3584
|
+
const { readProcessEnvInventory } = await import('./process-env-inventory.js');
|
|
3585
|
+
writeJson(res, 200, { ok: true, vars: readProcessEnvInventory() });
|
|
3586
|
+
}
|
|
3587
|
+
catch (err) {
|
|
3588
|
+
writeJson(res, 500, { ok: false, error: err instanceof Error ? err.message : String(err) });
|
|
3589
|
+
}
|
|
3590
|
+
})();
|
|
3591
|
+
return;
|
|
3592
|
+
}
|
|
3593
|
+
if (pathname === '/api/process-env' && req.method === 'POST') {
|
|
3594
|
+
void (async () => {
|
|
3595
|
+
const session = adminSessions.validate(bearerToken(req));
|
|
3596
|
+
if (!session) {
|
|
3597
|
+
writeJson(res, 401, { ok: false, error: 'Not authenticated — log in first.' });
|
|
3598
|
+
return;
|
|
3599
|
+
}
|
|
3600
|
+
if (!roleCan(session.role, 'routing.operate')) {
|
|
3601
|
+
writeJson(res, 403, {
|
|
3602
|
+
ok: false,
|
|
3603
|
+
error: `Access denied — role '${session.role}' cannot change the process environment (requires admin or operator).`,
|
|
3604
|
+
});
|
|
3605
|
+
return;
|
|
3606
|
+
}
|
|
3607
|
+
const body = await readJsonBody(req);
|
|
3608
|
+
const name = typeof body?.name === 'string' ? body.name.trim() : '';
|
|
3609
|
+
const rawValue = typeof body?.value === 'string' ? body.value : '';
|
|
3610
|
+
const normalized = normalizeProcessEnvValue(name, rawValue);
|
|
3611
|
+
if (!normalized.ok) {
|
|
3612
|
+
writeJson(res, 400, { ok: false, error: describeProcessEnvRefusal(name, normalized.reason), reason: normalized.reason });
|
|
3613
|
+
return;
|
|
3614
|
+
}
|
|
3615
|
+
try {
|
|
3616
|
+
const { saveEnvValue } = await import('../skills/secret-capture.js');
|
|
3617
|
+
const written = saveEnvValue(name, normalized.value);
|
|
3618
|
+
if (!written.success) {
|
|
3619
|
+
writeJson(res, 400, { ok: false, error: `${name} could not be written to the env file (${written.reason ?? 'write-failed'}).` });
|
|
3620
|
+
return;
|
|
3621
|
+
}
|
|
3622
|
+
// Apply to THIS process, so the dashboard's own turns obey the switch
|
|
3623
|
+
// without a restart. It is also why the row stops reading as shadowed:
|
|
3624
|
+
// the process now holds the value that was just written to the file.
|
|
3625
|
+
try {
|
|
3626
|
+
const { applyEnvToProcess } = await import('../gateway/platform-config.js');
|
|
3627
|
+
applyEnvToProcess({ [name]: normalized.value });
|
|
3628
|
+
}
|
|
3629
|
+
catch {
|
|
3630
|
+
/* best-effort */
|
|
3631
|
+
}
|
|
3632
|
+
const { readProcessEnvInventory } = await import('./process-env-inventory.js');
|
|
3633
|
+
writeJson(res, 200, {
|
|
3634
|
+
ok: true,
|
|
3635
|
+
saved: { name, value: normalized.value },
|
|
3636
|
+
row: readProcessEnvInventory().find((r) => r.name === name),
|
|
3637
|
+
});
|
|
3638
|
+
}
|
|
3639
|
+
catch (err) {
|
|
3640
|
+
writeJson(res, 500, { ok: false, error: err instanceof Error ? err.message : String(err) });
|
|
3641
|
+
}
|
|
3642
|
+
})();
|
|
3643
|
+
return;
|
|
3644
|
+
}
|
|
3645
|
+
if (pathname === '/api/process-env/delete' && req.method === 'POST') {
|
|
3646
|
+
void (async () => {
|
|
3647
|
+
const session = adminSessions.validate(bearerToken(req));
|
|
3648
|
+
if (!session) {
|
|
3649
|
+
writeJson(res, 401, { ok: false, error: 'Not authenticated — log in first.' });
|
|
3650
|
+
return;
|
|
3651
|
+
}
|
|
3652
|
+
if (!roleCan(session.role, 'routing.operate')) {
|
|
3653
|
+
writeJson(res, 403, {
|
|
3654
|
+
ok: false,
|
|
3655
|
+
error: `Access denied — role '${session.role}' cannot change the process environment (requires admin or operator).`,
|
|
3656
|
+
});
|
|
3657
|
+
return;
|
|
3658
|
+
}
|
|
3659
|
+
const body = await readJsonBody(req);
|
|
3660
|
+
const name = typeof body?.name === 'string' ? body.name.trim() : '';
|
|
3661
|
+
// The same allowlist the write path uses: deleting an arbitrary name from
|
|
3662
|
+
// the credential file would be a write wearing a different verb.
|
|
3663
|
+
if (!isCuratedProcessEnvVar(name)) {
|
|
3664
|
+
writeJson(res, 400, { ok: false, error: describeProcessEnvRefusal(name, 'unknown-name'), reason: 'unknown-name' });
|
|
3665
|
+
return;
|
|
3666
|
+
}
|
|
3667
|
+
try {
|
|
3668
|
+
const { deleteEnvValue } = await import('../skills/secret-capture.js');
|
|
3669
|
+
const result = deleteEnvValue(name);
|
|
3670
|
+
if (!result.success) {
|
|
3671
|
+
writeJson(res, 400, { ok: false, error: result.reason ?? 'delete failed' });
|
|
3672
|
+
return;
|
|
3673
|
+
}
|
|
3674
|
+
// Drop it from this process too. "Unset" has to mean unset for the run
|
|
3675
|
+
// that is about to happen, or the row would read as unset while the
|
|
3676
|
+
// dashboard kept obeying the value it just removed.
|
|
3677
|
+
try {
|
|
3678
|
+
const { applyEnvToProcess } = await import('../gateway/platform-config.js');
|
|
3679
|
+
applyEnvToProcess({}, [name]);
|
|
3680
|
+
}
|
|
3681
|
+
catch {
|
|
3682
|
+
/* best-effort */
|
|
3683
|
+
}
|
|
3684
|
+
const { readProcessEnvInventory } = await import('./process-env-inventory.js');
|
|
3685
|
+
writeJson(res, 200, {
|
|
3686
|
+
ok: true,
|
|
3687
|
+
removed: result.removed,
|
|
3688
|
+
row: readProcessEnvInventory().find((r) => r.name === name),
|
|
3689
|
+
});
|
|
3690
|
+
}
|
|
3691
|
+
catch (err) {
|
|
3692
|
+
writeJson(res, 500, { ok: false, error: err instanceof Error ? err.message : String(err) });
|
|
3693
|
+
}
|
|
3694
|
+
})();
|
|
3695
|
+
return;
|
|
3696
|
+
}
|
|
3534
3697
|
// ── Agent Hub channel send-test (I11) — admin-gated gateway send ────────
|
|
3535
3698
|
// Mirrors `nuvira gateway send <target> <text>`: resolve the target (alias or
|
|
3536
3699
|
// platform:channelId) through the SAME ChannelDirectory, then send through
|
|
@@ -4318,25 +4481,34 @@ function handleRequest(req, res) {
|
|
|
4318
4481
|
const registryData = readJSON(join(MEMORY_DIR, 'model-registry.json'));
|
|
4319
4482
|
const now = Date.now();
|
|
4320
4483
|
const entries = Object.values(registryData?.entries ?? {});
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4484
|
+
// Two classifications per entry, deliberately separate — see
|
|
4485
|
+
// `model-reachability.ts` for why: "is the provider still listing it"
|
|
4486
|
+
// (probe age) and "would the router use it" (verification) are different
|
|
4487
|
+
// questions, and this endpoint previously answered both with one rule
|
|
4488
|
+
// while the panel's badges answered with another.
|
|
4489
|
+
const counts = {
|
|
4490
|
+
routable: 0,
|
|
4491
|
+
parked: 0,
|
|
4492
|
+
'proof-expired': 0,
|
|
4493
|
+
'proven-dead': 0,
|
|
4494
|
+
'never-verified': 0,
|
|
4495
|
+
};
|
|
4496
|
+
const freshnessCounts = {
|
|
4497
|
+
fresh: 0,
|
|
4498
|
+
stale: 0,
|
|
4499
|
+
'likely-removed': 0,
|
|
4500
|
+
};
|
|
4326
4501
|
const timelineEntries = entries.map((e) => {
|
|
4327
|
-
const
|
|
4328
|
-
const
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
removedCount++;
|
|
4332
|
-
else if (isStale)
|
|
4333
|
-
staleCount++;
|
|
4334
|
-
else
|
|
4335
|
-
freshCount++;
|
|
4502
|
+
const reachability = classifyReachability(e, now);
|
|
4503
|
+
const freshness = classifyFreshness(e.lastProbedAt, e.errorRate, now);
|
|
4504
|
+
counts[reachability] += 1;
|
|
4505
|
+
freshnessCounts[freshness] += 1;
|
|
4336
4506
|
return {
|
|
4337
4507
|
provider: e.provider,
|
|
4338
4508
|
model: e.model,
|
|
4339
4509
|
status: e.status,
|
|
4510
|
+
reachability,
|
|
4511
|
+
freshness,
|
|
4340
4512
|
lastVerifiedAt: e.lastVerifiedAt || 0,
|
|
4341
4513
|
lastProbedAt: e.lastProbedAt || 0,
|
|
4342
4514
|
lastUsedAt: e.lastUsedAt || 0,
|
|
@@ -4344,6 +4516,10 @@ function handleRequest(req, res) {
|
|
|
4344
4516
|
latencyMs: e.latencyMs,
|
|
4345
4517
|
contextWindowTokens: e.contextWindowTokens,
|
|
4346
4518
|
firstSeenAt: e.firstSeenAt || e.lastProbedAt || 0,
|
|
4519
|
+
/** How stale this model's PROOF is — the number `isUsable()` gates on. */
|
|
4520
|
+
daysSinceVerify: e.lastVerifiedAt
|
|
4521
|
+
? (now - e.lastVerifiedAt) / (24 * 60 * 60 * 1000)
|
|
4522
|
+
: null,
|
|
4347
4523
|
};
|
|
4348
4524
|
});
|
|
4349
4525
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
@@ -4351,9 +4527,15 @@ function handleRequest(req, res) {
|
|
|
4351
4527
|
entries: timelineEntries,
|
|
4352
4528
|
lastUpdated: now,
|
|
4353
4529
|
totalModels: entries.length,
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4530
|
+
counts,
|
|
4531
|
+
freshnessCounts,
|
|
4532
|
+
// The wording travels WITH the data, so the panel cannot drift from the
|
|
4533
|
+
// classifier that produced the state. `reachability-copy` is one small
|
|
4534
|
+
// map, not a per-row string, and it keeps the root learning modules out
|
|
4535
|
+
// of the browser bundle (they are server-side only).
|
|
4536
|
+
reachabilityCopy: REACHABILITY_COPY,
|
|
4537
|
+
freshnessCopy: FRESHNESS_COPY,
|
|
4538
|
+
freshDays: FRESH_DAYS,
|
|
4357
4539
|
}));
|
|
4358
4540
|
}
|
|
4359
4541
|
catch (err) {
|
|
@@ -4362,6 +4544,53 @@ function handleRequest(req, res) {
|
|
|
4362
4544
|
}
|
|
4363
4545
|
return;
|
|
4364
4546
|
}
|
|
4547
|
+
// ── Timeline action: "verify next N" the never-verified backlog ──────────
|
|
4548
|
+
// READ is open, like `/api/model-timeline` above: it carries model ids, counts
|
|
4549
|
+
// and prose, no credentials, and it renders on a page that is already readable
|
|
4550
|
+
// without a session. The WRITE below is where the gate belongs — a spot-check
|
|
4551
|
+
// is a real generation against the user's provider quota.
|
|
4552
|
+
if (pathname === '/api/models/verify-next' && req.method === 'GET') {
|
|
4553
|
+
writeJson(res, 200, {
|
|
4554
|
+
...getVerifyBacklogState(),
|
|
4555
|
+
defaultCount: VERIFY_BACKLOG_DEFAULT_PER_RUN,
|
|
4556
|
+
maxCount: VERIFY_BACKLOG_MAX_PER_RUN,
|
|
4557
|
+
});
|
|
4558
|
+
return;
|
|
4559
|
+
}
|
|
4560
|
+
// POST /api/models/verify-next — start probing the next N never-verified ids.
|
|
4561
|
+
// Returns as soon as the run is PLANNED; the probes continue in the background
|
|
4562
|
+
// and the panel watches them through the GET above. A run that is refused
|
|
4563
|
+
// (already running, or an empty backlog) is reported with `ok: false` plus the
|
|
4564
|
+
// reason, so "nothing left to do" never has to be inferred from a zero count.
|
|
4565
|
+
if (pathname === '/api/models/verify-next' && req.method === 'POST') {
|
|
4566
|
+
void (async () => {
|
|
4567
|
+
const session = adminSessions.validate(bearerToken(req));
|
|
4568
|
+
if (!session) {
|
|
4569
|
+
writeJson(res, 401, { ok: false, error: 'Not authenticated — log in first.' });
|
|
4570
|
+
return;
|
|
4571
|
+
}
|
|
4572
|
+
if (!roleCan(session.role, 'routing.operate')) {
|
|
4573
|
+
writeJson(res, 403, {
|
|
4574
|
+
ok: false,
|
|
4575
|
+
error: `Access denied — role '${session.role}' cannot spend provider quota on model verification.`,
|
|
4576
|
+
});
|
|
4577
|
+
return;
|
|
4578
|
+
}
|
|
4579
|
+
const body = await readJsonBody(req);
|
|
4580
|
+
const requested = typeof body?.count === 'number' ? body.count : VERIFY_BACKLOG_DEFAULT_PER_RUN;
|
|
4581
|
+
const result = startVerifyBacklogRun(new ConfigManager(), requested);
|
|
4582
|
+
writeJson(res, 200, {
|
|
4583
|
+
ok: result.started,
|
|
4584
|
+
started: result.started,
|
|
4585
|
+
error: result.error,
|
|
4586
|
+
refusal: result.state.refusal,
|
|
4587
|
+
state: result.state,
|
|
4588
|
+
defaultCount: VERIFY_BACKLOG_DEFAULT_PER_RUN,
|
|
4589
|
+
maxCount: VERIFY_BACKLOG_MAX_PER_RUN,
|
|
4590
|
+
});
|
|
4591
|
+
})();
|
|
4592
|
+
return;
|
|
4593
|
+
}
|
|
4365
4594
|
if (pathname === '/api/requests') {
|
|
4366
4595
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
4367
4596
|
res.end(JSON.stringify(readRequestsData()));
|