agent-nuvira 3.3.7 → 3.3.9
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/README.md +57 -50
- package/dist/agents/agents/planner.d.ts.map +1 -1
- package/dist/agents/agents/planner.js +41 -0
- package/dist/agents/agents/planner.js.map +1 -1
- package/dist/agents/agents/reasoner.d.ts.map +1 -1
- package/dist/agents/agents/reasoner.js +6 -0
- package/dist/agents/agents/reasoner.js.map +1 -1
- package/dist/agents/agents/runner.d.ts +6 -0
- package/dist/agents/agents/runner.d.ts.map +1 -1
- package/dist/agents/agents/runner.js +82 -5
- package/dist/agents/agents/runner.js.map +1 -1
- package/dist/agents/agents/writer-tool-calling.d.ts.map +1 -1
- package/dist/agents/agents/writer-tool-calling.js +5 -2
- package/dist/agents/agents/writer-tool-calling.js.map +1 -1
- package/dist/agents/agents/writer.d.ts.map +1 -1
- package/dist/agents/agents/writer.js +8 -4
- package/dist/agents/agents/writer.js.map +1 -1
- package/dist/agents/fanout-scheduler.d.ts +72 -0
- package/dist/agents/fanout-scheduler.d.ts.map +1 -0
- package/dist/agents/fanout-scheduler.js +100 -0
- package/dist/agents/fanout-scheduler.js.map +1 -0
- package/dist/agents/orchestrator.d.ts.map +1 -1
- package/dist/agents/orchestrator.js +30 -1
- package/dist/agents/orchestrator.js.map +1 -1
- package/dist/agents/plan-quality.d.ts +58 -0
- package/dist/agents/plan-quality.d.ts.map +1 -0
- package/dist/agents/plan-quality.js +152 -0
- package/dist/agents/plan-quality.js.map +1 -0
- package/dist/agents/prompt-assembly.d.ts +8 -0
- package/dist/agents/prompt-assembly.d.ts.map +1 -1
- package/dist/agents/prompt-assembly.js +18 -0
- package/dist/agents/prompt-assembly.js.map +1 -1
- package/dist/agents/step-handoff.d.ts +12 -2
- package/dist/agents/step-handoff.d.ts.map +1 -1
- package/dist/agents/step-handoff.js +65 -8
- package/dist/agents/step-handoff.js.map +1 -1
- package/dist/agents/tool-calling-agent.d.ts.map +1 -1
- package/dist/agents/tool-calling-agent.js +18 -0
- package/dist/agents/tool-calling-agent.js.map +1 -1
- package/dist/cli/cache.d.ts.map +1 -1
- package/dist/cli/cache.js +34 -4
- package/dist/cli/cache.js.map +1 -1
- package/dist/cli/chat.d.ts +6 -0
- package/dist/cli/chat.d.ts.map +1 -1
- package/dist/cli/chat.js +79 -18
- package/dist/cli/chat.js.map +1 -1
- package/dist/cli/cli-program.d.ts.map +1 -1
- package/dist/cli/cli-program.js +6 -0
- package/dist/cli/cli-program.js.map +1 -1
- package/dist/cli/config.d.ts +10 -0
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +179 -1
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/continuity.d.ts +61 -0
- package/dist/cli/continuity.d.ts.map +1 -0
- package/dist/cli/continuity.js +112 -0
- package/dist/cli/continuity.js.map +1 -0
- package/dist/cli/dashboard.d.ts +3 -1
- package/dist/cli/dashboard.d.ts.map +1 -1
- package/dist/cli/dashboard.js +5 -16
- package/dist/cli/dashboard.js.map +1 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +48 -1
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/execute.d.ts +8 -0
- package/dist/cli/execute.d.ts.map +1 -1
- package/dist/cli/execute.js +34 -1
- package/dist/cli/execute.js.map +1 -1
- package/dist/cli/loop-executor.d.ts +24 -0
- package/dist/cli/loop-executor.d.ts.map +1 -1
- package/dist/cli/loop-executor.js +134 -13
- package/dist/cli/loop-executor.js.map +1 -1
- package/dist/cli/website.d.ts +25 -0
- package/dist/cli/website.d.ts.map +1 -0
- package/dist/cli/website.js +58 -0
- package/dist/cli/website.js.map +1 -0
- package/dist/config/manager.d.ts.map +1 -1
- package/dist/config/manager.js +35 -0
- package/dist/config/manager.js.map +1 -1
- package/dist/config/process-env.d.ts.map +1 -1
- package/dist/config/process-env.js +20 -0
- package/dist/config/process-env.js.map +1 -1
- package/dist/config/provider-env.d.ts +2 -1
- package/dist/config/provider-env.d.ts.map +1 -1
- package/dist/config/provider-env.js +19 -2
- package/dist/config/provider-env.js.map +1 -1
- package/dist/config/service-catalog.d.ts +81 -0
- package/dist/config/service-catalog.d.ts.map +1 -0
- package/dist/config/service-catalog.js +286 -0
- package/dist/config/service-catalog.js.map +1 -0
- package/dist/config/service-probe.d.ts +27 -0
- package/dist/config/service-probe.d.ts.map +1 -0
- package/dist/config/service-probe.js +104 -0
- package/dist/config/service-probe.js.map +1 -0
- package/dist/config/types.d.ts +63 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/context/cache.d.ts +46 -2
- package/dist/context/cache.d.ts.map +1 -1
- package/dist/context/cache.js +85 -7
- package/dist/context/cache.js.map +1 -1
- package/dist/findings/verdicts.d.ts +59 -0
- package/dist/findings/verdicts.d.ts.map +1 -1
- package/dist/findings/verdicts.js +117 -0
- package/dist/findings/verdicts.js.map +1 -1
- package/dist/forwarded.d.ts +2 -0
- package/dist/forwarded.d.ts.map +1 -0
- package/dist/forwarded.js +2 -0
- package/dist/forwarded.js.map +1 -0
- package/dist/gateway/registry.d.ts.map +1 -1
- package/dist/gateway/registry.js +9 -0
- package/dist/gateway/registry.js.map +1 -1
- package/dist/learning/auto-router.d.ts +30 -0
- package/dist/learning/auto-router.d.ts.map +1 -1
- package/dist/learning/auto-router.js +69 -12
- package/dist/learning/auto-router.js.map +1 -1
- package/dist/learning/context-assembly.d.ts +37 -0
- package/dist/learning/context-assembly.d.ts.map +1 -0
- package/dist/learning/context-assembly.js +63 -0
- package/dist/learning/context-assembly.js.map +1 -0
- package/dist/learning/reasoning-trace.d.ts +26 -5
- package/dist/learning/reasoning-trace.d.ts.map +1 -1
- package/dist/learning/reasoning-trace.js +21 -1
- package/dist/learning/reasoning-trace.js.map +1 -1
- package/dist/learning/session-digest.d.ts +78 -0
- package/dist/learning/session-digest.d.ts.map +1 -0
- package/dist/learning/session-digest.js +185 -0
- package/dist/learning/session-digest.js.map +1 -0
- package/dist/learning/session-recall.d.ts +135 -0
- package/dist/learning/session-recall.d.ts.map +1 -0
- package/dist/learning/session-recall.js +305 -0
- package/dist/learning/session-recall.js.map +1 -0
- package/dist/learning/session-store.d.ts +172 -0
- package/dist/learning/session-store.d.ts.map +1 -0
- package/dist/learning/session-store.js +424 -0
- package/dist/learning/session-store.js.map +1 -0
- package/dist/learning/step-checkpoint.d.ts +28 -0
- package/dist/learning/step-checkpoint.d.ts.map +1 -1
- package/dist/learning/step-checkpoint.js +90 -6
- package/dist/learning/step-checkpoint.js.map +1 -1
- package/dist/learning/working-state.d.ts +19 -0
- package/dist/learning/working-state.d.ts.map +1 -1
- package/dist/learning/working-state.js +34 -1
- package/dist/learning/working-state.js.map +1 -1
- package/dist/parity/drivers.d.ts.map +1 -1
- package/dist/parity/drivers.js +29 -0
- package/dist/parity/drivers.js.map +1 -1
- package/dist/parity/wire-fixtures.d.ts +88 -0
- package/dist/parity/wire-fixtures.d.ts.map +1 -0
- package/dist/parity/wire-fixtures.js +405 -0
- package/dist/parity/wire-fixtures.js.map +1 -0
- package/dist/tools/artifact-append.d.ts +23 -0
- package/dist/tools/artifact-append.d.ts.map +1 -1
- package/dist/tools/artifact-append.js +28 -0
- package/dist/tools/artifact-append.js.map +1 -1
- package/dist/tools/ask-user.d.ts +5 -0
- package/dist/tools/ask-user.d.ts.map +1 -1
- package/dist/tools/ask-user.js +51 -5
- package/dist/tools/ask-user.js.map +1 -1
- package/dist/tools/child-agent-entry.js +1 -0
- package/dist/tools/child-agent-entry.js.map +1 -1
- package/dist/tools/child-agent-runtime.d.ts +7 -0
- package/dist/tools/child-agent-runtime.d.ts.map +1 -1
- package/dist/tools/child-agent-runtime.js +116 -6
- package/dist/tools/child-agent-runtime.js.map +1 -1
- package/dist/tools/child-agent-worker.d.ts +15 -0
- package/dist/tools/child-agent-worker.d.ts.map +1 -0
- package/dist/tools/child-agent-worker.js.map +1 -0
- package/dist/tools/coding-tools.d.ts.map +1 -1
- package/dist/tools/coding-tools.js +11 -0
- package/dist/tools/coding-tools.js.map +1 -1
- package/dist/tools/finding-tool.d.ts +6 -1
- package/dist/tools/finding-tool.d.ts.map +1 -1
- package/dist/tools/finding-tool.js +15 -2
- package/dist/tools/finding-tool.js.map +1 -1
- package/dist/tools/followup-utils.d.ts +35 -0
- package/dist/tools/followup-utils.d.ts.map +1 -1
- package/dist/tools/followup-utils.js +36 -0
- package/dist/tools/followup-utils.js.map +1 -1
- package/dist/tools/loop-project-context.d.ts +4 -1
- package/dist/tools/loop-project-context.d.ts.map +1 -1
- package/dist/tools/loop-project-context.js +48 -1
- package/dist/tools/loop-project-context.js.map +1 -1
- package/dist/tools/loop-skill-hint.d.ts.map +1 -1
- package/dist/tools/loop-skill-hint.js +7 -0
- package/dist/tools/loop-skill-hint.js.map +1 -1
- package/dist/tools/modality/image-gen.d.ts +25 -6
- package/dist/tools/modality/image-gen.d.ts.map +1 -1
- package/dist/tools/modality/image-gen.js +46 -27
- package/dist/tools/modality/image-gen.js.map +1 -1
- package/dist/tools/modality/image-providers.d.ts +71 -0
- package/dist/tools/modality/image-providers.d.ts.map +1 -0
- package/dist/tools/modality/image-providers.js +277 -0
- package/dist/tools/modality/image-providers.js.map +1 -0
- package/dist/tools/registry.d.ts +10 -1
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +35 -8
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/run-cli.d.ts.map +1 -1
- package/dist/tools/run-cli.js +39 -1
- package/dist/tools/run-cli.js.map +1 -1
- package/dist/tools/run-terminal.d.ts.map +1 -1
- package/dist/tools/run-terminal.js +50 -6
- package/dist/tools/run-terminal.js.map +1 -1
- package/dist/tools/subagent-spawner.d.ts +8 -0
- package/dist/tools/subagent-spawner.d.ts.map +1 -1
- package/dist/tools/subagent-spawner.js +3 -0
- package/dist/tools/subagent-spawner.js.map +1 -1
- package/dist/tools/tool-loop.d.ts +55 -0
- package/dist/tools/tool-loop.d.ts.map +1 -1
- package/dist/tools/tool-loop.js +136 -0
- package/dist/tools/tool-loop.js.map +1 -1
- package/dist/tools/untrusted-content.d.ts +29 -0
- package/dist/tools/untrusted-content.d.ts.map +1 -0
- package/dist/tools/untrusted-content.js +53 -0
- package/dist/tools/untrusted-content.js.map +1 -0
- package/dist/tools/web-research.d.ts +76 -14
- package/dist/tools/web-research.d.ts.map +1 -1
- package/dist/tools/web-research.js +280 -35
- package/dist/tools/web-research.js.map +1 -1
- package/dist/utils/effect-verification.d.ts +115 -0
- package/dist/utils/effect-verification.d.ts.map +1 -0
- package/dist/utils/effect-verification.js +337 -0
- package/dist/utils/effect-verification.js.map +1 -0
- package/dist/utils/open-url.d.ts +16 -0
- package/dist/utils/open-url.d.ts.map +1 -0
- package/dist/utils/open-url.js +32 -0
- package/dist/utils/open-url.js.map +1 -0
- package/dist/utils/project-env.d.ts +146 -0
- package/dist/utils/project-env.d.ts.map +1 -0
- package/dist/utils/project-env.js +584 -0
- package/dist/utils/project-env.js.map +1 -0
- package/dist/utils/shell.d.ts +7 -0
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +2 -0
- package/dist/utils/shell.js.map +1 -1
- package/dist/web-dashboard/chat-console.d.ts +2 -0
- package/dist/web-dashboard/chat-console.d.ts.map +1 -1
- package/dist/web-dashboard/chat-console.js +7 -0
- package/dist/web-dashboard/chat-console.js.map +1 -1
- package/dist/web-dashboard/server.d.ts +3 -1
- package/dist/web-dashboard/server.d.ts.map +1 -1
- package/dist/web-dashboard/server.js +534 -8
- package/dist/web-dashboard/server.js.map +1 -1
- package/dist/web-dashboard/src/types.d.ts +150 -1
- package/dist/web-dashboard/src/types.d.ts.map +1 -1
- package/dist/web-dashboard/workspace-guard.d.ts +40 -0
- package/dist/web-dashboard/workspace-guard.d.ts.map +1 -0
- package/dist/web-dashboard/workspace-guard.js +92 -0
- package/dist/web-dashboard/workspace-guard.js.map +1 -0
- package/package.json +4 -1
- package/src/web-dashboard/public/assets/index-Beportyl.js +207 -0
- package/src/web-dashboard/public/assets/index-Beportyl.js.map +1 -0
- package/src/web-dashboard/public/assets/index-gtQyg9nm.css +1 -0
- package/src/web-dashboard/public/index.html +2 -2
- package/src/web-dashboard/public/assets/index-D1YFCcIZ.js +0 -207
- package/src/web-dashboard/public/assets/index-D1YFCcIZ.js.map +0 -1
- package/src/web-dashboard/public/assets/index-jkW5qcf-.css +0 -1
|
@@ -20,6 +20,7 @@ import { registerDagHandlers } from '../observability/dag-bridge.js';
|
|
|
20
20
|
import { debugLogDir, debugLoggingEnabled, debugLogsForSession } from '../observability/debug-log.js';
|
|
21
21
|
import { scrubSecrets } from '../gateway/gateway-log.js';
|
|
22
22
|
import { resolveBuffConfigDir, resolveBuffConfigPath, resolveNuviraHome } from '../config/paths.js';
|
|
23
|
+
import { createRequire } from 'node:module';
|
|
23
24
|
import { loadEnv } from '../utils/env.js';
|
|
24
25
|
import { ConfigManager } from '../config/manager.js';
|
|
25
26
|
import { runAllChecks } from '../cli/doctor.js';
|
|
@@ -57,7 +58,9 @@ import { setSkillEnabled } from '../learning/hub-skill-catalog.js';
|
|
|
57
58
|
// hit the mocked readFileSync).
|
|
58
59
|
import { createConfiguredAdapters } from '../gateway/adapters.js';
|
|
59
60
|
import { ChannelDirectory, PLATFORM_ENV_VARS, platformConfigVars } from '../gateway/channel-directory.js';
|
|
60
|
-
import { applyEnvToProcess, configurablePlatforms, platformConfigStatus, platformEnvVarMeta, redactValue, writeEnvFile, } from '../gateway/platform-config.js';
|
|
61
|
+
import { applyEnvToProcess, configurablePlatforms, envVarState, platformConfigStatus, platformEnvVarMeta, redactValue, writeEnvFile, } from '../gateway/platform-config.js';
|
|
62
|
+
import { SERVICE_CATALOG, getServiceDefinition, } from '../config/service-catalog.js';
|
|
63
|
+
import { probeService } from '../config/service-probe.js';
|
|
61
64
|
// ─── Constants ──────────────────────────────────────────────────────────────
|
|
62
65
|
const PORT = parseInt(envBuff('DASHBOARD_PORT') || '3030', 10);
|
|
63
66
|
const HOST = envBuff('DASHBOARD_HOST') || '127.0.0.1';
|
|
@@ -1581,6 +1584,78 @@ function readBenchmarkData() {
|
|
|
1581
1584
|
})),
|
|
1582
1585
|
};
|
|
1583
1586
|
}
|
|
1587
|
+
/**
|
|
1588
|
+
* The ACTIVE vector-search backend ('json' | 'faiss-ivf' | 'faiss-native').
|
|
1589
|
+
*
|
|
1590
|
+
* `memory.backend` in this payload is the memory PROVIDER tier ('local'; Mem0
|
|
1591
|
+
* is an optional provider) — a different axis from how semantic search is
|
|
1592
|
+
* indexed, which is what the user actually asks about. Resolved the same way
|
|
1593
|
+
* the runtime resolves it: env VECTOR_BACKEND → config memory.vectorBackend →
|
|
1594
|
+
* default 'faiss'. The read MUST be synchronous (readMemoryData builds the
|
|
1595
|
+
* /api payload synchronously), so native detection is a module-presence check;
|
|
1596
|
+
* the CLI's `nuvira memory backend --check` runs the full smoke test.
|
|
1597
|
+
*/
|
|
1598
|
+
let vectorBackendNameCache = null;
|
|
1599
|
+
function resolveVectorBackendName() {
|
|
1600
|
+
if (vectorBackendNameCache)
|
|
1601
|
+
return vectorBackendNameCache;
|
|
1602
|
+
let preferred = 'faiss';
|
|
1603
|
+
try {
|
|
1604
|
+
const env = envBuff('VECTOR_BACKEND');
|
|
1605
|
+
if (env === 'json')
|
|
1606
|
+
preferred = 'json';
|
|
1607
|
+
else if (env === 'faiss' || env === 'auto')
|
|
1608
|
+
preferred = 'faiss';
|
|
1609
|
+
else {
|
|
1610
|
+
const cfg = readJSON(resolveBuffConfigPath());
|
|
1611
|
+
if (cfg?.memory?.vectorBackend === 'json')
|
|
1612
|
+
preferred = 'json';
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
catch {
|
|
1616
|
+
// fall through to the 'faiss' default
|
|
1617
|
+
}
|
|
1618
|
+
if (preferred === 'json')
|
|
1619
|
+
return (vectorBackendNameCache = 'json');
|
|
1620
|
+
try {
|
|
1621
|
+
const req = createRequire(import.meta.url);
|
|
1622
|
+
const native = req('@faiss-node/native');
|
|
1623
|
+
const mod = native?.default ?? native;
|
|
1624
|
+
if (mod && typeof mod.FaissIndex === 'function')
|
|
1625
|
+
return (vectorBackendNameCache = 'faiss-native');
|
|
1626
|
+
}
|
|
1627
|
+
catch {
|
|
1628
|
+
// native not installed/built — the pure-JS IVF tier handles it
|
|
1629
|
+
}
|
|
1630
|
+
return (vectorBackendNameCache = 'faiss-ivf');
|
|
1631
|
+
}
|
|
1632
|
+
/**
|
|
1633
|
+
* Per-namespace vector counts, read straight from `vectors*.json`. The default
|
|
1634
|
+
* namespace (`vectors.json`) only holds trajectory/session vectors — facts,
|
|
1635
|
+
* repo chunks and the model registry each live in their own file — so a small
|
|
1636
|
+
* "default" number is corpus size, not a broken index. Sorted largest-first.
|
|
1637
|
+
*/
|
|
1638
|
+
function readVectorNamespaces() {
|
|
1639
|
+
try {
|
|
1640
|
+
const out = [];
|
|
1641
|
+
for (const file of readdirSync(MEMORY_DIR)) {
|
|
1642
|
+
const m = /^vectors(?:-(.+))?\.json$/.exec(file);
|
|
1643
|
+
if (!m)
|
|
1644
|
+
continue;
|
|
1645
|
+
try {
|
|
1646
|
+
const d = readJSON(join(MEMORY_DIR, file));
|
|
1647
|
+
out.push({ name: m[1] || 'default', entries: d?.entries ? Object.keys(d.entries).length : 0 });
|
|
1648
|
+
}
|
|
1649
|
+
catch {
|
|
1650
|
+
// Unreadable index → skip; one bad file must not blank the panel.
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
return out.sort((a, b) => b.entries - a.entries || a.name.localeCompare(b.name));
|
|
1654
|
+
}
|
|
1655
|
+
catch {
|
|
1656
|
+
return [];
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1584
1659
|
function readMemoryData() {
|
|
1585
1660
|
const data = readJSON(join(MEMORY_DIR, 'trajectories.json'));
|
|
1586
1661
|
const trajectories = data?.trajectories
|
|
@@ -1623,6 +1698,13 @@ function readMemoryData() {
|
|
|
1623
1698
|
facts,
|
|
1624
1699
|
recall,
|
|
1625
1700
|
backend: 'local', // memory.backend tier (F1 Mem0 is an optional provider — local is the default)
|
|
1701
|
+
// The vector SEARCH backend — how semantic search is indexed (distinct from
|
|
1702
|
+
// the provider tier above). Shown on the Memory panel so the FAISS question
|
|
1703
|
+
// has an answer in the UI, not just in `nuvira memory backend`.
|
|
1704
|
+
vectorBackend: resolveVectorBackendName(),
|
|
1705
|
+
// Per-namespace entry counts so the panel can show where the vectors
|
|
1706
|
+
// actually are (default = trajectories/sessions, facts, repo, …).
|
|
1707
|
+
vectorNamespaces: readVectorNamespaces(),
|
|
1626
1708
|
};
|
|
1627
1709
|
}
|
|
1628
1710
|
function readHealthData() {
|
|
@@ -2438,6 +2520,29 @@ const PROVIDER_ENV_VARS = {
|
|
|
2438
2520
|
* detected the same way the CLI sees it: env override > vault ref > config
|
|
2439
2521
|
* plaintext > none (local providers are keyless by design).
|
|
2440
2522
|
*/
|
|
2523
|
+
/**
|
|
2524
|
+
* The directory a dashboard chat turn runs in when the user attached nothing.
|
|
2525
|
+
*
|
|
2526
|
+
* `dashboard.cwd` (set on the Admin page or with `nuvira config set
|
|
2527
|
+
* dashboard.cwd`) makes the fallback EXPLICIT. Returning `undefined` means
|
|
2528
|
+
* "unconfigured", which is the case the chat guard refuses to guess in — the two
|
|
2529
|
+
* outcomes are deliberately different, so an operator who wants the legacy
|
|
2530
|
+
* behaviour has to say so by setting a directory rather than by doing nothing.
|
|
2531
|
+
* A configured path that no longer exists is treated as unconfigured, never as a
|
|
2532
|
+
* silent pass-through to `process.cwd()`.
|
|
2533
|
+
*/
|
|
2534
|
+
function resolveDashboardCwd(configManager) {
|
|
2535
|
+
try {
|
|
2536
|
+
const configured = (configManager ?? new ConfigManager()).getAll().dashboard?.cwd;
|
|
2537
|
+
if (typeof configured !== 'string' || !configured.trim())
|
|
2538
|
+
return undefined;
|
|
2539
|
+
const dir = resolve(configured.trim());
|
|
2540
|
+
return existsSync(dir) && statSync(dir).isDirectory() ? dir : undefined;
|
|
2541
|
+
}
|
|
2542
|
+
catch {
|
|
2543
|
+
return undefined;
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2441
2546
|
function summarizeProvider(type, configManager) {
|
|
2442
2547
|
const c = (configManager.getAll().providers || {})[type];
|
|
2443
2548
|
const envVar = PROVIDER_ENV_VARS[type];
|
|
@@ -2475,6 +2580,37 @@ function buildAdminProviderSummary(configManager) {
|
|
|
2475
2580
|
const providers = configManager.getAll().providers || {};
|
|
2476
2581
|
return Object.keys(providers).map((type) => summarizeProvider(type, configManager));
|
|
2477
2582
|
}
|
|
2583
|
+
/**
|
|
2584
|
+
* Build one service row from the catalog. `envVarState` reads the credential
|
|
2585
|
+
* env file first (so a value written by the dashboard shows configured before
|
|
2586
|
+
* any restart), then the process env. Values are only returned in full to a
|
|
2587
|
+
* caller allowed to write credentials; everyone else gets a redaction.
|
|
2588
|
+
*/
|
|
2589
|
+
function buildServiceRow(def, canReadValues) {
|
|
2590
|
+
const envVars = def.envVars.map((v) => {
|
|
2591
|
+
const st = envVarState(v.varName);
|
|
2592
|
+
return {
|
|
2593
|
+
varName: v.varName,
|
|
2594
|
+
prompt: v.prompt,
|
|
2595
|
+
secret: v.secret,
|
|
2596
|
+
set: st.set,
|
|
2597
|
+
value: canReadValues ? st.value : st.set ? redactValue(st.value) : '',
|
|
2598
|
+
};
|
|
2599
|
+
});
|
|
2600
|
+
return {
|
|
2601
|
+
id: def.id,
|
|
2602
|
+
label: def.label,
|
|
2603
|
+
capability: def.capability,
|
|
2604
|
+
icon: def.icon,
|
|
2605
|
+
description: def.description,
|
|
2606
|
+
keyless: def.keyless,
|
|
2607
|
+
free: def.free,
|
|
2608
|
+
configured: def.keyless || envVars.every((v) => v.set),
|
|
2609
|
+
docsUrl: def.docsUrl,
|
|
2610
|
+
sharedNote: def.sharedNote,
|
|
2611
|
+
envVars,
|
|
2612
|
+
};
|
|
2613
|
+
}
|
|
2478
2614
|
/** Run ALL state checks — the dashboard command-runner payload. */
|
|
2479
2615
|
async function runAdminChecks() {
|
|
2480
2616
|
const configManager = new ConfigManager();
|
|
@@ -4380,6 +4516,125 @@ function handleRequest(req, res) {
|
|
|
4380
4516
|
})();
|
|
4381
4517
|
return;
|
|
4382
4518
|
}
|
|
4519
|
+
// ── Response cache, by workspace ────────────────────────────────────────
|
|
4520
|
+
// An answer is a statement about a DIRECTORY, so the cache is grouped and
|
|
4521
|
+
// cleared per workspace. Reads need a SESSION (the listing carries prompt
|
|
4522
|
+
// previews — conversation content, unlike the config the open admin reads
|
|
4523
|
+
// expose); clearing needs routing.operate.
|
|
4524
|
+
if (pathname === '/api/admin/cache' && req.method === 'GET') {
|
|
4525
|
+
void (async () => {
|
|
4526
|
+
const session = adminSessions.validate(bearerToken(req));
|
|
4527
|
+
if (!session) {
|
|
4528
|
+
writeJson(res, 401, { ok: false, error: 'Not authenticated — log in first.' });
|
|
4529
|
+
return;
|
|
4530
|
+
}
|
|
4531
|
+
const { getCache } = await import('../context/cache.js');
|
|
4532
|
+
const workspaces = await getCache().listByWorkspace();
|
|
4533
|
+
writeJson(res, 200, {
|
|
4534
|
+
ok: true,
|
|
4535
|
+
workspaces,
|
|
4536
|
+
total: workspaces.reduce((sum, w) => sum + w.count, 0),
|
|
4537
|
+
});
|
|
4538
|
+
})();
|
|
4539
|
+
return;
|
|
4540
|
+
}
|
|
4541
|
+
if (pathname === '/api/admin/cache/clear' && req.method === 'POST') {
|
|
4542
|
+
void (async () => {
|
|
4543
|
+
const session = adminSessions.validate(bearerToken(req));
|
|
4544
|
+
if (!session) {
|
|
4545
|
+
writeJson(res, 401, { ok: false, error: 'Not authenticated — log in first.' });
|
|
4546
|
+
return;
|
|
4547
|
+
}
|
|
4548
|
+
if (!roleCan(session.role, 'routing.operate')) {
|
|
4549
|
+
writeJson(res, 403, { ok: false, error: `Access denied — role '${session.role}' cannot clear the response cache (requires admin or operator).` });
|
|
4550
|
+
return;
|
|
4551
|
+
}
|
|
4552
|
+
const body = await readJsonBody(req);
|
|
4553
|
+
if (!body) {
|
|
4554
|
+
writeJson(res, 400, { ok: false, error: 'Invalid JSON body.' });
|
|
4555
|
+
return;
|
|
4556
|
+
}
|
|
4557
|
+
// `null` = the answers produced with no workspace. An absent key is a
|
|
4558
|
+
// mistake, not "the null bucket" — say so rather than clearing the wrong set.
|
|
4559
|
+
if (!('scope' in body)) {
|
|
4560
|
+
writeJson(res, 400, { ok: false, error: 'Expected { scope: string | null }.' });
|
|
4561
|
+
return;
|
|
4562
|
+
}
|
|
4563
|
+
const raw = body.scope;
|
|
4564
|
+
if (raw !== null && (typeof raw !== 'string' || !raw.trim())) {
|
|
4565
|
+
writeJson(res, 400, { ok: false, error: 'scope must be a directory path or null.' });
|
|
4566
|
+
return;
|
|
4567
|
+
}
|
|
4568
|
+
const scope = raw === null ? null : resolve(raw);
|
|
4569
|
+
const { getCache } = await import('../context/cache.js');
|
|
4570
|
+
const removed = await getCache().clearWorkspace(scope);
|
|
4571
|
+
writeJson(res, 200, { ok: true, removed, scope });
|
|
4572
|
+
})();
|
|
4573
|
+
return;
|
|
4574
|
+
}
|
|
4575
|
+
// ── Dashboard workspace (`dashboard.cwd`) ───────────────────────────────
|
|
4576
|
+
// The directory an UNATTACHED chat turn runs in. Reads are open (the chat
|
|
4577
|
+
// picker shows it); writes need routing.operate, the same capability that
|
|
4578
|
+
// gates attaching a project. A configured path is validated to exist — a
|
|
4579
|
+
// stale value would silently re-open the accidental-cwd hole this closes.
|
|
4580
|
+
if (pathname === '/api/admin/workspace' && req.method === 'GET') {
|
|
4581
|
+
const configManager = new ConfigManager();
|
|
4582
|
+
const configured = configManager.getAll().dashboard?.cwd ?? null;
|
|
4583
|
+
const effective = resolveDashboardCwd(configManager) ?? null;
|
|
4584
|
+
writeJson(res, 200, {
|
|
4585
|
+
ok: true,
|
|
4586
|
+
configured,
|
|
4587
|
+
effective,
|
|
4588
|
+
// The process cwd is what an unconfigured dashboard falls back to — shown
|
|
4589
|
+
// so an operator can see WHY the guard fires.
|
|
4590
|
+
processCwd: process.cwd(),
|
|
4591
|
+
configuredValid: configured !== null && effective !== null,
|
|
4592
|
+
});
|
|
4593
|
+
return;
|
|
4594
|
+
}
|
|
4595
|
+
if (pathname === '/api/admin/workspace' && req.method === 'PUT') {
|
|
4596
|
+
void (async () => {
|
|
4597
|
+
const session = adminSessions.validate(bearerToken(req));
|
|
4598
|
+
if (!session) {
|
|
4599
|
+
writeJson(res, 401, { ok: false, error: 'Not authenticated — log in first.' });
|
|
4600
|
+
return;
|
|
4601
|
+
}
|
|
4602
|
+
if (!roleCan(session.role, 'routing.operate')) {
|
|
4603
|
+
writeJson(res, 403, { ok: false, error: `Access denied — role '${session.role}' cannot change the dashboard workspace (requires admin or operator).` });
|
|
4604
|
+
return;
|
|
4605
|
+
}
|
|
4606
|
+
const body = await readJsonBody(req);
|
|
4607
|
+
if (!body) {
|
|
4608
|
+
writeJson(res, 400, { ok: false, error: 'Invalid JSON body.' });
|
|
4609
|
+
return;
|
|
4610
|
+
}
|
|
4611
|
+
const configManager = new ConfigManager();
|
|
4612
|
+
// An explicit empty value CLEARS the setting (back to "ask for a folder").
|
|
4613
|
+
if (typeof body.cwd === 'string' && !body.cwd.trim()) {
|
|
4614
|
+
configManager.save({ dashboard: { cwd: undefined } });
|
|
4615
|
+
writeJson(res, 200, { ok: true, configured: null, effective: null, processCwd: process.cwd(), configuredValid: false });
|
|
4616
|
+
return;
|
|
4617
|
+
}
|
|
4618
|
+
if (typeof body.cwd !== 'string') {
|
|
4619
|
+
writeJson(res, 400, { ok: false, error: 'Expected { cwd: string } (empty string clears it).' });
|
|
4620
|
+
return;
|
|
4621
|
+
}
|
|
4622
|
+
const target = resolve(body.cwd.trim());
|
|
4623
|
+
if (!existsSync(target) || !statSync(target).isDirectory()) {
|
|
4624
|
+
writeJson(res, 400, { ok: false, error: `Not a readable directory: ${body.cwd.trim()}` });
|
|
4625
|
+
return;
|
|
4626
|
+
}
|
|
4627
|
+
configManager.save({ dashboard: { cwd: target } });
|
|
4628
|
+
writeJson(res, 200, {
|
|
4629
|
+
ok: true,
|
|
4630
|
+
configured: target,
|
|
4631
|
+
effective: resolveDashboardCwd(configManager) ?? null,
|
|
4632
|
+
processCwd: process.cwd(),
|
|
4633
|
+
configuredValid: true,
|
|
4634
|
+
});
|
|
4635
|
+
})();
|
|
4636
|
+
return;
|
|
4637
|
+
}
|
|
4383
4638
|
// ── Session 36: user-declared daily budget (routing.quota + cost cap) ──
|
|
4384
4639
|
// Same config the CLI writes (`nuvira model quota set`) — dashboard is a
|
|
4385
4640
|
// parallel GUI, never a fork. Reads are open (like checks/catalog); writes
|
|
@@ -5144,6 +5399,138 @@ function handleRequest(req, res) {
|
|
|
5144
5399
|
writeJson(res, 200, { ok: true, removed: keys, status: platformConfigStatus(platform) });
|
|
5145
5400
|
return;
|
|
5146
5401
|
}
|
|
5402
|
+
// ── Service-provider API keys (image / video / search / vision / speech) ──
|
|
5403
|
+
// These are the THIRD-PARTY services the agent calls directly, as opposed to
|
|
5404
|
+
// the LLM providers configured above. Values live in the same 0600 credential
|
|
5405
|
+
// env file (`~/.nuvira/.env`) the platform config writes, read at startup by
|
|
5406
|
+
// loadEnv() and hot-applied to this process — so a save here is honored by
|
|
5407
|
+
// the tool path on the next call, never a cosmetic setting.
|
|
5408
|
+
// GET /api/admin/services — list every service with per-var status. Any
|
|
5409
|
+
// authenticated session may read; values are full only for credential.write.
|
|
5410
|
+
if (pathname === '/api/admin/services' && req.method === 'GET') {
|
|
5411
|
+
const session = adminSessions.validate(bearerToken(req));
|
|
5412
|
+
if (!session) {
|
|
5413
|
+
writeJson(res, 401, { ok: false, error: 'Not authenticated — log in first.' });
|
|
5414
|
+
return;
|
|
5415
|
+
}
|
|
5416
|
+
const canReadValues = roleCan(session.role, 'credential.write');
|
|
5417
|
+
writeJson(res, 200, {
|
|
5418
|
+
ok: true,
|
|
5419
|
+
services: SERVICE_CATALOG.map((d) => buildServiceRow(d, canReadValues)),
|
|
5420
|
+
});
|
|
5421
|
+
return;
|
|
5422
|
+
}
|
|
5423
|
+
const serviceMatch = /^\/api\/admin\/services\/([a-z0-9-]+)$/.exec(pathname);
|
|
5424
|
+
// PUT /api/admin/services/:id — write the service's env values (blank = remove).
|
|
5425
|
+
if (serviceMatch && req.method === 'PUT') {
|
|
5426
|
+
void (async () => {
|
|
5427
|
+
const session = adminSessions.validate(bearerToken(req));
|
|
5428
|
+
if (!session) {
|
|
5429
|
+
writeJson(res, 401, { ok: false, error: 'Not authenticated — log in first.' });
|
|
5430
|
+
return;
|
|
5431
|
+
}
|
|
5432
|
+
if (!roleCan(session.role, 'credential.write')) {
|
|
5433
|
+
writeJson(res, 403, { ok: false, error: `Access denied — role '${session.role}' cannot configure service keys (requires admin).` });
|
|
5434
|
+
return;
|
|
5435
|
+
}
|
|
5436
|
+
const def = getServiceDefinition(serviceMatch[1]);
|
|
5437
|
+
if (!def) {
|
|
5438
|
+
writeJson(res, 404, { ok: false, error: `Unknown service '${serviceMatch[1]}'.` });
|
|
5439
|
+
return;
|
|
5440
|
+
}
|
|
5441
|
+
if (def.envVars.length === 0) {
|
|
5442
|
+
writeJson(res, 400, { ok: false, error: `${def.label} is keyless — there is nothing to configure.` });
|
|
5443
|
+
return;
|
|
5444
|
+
}
|
|
5445
|
+
const body = await readJsonBody(req);
|
|
5446
|
+
const values = (body?.values ?? {});
|
|
5447
|
+
const allowed = new Set(def.envVars.map((v) => v.varName));
|
|
5448
|
+
const updates = {};
|
|
5449
|
+
const removes = [];
|
|
5450
|
+
for (const [key, value] of Object.entries(values)) {
|
|
5451
|
+
if (!allowed.has(key)) {
|
|
5452
|
+
writeJson(res, 400, { ok: false, error: `Unknown env var '${key}' for ${def.label}.` });
|
|
5453
|
+
return;
|
|
5454
|
+
}
|
|
5455
|
+
if (typeof value !== 'string')
|
|
5456
|
+
continue;
|
|
5457
|
+
const trimmed = value.trim();
|
|
5458
|
+
if (trimmed === '')
|
|
5459
|
+
removes.push(key);
|
|
5460
|
+
else
|
|
5461
|
+
updates[key] = trimmed;
|
|
5462
|
+
}
|
|
5463
|
+
if (Object.keys(updates).length === 0 && removes.length === 0) {
|
|
5464
|
+
writeJson(res, 400, { ok: false, error: 'Provide at least one value.' });
|
|
5465
|
+
return;
|
|
5466
|
+
}
|
|
5467
|
+
writeEnvFile(updates, removes);
|
|
5468
|
+
applyEnvToProcess(updates, removes);
|
|
5469
|
+
writeJson(res, 200, {
|
|
5470
|
+
ok: true,
|
|
5471
|
+
wrote: Object.keys(updates),
|
|
5472
|
+
removed: removes,
|
|
5473
|
+
service: buildServiceRow(def, true),
|
|
5474
|
+
});
|
|
5475
|
+
})();
|
|
5476
|
+
return;
|
|
5477
|
+
}
|
|
5478
|
+
// DELETE /api/admin/services/:id — remove every env var of the service.
|
|
5479
|
+
if (serviceMatch && req.method === 'DELETE') {
|
|
5480
|
+
const session = adminSessions.validate(bearerToken(req));
|
|
5481
|
+
if (!session) {
|
|
5482
|
+
writeJson(res, 401, { ok: false, error: 'Not authenticated — log in first.' });
|
|
5483
|
+
return;
|
|
5484
|
+
}
|
|
5485
|
+
if (!roleCan(session.role, 'credential.write')) {
|
|
5486
|
+
writeJson(res, 403, { ok: false, error: `Access denied — role '${session.role}' cannot configure service keys (requires admin).` });
|
|
5487
|
+
return;
|
|
5488
|
+
}
|
|
5489
|
+
const def = getServiceDefinition(serviceMatch[1]);
|
|
5490
|
+
if (!def) {
|
|
5491
|
+
writeJson(res, 404, { ok: false, error: `Unknown service '${serviceMatch[1]}'.` });
|
|
5492
|
+
return;
|
|
5493
|
+
}
|
|
5494
|
+
const keys = def.envVars.map((v) => v.varName);
|
|
5495
|
+
if (keys.length > 0) {
|
|
5496
|
+
writeEnvFile({}, keys);
|
|
5497
|
+
applyEnvToProcess({}, keys);
|
|
5498
|
+
}
|
|
5499
|
+
writeJson(res, 200, { ok: true, removed: keys, service: buildServiceRow(def, true) });
|
|
5500
|
+
return;
|
|
5501
|
+
}
|
|
5502
|
+
// POST /api/admin/services/:id/test — probe the configured backend.
|
|
5503
|
+
const serviceTestMatch = /^\/api\/admin\/services\/([a-z0-9-]+)\/test$/.exec(pathname);
|
|
5504
|
+
if (serviceTestMatch && req.method === 'POST') {
|
|
5505
|
+
void (async () => {
|
|
5506
|
+
const session = adminSessions.validate(bearerToken(req));
|
|
5507
|
+
if (!session) {
|
|
5508
|
+
writeJson(res, 401, { ok: false, error: 'Not authenticated — log in first.' });
|
|
5509
|
+
return;
|
|
5510
|
+
}
|
|
5511
|
+
if (!roleCan(session.role, 'credential.write')) {
|
|
5512
|
+
writeJson(res, 403, { ok: false, error: `Access denied — role '${session.role}' cannot test service keys (requires admin).` });
|
|
5513
|
+
return;
|
|
5514
|
+
}
|
|
5515
|
+
const def = getServiceDefinition(serviceTestMatch[1]);
|
|
5516
|
+
if (!def) {
|
|
5517
|
+
writeJson(res, 404, { ok: false, error: `Unknown service '${serviceTestMatch[1]}'.` });
|
|
5518
|
+
return;
|
|
5519
|
+
}
|
|
5520
|
+
// A probe is only meaningful once the required vars are present.
|
|
5521
|
+
const missing = def.envVars.filter((v) => def.requiresAllVars && !envVarState(v.varName).set);
|
|
5522
|
+
if (missing.length > 0) {
|
|
5523
|
+
writeJson(res, 200, { ok: false, error: `Not configured yet — set ${missing.map((m) => m.varName).join(', ')} first.` });
|
|
5524
|
+
return;
|
|
5525
|
+
}
|
|
5526
|
+
const values = {};
|
|
5527
|
+
for (const v of def.envVars)
|
|
5528
|
+
values[v.varName] = envVarState(v.varName).value;
|
|
5529
|
+
const result = await probeService(def, values);
|
|
5530
|
+
writeJson(res, 200, { ok: result.ok, detail: result.detail });
|
|
5531
|
+
})();
|
|
5532
|
+
return;
|
|
5533
|
+
}
|
|
5147
5534
|
// ── Bedrock onboarding endpoints ────────────────────────────────────────
|
|
5148
5535
|
// GET /api/bedrock/status — current Bedrock config status
|
|
5149
5536
|
if (pathname === '/api/bedrock/status' && req.method === 'GET') {
|
|
@@ -5273,10 +5660,19 @@ function handleRequest(req, res) {
|
|
|
5273
5660
|
writeJson(res, 403, { ok: false, error: `Access denied — role '${session.role}' cannot view projects.` });
|
|
5274
5661
|
return;
|
|
5275
5662
|
}
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
|
|
5663
|
+
// The configured default workspace is surfaced as `kind: 'cwd'` so the
|
|
5664
|
+
// picker can show where an unattached turn would land — the directory that
|
|
5665
|
+
// used to be invisible until the agent reported on the wrong folder.
|
|
5666
|
+
const configuredCwd = resolveDashboardCwd();
|
|
5667
|
+
const projects = [
|
|
5668
|
+
...(configuredCwd
|
|
5669
|
+
? [{ path: configuredCwd, name: basename(configuredCwd) || configuredCwd, kind: 'cwd' }]
|
|
5670
|
+
: []),
|
|
5671
|
+
...[...recentProjects]
|
|
5672
|
+
.map((p) => ({ path: p, name: basename(p) || p, kind: 'recent' }))
|
|
5673
|
+
.filter((p) => existsSync(p.path) && statSync(p.path).isDirectory()),
|
|
5674
|
+
];
|
|
5675
|
+
writeJson(res, 200, { ok: true, projects, configuredCwd: configuredCwd ?? null });
|
|
5280
5676
|
return;
|
|
5281
5677
|
}
|
|
5282
5678
|
// POST /api/projects/attach — validate a directory and build (or reuse) its
|
|
@@ -5713,6 +6109,100 @@ function handleRequest(req, res) {
|
|
|
5713
6109
|
writeJson(res, 200, { ok: true, session: { id, ...rec } });
|
|
5714
6110
|
return;
|
|
5715
6111
|
}
|
|
6112
|
+
// GET /api/continuity — the stored session snapshots + the semantic recall
|
|
6113
|
+
// index, and the EFFECTIVE on/off state of both continuity switches. Same auth
|
|
6114
|
+
// as the chat session sidebar: this is local memory the chat surface owns.
|
|
6115
|
+
if (pathname === '/api/continuity' && req.method === 'GET') {
|
|
6116
|
+
const session = adminSessions.validate(bearerToken(req));
|
|
6117
|
+
if (!session) {
|
|
6118
|
+
writeJson(res, 401, { ok: false, error: 'Not authenticated — log in first.' });
|
|
6119
|
+
return;
|
|
6120
|
+
}
|
|
6121
|
+
if (!roleCan(session.role, 'routing.operate')) {
|
|
6122
|
+
writeJson(res, 403, { ok: false, error: `Access denied — role '${session.role}' cannot view continuity data.` });
|
|
6123
|
+
return;
|
|
6124
|
+
}
|
|
6125
|
+
void (async () => {
|
|
6126
|
+
const { listSessionSnapshots, resolveSessionStore } = await import('../learning/session-store.js');
|
|
6127
|
+
const { listRecallEntries, resolveSessionRecall } = await import('../learning/session-recall.js');
|
|
6128
|
+
const cm = new ConfigManager();
|
|
6129
|
+
const sessions = listSessionSnapshots().map((s) => ({
|
|
6130
|
+
id: s.id,
|
|
6131
|
+
goal: s.goal,
|
|
6132
|
+
cwd: s.cwd,
|
|
6133
|
+
savedAt: s.savedAt,
|
|
6134
|
+
open: s.open,
|
|
6135
|
+
steps: s.accumulators.steps,
|
|
6136
|
+
successfulTools: s.accumulators.successfulTools.length,
|
|
6137
|
+
mutatedPaths: s.accumulators.mutatedPaths,
|
|
6138
|
+
messages: s.messages.length,
|
|
6139
|
+
}));
|
|
6140
|
+
const recall = listRecallEntries().map((e) => ({
|
|
6141
|
+
id: e.id,
|
|
6142
|
+
projectPath: e.projectPath,
|
|
6143
|
+
goal: e.goal,
|
|
6144
|
+
outcome: e.outcome,
|
|
6145
|
+
savedAt: e.savedAt,
|
|
6146
|
+
}));
|
|
6147
|
+
writeJson(res, 200, {
|
|
6148
|
+
ok: true,
|
|
6149
|
+
sessions,
|
|
6150
|
+
recall,
|
|
6151
|
+
toggles: {
|
|
6152
|
+
sessionStore: resolveSessionStore({ configManager: cm }),
|
|
6153
|
+
sessionRecall: resolveSessionRecall({ configManager: cm }),
|
|
6154
|
+
},
|
|
6155
|
+
});
|
|
6156
|
+
})();
|
|
6157
|
+
return;
|
|
6158
|
+
}
|
|
6159
|
+
// POST /api/continuity/clear — forget stored continuity data. Body:
|
|
6160
|
+
// { target: 'sessions' | 'recall' } — clear all of one kind
|
|
6161
|
+
// { target: 'session' | 'recall-entry', id } — clear one entry
|
|
6162
|
+
if (pathname === '/api/continuity/clear' && req.method === 'POST') {
|
|
6163
|
+
const session = adminSessions.validate(bearerToken(req));
|
|
6164
|
+
if (!session) {
|
|
6165
|
+
writeJson(res, 401, { ok: false, error: 'Not authenticated — log in first.' });
|
|
6166
|
+
return;
|
|
6167
|
+
}
|
|
6168
|
+
if (!roleCan(session.role, 'routing.operate')) {
|
|
6169
|
+
writeJson(res, 403, { ok: false, error: `Access denied — role '${session.role}' cannot change continuity data.` });
|
|
6170
|
+
return;
|
|
6171
|
+
}
|
|
6172
|
+
void (async () => {
|
|
6173
|
+
const body = await readJsonBody(req);
|
|
6174
|
+
const target = typeof body?.target === 'string' ? body.target : '';
|
|
6175
|
+
const id = typeof body?.id === 'string' ? body.id : undefined;
|
|
6176
|
+
let removed = 0;
|
|
6177
|
+
if (target === 'sessions') {
|
|
6178
|
+
const { listSessionSnapshots, clearSession } = await import('../learning/session-store.js');
|
|
6179
|
+
for (const s of listSessionSnapshots())
|
|
6180
|
+
if (clearSession(s.id))
|
|
6181
|
+
removed += 1;
|
|
6182
|
+
}
|
|
6183
|
+
else if (target === 'recall') {
|
|
6184
|
+
const { clearSessionRecallIndex } = await import('../learning/session-recall.js');
|
|
6185
|
+
if (clearSessionRecallIndex())
|
|
6186
|
+
removed = 1;
|
|
6187
|
+
}
|
|
6188
|
+
else if (target === 'session' && id) {
|
|
6189
|
+
const { clearSession } = await import('../learning/session-store.js');
|
|
6190
|
+
if (clearSession(id))
|
|
6191
|
+
removed = 1;
|
|
6192
|
+
}
|
|
6193
|
+
else if (target === 'recall-entry' && id) {
|
|
6194
|
+
const { removeRecallEntry } = await import('../learning/session-recall.js');
|
|
6195
|
+
if (removeRecallEntry(id))
|
|
6196
|
+
removed = 1;
|
|
6197
|
+
}
|
|
6198
|
+
else {
|
|
6199
|
+
writeJson(res, 400, { ok: false, error: "Expected { target: 'sessions'|'recall'|'session'|'recall-entry', id? }." });
|
|
6200
|
+
return;
|
|
6201
|
+
}
|
|
6202
|
+
writeJson(res, 200, { ok: true, removed });
|
|
6203
|
+
})();
|
|
6204
|
+
return;
|
|
6205
|
+
}
|
|
5716
6206
|
// POST /api/chat/resolve — resolve a plain-English ask into the CLI
|
|
5717
6207
|
// command(s) the intent router would run (the dashboard twin of
|
|
5718
6208
|
// `nuvira intent resolve`). The Chat UI calls this BEFORE sending to the
|
|
@@ -5800,12 +6290,48 @@ function handleRequest(req, res) {
|
|
|
5800
6290
|
// describing the codebase. Cache misses rebuild automatically.
|
|
5801
6291
|
let projectContext;
|
|
5802
6292
|
let projectPath;
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
6293
|
+
const attachedPath = typeof body?.projectPath === 'string' && body.projectPath.trim()
|
|
6294
|
+
? body.projectPath.trim()
|
|
6295
|
+
: undefined;
|
|
6296
|
+
/**
|
|
6297
|
+
* The workspace for this turn, in priority order: the folder the user
|
|
6298
|
+
* ATTACHED, then the operator-configured `dashboard.cwd`, and otherwise
|
|
6299
|
+
* NOTHING.
|
|
6300
|
+
*
|
|
6301
|
+
* "Nothing" is the important case. The turn used to fall back to the
|
|
6302
|
+
* dashboard server's own working directory, so a service started from the
|
|
6303
|
+
* home folder answered "assess this project" by listing `~` and describing
|
|
6304
|
+
* whatever unrelated checkout it found there — a confident answer about a
|
|
6305
|
+
* project nobody mentioned (reported as "it went to kuttaaddon"). The
|
|
6306
|
+
* guard below turns that into a request for a folder.
|
|
6307
|
+
*/
|
|
6308
|
+
const configuredCwd = resolveDashboardCwd();
|
|
6309
|
+
const effectiveProjectPath = attachedPath ?? configuredCwd;
|
|
6310
|
+
if (effectiveProjectPath) {
|
|
6311
|
+
projectPath = effectiveProjectPath;
|
|
6312
|
+
const bundle = getProjectBundle(effectiveProjectPath);
|
|
5806
6313
|
if (bundle)
|
|
5807
6314
|
projectContext = formatProjectText(bundle);
|
|
5808
6315
|
}
|
|
6316
|
+
// No workspace and no configured default: an ask that names a project, or
|
|
6317
|
+
// that produces/consumes files, gets a folder request instead of an answer
|
|
6318
|
+
// about the wrong tree. General chat is unaffected.
|
|
6319
|
+
if (!effectiveProjectPath) {
|
|
6320
|
+
const { needsProjectAttachment, projectAttachmentPrompt, projectAttachmentFollowups } = await import('./workspace-guard.js');
|
|
6321
|
+
if (needsProjectAttachment(message)) {
|
|
6322
|
+
writeJson(res, 200, {
|
|
6323
|
+
ok: true,
|
|
6324
|
+
sessionId,
|
|
6325
|
+
content: projectAttachmentPrompt(),
|
|
6326
|
+
followups: projectAttachmentFollowups(),
|
|
6327
|
+
provider: null,
|
|
6328
|
+
model: null,
|
|
6329
|
+
generationFailed: false,
|
|
6330
|
+
needsProject: true,
|
|
6331
|
+
});
|
|
6332
|
+
return;
|
|
6333
|
+
}
|
|
6334
|
+
}
|
|
5809
6335
|
// P4 — the dashboard's Cancel button aborts the POST fetch: the server
|
|
5810
6336
|
// sees the request close and cancels the in-flight turn (the engine
|
|
5811
6337
|
// aborts the provider request — quota is not spent on a cancelled turn).
|