agent-nuvira 3.3.3 → 3.3.5
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 +13 -5
- package/dist/agent-sdk/src/agent.d.ts +2 -0
- package/dist/agent-sdk/src/agent.d.ts.map +1 -1
- package/dist/agent-sdk/src/define.d.ts +64 -0
- package/dist/agent-sdk/src/define.d.ts.map +1 -0
- package/dist/agent-sdk/src/define.js +76 -0
- package/dist/agent-sdk/src/define.js.map +1 -0
- package/dist/agent-sdk/src/index.d.ts +9 -0
- package/dist/agent-sdk/src/index.d.ts.map +1 -1
- package/dist/agent-sdk/src/index.js +9 -0
- package/dist/agent-sdk/src/index.js.map +1 -1
- package/dist/agent-sdk/src/scaffold.d.ts +11 -0
- package/dist/agent-sdk/src/scaffold.d.ts.map +1 -1
- package/dist/agent-sdk/src/scaffold.js +16 -6
- package/dist/agent-sdk/src/scaffold.js.map +1 -1
- package/dist/agents/long-form-plan.d.ts.map +1 -1
- package/dist/agents/long-form-plan.js +2 -1
- package/dist/agents/long-form-plan.js.map +1 -1
- package/dist/agents/orchestrator.d.ts.map +1 -1
- package/dist/agents/orchestrator.js +5 -4
- package/dist/agents/orchestrator.js.map +1 -1
- package/dist/cli/agent.d.ts +2 -2
- package/dist/cli/agent.js +10 -10
- package/dist/cli/chat.d.ts +94 -0
- package/dist/cli/chat.d.ts.map +1 -1
- package/dist/cli/chat.js +354 -20
- package/dist/cli/chat.js.map +1 -1
- package/dist/cli/cli-program.d.ts.map +1 -1
- package/dist/cli/cli-program.js +5 -0
- package/dist/cli/cli-program.js.map +1 -1
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +9 -1
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +3 -2
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/edit.js +2 -2
- package/dist/cli/eval.d.ts +17 -0
- package/dist/cli/eval.d.ts.map +1 -1
- package/dist/cli/eval.js +105 -2
- package/dist/cli/eval.js.map +1 -1
- package/dist/cli/execute.d.ts +16 -1
- package/dist/cli/execute.d.ts.map +1 -1
- package/dist/cli/execute.js +192 -22
- package/dist/cli/execute.js.map +1 -1
- package/dist/cli/loop-executor.d.ts +55 -0
- package/dist/cli/loop-executor.d.ts.map +1 -1
- package/dist/cli/loop-executor.js +212 -13
- package/dist/cli/loop-executor.js.map +1 -1
- package/dist/cli/model.d.ts.map +1 -1
- package/dist/cli/model.js +9 -8
- package/dist/cli/model.js.map +1 -1
- package/dist/cli/models.d.ts +1 -1
- package/dist/cli/models.js +4 -4
- package/dist/cli/parity.d.ts +85 -0
- package/dist/cli/parity.d.ts.map +1 -0
- package/dist/cli/parity.js +506 -0
- package/dist/cli/parity.js.map +1 -0
- package/dist/cli/plan.d.ts.map +1 -1
- package/dist/cli/plan.js +2 -1
- package/dist/cli/plan.js.map +1 -1
- package/dist/cli/retrieval.d.ts.map +1 -1
- package/dist/cli/retrieval.js +5 -4
- package/dist/cli/retrieval.js.map +1 -1
- package/dist/cli/sdk.js +4 -4
- package/dist/cli/sdk.js.map +1 -1
- package/dist/cli/trace.d.ts.map +1 -1
- package/dist/cli/trace.js +2 -1
- package/dist/cli/trace.js.map +1 -1
- package/dist/cli/workflow.js +2 -2
- package/dist/cli/workflow.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/config/types.d.ts +44 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/findings/verdicts.d.ts +241 -0
- package/dist/findings/verdicts.d.ts.map +1 -0
- package/dist/findings/verdicts.js +284 -0
- package/dist/findings/verdicts.js.map +1 -0
- package/dist/gateway/adapters.d.ts +65 -0
- package/dist/gateway/adapters.d.ts.map +1 -1
- package/dist/gateway/adapters.js +216 -10
- package/dist/gateway/adapters.js.map +1 -1
- package/dist/gateway/channel-directory.d.ts +31 -0
- package/dist/gateway/channel-directory.d.ts.map +1 -1
- package/dist/gateway/channel-directory.js +40 -0
- package/dist/gateway/channel-directory.js.map +1 -1
- package/dist/gateway/gateway-log.d.ts +1 -1
- package/dist/gateway/gateway-log.d.ts.map +1 -1
- package/dist/gateway/gateway-log.js.map +1 -1
- package/dist/gateway/hooks.d.ts +87 -19
- package/dist/gateway/hooks.d.ts.map +1 -1
- package/dist/gateway/hooks.js +62 -23
- package/dist/gateway/hooks.js.map +1 -1
- package/dist/gateway/inbound-media.d.ts +147 -0
- package/dist/gateway/inbound-media.d.ts.map +1 -0
- package/dist/gateway/inbound-media.js +317 -0
- package/dist/gateway/inbound-media.js.map +1 -0
- package/dist/gateway/inbox.d.ts +8 -1
- package/dist/gateway/inbox.d.ts.map +1 -1
- package/dist/gateway/inbox.js.map +1 -1
- package/dist/gateway/platform-config.d.ts +14 -0
- package/dist/gateway/platform-config.d.ts.map +1 -1
- package/dist/gateway/platform-config.js +26 -8
- package/dist/gateway/platform-config.js.map +1 -1
- package/dist/gateway/realtime.d.ts +114 -0
- package/dist/gateway/realtime.d.ts.map +1 -0
- package/dist/gateway/realtime.js +402 -0
- package/dist/gateway/realtime.js.map +1 -0
- package/dist/gateway/registry.d.ts +31 -0
- package/dist/gateway/registry.d.ts.map +1 -1
- package/dist/gateway/registry.js +224 -4
- package/dist/gateway/registry.js.map +1 -1
- package/dist/gateway/whatsapp/baileys-bridge.d.ts +11 -1
- package/dist/gateway/whatsapp/baileys-bridge.d.ts.map +1 -1
- package/dist/gateway/whatsapp/baileys-bridge.js +123 -4
- package/dist/gateway/whatsapp/baileys-bridge.js.map +1 -1
- package/dist/gateway/whatsapp/bridge.d.ts +6 -2
- package/dist/gateway/whatsapp/bridge.d.ts.map +1 -1
- package/dist/gateway/whatsapp/bridge.js.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/inference/factory.d.ts +14 -0
- package/dist/inference/factory.d.ts.map +1 -1
- package/dist/inference/factory.js +17 -0
- package/dist/inference/factory.js.map +1 -1
- package/dist/inference/groq-adapter.d.ts +2 -0
- package/dist/inference/groq-adapter.d.ts.map +1 -1
- package/dist/inference/groq-adapter.js +16 -6
- package/dist/inference/groq-adapter.js.map +1 -1
- package/dist/inference/tools.d.ts.map +1 -1
- package/dist/inference/tools.js +29 -0
- package/dist/inference/tools.js.map +1 -1
- package/dist/learning/benchmark.d.ts.map +1 -1
- package/dist/learning/benchmark.js +3 -2
- package/dist/learning/benchmark.js.map +1 -1
- package/dist/learning/continuation.d.ts.map +1 -1
- package/dist/learning/continuation.js +2 -1
- package/dist/learning/continuation.js.map +1 -1
- package/dist/learning/cost-tracker.d.ts.map +1 -1
- package/dist/learning/cost-tracker.js +2 -1
- package/dist/learning/cost-tracker.js.map +1 -1
- package/dist/learning/deferred-task.d.ts.map +1 -1
- package/dist/learning/deferred-task.js +13 -4
- package/dist/learning/deferred-task.js.map +1 -1
- package/dist/learning/eval-framework.d.ts.map +1 -1
- package/dist/learning/eval-framework.js +2 -1
- package/dist/learning/eval-framework.js.map +1 -1
- package/dist/learning/long-form.d.ts.map +1 -1
- package/dist/learning/long-form.js +2 -1
- package/dist/learning/long-form.js.map +1 -1
- 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 +26 -1
- 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/learning/reasoning-cache.d.ts.map +1 -1
- package/dist/learning/reasoning-cache.js +2 -1
- package/dist/learning/reasoning-cache.js.map +1 -1
- package/dist/learning/reasoning-trace.d.ts +37 -1
- package/dist/learning/reasoning-trace.d.ts.map +1 -1
- package/dist/learning/reasoning-trace.js +66 -0
- package/dist/learning/reasoning-trace.js.map +1 -1
- package/dist/learning/resilient-call.d.ts.map +1 -1
- package/dist/learning/resilient-call.js +2 -1
- package/dist/learning/resilient-call.js.map +1 -1
- package/dist/learning/retrieval.d.ts.map +1 -1
- package/dist/learning/retrieval.js +2 -1
- package/dist/learning/retrieval.js.map +1 -1
- package/dist/learning/seeded-benchmark.d.ts +160 -0
- package/dist/learning/seeded-benchmark.d.ts.map +1 -0
- package/dist/learning/seeded-benchmark.js +321 -0
- package/dist/learning/seeded-benchmark.js.map +1 -0
- package/dist/learning/seeded-bugs.d.ts +142 -0
- package/dist/learning/seeded-bugs.d.ts.map +1 -0
- package/dist/learning/seeded-bugs.js +535 -0
- package/dist/learning/seeded-bugs.js.map +1 -0
- package/dist/learning/step-checkpoint.d.ts +127 -0
- package/dist/learning/step-checkpoint.d.ts.map +1 -0
- package/dist/learning/step-checkpoint.js +244 -0
- package/dist/learning/step-checkpoint.js.map +1 -0
- package/dist/nlu/intent-confirm.d.ts +23 -1
- package/dist/nlu/intent-confirm.d.ts.map +1 -1
- package/dist/nlu/intent-confirm.js +85 -2
- package/dist/nlu/intent-confirm.js.map +1 -1
- package/dist/nlu/learnings.d.ts +7 -0
- package/dist/nlu/learnings.d.ts.map +1 -1
- package/dist/nlu/learnings.js +7 -0
- package/dist/nlu/learnings.js.map +1 -1
- package/dist/observability/debug-log.d.ts +250 -0
- package/dist/observability/debug-log.d.ts.map +1 -0
- package/dist/observability/debug-log.js +500 -0
- package/dist/observability/debug-log.js.map +1 -0
- package/dist/observability/event-bus.d.ts.map +1 -1
- package/dist/observability/event-bus.js +4 -1
- package/dist/observability/event-bus.js.map +1 -1
- package/dist/observability/otel.d.ts +278 -0
- package/dist/observability/otel.d.ts.map +1 -0
- package/dist/observability/otel.js +590 -0
- package/dist/observability/otel.js.map +1 -0
- package/dist/parity/drivers.d.ts +99 -0
- package/dist/parity/drivers.d.ts.map +1 -0
- package/dist/parity/drivers.js +1362 -0
- package/dist/parity/drivers.js.map +1 -0
- package/dist/parity/graph.d.ts +73 -0
- package/dist/parity/graph.d.ts.map +1 -0
- package/dist/parity/graph.js +162 -0
- package/dist/parity/graph.js.map +1 -0
- package/dist/parity/matrix.d.ts +105 -0
- package/dist/parity/matrix.d.ts.map +1 -0
- package/dist/parity/matrix.js +352 -0
- package/dist/parity/matrix.js.map +1 -0
- package/dist/parity/observation.d.ts +444 -0
- package/dist/parity/observation.d.ts.map +1 -0
- package/dist/parity/observation.js +333 -0
- package/dist/parity/observation.js.map +1 -0
- package/dist/parity/scenarios.d.ts +229 -0
- package/dist/parity/scenarios.d.ts.map +1 -0
- package/dist/parity/scenarios.js +175 -0
- package/dist/parity/scenarios.js.map +1 -0
- package/dist/parity/surfaces.d.ts +122 -0
- package/dist/parity/surfaces.d.ts.map +1 -0
- package/dist/parity/surfaces.js +190 -0
- package/dist/parity/surfaces.js.map +1 -0
- package/dist/runtime/fault-injection.d.ts +173 -0
- package/dist/runtime/fault-injection.d.ts.map +1 -0
- package/dist/runtime/fault-injection.js +281 -0
- package/dist/runtime/fault-injection.js.map +1 -0
- package/dist/tools/child-agent-entry.d.ts +23 -0
- package/dist/tools/child-agent-entry.d.ts.map +1 -0
- package/dist/tools/child-agent-entry.js +129 -0
- package/dist/tools/child-agent-entry.js.map +1 -0
- package/dist/tools/child-agent-runtime.d.ts +124 -0
- package/dist/tools/child-agent-runtime.d.ts.map +1 -0
- package/dist/tools/child-agent-runtime.js +704 -0
- package/dist/tools/child-agent-runtime.js.map +1 -0
- package/dist/tools/coding-tools.d.ts.map +1 -1
- package/dist/tools/coding-tools.js +82 -10
- package/dist/tools/coding-tools.js.map +1 -1
- package/dist/tools/delegation-system.d.ts +31 -0
- package/dist/tools/delegation-system.d.ts.map +1 -1
- package/dist/tools/delegation-system.js +70 -9
- package/dist/tools/delegation-system.js.map +1 -1
- package/dist/tools/extract/docx.d.ts +27 -0
- package/dist/tools/extract/docx.d.ts.map +1 -0
- package/dist/tools/extract/docx.js +48 -0
- package/dist/tools/extract/docx.js.map +1 -0
- package/dist/tools/extract/html-text.d.ts +27 -0
- package/dist/tools/extract/html-text.d.ts.map +1 -0
- package/dist/tools/extract/html-text.js +86 -0
- package/dist/tools/extract/html-text.js.map +1 -0
- package/dist/tools/extract/pdf-ocr.d.ts +58 -0
- package/dist/tools/extract/pdf-ocr.d.ts.map +1 -0
- package/dist/tools/extract/pdf-ocr.js +116 -0
- package/dist/tools/extract/pdf-ocr.js.map +1 -0
- package/dist/tools/extract/pdf.d.ts +65 -0
- package/dist/tools/extract/pdf.d.ts.map +1 -0
- package/dist/tools/extract/pdf.js +197 -0
- package/dist/tools/extract/pdf.js.map +1 -0
- package/dist/tools/extract/pptx.d.ts +32 -0
- package/dist/tools/extract/pptx.d.ts.map +1 -0
- package/dist/tools/extract/pptx.js +77 -0
- package/dist/tools/extract/pptx.js.map +1 -0
- package/dist/tools/extract/xlsx.d.ts +47 -0
- package/dist/tools/extract/xlsx.d.ts.map +1 -0
- package/dist/tools/extract/xlsx.js +111 -0
- package/dist/tools/extract/xlsx.js.map +1 -0
- package/dist/tools/finding-tool.d.ts +76 -0
- package/dist/tools/finding-tool.d.ts.map +1 -0
- package/dist/tools/finding-tool.js +125 -0
- package/dist/tools/finding-tool.js.map +1 -0
- package/dist/tools/messaging-tools.d.ts +41 -11
- package/dist/tools/messaging-tools.d.ts.map +1 -1
- package/dist/tools/messaging-tools.js +104 -55
- package/dist/tools/messaging-tools.js.map +1 -1
- package/dist/tools/neutts-synth.d.ts +27 -3
- package/dist/tools/neutts-synth.d.ts.map +1 -1
- package/dist/tools/neutts-synth.js +57 -13
- package/dist/tools/neutts-synth.js.map +1 -1
- package/dist/tools/pipeline-tool.d.ts +43 -1
- package/dist/tools/pipeline-tool.d.ts.map +1 -1
- package/dist/tools/pipeline-tool.js +13 -2
- package/dist/tools/pipeline-tool.js.map +1 -1
- package/dist/tools/read-extract.d.ts +116 -45
- package/dist/tools/read-extract.d.ts.map +1 -1
- package/dist/tools/read-extract.js +494 -158
- package/dist/tools/read-extract.js.map +1 -1
- package/dist/tools/registry.d.ts +2 -2
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +152 -23
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/subagent-refusal.d.ts +15 -0
- package/dist/tools/subagent-refusal.d.ts.map +1 -0
- package/dist/tools/subagent-refusal.js +18 -0
- package/dist/tools/subagent-refusal.js.map +1 -0
- package/dist/tools/subagent-spawner.d.ts +122 -0
- package/dist/tools/subagent-spawner.d.ts.map +1 -1
- package/dist/tools/subagent-spawner.js +249 -28
- package/dist/tools/subagent-spawner.js.map +1 -1
- package/dist/tools/tool-hooks.d.ts +177 -0
- package/dist/tools/tool-hooks.d.ts.map +1 -0
- package/dist/tools/tool-hooks.js +427 -0
- package/dist/tools/tool-hooks.js.map +1 -0
- package/dist/tools/tool-loop.d.ts +82 -0
- package/dist/tools/tool-loop.d.ts.map +1 -1
- package/dist/tools/tool-loop.js +167 -9
- package/dist/tools/tool-loop.js.map +1 -1
- package/dist/tools/tool-refusal.d.ts +68 -0
- package/dist/tools/tool-refusal.d.ts.map +1 -0
- package/dist/tools/tool-refusal.js +78 -0
- package/dist/tools/tool-refusal.js.map +1 -0
- package/dist/tools/toolsets.d.ts +8 -0
- package/dist/tools/toolsets.d.ts.map +1 -1
- package/dist/tools/toolsets.js +12 -2
- package/dist/tools/toolsets.js.map +1 -1
- package/dist/tools/vision-tools.d.ts +88 -83
- package/dist/tools/vision-tools.d.ts.map +1 -1
- package/dist/tools/vision-tools.js +134 -103
- package/dist/tools/vision-tools.js.map +1 -1
- package/dist/tools/worktree.d.ts +210 -0
- package/dist/tools/worktree.d.ts.map +1 -0
- package/dist/tools/worktree.js +374 -0
- package/dist/tools/worktree.js.map +1 -0
- package/dist/utils/format.d.ts +3 -0
- package/dist/utils/format.d.ts.map +1 -0
- package/dist/utils/format.js +32 -0
- package/dist/utils/format.js.map +1 -0
- package/dist/web-dashboard/attachment-extract.d.ts +64 -0
- package/dist/web-dashboard/attachment-extract.d.ts.map +1 -0
- package/dist/web-dashboard/attachment-extract.js +154 -0
- package/dist/web-dashboard/attachment-extract.js.map +1 -0
- package/dist/web-dashboard/chat-console.d.ts +108 -1
- package/dist/web-dashboard/chat-console.d.ts.map +1 -1
- package/dist/web-dashboard/chat-console.js +36 -0
- package/dist/web-dashboard/chat-console.js.map +1 -1
- package/dist/web-dashboard/hub-data.d.ts +37 -0
- package/dist/web-dashboard/hub-data.d.ts.map +1 -1
- package/dist/web-dashboard/hub-data.js +61 -1
- package/dist/web-dashboard/hub-data.js.map +1 -1
- 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 +14 -0
- package/dist/web-dashboard/server.d.ts.map +1 -1
- package/dist/web-dashboard/server.js +493 -30
- package/dist/web-dashboard/server.js.map +1 -1
- package/dist/web-dashboard/src/types.d.ts +261 -48
- package/dist/web-dashboard/src/types.d.ts.map +1 -1
- package/package.json +18 -3
- package/src/web-dashboard/public/assets/{index-Cyd6tIew.css → index-CIJ6FHHZ.css} +1 -1
- package/src/web-dashboard/public/assets/index-CsySzR41.js +207 -0
- package/src/web-dashboard/public/assets/index-CsySzR41.js.map +1 -0
- package/src/web-dashboard/public/index.html +2 -2
- package/dist/tools/child-agent-worker.js +0 -212
- package/src/web-dashboard/public/assets/index-CxDj7p6i.js +0 -207
- package/src/web-dashboard/public/assets/index-CxDj7p6i.js.map +0 -1
|
@@ -17,6 +17,8 @@ import { fileURLToPath } from 'node:url';
|
|
|
17
17
|
import { homedir } from 'node:os';
|
|
18
18
|
import { parseRbacUsers } from '../enterprise/rbac.js';
|
|
19
19
|
import { registerDagHandlers } from '../observability/dag-bridge.js';
|
|
20
|
+
import { debugLogDir, debugLoggingEnabled, debugLogsForSession } from '../observability/debug-log.js';
|
|
21
|
+
import { scrubSecrets } from '../gateway/gateway-log.js';
|
|
20
22
|
import { resolveBuffConfigDir, resolveBuffConfigPath, resolveNuviraHome } from '../config/paths.js';
|
|
21
23
|
import { loadEnv } from '../utils/env.js';
|
|
22
24
|
import { ConfigManager } from '../config/manager.js';
|
|
@@ -41,6 +43,9 @@ import { clearModelListCache } from '../inference/model-validator.js';
|
|
|
41
43
|
import { probeProviderList } from '../inference/model-probe.js';
|
|
42
44
|
import { CATALOG_PROVIDER_IDS, getCatalogProvider } from '../inference/provider-catalog.js';
|
|
43
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';
|
|
44
49
|
import { setToolsetEnabled } from '../tools/toolsets.js';
|
|
45
50
|
import { setSkillEnabled } from '../learning/hub-skill-catalog.js';
|
|
46
51
|
// I11 — dashboard channel send-test: the same registry/adapters `nuvira gateway
|
|
@@ -51,7 +56,7 @@ import { setSkillEnabled } from '../learning/hub-skill-catalog.js';
|
|
|
51
56
|
// mock node:fs before importing the server (router's package.json read would
|
|
52
57
|
// hit the mocked readFileSync).
|
|
53
58
|
import { createConfiguredAdapters } from '../gateway/adapters.js';
|
|
54
|
-
import { ChannelDirectory, PLATFORM_ENV_VARS } from '../gateway/channel-directory.js';
|
|
59
|
+
import { ChannelDirectory, PLATFORM_ENV_VARS, platformConfigVars } from '../gateway/channel-directory.js';
|
|
55
60
|
import { applyEnvToProcess, configurablePlatforms, platformConfigStatus, platformEnvVarMeta, redactValue, writeEnvFile, } from '../gateway/platform-config.js';
|
|
56
61
|
// ─── Constants ──────────────────────────────────────────────────────────────
|
|
57
62
|
const PORT = parseInt(envBuff('DASHBOARD_PORT') || '3030', 10);
|
|
@@ -469,6 +474,88 @@ function generateZip(files) {
|
|
|
469
474
|
parts.push(new Uint8Array(eocd));
|
|
470
475
|
return Buffer.concat(parts.map((p) => Buffer.from(p.buffer, p.byteOffset, p.byteLength)));
|
|
471
476
|
}
|
|
477
|
+
// ─── WS2 (#24) — the attachable support bundle ──────────────────────────────
|
|
478
|
+
/** Cap on the transcript the bundle carries, so one long chat cannot make a 50 MB zip. */
|
|
479
|
+
const MAX_SUPPORT_TRANSCRIPT_CHARS = 400_000;
|
|
480
|
+
/**
|
|
481
|
+
* The front page of a support bundle: what is inside, and — just as important —
|
|
482
|
+
* what is deliberately NOT.
|
|
483
|
+
*
|
|
484
|
+
* Load-bearing rather than decorative. A bundle exists to be pasted into a bug
|
|
485
|
+
* report, often a public one, so it has to say plainly that credentials are
|
|
486
|
+
* redacted and that the logs carry previews instead of payloads. A user who
|
|
487
|
+
* cannot tell what they are about to attach either does not attach it or
|
|
488
|
+
* attaches it and regrets it, and both of those make the artifact useless.
|
|
489
|
+
*/
|
|
490
|
+
function renderSupportManifest(opts) {
|
|
491
|
+
const named = (values) => [
|
|
492
|
+
...new Set(values.filter((v) => typeof v === 'string' && v.length > 0)),
|
|
493
|
+
];
|
|
494
|
+
const surfaces = named(opts.logs.map((l) => l.header.surface));
|
|
495
|
+
const versions = named(opts.logs.map((l) => l.header.version));
|
|
496
|
+
const backends = [
|
|
497
|
+
...new Set(opts.logs
|
|
498
|
+
.map((l) => [l.header.provider, l.header.model, l.header.transport].filter(Boolean).join(' / '))
|
|
499
|
+
.filter((b) => b.length > 0)),
|
|
500
|
+
];
|
|
501
|
+
return [
|
|
502
|
+
'nuvira support bundle',
|
|
503
|
+
`generated: ${opts.generatedAt.toISOString()}`,
|
|
504
|
+
`session: ${opts.sessionId}`,
|
|
505
|
+
`surface(s): ${surfaces.join(', ') || 'unknown'}`,
|
|
506
|
+
`version(s): ${versions.join(', ') || 'unknown'}`,
|
|
507
|
+
`debug logs: ${opts.logs.length} (oldest first)`,
|
|
508
|
+
...(backends.length > 0 ? [`backend(s) seen: ${backends.join(' | ')}`] : []),
|
|
509
|
+
...(opts.turns !== null ? [`conversation turns: ${opts.turns}`] : []),
|
|
510
|
+
'',
|
|
511
|
+
'contents:',
|
|
512
|
+
...opts.files.map((f) => ` - ${f}`),
|
|
513
|
+
'',
|
|
514
|
+
'notes:',
|
|
515
|
+
' - credentials are redacted, with the same scrubber the gateway log uses',
|
|
516
|
+
' - a session debug log carries bounded previews and event lines, not full payloads',
|
|
517
|
+
' - each log names the backend that ACTUALLY served its turn, after any failover',
|
|
518
|
+
...(opts.turns !== null
|
|
519
|
+
? [' - conversation.txt is your own chat with the agent, included so the reading is unambiguous']
|
|
520
|
+
: []),
|
|
521
|
+
'',
|
|
522
|
+
].join('\n');
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* The conversation as text — the other half of a bug report, and the reason the
|
|
526
|
+
* bundle is worth more than the log alone ("it did not answer" needs the ask).
|
|
527
|
+
*
|
|
528
|
+
* Redacted with the same `scrubSecrets` for the same reason as the logs: a user
|
|
529
|
+
* pasting this into a public issue must not leak a key they happened to type
|
|
530
|
+
* into a prompt three turns earlier. Bounded by characters, and it SAYS when it
|
|
531
|
+
* was cut, so a truncated bundle is never mistaken for the whole story.
|
|
532
|
+
*/
|
|
533
|
+
function renderSupportTranscript(sessionId, record, generatedAt) {
|
|
534
|
+
const lines = [
|
|
535
|
+
`Conversation with the dashboard agent (session ${sessionId})`,
|
|
536
|
+
`Exported: ${generatedAt.toISOString()}`,
|
|
537
|
+
`Turns: ${record.turns.length}`,
|
|
538
|
+
'─'.repeat(60),
|
|
539
|
+
'',
|
|
540
|
+
];
|
|
541
|
+
let used = 0;
|
|
542
|
+
let truncated = false;
|
|
543
|
+
for (const turn of record.turns) {
|
|
544
|
+
const body = scrubSecrets(turn.content);
|
|
545
|
+
if (used + body.length > MAX_SUPPORT_TRANSCRIPT_CHARS) {
|
|
546
|
+
truncated = true;
|
|
547
|
+
break;
|
|
548
|
+
}
|
|
549
|
+
used += body.length;
|
|
550
|
+
lines.push(turn.role === 'user' ? 'You:' : 'Agent:');
|
|
551
|
+
lines.push(body);
|
|
552
|
+
lines.push('');
|
|
553
|
+
}
|
|
554
|
+
if (truncated) {
|
|
555
|
+
lines.push(`[truncated at ${MAX_SUPPORT_TRANSCRIPT_CHARS} characters — the full transcript is in the session store]`);
|
|
556
|
+
}
|
|
557
|
+
return lines.join('\n');
|
|
558
|
+
}
|
|
472
559
|
let activePipeline = null; // goal/description of current pipeline
|
|
473
560
|
let activeNodes = [];
|
|
474
561
|
let activeEdges = [];
|
|
@@ -2682,6 +2769,32 @@ function writeJson(res, status, data) {
|
|
|
2682
2769
|
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
2683
2770
|
res.end(JSON.stringify(data));
|
|
2684
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
|
+
}
|
|
2685
2798
|
// ─── Request Handler ────────────────────────────────────────────────────────
|
|
2686
2799
|
function handleRequest(req, res) {
|
|
2687
2800
|
const url = new URL(req.url || '/', `http://${req.headers.host || HOST}`);
|
|
@@ -3447,6 +3560,140 @@ function handleRequest(req, res) {
|
|
|
3447
3560
|
})();
|
|
3448
3561
|
return;
|
|
3449
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
|
+
}
|
|
3450
3697
|
// ── Agent Hub channel send-test (I11) — admin-gated gateway send ────────
|
|
3451
3698
|
// Mirrors `nuvira gateway send <target> <text>`: resolve the target (alias or
|
|
3452
3699
|
// platform:channelId) through the SAME ChannelDirectory, then send through
|
|
@@ -4234,25 +4481,34 @@ function handleRequest(req, res) {
|
|
|
4234
4481
|
const registryData = readJSON(join(MEMORY_DIR, 'model-registry.json'));
|
|
4235
4482
|
const now = Date.now();
|
|
4236
4483
|
const entries = Object.values(registryData?.entries ?? {});
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
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
|
+
};
|
|
4242
4501
|
const timelineEntries = entries.map((e) => {
|
|
4243
|
-
const
|
|
4244
|
-
const
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
removedCount++;
|
|
4248
|
-
else if (isStale)
|
|
4249
|
-
staleCount++;
|
|
4250
|
-
else
|
|
4251
|
-
freshCount++;
|
|
4502
|
+
const reachability = classifyReachability(e, now);
|
|
4503
|
+
const freshness = classifyFreshness(e.lastProbedAt, e.errorRate, now);
|
|
4504
|
+
counts[reachability] += 1;
|
|
4505
|
+
freshnessCounts[freshness] += 1;
|
|
4252
4506
|
return {
|
|
4253
4507
|
provider: e.provider,
|
|
4254
4508
|
model: e.model,
|
|
4255
4509
|
status: e.status,
|
|
4510
|
+
reachability,
|
|
4511
|
+
freshness,
|
|
4256
4512
|
lastVerifiedAt: e.lastVerifiedAt || 0,
|
|
4257
4513
|
lastProbedAt: e.lastProbedAt || 0,
|
|
4258
4514
|
lastUsedAt: e.lastUsedAt || 0,
|
|
@@ -4260,6 +4516,10 @@ function handleRequest(req, res) {
|
|
|
4260
4516
|
latencyMs: e.latencyMs,
|
|
4261
4517
|
contextWindowTokens: e.contextWindowTokens,
|
|
4262
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,
|
|
4263
4523
|
};
|
|
4264
4524
|
});
|
|
4265
4525
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
@@ -4267,9 +4527,15 @@ function handleRequest(req, res) {
|
|
|
4267
4527
|
entries: timelineEntries,
|
|
4268
4528
|
lastUpdated: now,
|
|
4269
4529
|
totalModels: entries.length,
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
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,
|
|
4273
4539
|
}));
|
|
4274
4540
|
}
|
|
4275
4541
|
catch (err) {
|
|
@@ -4278,6 +4544,53 @@ function handleRequest(req, res) {
|
|
|
4278
4544
|
}
|
|
4279
4545
|
return;
|
|
4280
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
|
+
}
|
|
4281
4594
|
if (pathname === '/api/requests') {
|
|
4282
4595
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
4283
4596
|
res.end(JSON.stringify(readRequestsData()));
|
|
@@ -4747,6 +5060,9 @@ function handleRequest(req, res) {
|
|
|
4747
5060
|
varName: v.varName,
|
|
4748
5061
|
set: v.set,
|
|
4749
5062
|
value: canWrite ? v.value : v.set ? redactValue(v.value) : '',
|
|
5063
|
+
// `prompt` carries the "(optional)" marker for additive transport
|
|
5064
|
+
// modes (platformEnvVarMeta), so the form labels them without the UI
|
|
5065
|
+
// having to know which vars are required.
|
|
4750
5066
|
prompt: m?.prompt ?? v.varName,
|
|
4751
5067
|
secret: m?.secret ?? false,
|
|
4752
5068
|
};
|
|
@@ -4780,7 +5096,10 @@ function handleRequest(req, res) {
|
|
|
4780
5096
|
}
|
|
4781
5097
|
const body = await readJsonBody(req);
|
|
4782
5098
|
const values = (body?.values ?? {});
|
|
4783
|
-
|
|
5099
|
+
// platformConfigVars, not PLATFORM_ENV_VARS: the form renders every managed
|
|
5100
|
+
// var (including the additional transport modes), so a key it displays must
|
|
5101
|
+
// be writable here — otherwise a value the UI offers is rejected as unknown.
|
|
5102
|
+
const allowed = new Set(platformConfigVars(platform));
|
|
4784
5103
|
const clean = {};
|
|
4785
5104
|
for (const [key, value] of Object.entries(values)) {
|
|
4786
5105
|
if (!allowed.has(key)) {
|
|
@@ -4819,7 +5138,7 @@ function handleRequest(req, res) {
|
|
|
4819
5138
|
writeJson(res, 400, { ok: false, error: `Platform '${platform}' is not env-configurable.` });
|
|
4820
5139
|
return;
|
|
4821
5140
|
}
|
|
4822
|
-
const keys =
|
|
5141
|
+
const keys = platformConfigVars(platform);
|
|
4823
5142
|
writeEnvFile({}, keys);
|
|
4824
5143
|
applyEnvToProcess({}, keys);
|
|
4825
5144
|
writeJson(res, 200, { ok: true, removed: keys, status: platformConfigStatus(platform) });
|
|
@@ -5448,14 +5767,34 @@ function handleRequest(req, res) {
|
|
|
5448
5767
|
: newChatSessionId();
|
|
5449
5768
|
const provider = typeof body?.provider === 'string' ? body.provider : undefined;
|
|
5450
5769
|
const model = typeof body?.model === 'string' ? body.model : undefined;
|
|
5451
|
-
//
|
|
5452
|
-
//
|
|
5453
|
-
//
|
|
5454
|
-
|
|
5455
|
-
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5770
|
+
// WS5 (#27) — per-turn isolation and resume, from the GUI.
|
|
5771
|
+
//
|
|
5772
|
+
// Read as a strict TRI-state, because the three cases are genuinely
|
|
5773
|
+
// different here: `true` asks for it, `false` declines a deployment-wide
|
|
5774
|
+
// `NUVIRA_ISOLATE`/`NUVIRA_RESUME` for this one turn, and ABSENT (`undefined`)
|
|
5775
|
+
// defers to that environment. Collapsing absent into `false` would make the
|
|
5776
|
+
// GUI's silence outrank the deployment's configuration — the same defect the
|
|
5777
|
+
// CLI's option defaults had.
|
|
5778
|
+
const worktree = body?.worktree === true ? true : body?.worktree === false ? false : undefined;
|
|
5779
|
+
const keepWorktree = body?.keepWorktree === true ? true : body?.keepWorktree === false ? false : undefined;
|
|
5780
|
+
const resume = typeof body?.resume === 'string'
|
|
5781
|
+
? body.resume
|
|
5782
|
+
: body?.resume === true
|
|
5783
|
+
? true
|
|
5784
|
+
: body?.resume === false
|
|
5785
|
+
? false
|
|
5786
|
+
: undefined;
|
|
5787
|
+
// P8 + P2 — turn attachments (file picker / paste-as-attachment / drag-drop).
|
|
5788
|
+
//
|
|
5789
|
+
// The GUI sends TEXT inline. For anything it cannot decode as text (PDF, DOCX,
|
|
5790
|
+
// XLSX, PPTX, images) it sends the BYTES as base64 and the extraction happens
|
|
5791
|
+
// here, through the same `read_extract` the agent uses for a file in the project
|
|
5792
|
+
// folder — so a refusal reaches the turn as a refusal instead of as mojibake.
|
|
5793
|
+
// Previously every file was read with `File.text()` client-side, which is how a
|
|
5794
|
+
// PDF arrived as `%PDF-1.4 …` with nothing indicating extraction had failed
|
|
5795
|
+
// (TOOL_TRUTHFULNESS_TRACKER.md findings #1/#7 and P2).
|
|
5796
|
+
const { hydrateAttachments } = await import('./attachment-extract.js');
|
|
5797
|
+
const attachments = await hydrateAttachments(body?.attachments);
|
|
5459
5798
|
// P3 — an attached project's bounded snapshot rides into the turn as
|
|
5460
5799
|
// `[Project context]`, so "assess THIS project" works without the user
|
|
5461
5800
|
// describing the codebase. Cache misses rebuild automatically.
|
|
@@ -5500,7 +5839,16 @@ function handleRequest(req, res) {
|
|
|
5500
5839
|
// Mark the failover log BEFORE the turn so a failure can report which
|
|
5501
5840
|
// models were actually tried (same contract as the gateway).
|
|
5502
5841
|
const attemptMark = markFailoverAttempts();
|
|
5503
|
-
const result = await chatConsole.answer(sessionId, message, {
|
|
5842
|
+
const result = await chatConsole.answer(sessionId, message, {
|
|
5843
|
+
provider,
|
|
5844
|
+
model,
|
|
5845
|
+
projectContext,
|
|
5846
|
+
projectPath,
|
|
5847
|
+
attachments,
|
|
5848
|
+
...(worktree === undefined ? {} : { worktree }),
|
|
5849
|
+
...(keepWorktree === undefined ? {} : { keepWorktree }),
|
|
5850
|
+
...(resume === undefined ? {} : { resume }),
|
|
5851
|
+
});
|
|
5504
5852
|
// The response is written below — remove the disconnect listener so a
|
|
5505
5853
|
// post-completion close can never touch the console again.
|
|
5506
5854
|
res.off('close', onResClose);
|
|
@@ -5521,7 +5869,12 @@ function handleRequest(req, res) {
|
|
|
5521
5869
|
// retrying on its own — a manual re-send on top of the queued one would
|
|
5522
5870
|
// run the same ask twice.
|
|
5523
5871
|
let retryQueued = false;
|
|
5524
|
-
|
|
5872
|
+
// WS5 (#27) — a REFUSED turn is failed but not retryable: the turn declined
|
|
5873
|
+
// to run (isolation was asked for and could not be made), and the answer is
|
|
5874
|
+
// the reason, already in `content`. The retry machinery below would replace
|
|
5875
|
+
// that reason with a "keep trying" offer for a request that cannot succeed,
|
|
5876
|
+
// and would queue a background re-run of the very ask that refused.
|
|
5877
|
+
if (result.generationFailed === true && result.refused !== true) {
|
|
5525
5878
|
const report = chatRetryBroker.onFailure(sessionId, message, attemptMark);
|
|
5526
5879
|
if (report) {
|
|
5527
5880
|
content = content.trim() ? `${content}\n\n${report}` : report;
|
|
@@ -5542,6 +5895,26 @@ function handleRequest(req, res) {
|
|
|
5542
5895
|
followups: result.followups ?? [],
|
|
5543
5896
|
provider: result.provider ?? null,
|
|
5544
5897
|
model: result.model ?? null,
|
|
5898
|
+
// WS1 — the findings this turn recorded, with the gate's verdicts, so
|
|
5899
|
+
// a dashboard client reads the same wire form the CLI, the gateway and
|
|
5900
|
+
// the subagent report (the GUI card is a rendering decision; the FACT
|
|
5901
|
+
// travels here rather than being dropped at the HTTP boundary).
|
|
5902
|
+
findings: result.findings ?? [],
|
|
5903
|
+
// WS5 — the isolation this turn ACTUALLY had, as the console reported it
|
|
5904
|
+
// (absent when the turn was not isolated), never re-derived from the flags
|
|
5905
|
+
// this request sent: a client that asked for isolation and got a refusal
|
|
5906
|
+
// must be able to see the difference, and the refusal is the turn's own
|
|
5907
|
+
// content. The diff arrives with it — `diff.payload` is the same
|
|
5908
|
+
// `{files, summary}` shape the `git:diff` event and the diff card already
|
|
5909
|
+
// use, so the GUI renders one card for both.
|
|
5910
|
+
...(result.worktree ? { worktree: result.worktree } : {}),
|
|
5911
|
+
// WS5 — what a resume replayed. Sent for the same reason: a deployment
|
|
5912
|
+
// that sets `NUVIRA_RESUME` gets resumed turns, and a reader who is never
|
|
5913
|
+
// told cannot tell a replayed turn from a fresh one.
|
|
5914
|
+
...(result.resume ? { resume: result.resume } : {}),
|
|
5915
|
+
// WS5 — whether the turn declined to run, so the client labels the failure
|
|
5916
|
+
// as the refusal it is instead of offering a Retry that cannot help.
|
|
5917
|
+
refused: result.refused === true,
|
|
5545
5918
|
generationFailed: result.generationFailed === true,
|
|
5546
5919
|
retryQueued,
|
|
5547
5920
|
});
|
|
@@ -5650,6 +6023,89 @@ function handleRequest(req, res) {
|
|
|
5650
6023
|
})();
|
|
5651
6024
|
return;
|
|
5652
6025
|
}
|
|
6026
|
+
// GET /api/chat/:sessionId/support-bundle — the debug logs THIS conversation
|
|
6027
|
+
// wrote, plus the conversation and a manifest, as one downloadable ZIP.
|
|
6028
|
+
//
|
|
6029
|
+
// WS2 (#24) taught every surface to write an attachable log; a log the user
|
|
6030
|
+
// cannot FIND is only half an artifact, and `~/.nuvira/debug-logs/` is exactly
|
|
6031
|
+
// the kind of path nobody goes and greps while filing a bug. So the chat the
|
|
6032
|
+
// user is already looking at becomes the way in.
|
|
6033
|
+
//
|
|
6034
|
+
// Selected by the log's OWN `# session:` header — never by recency, which with
|
|
6035
|
+
// a second tab open is somebody else's conversation. And when there is no log
|
|
6036
|
+
// the endpoint REFUSES rather than handing over a bundle without the one thing
|
|
6037
|
+
// a bug report needs, saying which step is missing instead.
|
|
6038
|
+
const chatBundleMatch = /^\/api\/chat\/([^/]+)\/support-bundle$/.exec(pathname);
|
|
6039
|
+
if (chatBundleMatch && req.method === 'GET') {
|
|
6040
|
+
void (async () => {
|
|
6041
|
+
// The `?token=` fallback as well as the header, for the same reason the
|
|
6042
|
+
// events stream has one: this is a plain GET that a browser or curl can be
|
|
6043
|
+
// handed, so it must not depend on a JS fetch that sets headers.
|
|
6044
|
+
const token = bearerToken(req) ?? new URL(req.url ?? '/', 'http://localhost').searchParams.get('token');
|
|
6045
|
+
const admin = adminSessions.validate(token);
|
|
6046
|
+
if (!admin) {
|
|
6047
|
+
writeJson(res, 401, { ok: false, error: 'Not authenticated — log in first.' });
|
|
6048
|
+
return;
|
|
6049
|
+
}
|
|
6050
|
+
if (!roleCan(admin.role, 'routing.operate')) {
|
|
6051
|
+
writeJson(res, 403, { ok: false, error: `Access denied — role '${admin.role}' cannot read a chat session.` });
|
|
6052
|
+
return;
|
|
6053
|
+
}
|
|
6054
|
+
const sessionId = decodeURIComponent(chatBundleMatch[1]);
|
|
6055
|
+
try {
|
|
6056
|
+
const logs = debugLogsForSession(sessionId);
|
|
6057
|
+
if (logs.length === 0) {
|
|
6058
|
+
// Two different nothings with two different fixes, so they are NOT
|
|
6059
|
+
// collapsed into one message that fits neither.
|
|
6060
|
+
const enabled = debugLoggingEnabled();
|
|
6061
|
+
writeJson(res, 200, {
|
|
6062
|
+
ok: false,
|
|
6063
|
+
reason: enabled ? 'no_logs' : 'logging_disabled',
|
|
6064
|
+
error: enabled
|
|
6065
|
+
? 'No debug log for this conversation yet. Logging is on, so the next turn here writes one — send a message (a log is written when the turn ENDS, not while it runs), then download again.'
|
|
6066
|
+
: `Session debug logging is off in the dashboard process, so there is nothing to attach yet. Set NUVIRA_DEBUG_LOG=1, restart the dashboard, send a message, then download again. Logs land in ${debugLogDir()}.`,
|
|
6067
|
+
});
|
|
6068
|
+
return;
|
|
6069
|
+
}
|
|
6070
|
+
const now = new Date();
|
|
6071
|
+
const record = chatConsole.get(sessionId);
|
|
6072
|
+
const safeId = sessionId.replace(/[^\w.-]/g, '_').slice(0, 48) || 'session';
|
|
6073
|
+
const files = [];
|
|
6074
|
+
for (const log of logs) {
|
|
6075
|
+
// The log's own basename carries its surface and start time, so two
|
|
6076
|
+
// turns of one conversation cannot collide inside the archive.
|
|
6077
|
+
files.push({ name: basename(log.path).replace(/[^\w.-]/g, '_'), content: log.text });
|
|
6078
|
+
}
|
|
6079
|
+
if (record) {
|
|
6080
|
+
files.push({ name: 'conversation.txt', content: renderSupportTranscript(sessionId, record, now) });
|
|
6081
|
+
}
|
|
6082
|
+
files.unshift({
|
|
6083
|
+
name: 'manifest.txt',
|
|
6084
|
+
content: renderSupportManifest({
|
|
6085
|
+
sessionId,
|
|
6086
|
+
logs,
|
|
6087
|
+
files: ['manifest.txt', ...files.map((f) => f.name)],
|
|
6088
|
+
turns: record ? record.turns.length : null,
|
|
6089
|
+
generatedAt: now,
|
|
6090
|
+
}),
|
|
6091
|
+
});
|
|
6092
|
+
const zip = generateZip(files);
|
|
6093
|
+
res.writeHead(200, {
|
|
6094
|
+
'Content-Type': 'application/zip',
|
|
6095
|
+
'Content-Disposition': `attachment; filename="nuvira-support-${safeId}.zip"`,
|
|
6096
|
+
'Content-Length': zip.length,
|
|
6097
|
+
});
|
|
6098
|
+
res.end(zip);
|
|
6099
|
+
}
|
|
6100
|
+
catch (err) {
|
|
6101
|
+
writeJson(res, 500, {
|
|
6102
|
+
ok: false,
|
|
6103
|
+
error: `Support bundle failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
6104
|
+
});
|
|
6105
|
+
}
|
|
6106
|
+
})();
|
|
6107
|
+
return;
|
|
6108
|
+
}
|
|
5653
6109
|
// GET /api/chat/:sessionId/events — SSE stream of the session's LIVE
|
|
5654
6110
|
// progress (agent working steps) + status while a turn is in flight. The
|
|
5655
6111
|
// client subscribes FIRST, then POSTs the message; tool-call lines stream
|
|
@@ -5726,6 +6182,13 @@ function handleRequest(req, res) {
|
|
|
5726
6182
|
updatedAt: event.updatedAt,
|
|
5727
6183
|
})}\n\n`);
|
|
5728
6184
|
}
|
|
6185
|
+
else if (event.kind === 'finding') {
|
|
6186
|
+
// WS1 — a recorded finding: the claim, the outcome, the evidence and
|
|
6187
|
+
// the verdict the GATE computed. Rendered as its own card so a
|
|
6188
|
+
// CONFIRMED verdict and a PLAUSIBLE guess never read the same in the
|
|
6189
|
+
// thread — the whole point of the row.
|
|
6190
|
+
res.write(`event: finding\ndata: ${JSON.stringify({ finding: event.finding })}\n\n`);
|
|
6191
|
+
}
|
|
5729
6192
|
else if (event.kind === 'status') {
|
|
5730
6193
|
res.write(`event: status\ndata: ${JSON.stringify({ status: event.status })}\n\n`);
|
|
5731
6194
|
}
|