2sagaco48 1.0.0
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/diagnostics.mjs +141 -0
- package/dist/compute.worker-j5ahIpF-.js +1 -0
- package/dist/graph.worker-DG6iGCB9.js +1 -0
- package/dist/index.html +2314 -0
- package/flatten-guide.md +248 -0
- package/index.html +12 -0
- package/package.json +38 -0
- package/src/App.orig.tsx +204 -0
- package/src/App.orig1.tsx +27 -0
- package/src/App.orig2.tsx +25 -0
- package/src/App.tsx +3 -0
- package/src/BaseApp.tsx +18 -0
- package/src/INTEGRATION_LEDGER.md +22 -0
- package/src/PERSIST_CANARY.orig.txt +1 -0
- package/src/PERSIST_CANARY.txt +1 -0
- package/src/REGRESSION_LEDGER.md +651 -0
- package/src/TEMPLATE_PIPELINE_AUDIT.md +208 -0
- package/src/WILLIAMS_PERSONA_GUIDE.md +62 -0
- package/src/chat/VeritasChat.tsx +69 -0
- package/src/chat/synthesis.ts +31 -0
- package/src/chat/tier.ts +45 -0
- package/src/components/AdaptersPage.orig.tsx +99 -0
- package/src/components/AdaptersPage.tsx +3 -0
- package/src/components/AdversarialPanel.tsx +136 -0
- package/src/components/AnchorBaselinePanel.tsx +75 -0
- package/src/components/AnswerPanel.tsx +60 -0
- package/src/components/ArtifactPanel.tsx +43 -0
- package/src/components/ChatApp.orig.tsx +1582 -0
- package/src/components/ChatApp.orig1.tsx +722 -0
- package/src/components/ChatApp.tsx +95 -0
- package/src/components/ChatAugmentPanels.tsx +73 -0
- package/src/components/CitationLedgerPanel.tsx +313 -0
- package/src/components/ComputeSandboxPanel.tsx +130 -0
- package/src/components/ConfigPanel.tsx +110 -0
- package/src/components/ControlPlanePage.tsx +195 -0
- package/src/components/CreativeTreeLifePage.tsx +234 -0
- package/src/components/CreativeTreeOfLifePanel.tsx +149 -0
- package/src/components/DebugTracePanel.tsx +137 -0
- package/src/components/DeepReasoningTrace.tsx +48 -0
- package/src/components/EntitySheetPanel.tsx +51 -0
- package/src/components/FailureModesPanel.tsx +104 -0
- package/src/components/GBSDashboard.tsx +239 -0
- package/src/components/GraphView.tsx +116 -0
- package/src/components/HUD.tsx +125 -0
- package/src/components/HypothesisPanel.tsx +67 -0
- package/src/components/InnovationGenomeEngine.tsx +792 -0
- package/src/components/InnovationPersonaGuide.tsx +348 -0
- package/src/components/InnovationPersonaPanel.tsx +289 -0
- package/src/components/LiveResourceHUD.tsx +109 -0
- package/src/components/LongWriterPanel.tsx +112 -0
- package/src/components/MainPipelineV10Bridge.tsx +72 -0
- package/src/components/MarkdownLite.tsx +63 -0
- package/src/components/MemoryInspector.tsx +38 -0
- package/src/components/MemoryMonitor.tsx +52 -0
- package/src/components/MemoryStressPanel.tsx +176 -0
- package/src/components/ModulesPage.tsx +171 -0
- package/src/components/PipelineDebugConsole.orig.tsx +1856 -0
- package/src/components/PipelineDebugConsole.tsx +3 -0
- package/src/components/PreFlightHUD.tsx +67 -0
- package/src/components/PrismaFetchTracePanel.tsx +87 -0
- package/src/components/ReportOSPanel.tsx +165 -0
- package/src/components/ResourceEstimatorPage.tsx +327 -0
- package/src/components/RichText.tsx +181 -0
- package/src/components/SagaOsPanel.tsx +605 -0
- package/src/components/SagaOsReference.tsx +421 -0
- package/src/components/SagaOsV2Panel.tsx +594 -0
- package/src/components/SagaStyleTab.tsx +382 -0
- package/src/components/SageBlueprintPanel.tsx +416 -0
- package/src/components/SharedChatInput.tsx +77 -0
- package/src/components/StatFinancePanel.tsx +190 -0
- package/src/components/StylePersonaPanel.tsx +128 -0
- package/src/components/TemplatesPage.tsx +300 -0
- package/src/components/TestPanel.tsx +63 -0
- package/src/components/TraceLog.tsx +89 -0
- package/src/components/V15CalibrationAugment.tsx +1134 -0
- package/src/components/V15CalibrationDialog.tsx +1871 -0
- package/src/components/V15Overlay.orig.tsx +120 -0
- package/src/components/V15Overlay.tsx +4 -0
- package/src/components/V15OverlayWrapper.tsx +362 -0
- package/src/components/V15Toggle.tsx +34 -0
- package/src/console/DiagnosticRecoveryPanel.tsx +358 -0
- package/src/console/EngineeringConsole.tsx +103 -0
- package/src/console/FictionSystemPanels.tsx +882 -0
- package/src/console/InspectorPanels.tsx +254 -0
- package/src/console/ManuscriptGraderPanel.tsx +182 -0
- package/src/console/RecursiveReviserPanel.tsx +245 -0
- package/src/console/SeedControlPanel.tsx +135 -0
- package/src/console/StoryControllerPanel.tsx +443 -0
- package/src/console/engineBridge.ts +258 -0
- package/src/console/gemini.ts +106 -0
- package/src/console/grading.ts +250 -0
- package/src/console/util.ts +45 -0
- package/src/fiction/continuity.ts +372 -0
- package/src/fiction/crash-protection.ts +111 -0
- package/src/fiction/diagnostics.ts +208 -0
- package/src/fiction/diff.ts +74 -0
- package/src/fiction/entity-state-machine.ts +214 -0
- package/src/fiction/nive-engine.ts +404 -0
- package/src/fiction/pipeline-selftest.ts +132 -0
- package/src/fiction/seed-control.ts +193 -0
- package/src/fiction/story-engine.ts +326 -0
- package/src/fiction/types.ts +68 -0
- package/src/flatten-guide.md +379 -0
- package/src/index.css +14 -0
- package/src/index.orig.css +13 -0
- package/src/lib/academic-sources.base.ts +188 -0
- package/src/lib/academic-sources.orig.ts +28 -0
- package/src/lib/academic-sources.ts +10 -0
- package/src/lib/advanced-math.ts +119 -0
- package/src/lib/adversarial-engine.base.ts +409 -0
- package/src/lib/adversarial-engine.ts +82 -0
- package/src/lib/ai-revision.ts +623 -0
- package/src/lib/app-state.tsx +426 -0
- package/src/lib/artifact-registry.ts +97 -0
- package/src/lib/artifacts.ts +180 -0
- package/src/lib/atlas-dr.ts +167 -0
- package/src/lib/browser-mtls.ts +127 -0
- package/src/lib/browser-queue.ts +221 -0
- package/src/lib/browser-search-scraper.base.ts +382 -0
- package/src/lib/browser-search-scraper.orig.ts +90 -0
- package/src/lib/browser-search-scraper.ts +10 -0
- package/src/lib/calc-interceptor.ts +82 -0
- package/src/lib/citation-lane-tap.ts +78 -0
- package/src/lib/citation-ledger-store.ts +369 -0
- package/src/lib/citation-ledger.ts +203 -0
- package/src/lib/compute-sandbox.ts +215 -0
- package/src/lib/compute.worker.ts +153 -0
- package/src/lib/connectors/gemini.ts +120 -0
- package/src/lib/connectors/jina.ts +233 -0
- package/src/lib/connectors/marketdata.ts +51 -0
- package/src/lib/connectors/prismafetch.base.ts +175 -0
- package/src/lib/connectors/prismafetch.ts +1 -0
- package/src/lib/connectors/serpapi.ts +65 -0
- package/src/lib/connectors/wikidata.ts +44 -0
- package/src/lib/constraints.ts +331 -0
- package/src/lib/continuation-detector.base.ts +153 -0
- package/src/lib/continuation-detector.ts +48 -0
- package/src/lib/contradraft.ts +168 -0
- package/src/lib/cors-proxy.ts +120 -0
- package/src/lib/coverage.ts +81 -0
- package/src/lib/debug/architecture-prescription.ts +556 -0
- package/src/lib/debug/covea-repair.ts +543 -0
- package/src/lib/debug/helios-ground.ts +319 -0
- package/src/lib/debug/intent-decomposer.ts +281 -0
- package/src/lib/debug/intent-lattice.ts +349 -0
- package/src/lib/debug/pipeline-diagnosis.ts +1149 -0
- package/src/lib/debug/pipeline-trace-bus.ts +424 -0
- package/src/lib/debug/prompt-forge.ts +678 -0
- package/src/lib/debug/repair-sites.ts +513 -0
- package/src/lib/debug/research-phase.ts +266 -0
- package/src/lib/debug/retrieval-context.ts +76 -0
- package/src/lib/debug/retrieval-hardener.ts +142 -0
- package/src/lib/debug/scraper-debug-runner.ts +576 -0
- package/src/lib/debug/scraper-forensics.ts +418 -0
- package/src/lib/debug/scraper-lane-roadmap.ts +136 -0
- package/src/lib/debug/self-test.ts +415 -0
- package/src/lib/debug/step-attribution.ts +211 -0
- package/src/lib/debug/template-rubric.ts +301 -0
- package/src/lib/debug/unified-innovation.ts +126 -0
- package/src/lib/debug/veritas-hybrid-scraper.ts +335 -0
- package/src/lib/defense-registry.ts +104 -0
- package/src/lib/defenses.ts +162 -0
- package/src/lib/deterministic-citation-ledger.ts +174 -0
- package/src/lib/elo-registry.base.ts +117 -0
- package/src/lib/elo-registry.ts +1 -0
- package/src/lib/entity-resolver.ts +291 -0
- package/src/lib/failure-modes.ts +267 -0
- package/src/lib/feature-registry.ts +69 -0
- package/src/lib/flaw-registry.ts +143 -0
- package/src/lib/flaws/_template.ts +52 -0
- package/src/lib/flaws/builtins.ts +63 -0
- package/src/lib/flaws/finance.ts +169 -0
- package/src/lib/flaws/fixers.ts +10 -0
- package/src/lib/flaws/index.ts +32 -0
- package/src/lib/flaws/legal.ts +157 -0
- package/src/lib/flaws/medical.ts +188 -0
- package/src/lib/flaws/original-defenses-pack.ts +81 -0
- package/src/lib/flaws/sample-declarative-pack.json +38 -0
- package/src/lib/flaws/selftest.ts +201 -0
- package/src/lib/flaws/software-extended.ts +125 -0
- package/src/lib/flaws/software-rn-webgl.ts +182 -0
- package/src/lib/flaws/statistics-advanced.ts +46 -0
- package/src/lib/flaws/statistics.ts +61 -0
- package/src/lib/gbse/config.ts +20 -0
- package/src/lib/gbse/engine.ts +226 -0
- package/src/lib/gbse/graph.ts +118 -0
- package/src/lib/gbse/graph.worker.ts +21 -0
- package/src/lib/gbse/tests.ts +117 -0
- package/src/lib/gbse/tiers.ts +65 -0
- package/src/lib/gbse/types.ts +94 -0
- package/src/lib/innovation-genome-engine-v2.orig.ts +1001 -0
- package/src/lib/innovation-genome-engine-v2.ts +3 -0
- package/src/lib/innovation-genome-engine.orig.ts +330 -0
- package/src/lib/innovation-genome-engine.ts +3 -0
- package/src/lib/innovation-genome-v10.orig.ts +314 -0
- package/src/lib/innovation-genome-v10.ts +2 -0
- package/src/lib/innovation-genome-v3.orig.ts +2015 -0
- package/src/lib/innovation-genome-v3.ts +2 -0
- package/src/lib/innovation-genome-v4.orig.ts +1959 -0
- package/src/lib/innovation-genome-v4.ts +2 -0
- package/src/lib/innovation-genome-v5.orig.ts +1210 -0
- package/src/lib/innovation-genome-v5.ts +2 -0
- package/src/lib/innovation-genome-v7.orig.ts +2549 -0
- package/src/lib/innovation-genome-v7.ts +2 -0
- package/src/lib/innovation-genome-v8.orig.ts +1485 -0
- package/src/lib/innovation-genome-v8.ts +2 -0
- package/src/lib/innovation-genome-v9.orig.ts +643 -0
- package/src/lib/innovation-genome-v9.ts +2 -0
- package/src/lib/jina.base.ts +293 -0
- package/src/lib/jina.ts +1 -0
- package/src/lib/live-telemetry.ts +61 -0
- package/src/lib/longwriter.ts +84 -0
- package/src/lib/manuscript-grade-v2.ts +298 -0
- package/src/lib/memory-governor.ts +173 -0
- package/src/lib/memory-stress-tests.ts +248 -0
- package/src/lib/model-intelligence.ts +48 -0
- package/src/lib/model-rotator.ts +75 -0
- package/src/lib/models.orig.ts +513 -0
- package/src/lib/models.ts +151 -0
- package/src/lib/n-deep.base.ts +592 -0
- package/src/lib/n-deep.ts +51 -0
- package/src/lib/nih-simulator.ts +56 -0
- package/src/lib/nih-vulnerability-fixes.ts +460 -0
- package/src/lib/omega-templates.base.ts +294 -0
- package/src/lib/omega-templates.ts +225 -0
- package/src/lib/omni-nexus.ts +64 -0
- package/src/lib/oracle-adapters.ts +67 -0
- package/src/lib/oracle-registry.ts +163 -0
- package/src/lib/orchestrator.ts +357 -0
- package/src/lib/output-boundary.ts +235 -0
- package/src/lib/overrides/vite-native-chaos-harness.ts +154 -0
- package/src/lib/overrides/vite-native-contract-plane.ts +245 -0
- package/src/lib/overrides/vite-native-doctor-plane.ts +208 -0
- package/src/lib/overrides/vite-native-knowledge-store.ts +267 -0
- package/src/lib/overrides/vite-native-policy-plane.ts +190 -0
- package/src/lib/overrides/vite-native-replay-plane.ts +197 -0
- package/src/lib/overrides/vite-native-runtime-plane.ts +106 -0
- package/src/lib/overrides/vite-native-scraper.ts +848 -0
- package/src/lib/overrides/vite-native-selftest-functional.ts +217 -0
- package/src/lib/overrides/vite-native-selftest.ts +183 -0
- package/src/lib/overrides/vite-native-snapshot-plane.ts +123 -0
- package/src/lib/philosophy-toolkit.ts +75 -0
- package/src/lib/pipeline.orig.ts +659 -0
- package/src/lib/pipeline.ts +42 -0
- package/src/lib/precache.ts +128 -0
- package/src/lib/py-sandbox.ts +205 -0
- package/src/lib/quality-score.ts +96 -0
- package/src/lib/quant-engine.ts +231 -0
- package/src/lib/quant-lib.ts +80 -0
- package/src/lib/reportos.ts +175 -0
- package/src/lib/research-os.ts +306 -0
- package/src/lib/resource-estimator.ts +114 -0
- package/src/lib/rpm-governor.ts +93 -0
- package/src/lib/runtime-estimator.ts +135 -0
- package/src/lib/sage/empty-space-v6.ts +560 -0
- package/src/lib/sage/empty-space-v7.ts +222 -0
- package/src/lib/sage/empty-space.ts +733 -0
- package/src/lib/sage/engine.ts +786 -0
- package/src/lib/sage/index.ts +17 -0
- package/src/lib/sage/judge.ts +246 -0
- package/src/lib/sage/prng.ts +53 -0
- package/src/lib/sage/reality-grounding.ts +380 -0
- package/src/lib/sage/repetition.ts +151 -0
- package/src/lib/sage/saga-os-v2-engines.ts +1870 -0
- package/src/lib/sage/saga-os-v2.ts +274 -0
- package/src/lib/sage/saga-os-v3-core.ts +1491 -0
- package/src/lib/sage/saga-os-v3-release.ts +739 -0
- package/src/lib/sage/saga-os-v4-aureate.ts +1161 -0
- package/src/lib/sage/saga-os-v4.ts +212 -0
- package/src/lib/sage/saga-os-v5.ts +205 -0
- package/src/lib/sage/saga-os-v6.ts +346 -0
- package/src/lib/sage/saga-os-v7.ts +314 -0
- package/src/lib/sage/saga-os-v8.ts +505 -0
- package/src/lib/sage/saga-os.ts +329 -0
- package/src/lib/sage/taxonomy.ts +403 -0
- package/src/lib/sage/unified-roll.ts +524 -0
- package/src/lib/sage/worked-sketches.ts +44 -0
- package/src/lib/sage.ts +166 -0
- package/src/lib/scraper-debug-bus.ts +53 -0
- package/src/lib/scraper-enhanced.orig.ts +153 -0
- package/src/lib/scraper-enhanced.ts +10 -0
- package/src/lib/scraper-hardener.base.orig.ts +155 -0
- package/src/lib/scraper-hardener.base.ts +317 -0
- package/src/lib/scraper-hardener.ts +1 -0
- package/src/lib/scraper-palisade/palisade-adjudicator.orig.ts +419 -0
- package/src/lib/scraper-palisade/palisade-adjudicator.ts +13 -0
- package/src/lib/scraper-vnext/arbiter-omega.orig.ts +688 -0
- package/src/lib/scraper-vnext/arbiter-omega.ts +13 -0
- package/src/lib/scraper-vnext/canonical-portfolio-augments.ts +479 -0
- package/src/lib/scraper-vnext/canonical-portfolio-orchestrator.orig.ts +1529 -0
- package/src/lib/scraper-vnext/canonical-portfolio-orchestrator.ts +13 -0
- package/src/lib/scraper-vnext/conclave-omega.ts +1830 -0
- package/src/lib/scraper-vnext/content-extractor-v2.ts +226 -0
- package/src/lib/scraper-vnext/diagnostics-suite.ts +36 -0
- package/src/lib/scraper-vnext/epistemic-packer.ts +97 -0
- package/src/lib/scraper-vnext/fusion-v2.ts +65 -0
- package/src/lib/scraper-vnext/hydra-reader.orig.ts +1216 -0
- package/src/lib/scraper-vnext/hydra-reader.ts +13 -0
- package/src/lib/scraper-vnext/native-scraper-browser-vnext.orig.ts +91 -0
- package/src/lib/scraper-vnext/native-scraper-browser-vnext.ts +13 -0
- package/src/lib/scraper-vnext/nexus-consensus.orig.ts +905 -0
- package/src/lib/scraper-vnext/nexus-consensus.ts +13 -0
- package/src/lib/scraper-vnext/portfolio-consensus-adjudicator.ts +804 -0
- package/src/lib/scraper-vnext/portfolio-consensus-memory.ts +377 -0
- package/src/lib/scraper-vnext/portfolio-terminal-governor.ts +548 -0
- package/src/lib/scraper-vnext/query-strategist.orig.ts +224 -0
- package/src/lib/scraper-vnext/query-strategist.ts +423 -0
- package/src/lib/scraper-vnext/retrieval-accelerator.ts +708 -0
- package/src/lib/scraper-vnext/retrieval-audit-augments.ts +477 -0
- package/src/lib/scraper-vnext/retrieval-control-plane.ts +904 -0
- package/src/lib/scraper-vnext/retrieval-policy-augments.ts +358 -0
- package/src/lib/scraper-vnext/safe-fetch-v2.ts +157 -0
- package/src/lib/scraper-vnext/sentinel-omega.ts +56 -0
- package/src/lib/scraper-vnext/sentinel-orchestrator.ts +69 -0
- package/src/lib/scraper-vnext/sibyl-oracle.orig.ts +413 -0
- package/src/lib/scraper-vnext/sibyl-oracle.ts +13 -0
- package/src/lib/scraper-vnext/smart-read-v2.ts +55 -0
- package/src/lib/scraper-vnext/spa-rescue-bridge.ts +608 -0
- package/src/lib/scraper-vnext/strata-engine.orig.ts +2666 -0
- package/src/lib/scraper-vnext/strata-engine.ts +13 -0
- package/src/lib/scraper-vnext/structured-source-adapter.orig.ts +369 -0
- package/src/lib/scraper-vnext/structured-source-adapter.ts +34 -0
- package/src/lib/scraper-vnext/terminal-complete.ts +224 -0
- package/src/lib/scraper-vnext/terminal-final.ts +277 -0
- package/src/lib/scraper-vnext/terminal-saturation.ts +345 -0
- package/src/lib/scraper-vnext/terminal-wire.ts +425 -0
- package/src/lib/scraper-vnext/vanguard-titanium.orig.ts +467 -0
- package/src/lib/scraper-vnext/vanguard-titanium.ts +17 -0
- package/src/lib/search-cache.ts +138 -0
- package/src/lib/sloop-runner.base.ts +223 -0
- package/src/lib/sloop-runner.ts +104 -0
- package/src/lib/sloop.ts +114 -0
- package/src/lib/small-model-adapters.ts +82 -0
- package/src/lib/sscp.ts +86 -0
- package/src/lib/system-map.ts +54 -0
- package/src/lib/uni-node.ts +86 -0
- package/src/lib/universal-rigor-guard.ts +433 -0
- package/src/lib/v15-gate-testbed.ts +274 -0
- package/src/lib/v15-gemini.base.ts +89 -0
- package/src/lib/v15-gemini.ts +1 -0
- package/src/lib/v15-grounding.orig.ts +241 -0
- package/src/lib/v15-grounding.ts +79 -0
- package/src/lib/v15-pipeline.base.ts +769 -0
- package/src/lib/v15-pipeline.orig.ts +1154 -0
- package/src/lib/v15-pipeline.ts +674 -0
- package/src/lib/v15-questions.ts +64 -0
- package/src/lib/v15-rate-limiter.orig.ts +91 -0
- package/src/lib/v15-rate-limiter.ts +48 -0
- package/src/lib/v15-state.base.ts +87 -0
- package/src/lib/v15-state.ts +28 -0
- package/src/lib/visual-table-generator.tsx +207 -0
- package/src/lib/wasm-runtime.ts +133 -0
- package/src/lib/williams-persona-guide.ts +265 -0
- package/src/lib/williams-style.base.ts +464 -0
- package/src/lib/williams-style.ts +59 -0
- package/src/lib/worker-pool.ts +96 -0
- package/src/lib/writing-tiers.ts +125 -0
- package/src/main.orig.tsx +10 -0
- package/src/main.tsx +10 -0
- package/src/utils/cn.orig.ts +6 -0
- package/src/utils/cn.ts +6 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.json +31 -0
- package/unify.mjs +537 -0
- package/vite.config.ts +18 -0
|
@@ -0,0 +1,1582 @@
|
|
|
1
|
+
import { useEffect, useRef, useCallback, useState } from "react";
|
|
2
|
+
import { searchWithGroundingFallback, type GroundingBackend } from "../lib/jina";
|
|
3
|
+
import { prismaFetchHealth } from "../lib/connectors/prismafetch";
|
|
4
|
+
import {
|
|
5
|
+
generateSynthesizedResponse,
|
|
6
|
+
generateVerificationPlan,
|
|
7
|
+
testConnection,
|
|
8
|
+
MODELS,
|
|
9
|
+
type ModelId,
|
|
10
|
+
type ProviderId,
|
|
11
|
+
type VerificationHypothesis,
|
|
12
|
+
} from "../lib/models";
|
|
13
|
+
import { detectInjection, extractClaims, checkCoherence } from "../lib/defenses";
|
|
14
|
+
import { FAILURE_MODES, SUPERCLASS_SUMMARY } from "../lib/failure-modes";
|
|
15
|
+
import { TOTAL_DEFENSES } from "../lib/defense-registry";
|
|
16
|
+
import { StylePersonaPanel } from "./StylePersonaPanel";
|
|
17
|
+
import { HUD } from "./HUD";
|
|
18
|
+
import { LiveResourceHUD } from "./LiveResourceHUD";
|
|
19
|
+
import {
|
|
20
|
+
useAppState,
|
|
21
|
+
type ChatMessage,
|
|
22
|
+
type HypothesisEvidence,
|
|
23
|
+
type ToolResult,
|
|
24
|
+
} from "../lib/app-state";
|
|
25
|
+
import {
|
|
26
|
+
buildConstraintBlock,
|
|
27
|
+
extractConstraints,
|
|
28
|
+
sanitizeOutput,
|
|
29
|
+
summarizeConstraints,
|
|
30
|
+
} from "../lib/constraints";
|
|
31
|
+
import { runMultiPassPipeline, computeTemporalAnchor } from "../lib/pipeline";
|
|
32
|
+
import type { PipelineTrace } from "../lib/pipeline";
|
|
33
|
+
import { runNDeep } from "../lib/n-deep";
|
|
34
|
+
import { estTokens } from "../lib/live-telemetry";
|
|
35
|
+
import { MemoryMonitor, getSafeNDeepCap } from "./MemoryMonitor";
|
|
36
|
+
import { clearAllocationsByPrefix, readMemoryReport, safeClusterWidth, settleHeap } from "../lib/memory-governor";
|
|
37
|
+
import { buildTemplatePrompt, findTemplate } from "../lib/omega-templates";
|
|
38
|
+
import { buildSSCPReceipt, type SSCPLeaf, type EvidenceTier } from "../lib/sscp";
|
|
39
|
+
import type { ComputeRecord } from "../lib/compute-sandbox";
|
|
40
|
+
import { ComputeRecordsInline } from "./ComputeSandboxPanel";
|
|
41
|
+
import { resolveEntities } from "../lib/entity-resolver";
|
|
42
|
+
import { EntitySheetInline } from "./EntitySheetPanel";
|
|
43
|
+
import { ArtifactInline } from "./ArtifactPanel";
|
|
44
|
+
import { RichText } from "./RichText";
|
|
45
|
+
import { measureCoverage } from "../lib/coverage";
|
|
46
|
+
import {
|
|
47
|
+
buildArtifactPromptBlock,
|
|
48
|
+
resolveArtifactRequest,
|
|
49
|
+
shouldResolveArtifacts,
|
|
50
|
+
setLiveStockResolver,
|
|
51
|
+
type ArtifactResponse
|
|
52
|
+
} from "../lib/artifacts";
|
|
53
|
+
import { alphaVantageStockResolver } from "../lib/connectors/marketdata";
|
|
54
|
+
import { newState, applyEvidence, setAnchor } from "../lib/gbse/engine";
|
|
55
|
+
import { defaultConfig } from "../lib/gbse/config";
|
|
56
|
+
import { Verdict } from "../lib/gbse/types";
|
|
57
|
+
import { RelevanceGraph } from "../lib/gbse/graph";
|
|
58
|
+
import { runAtlasDR } from "../lib/atlas-dr";
|
|
59
|
+
import { runAdversarialRedTeam, buildRepairBlock } from "../lib/adversarial-engine";
|
|
60
|
+
import { throttle, resetThrottle } from "../lib/rpm-governor";
|
|
61
|
+
import { scoreAnswer } from "../lib/quality-score";
|
|
62
|
+
import { cleanOutput as cleanOutputBoundary } from "../lib/output-boundary";
|
|
63
|
+
import { diagnoseOutput, buildContinuationPrompt, spliceContinuation } from "../lib/continuation-detector";
|
|
64
|
+
import { hasCalcRequest, interceptCalcRequests, renderInterceptedCalcs } from "../lib/calc-interceptor";
|
|
65
|
+
import { runSloopReport } from "../lib/sloop-runner";
|
|
66
|
+
import { DebugTracePanel } from "./DebugTracePanel";
|
|
67
|
+
|
|
68
|
+
export function ChatApp({ onSubmitFromShared }: { onSubmitFromShared?: number } = {}) {
|
|
69
|
+
const {
|
|
70
|
+
messages, pushMessage, clearMessages,
|
|
71
|
+
input, setInput,
|
|
72
|
+
keys, setKeys,
|
|
73
|
+
statuses, setStatus,
|
|
74
|
+
model, setModel,
|
|
75
|
+
persona, reseedPersona,
|
|
76
|
+
busyState, setBusyState,
|
|
77
|
+
setLastRun,
|
|
78
|
+
searchDepth, setSearchDepth,
|
|
79
|
+
settings, setSetting,
|
|
80
|
+
pushDebugEvent, clearDebugEvents, debugEvents,
|
|
81
|
+
patchTelemetry, resetTelemetry, templates, setSscpReceipt, telemetry,
|
|
82
|
+
setBeliefState, setRelevanceGraph,
|
|
83
|
+
memory,
|
|
84
|
+
} = useAppState();
|
|
85
|
+
|
|
86
|
+
const [collapsedTools, setCollapsedTools] = useState<Record<string, boolean>>({});
|
|
87
|
+
const [showSettings, setShowSettings] = useState(false);
|
|
88
|
+
const [showModes, setShowModes] = useState(false);
|
|
89
|
+
const [showDebug, setShowDebug] = useState(false);
|
|
90
|
+
const [filterSuperclass, setFilterSuperclass] = useState<string | null>(null);
|
|
91
|
+
const endRef = useRef<HTMLDivElement>(null);
|
|
92
|
+
const taRef = useRef<HTMLTextAreaElement>(null);
|
|
93
|
+
const debugRef = useRef<HTMLDivElement>(null);
|
|
94
|
+
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
endRef.current?.scrollIntoView({ behavior: "smooth" });
|
|
97
|
+
}, [messages, busyState]);
|
|
98
|
+
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
if (debugRef.current) debugRef.current.scrollTop = debugRef.current.scrollHeight;
|
|
101
|
+
}, [debugEvents]);
|
|
102
|
+
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
if (taRef.current) {
|
|
105
|
+
taRef.current.style.height = "auto";
|
|
106
|
+
taRef.current.style.height = `${Math.min(taRef.current.scrollHeight, 160)}px`;
|
|
107
|
+
}
|
|
108
|
+
}, [input]);
|
|
109
|
+
|
|
110
|
+
const checkProviderStatus = useCallback(async (p: ProviderId | "jina" | "prismafetch", key: string) => {
|
|
111
|
+
if (!key) { setStatus(p, "idle"); return; }
|
|
112
|
+
setStatus(p, "testing");
|
|
113
|
+
pushDebugEvent(`Testing ${p} connection…`);
|
|
114
|
+
let ok = false;
|
|
115
|
+
if (p === "jina") {
|
|
116
|
+
try {
|
|
117
|
+
// Force the Jina path so the test actually exercises Jina, not the
|
|
118
|
+
// PrismaFetch primary that would otherwise be tried first.
|
|
119
|
+
const r = await searchWithGroundingFallback("veritas test", key, 1, {
|
|
120
|
+
forceJina: true,
|
|
121
|
+
allowJinaFallback: true,
|
|
122
|
+
onDebug: (msg) => pushDebugEvent(`[Jina test] ${msg}`),
|
|
123
|
+
});
|
|
124
|
+
ok = Array.isArray(r.results) && r.provider === "jina";
|
|
125
|
+
} catch {
|
|
126
|
+
ok = false;
|
|
127
|
+
}
|
|
128
|
+
} else if (p === "prismafetch") {
|
|
129
|
+
ok = await prismaFetchHealth(key);
|
|
130
|
+
} else {
|
|
131
|
+
ok = await testConnection(p as ProviderId, key);
|
|
132
|
+
}
|
|
133
|
+
setStatus(p, ok ? "connected" : "error");
|
|
134
|
+
pushDebugEvent(`${p}: ${ok ? "connected ✓" : "error ✗"}`);
|
|
135
|
+
}, [setStatus, pushDebugEvent]);
|
|
136
|
+
|
|
137
|
+
const auditAllConnectors = useCallback(() => {
|
|
138
|
+
(["claude", "grok", "deepseek", "gemini", "jina"] as (ProviderId | "jina")[]).forEach(p => {
|
|
139
|
+
const k = p === "jina" ? keys.jina : (keys[p as keyof typeof keys] as string);
|
|
140
|
+
if (k) checkProviderStatus(p, k);
|
|
141
|
+
});
|
|
142
|
+
if (settings.prismafetchEnabled) {
|
|
143
|
+
checkProviderStatus("prismafetch", settings.prismafetchUrl);
|
|
144
|
+
}
|
|
145
|
+
}, [keys, settings.prismafetchEnabled, settings.prismafetchUrl, checkProviderStatus]);
|
|
146
|
+
|
|
147
|
+
const updateKey = (provider: keyof typeof keys, val: string) => {
|
|
148
|
+
setKeys(prev => ({ ...prev, [provider]: val }));
|
|
149
|
+
if (["gemini", "claude", "grok", "deepseek", "jina"].includes(provider)) {
|
|
150
|
+
checkProviderStatus(provider as ProviderId | "jina", val);
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
const toggleToolCollapse = (msgId: string) => {
|
|
155
|
+
setCollapsedTools(prev => ({ ...prev, [msgId]: prev[msgId] === undefined ? false : !prev[msgId] }));
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const isSourceRichRefusal = (answer: string, sourceCount: number) => {
|
|
159
|
+
if (sourceCount < 8) return false;
|
|
160
|
+
return /\b(provided data|retrieved sources|source context|current source context|available evidence)\b[\s\S]{0,160}\b(does not contain|do not contain|lacks|lack|insufficient|no direct mention|no information regarding|cannot propose|unable to propose)\b/i.test(answer)
|
|
161
|
+
|| /A defensible answer can be built from the retrieved evidence/i.test(answer)
|
|
162
|
+
|| /safest source-backed synthesis is to frame the recommendation/i.test(answer);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const handleSubmit = useCallback(async () => {
|
|
166
|
+
if (!input.trim() || busyState !== null) return;
|
|
167
|
+
const startedAt = Date.now();
|
|
168
|
+
|
|
169
|
+
clearDebugEvents();
|
|
170
|
+
resetTelemetry();
|
|
171
|
+
resetThrottle(); // Prevent stale promise chain from prior run causing deadlock
|
|
172
|
+
patchTelemetry({ running: true, startedAt, phase: "Phase 1: Input Sanitization", entropy: 1 });
|
|
173
|
+
pushDebugEvent("Phase 1: Input Sanitization & Threat Quarantine");
|
|
174
|
+
setBusyState("Phase 1: Input Sanitization");
|
|
175
|
+
await new Promise(r => setTimeout(r, 100));
|
|
176
|
+
|
|
177
|
+
const injection = detectInjection(input);
|
|
178
|
+
const constraints = extractConstraints(input);
|
|
179
|
+
const anchor = computeTemporalAnchor(input);
|
|
180
|
+
patchTelemetry({ tokensIn: estTokens(input) });
|
|
181
|
+
|
|
182
|
+
pushDebugEvent(`Constraints detected: ${summarizeConstraints(constraints)}`);
|
|
183
|
+
pushDebugEvent(`Temporal anchor: ${anchor.currentDateHuman}${anchor.horizonEndHuman ? ` → ${anchor.horizonEndHuman}` : ""}`);
|
|
184
|
+
|
|
185
|
+
const userMsg: ChatMessage = {
|
|
186
|
+
id: `u-${Date.now()}`,
|
|
187
|
+
role: "user",
|
|
188
|
+
content: injection.blocked ? injection.sanitized : input,
|
|
189
|
+
ts: Date.now(),
|
|
190
|
+
injection: injection.blocked ? injection : undefined,
|
|
191
|
+
constraints,
|
|
192
|
+
};
|
|
193
|
+
pushMessage(userMsg);
|
|
194
|
+
setInput("");
|
|
195
|
+
|
|
196
|
+
patchTelemetry({ injectionsBlocked: injection.blocked ? injection.patterns.length : 0 });
|
|
197
|
+
if (injection.blocked) {
|
|
198
|
+
pushDebugEvent(`Injection blocked: ${injection.patterns.join(", ")}`);
|
|
199
|
+
patchTelemetry({ running: false, phase: "blocked" });
|
|
200
|
+
setBusyState(null);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const currentOption = MODELS.find(m => m.id === model);
|
|
205
|
+
if (!currentOption) { setBusyState(null); return; }
|
|
206
|
+
|
|
207
|
+
const provider = currentOption.provider;
|
|
208
|
+
const requiredKey = keys[provider as keyof typeof keys] as string;
|
|
209
|
+
const prismaPrimaryEnabled = settings.prismafetchEnabled;
|
|
210
|
+
// Jina is BACKUP-ONLY and only when a key exists. No key → auto-skip entirely.
|
|
211
|
+
const allowJinaBackup = !!keys.jina;
|
|
212
|
+
if (!requiredKey) {
|
|
213
|
+
pushMessage({ id: `a-${Date.now()}`, role: "assistant", content: `Please configure your ${provider.toUpperCase()} API key in Settings.`, ts: Date.now(), model });
|
|
214
|
+
setBusyState(null);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (prismaPrimaryEnabled) {
|
|
218
|
+
const localOk = await prismaFetchHealth(settings.prismafetchUrl);
|
|
219
|
+
setStatus("prismafetch", localOk ? "connected" : "error");
|
|
220
|
+
if (!localOk) {
|
|
221
|
+
pushDebugEvent(`PrismaFetch primary unreachable at ${settings.prismafetchUrl} — continuing to OG browser scraper${keys.jina ? ", then Jina backup if needed" : " (no Jina key — Jina skipped)"}.`);
|
|
222
|
+
}
|
|
223
|
+
} else {
|
|
224
|
+
pushDebugEvent(`PrismaFetch disabled · OG browser scraper is the workhorse${keys.jina ? " · Jina backup available" : " · no Jina key (skipped)"}.`);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ── Per-run URL dedup ledger ────────────────────────────────────
|
|
228
|
+
// Any URL already fetched (by either backend) is recorded here so the
|
|
229
|
+
// second-pass searches/reads short-circuit and never duplicate work.
|
|
230
|
+
const fetchedUrls = new Set<string>();
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
const history = messages
|
|
234
|
+
.filter(m => m.role === "user" || m.role === "assistant")
|
|
235
|
+
.map(m => ({ role: m.role, text: m.content }));
|
|
236
|
+
|
|
237
|
+
// ── OMEGA-FORGE pre-draft adversarial phases (v29 §181-§186) ──
|
|
238
|
+
// These run BEFORE search/synthesis so naive paradigms, worse
|
|
239
|
+
// alternatives, tournament exclusions, and H_neg are available to the
|
|
240
|
+
// rest of the chain rather than being decorative telemetry at the end.
|
|
241
|
+
const preDraftAtlas = runAtlasDR(input, "Candidate answer must produce a concrete, evidence-bounded recommendation rather than a generic refusal.");
|
|
242
|
+
pushDebugEvent(`Pre-draft ContraDraft: ${preDraftAtlas.tournament.allAlts.length} alternatives generated; winner=${preDraftAtlas.tournament.winner.id}`);
|
|
243
|
+
preDraftAtlas.tournament.exclusionProofs.slice(0, 3).forEach(p => pushDebugEvent(`Pre-draft Tournament exclusion: ${p}`));
|
|
244
|
+
pushDebugEvent(`Pre-draft Falsification H_neg: ${preDraftAtlas.falsification.h_neg.slice(0, 140)}`);
|
|
245
|
+
pushDebugEvent(`Pre-draft AntiAnchor quarantined: ${preDraftAtlas.antiAnchor.naiveParadigms.map(p => p.label).join("; ")}`);
|
|
246
|
+
|
|
247
|
+
// Phase 2: Initial grounding (PrismaFetch primary, OG browser scraper secondary, Jina backup)
|
|
248
|
+
setBusyState(`Phase 2: Grounding Search (depth ${searchDepth})`);
|
|
249
|
+
patchTelemetry({ phase: "Phase 2: Grounding Search", pipelineStage: 2, evidenceTier: "TOOL", entropy: 0.85 });
|
|
250
|
+
pushDebugEvent(`Grounding policy: PrismaFetch PRIMARY${prismaPrimaryEnabled ? "" : " (disabled)"} → OG browser scraper SECONDARY → Jina BACKUP${allowJinaBackup ? "" : " (disabled)"} · depth=${searchDepth}`);
|
|
251
|
+
const initialRun = await searchWithGroundingFallback(input, keys.jina, searchDepth, {
|
|
252
|
+
allowJinaFallback: allowJinaBackup,
|
|
253
|
+
prismaEnabled: prismaPrimaryEnabled,
|
|
254
|
+
prismafetchUrl: settings.prismafetchUrl,
|
|
255
|
+
onDebug: (msg) => pushDebugEvent(`[Grounding] ${msg}`),
|
|
256
|
+
});
|
|
257
|
+
const initial = initialRun.results;
|
|
258
|
+
const initialProvider: GroundingBackend = initialRun.provider;
|
|
259
|
+
initial.forEach((r) => { if (r.url) fetchedUrls.add(r.url); });
|
|
260
|
+
patchTelemetry({ searchCalls: 1, toolCalls: 1, sources: initial.length });
|
|
261
|
+
const initialBackendLabel = initialProvider === "jina"
|
|
262
|
+
? "Jina (backup)"
|
|
263
|
+
: initialProvider === "browser-scraper"
|
|
264
|
+
? "OG browser scraper (secondary)"
|
|
265
|
+
: "PrismaFetch (primary)";
|
|
266
|
+
pushDebugEvent(`Initial pass: ${initial.length} sources via ${initialBackendLabel} · ${fetchedUrls.size} URLs tracked for dedup`);
|
|
267
|
+
initial.forEach((r: { title: string }, i: number) => pushDebugEvent(` [S${i + 1}] ${r.title.slice(0, 60)}`));
|
|
268
|
+
|
|
269
|
+
const SOURCE_CONTENT_CAP = 800;
|
|
270
|
+
let initialWebData: ToolResult[] = initial.map((r) => ({
|
|
271
|
+
title: r.title,
|
|
272
|
+
url: r.url,
|
|
273
|
+
content: String((r.content || r.description || "").slice(0, SOURCE_CONTENT_CAP)),
|
|
274
|
+
phase: "initial" as const,
|
|
275
|
+
provider: initialProvider,
|
|
276
|
+
}));
|
|
277
|
+
|
|
278
|
+
// Phase 3+4: verification plan + second pass
|
|
279
|
+
let verificationPlan: HypothesisEvidence[] = [];
|
|
280
|
+
if (settings.deepResearch || settings.nDeepEnabled || settings.templateEnabled || initialWebData.length < 6) {
|
|
281
|
+
setBusyState("Phase 3: Hypothesis Planning");
|
|
282
|
+
patchTelemetry({ phase: "Phase 3: Hypothesis Planning", pipelineStage: 3, entropy: 0.7 });
|
|
283
|
+
pushDebugEvent("Running verification plan generation…");
|
|
284
|
+
const plan = await generateVerificationPlan({
|
|
285
|
+
provider, model, apiKey: requiredKey,
|
|
286
|
+
userMessage: input, retrievedWebData: initialWebData, conversationHistory: history,
|
|
287
|
+
});
|
|
288
|
+
patchTelemetry({ modelCalls: 1, hypotheses: plan.hypotheses.length });
|
|
289
|
+
pushDebugEvent(`Generated ${plan.hypotheses.length} hypotheses`);
|
|
290
|
+
plan.hypotheses.forEach(h => pushDebugEvent(` H: ${h.claim.slice(0, 80)}`));
|
|
291
|
+
|
|
292
|
+
setBusyState(`Phase 4: Second-Pass (${plan.hypotheses.length} hypotheses)`);
|
|
293
|
+
patchTelemetry({ phase: "Phase 4: Second-Pass Confirmation", pipelineStage: 4, entropy: 0.55 });
|
|
294
|
+
|
|
295
|
+
// Cluster search: preserve the user's chosen width unless memory is in
|
|
296
|
+
// the critical zone. Yield + free between waves so clustering does not
|
|
297
|
+
// pile up source bodies in memory.
|
|
298
|
+
const requestedWidth = settings.clusterSearch ? Math.max(2, settings.clusterSize) : 3;
|
|
299
|
+
const BATCH_SIZE = settings.clusterSearch ? safeClusterWidth(requestedWidth, readMemoryReport()) : 3;
|
|
300
|
+
if (settings.clusterSearch) pushDebugEvent(`Cluster search ON: ${plan.hypotheses.length} hypotheses in waves of ${BATCH_SIZE} (parallel)`);
|
|
301
|
+
for (let i = 0; i < plan.hypotheses.length; i += BATCH_SIZE) {
|
|
302
|
+
const batch = plan.hypotheses.slice(i, i + BATCH_SIZE);
|
|
303
|
+
// STREAMING EXTRACT-AND-DISCARD: instead of Promise.all() which
|
|
304
|
+
// retains ALL parallel raw scraper responses simultaneously
|
|
305
|
+
// (8 × 5 results × ~80KB raw body = 3MB+ peak → OOM), each promise
|
|
306
|
+
// compacts its own result the instant it resolves and nulls the
|
|
307
|
+
// raw response array, so peak live memory is bounded to a single
|
|
308
|
+
// in-flight response instead of the whole batch.
|
|
309
|
+
const batchPromises = batch.map((h) =>
|
|
310
|
+
searchWithGroundingFallback(h.searchQuery, keys.jina, searchDepth, {
|
|
311
|
+
allowJinaFallback: allowJinaBackup,
|
|
312
|
+
prismaEnabled: prismaPrimaryEnabled,
|
|
313
|
+
prismafetchUrl: settings.prismafetchUrl,
|
|
314
|
+
onDebug: (msg) => pushDebugEvent(`[Grounding] ${msg}`),
|
|
315
|
+
}).then((run) => {
|
|
316
|
+
const sourceProvider: GroundingBackend = run.provider;
|
|
317
|
+
const backendLabel = sourceProvider === "jina"
|
|
318
|
+
? "Jina (backup)"
|
|
319
|
+
: sourceProvider === "browser-scraper"
|
|
320
|
+
? "OG browser scraper (secondary)"
|
|
321
|
+
: "PrismaFetch (primary)";
|
|
322
|
+
const fresh: ToolResult[] = [];
|
|
323
|
+
let skipped = 0;
|
|
324
|
+
for (const res of run.results) {
|
|
325
|
+
if (!res.url) continue;
|
|
326
|
+
if (fetchedUrls.has(res.url)) { skipped++; continue; }
|
|
327
|
+
fetchedUrls.add(res.url);
|
|
328
|
+
fresh.push({
|
|
329
|
+
title: String((res.title || "").slice(0, 240)),
|
|
330
|
+
url: String(res.url),
|
|
331
|
+
content: String((res.content || res.description || "").slice(0, SOURCE_CONTENT_CAP)),
|
|
332
|
+
phase: "second-pass" as const,
|
|
333
|
+
hypothesis: h.claim,
|
|
334
|
+
provider: sourceProvider,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
pushDebugEvent(` Confirmed "${h.claim.slice(0, 50)}" via ${backendLabel} — ${fresh.length} new${skipped > 0 ? ` (${skipped} deduped)` : ""}`);
|
|
338
|
+
// Null the raw response array so V8 can release the original
|
|
339
|
+
// scraper bodies even before sibling promises resolve.
|
|
340
|
+
(run as { results: unknown }).results = [];
|
|
341
|
+
verificationPlan.push({
|
|
342
|
+
...(h as VerificationHypothesis),
|
|
343
|
+
status: fresh.length > 0 ? "CONFIRMED" : "UNCONFIRMED",
|
|
344
|
+
sources: fresh,
|
|
345
|
+
});
|
|
346
|
+
return fresh.length;
|
|
347
|
+
}).catch((e) => {
|
|
348
|
+
pushDebugEvent(`[Grounding] batch item failed: ${(e as Error).message}`);
|
|
349
|
+
return 0;
|
|
350
|
+
})
|
|
351
|
+
);
|
|
352
|
+
await Promise.all(batchPromises);
|
|
353
|
+
patchTelemetry({ searchCalls: i + batch.length + 1, toolCalls: i + batch.length + 1 });
|
|
354
|
+
await settleHeap(8);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const seen = new Set<string>();
|
|
359
|
+
const allCollectedSources = [...initialWebData, ...verificationPlan.flatMap(h => h.sources)];
|
|
360
|
+
let retrievedWebData = allCollectedSources
|
|
361
|
+
.filter(src => {
|
|
362
|
+
if (!src.url || seen.has(src.url)) return false;
|
|
363
|
+
seen.add(src.url);
|
|
364
|
+
return true;
|
|
365
|
+
})
|
|
366
|
+
// Keep full grounding breadth while relying on compacted 800-char snippets.
|
|
367
|
+
.slice(0, 32)
|
|
368
|
+
.map(src => ({
|
|
369
|
+
...src,
|
|
370
|
+
content: String((src.content || "").slice(0, SOURCE_CONTENT_CAP)),
|
|
371
|
+
}));
|
|
372
|
+
const finalDedupCount = allCollectedSources.length - retrievedWebData.length;
|
|
373
|
+
// Drop references to the large intermediate arrays as soon as the compact
|
|
374
|
+
// source set exists. JS GC is nondeterministic, but this prevents further
|
|
375
|
+
// retention through accidental use below.
|
|
376
|
+
initialWebData = [];
|
|
377
|
+
allCollectedSources.length = 0;
|
|
378
|
+
// Keep lightweight evidence previews for UI/Bayesian graph, but release
|
|
379
|
+
// duplicate source bodies now that retrievedWebData owns the full snippets.
|
|
380
|
+
for (const h of verificationPlan) {
|
|
381
|
+
h.sources = h.sources.slice(0, 4).map(s => ({ ...s, content: String((s.content || "").slice(0, 200)) }));
|
|
382
|
+
}
|
|
383
|
+
patchTelemetry({ sources: retrievedWebData.length });
|
|
384
|
+
|
|
385
|
+
// Minimum-source gate: do not proceed to synthesis with too little
|
|
386
|
+
// grounding. This prevents "0/4 confirmed" and generic non-answers.
|
|
387
|
+
const MIN_GROUNDED_SOURCES = settings.templateEnabled || settings.deepResearch || settings.nDeepEnabled ? 8 : 4;
|
|
388
|
+
if (retrievedWebData.length < MIN_GROUNDED_SOURCES) {
|
|
389
|
+
pushDebugEvent(`Minimum-source gate: only ${retrievedWebData.length}/${MIN_GROUNDED_SOURCES} sources — running targeted expansion searches`);
|
|
390
|
+
const expansionQueries = [
|
|
391
|
+
`${input} official source`,
|
|
392
|
+
`${input} NIH Guide NOFO NOSI`,
|
|
393
|
+
`${input} NIH RePORTER awarded projects`,
|
|
394
|
+
`${input} NIMHD NIMH NIA funding priorities`,
|
|
395
|
+
].slice(0, 4);
|
|
396
|
+
const expansionRuns = await Promise.all(expansionQueries.map(q => searchWithGroundingFallback(q, keys.jina, Math.max(5, searchDepth), {
|
|
397
|
+
allowJinaFallback: allowJinaBackup,
|
|
398
|
+
prismaEnabled: prismaPrimaryEnabled,
|
|
399
|
+
prismafetchUrl: settings.prismafetchUrl,
|
|
400
|
+
onDebug: (msg) => pushDebugEvent(`[Min-source] ${msg}`),
|
|
401
|
+
}).catch(() => ({ provider: "browser-scraper" as GroundingBackend, results: [] }))));
|
|
402
|
+
const expanded = [...retrievedWebData];
|
|
403
|
+
const expandedSeen = new Set(expanded.map(s => s.url));
|
|
404
|
+
for (const run of expansionRuns) {
|
|
405
|
+
for (const r of run.results) {
|
|
406
|
+
if (!r.url || expandedSeen.has(r.url)) continue;
|
|
407
|
+
expandedSeen.add(r.url);
|
|
408
|
+
expanded.push({ title: r.title, url: r.url, content: String((r.content || r.description || "").slice(0, SOURCE_CONTENT_CAP)), phase: "second-pass", provider: run.provider });
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
retrievedWebData = expanded.slice(0, 32);
|
|
412
|
+
patchTelemetry({ sources: retrievedWebData.length });
|
|
413
|
+
pushDebugEvent(`Minimum-source gate complete: ${retrievedWebData.length} source(s) available`);
|
|
414
|
+
}
|
|
415
|
+
const providerBreakdown = retrievedWebData.reduce<Record<string, number>>((acc, src) => {
|
|
416
|
+
const key = src.provider ?? "unknown";
|
|
417
|
+
acc[key] = (acc[key] ?? 0) + 1;
|
|
418
|
+
return acc;
|
|
419
|
+
}, {});
|
|
420
|
+
pushDebugEvent(`Total unique sources: ${retrievedWebData.length} · ${Object.entries(providerBreakdown).map(([k, v]) => `${k}=${v}`).join(", ")}${finalDedupCount > 0 ? ` · ${finalDedupCount} duplicate(s) collapsed at final merge` : ""}`);
|
|
421
|
+
|
|
422
|
+
// ── Legacy Bayesian engine — runs on EVERY chat input ──────────
|
|
423
|
+
// Builds a real belief state + signed PPR graph from the verification
|
|
424
|
+
// hypotheses and their confirmed/unconfirmed evidence. Drives the
|
|
425
|
+
// dashboard's HypothesisPanel / GraphView / HUD with live data.
|
|
426
|
+
try {
|
|
427
|
+
const cfg = defaultConfig();
|
|
428
|
+
const hypTexts = verificationPlan.length > 0
|
|
429
|
+
? verificationPlan.map(h => h.claim)
|
|
430
|
+
: [`${input.slice(0, 80)} — supported`, `${input.slice(0, 80)} — unsupported`];
|
|
431
|
+
const bState = newState(hypTexts, null, cfg);
|
|
432
|
+
const hids = Object.keys(bState.hyps);
|
|
433
|
+
const graph = new RelevanceGraph(cfg.graph);
|
|
434
|
+
graph.addNode("__query__", input.slice(0, 40), 1.0);
|
|
435
|
+
verificationPlan.forEach((h, i) => {
|
|
436
|
+
const hid = hids[i];
|
|
437
|
+
if (!hid) return;
|
|
438
|
+
graph.addNode(hid, h.claim.slice(0, 36), 0.5);
|
|
439
|
+
graph.addEdge("__query__", hid, 0.6);
|
|
440
|
+
// Each confirmed source = supporting evidence; unconfirmed = silent
|
|
441
|
+
h.sources.forEach((src, j) => {
|
|
442
|
+
const verdict = h.status === "CONFIRMED" ? Verdict.SUPPORT : Verdict.SILENT;
|
|
443
|
+
applyEvidence(bState, hid, {
|
|
444
|
+
sourceId: src.url || `s${i}-${j}`,
|
|
445
|
+
text: src.title.slice(0, 60),
|
|
446
|
+
verdict,
|
|
447
|
+
reliability: 0.7,
|
|
448
|
+
strength: h.confidence === "high" ? 0.8 : h.confidence === "medium" ? 0.6 : 0.4,
|
|
449
|
+
tokenCost: 0,
|
|
450
|
+
ts: Date.now(),
|
|
451
|
+
}, cfg);
|
|
452
|
+
const nodeId = `src:${i}:${j}`;
|
|
453
|
+
graph.addNode(nodeId, src.title.slice(0, 28), 0.4);
|
|
454
|
+
graph.addEdge(hid, nodeId, verdict === Verdict.SUPPORT ? 0.7 : 0.2);
|
|
455
|
+
});
|
|
456
|
+
if (h.status === "CONFIRMED") setAnchor(bState, hid, Verdict.SUPPORT, 0.6, cfg);
|
|
457
|
+
});
|
|
458
|
+
setBeliefState({ ...bState, hyps: { ...bState.hyps } });
|
|
459
|
+
setRelevanceGraph(graph);
|
|
460
|
+
patchTelemetry({ contradictions: verificationPlan.filter(h => h.status === "UNCONFIRMED").length });
|
|
461
|
+
pushDebugEvent(`Bayesian engine: ${hids.length} hypotheses, ${graph.nodes.size} graph nodes`);
|
|
462
|
+
} catch (e) {
|
|
463
|
+
pushDebugEvent(`Bayesian engine skipped: ${(e as Error).message}`);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Active OMEGA template (if enabled) — injected into synthesis prompt
|
|
467
|
+
const activeTemplate = settings.templateEnabled
|
|
468
|
+
? findTemplate(templates, settings.activeTemplateId)
|
|
469
|
+
: undefined;
|
|
470
|
+
const sloopBlock = settings.forceSloop
|
|
471
|
+
? `\n\nSLOOP LONG-FORM MODE: Produce a COMPLETE approximately ${settings.sloopPages}-page report. Every section must be fully written with substantive, detailed prose (no stubs, no bare headers, no 'see above'). Allocate roughly ${Math.max(350, settings.sloopPages * 450)}-${Math.max(700, settings.sloopPages * 700)} words. Expand each section with specifics, examples, calculations, and concrete implementation detail. Do not summarize prematurely.`
|
|
472
|
+
: "";
|
|
473
|
+
const templateBlock = (activeTemplate ? buildTemplatePrompt(activeTemplate, settings.styleMode) : "") + sloopBlock;
|
|
474
|
+
if (activeTemplate) pushDebugEvent(`Template applied: ${activeTemplate.name} (${settings.styleMode})`);
|
|
475
|
+
if (settings.forceSloop) pushDebugEvent(`SLOOP long-form mode ON — target ${settings.sloopPages} page(s)`);
|
|
476
|
+
|
|
477
|
+
// ── Deterministic Artifact Discovery (Equities) ────────────────
|
|
478
|
+
let artifactResponse: ArtifactResponse | undefined;
|
|
479
|
+
let artifactBlock = "";
|
|
480
|
+
if (shouldResolveArtifacts(input)) {
|
|
481
|
+
const seedTickers = new Set<string>([
|
|
482
|
+
...extractConstraints(input).explicitComparisonTargets,
|
|
483
|
+
...resolveEntities(retrievedWebData, input).entities.map(e => e.ticker),
|
|
484
|
+
]);
|
|
485
|
+
artifactResponse = await resolveArtifactRequest({
|
|
486
|
+
type: "stocks",
|
|
487
|
+
tickers: [...seedTickers].slice(0, 12),
|
|
488
|
+
windowStart: anchor.currentDate,
|
|
489
|
+
windowEnd: anchor.horizonEnd,
|
|
490
|
+
});
|
|
491
|
+
artifactBlock = buildArtifactPromptBlock(artifactResponse!);
|
|
492
|
+
pushDebugEvent(`Artifact store: ${artifactResponse!.resolved.length} resolved`);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
let responseText = "";
|
|
496
|
+
let pipelineTrace: PipelineTrace[] = [];
|
|
497
|
+
let usedMultiPass = false;
|
|
498
|
+
let sanitizerRemovedSegments = 0;
|
|
499
|
+
let computeRecords: ComputeRecord[] = [];
|
|
500
|
+
let entitySheet: import("../lib/entity-resolver").EntitySheet | undefined;
|
|
501
|
+
let adversarialPassesForUi: import("../lib/n-deep").NDeepPassSummary[] = [];
|
|
502
|
+
|
|
503
|
+
// Optional live market data resolver. If no key is present, artifacts
|
|
504
|
+
// resolve names/sectors only and numeric fields remain null.
|
|
505
|
+
setLiveStockResolver(keys.marketData
|
|
506
|
+
? (ticker) => alphaVantageStockResolver(ticker, keys.marketData)
|
|
507
|
+
: null);
|
|
508
|
+
|
|
509
|
+
if (settings.nDeepEnabled) {
|
|
510
|
+
// ── N-Deep recursive convergence engine ─────────────────────
|
|
511
|
+
const sourceCharTotal = retrievedWebData.reduce((sum, s) => sum + (s.content?.length ?? 0), 0);
|
|
512
|
+
const safeCap = getSafeNDeepCap(settings.nDeepMaxPasses ?? 4, sourceCharTotal);
|
|
513
|
+
if (safeCap < (settings.nDeepMaxPasses ?? 4)) {
|
|
514
|
+
pushDebugEvent(`[OOM guard] source load=${Math.round(sourceCharTotal/1024)}KB → capping N-Deep from ${settings.nDeepMaxPasses} to ${safeCap} passes`);
|
|
515
|
+
}
|
|
516
|
+
const maxIter = safeCap;
|
|
517
|
+
setBusyState(`Phase 5: N-Deep (max ${maxIter} passes)`);
|
|
518
|
+
patchTelemetry({ phase: "Phase 5: N-Deep recursive convergence", pipelineStage: 5, entropy: 0.4 });
|
|
519
|
+
const modelRpm = MODELS.find(m => m.id === model)?.rpm ?? 10;
|
|
520
|
+
pushDebugEvent(`N-Deep ON — recursive draft↔critique with adversarial gates (cap ${maxIter}) · model RPM=${modelRpm}`);
|
|
521
|
+
|
|
522
|
+
// OOM FIX: N-Deep replaces the 4-stage pipeline, not stacks on top.
|
|
523
|
+
// Running both causes 6+ LLM calls with retained strings → OOM.
|
|
524
|
+
// Single direct synthesis → N-Deep critique/repair is memory-safe.
|
|
525
|
+
const cappedSources = retrievedWebData.slice(0, 16);
|
|
526
|
+
let initialDraft = "";
|
|
527
|
+
if (settings.forceSloop) {
|
|
528
|
+
pushDebugEvent(`N-Deep entry: SLOOP sectioned report (${settings.sloopPages}p) → N-Deep critique/repair`);
|
|
529
|
+
const sloop = await runSloopReport({
|
|
530
|
+
query: input,
|
|
531
|
+
baseParams: { provider, model, apiKey: requiredKey, userMessage: "", conversationHistory: [] },
|
|
532
|
+
sources: cappedSources,
|
|
533
|
+
templateId: settings.templateEnabled ? settings.activeTemplateId : undefined,
|
|
534
|
+
pages: settings.sloopPages,
|
|
535
|
+
onTrace: (t) => { pipelineTrace.push(t); patchTelemetry({ pipelineStage: t.stage }); },
|
|
536
|
+
onDebug: (msg) => pushDebugEvent(msg),
|
|
537
|
+
});
|
|
538
|
+
initialDraft = sloop.finalText;
|
|
539
|
+
patchTelemetry({ modelCalls: sloop.calls });
|
|
540
|
+
} else {
|
|
541
|
+
const draftPrompt = [
|
|
542
|
+
"You MUST produce a substantive, concrete answer to the question below.",
|
|
543
|
+
"Do NOT say 'the data does not contain', 'insufficient evidence', or 'I cannot propose'.",
|
|
544
|
+
"If the user asks for a grant topic, PRODUCE the topic, specific aims, candidate NIH IC, innovation, approach, and risks.",
|
|
545
|
+
"If some details are missing from the sources, fill them with well-reasoned scientific proposals clearly marked as [PROPOSED] rather than refusing entirely.",
|
|
546
|
+
"Use the retrieved sources for grounding.",
|
|
547
|
+
templateBlock ? `\nTEMPLATE:\n${templateBlock}` : "",
|
|
548
|
+
"",
|
|
549
|
+
`USER QUESTION: ${input}`,
|
|
550
|
+
].filter(Boolean).join("\n");
|
|
551
|
+
pushDebugEvent("N-Deep entry: single direct synthesis → N-Deep critique/repair (memory-safe path)");
|
|
552
|
+
initialDraft = await generateSynthesizedResponse({
|
|
553
|
+
provider, model, apiKey: requiredKey, userMessage: draftPrompt,
|
|
554
|
+
retrievedWebData: cappedSources,
|
|
555
|
+
conversationHistory: history.slice(-2),
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Critical OOM fix: strip source bodies before N-Deep. Pass 1 already
|
|
560
|
+
// has the grounded draft; repairs only need defect list + digest.
|
|
561
|
+
const nDeepSourceStubs = cappedSources.map(s => ({ title: s.title, url: s.url, content: "" }));
|
|
562
|
+
cappedSources.length = 0;
|
|
563
|
+
const nDeep = await runNDeep({
|
|
564
|
+
userQuery: input,
|
|
565
|
+
initialDraft,
|
|
566
|
+
fullSloopReport: settings.forceSloop,
|
|
567
|
+
// Judge = the user's selected core model. The judge accepts/rejects
|
|
568
|
+
// each critic-proposed section revision; ties are broken by Arena
|
|
569
|
+
// intelligence rank (see ./lib/model-intelligence).
|
|
570
|
+
judgeModel: model,
|
|
571
|
+
baseParams: {
|
|
572
|
+
provider, model, apiKey: requiredKey, userMessage: "",
|
|
573
|
+
retrievedWebData: nDeepSourceStubs,
|
|
574
|
+
conversationHistory: [],
|
|
575
|
+
},
|
|
576
|
+
maxPasses: maxIter,
|
|
577
|
+
rpm: modelRpm,
|
|
578
|
+
onDebug: (msg) => pushDebugEvent(msg),
|
|
579
|
+
onTrace: (t) => {
|
|
580
|
+
pipelineTrace.push(t);
|
|
581
|
+
patchTelemetry({ pipelineStage: t.stage });
|
|
582
|
+
},
|
|
583
|
+
});
|
|
584
|
+
|
|
585
|
+
responseText = nDeep.finalText;
|
|
586
|
+
usedMultiPass = true;
|
|
587
|
+
sanitizerRemovedSegments = 0;
|
|
588
|
+
patchTelemetry({ tokensOut: estTokens(responseText), modelCalls: nDeep.totalLlmCalls + 1 });
|
|
589
|
+
setBusyState(`Phase 5: N-Deep ${nDeep.stable ? "stable" : "hit cap"} at pass ${nDeep.passes.length}`);
|
|
590
|
+
pushDebugEvent(`N-Deep complete: ${nDeep.passes.length} passes, ${nDeep.totalLlmCalls + 1} LLM calls, ${nDeep.stable ? "stable" : "hit hard cap"}`);
|
|
591
|
+
} else if (settings.deepResearch) {
|
|
592
|
+
// ── 4-stage multi-pass pipeline ─────────────────────────────
|
|
593
|
+
setBusyState("Phase 5: Multi-Pass Pipeline (Stage 2: Logic Engine)");
|
|
594
|
+
patchTelemetry({ phase: "Phase 5: Multi-Pass Pipeline", pipelineStage: 5, modelCalls: 2, entropy: 0.4 });
|
|
595
|
+
const modelRpm = MODELS.find(m => m.id === model)?.rpm ?? 10;
|
|
596
|
+
pushDebugEvent(`Deep Research ON — 4-stage pipeline · model RPM=${modelRpm}`);
|
|
597
|
+
const result = await runMultiPassPipeline({
|
|
598
|
+
userQuery: input,
|
|
599
|
+
retrievedData: retrievedWebData,
|
|
600
|
+
baseParams: { provider, model, apiKey: requiredKey, userMessage: "", conversationHistory: history },
|
|
601
|
+
persona,
|
|
602
|
+
templateBlock,
|
|
603
|
+
artifactBlock,
|
|
604
|
+
memory,
|
|
605
|
+
templateId: settings.templateEnabled ? settings.activeTemplateId : undefined,
|
|
606
|
+
rpm: modelRpm,
|
|
607
|
+
onTrace: (t) => {
|
|
608
|
+
pipelineTrace.push(t);
|
|
609
|
+
patchTelemetry({ pipelineStage: t.stage });
|
|
610
|
+
pushDebugEvent(`[Stage ${t.stage}] ${t.label}`);
|
|
611
|
+
if (t.data) pushDebugEvent(` data: ${JSON.stringify(t.data).slice(0, 120)}`);
|
|
612
|
+
},
|
|
613
|
+
});
|
|
614
|
+
responseText = result.finalText;
|
|
615
|
+
pipelineTrace = result.trace;
|
|
616
|
+
usedMultiPass = result.usedMultiPass;
|
|
617
|
+
sanitizerRemovedSegments = result.sanitizerRemovedSegments;
|
|
618
|
+
computeRecords = result.computeRecords;
|
|
619
|
+
entitySheet = result.entitySheet ?? undefined;
|
|
620
|
+
artifactResponse = result.artifactResponse ?? undefined;
|
|
621
|
+
adversarialPassesForUi = result.adversarialPasses ?? [];
|
|
622
|
+
if (computeRecords.length > 0) {
|
|
623
|
+
patchTelemetry({ computeCalls: computeRecords.length });
|
|
624
|
+
pushDebugEvent(`[Stage 2.5] Deterministic compute: ${computeRecords.filter(r => r.ok).length}/${computeRecords.length} verified`);
|
|
625
|
+
}
|
|
626
|
+
patchTelemetry({ tokensOut: estTokens(responseText), modelCalls: 4 });
|
|
627
|
+
setBusyState("Phase 5: Multi-Pass Pipeline complete");
|
|
628
|
+
} else {
|
|
629
|
+
// ── Standard single-pass synthesis ───────────────────────────
|
|
630
|
+
setBusyState("Phase 5: Constrained Synthesis");
|
|
631
|
+
patchTelemetry({ phase: "Phase 5: Constrained Synthesis", pipelineStage: 5, entropy: 0.4 });
|
|
632
|
+
pushDebugEvent("Single-pass synthesis with constraint block");
|
|
633
|
+
// Entity resolution — deterministic, no LLM call
|
|
634
|
+
entitySheet = resolveEntities(retrievedWebData, input);
|
|
635
|
+
pushDebugEvent(`Entity resolver: ${entitySheet.entities.length} verified, ${entitySheet.weakEntities.length} weak`);
|
|
636
|
+
entitySheet.entities.forEach(e => pushDebugEvent(` ${e.ticker} (${e.name}): ${Object.keys(e.facts).length} facts from ${e.sourceCount} sources`));
|
|
637
|
+
if (shouldResolveArtifacts(input)) {
|
|
638
|
+
const tickers = Array.from(new Set([...constraints.namedEntities, ...entitySheet.entities.map(e => e.ticker)])).slice(0, 12);
|
|
639
|
+
artifactResponse = await resolveArtifactRequest({ type: "earnings", tickers, windowStart: anchor.currentDate, windowEnd: anchor.horizonEnd });
|
|
640
|
+
pushDebugEvent(`Artifact resolver: ${artifactResponse.resolved.length} resolved, ${artifactResponse.unresolved.length} unresolved`);
|
|
641
|
+
}
|
|
642
|
+
const constraintBlock = buildConstraintBlock(constraints);
|
|
643
|
+
const styleBlock = persona.systemPromptFragment;
|
|
644
|
+
const systemOverlay = [
|
|
645
|
+
anchor.anchorStatement,
|
|
646
|
+
"",
|
|
647
|
+
constraintBlock,
|
|
648
|
+
"",
|
|
649
|
+
entitySheet.promptBlock,
|
|
650
|
+
artifactResponse ? buildArtifactPromptBlock(artifactResponse) : "",
|
|
651
|
+
"",
|
|
652
|
+
"STYLE PERSONA (apply silently; never label this in output):",
|
|
653
|
+
styleBlock,
|
|
654
|
+
templateBlock ? "\n" + templateBlock : "",
|
|
655
|
+
"",
|
|
656
|
+
"FINAL OUTPUT INSTRUCTIONS:",
|
|
657
|
+
"- Begin with the first substantive sentence of the answer.",
|
|
658
|
+
"- Use ONLY the ticker symbols, company names, prices, and facts from the VERIFIED ENTITY SHEET above.",
|
|
659
|
+
"- If a ticker has no verified facts, say so — do NOT invent numbers.",
|
|
660
|
+
"- NEVER include 'Constraints:', 'Persona:', 'Structure:', 'Direct Answer:', 'Question:' labels.",
|
|
661
|
+
"- NEVER print outlines, plans, scratchpads, or bullet-dumps before prose.",
|
|
662
|
+
"- Cite inline as [Source N] only. Weave them into prose, never list them separately first.",
|
|
663
|
+
"- If a catalyst falls OUTSIDE the stated time window, label it with ⚠️ Outside window.",
|
|
664
|
+
"- End with a single decisive bottom-line sentence. Nothing after it.",
|
|
665
|
+
...(settings.templateEnabled && (settings.activeTemplateId === "OMEGA-SCIENCE" || settings.activeTemplateId === "NIH-GRANT-SRF") ? [
|
|
666
|
+
"",
|
|
667
|
+
"NIH / SCIENCE STRUCTURAL GATES (mandatory):",
|
|
668
|
+
"- NEVER leave bracketed placeholders ([list of...], [description of...], [insert...], [TBD]). Fill every slot with specific content or omit it.",
|
|
669
|
+
"- A 'SABV' heading MUST contain a real Sex-as-a-Biological-Variable plan (sex stratification/disaggregation). NEVER label statistical nesting/GLMM/clustering as SABV.",
|
|
670
|
+
"- Clustering/GLMM/HLM content belongs under 'Statistical Analysis', not under SABV.",
|
|
671
|
+
"- Name the awarding NIH Institute/Center (e.g., NIMH, NIMHD); coordinating offices (OBSSR, ODP) cannot award grants.",
|
|
672
|
+
"- Hypothetical/projected outcomes go under 'Expected Outcomes / Impact', never under a 'Results' heading.",
|
|
673
|
+
"- Do not import another agency's framework labels (e.g., ARPA-H priorities) into an NIH proposal.",
|
|
674
|
+
] : []),
|
|
675
|
+
].join("\n");
|
|
676
|
+
const synthesisUser = `Answer the user's question directly. Use only retrieved evidence, deterministic entity facts, artifact facts, and verified calculations.\n\nUSER QUESTION: ${input}`;
|
|
677
|
+
|
|
678
|
+
const rawResponse = await generateSynthesizedResponse({
|
|
679
|
+
provider, model, apiKey: requiredKey,
|
|
680
|
+
userMessage: synthesisUser,
|
|
681
|
+
retrievedWebData: retrievedWebData.slice(0, 20),
|
|
682
|
+
conversationHistory: history.slice(-6),
|
|
683
|
+
extraSystem: systemOverlay,
|
|
684
|
+
});
|
|
685
|
+
patchTelemetry({ modelCalls: 2, tokensOut: estTokens(rawResponse) });
|
|
686
|
+
pushDebugEvent(`Raw response length: ${rawResponse.length} chars`);
|
|
687
|
+
|
|
688
|
+
const san = sanitizeOutput(rawResponse);
|
|
689
|
+
responseText = san.cleaned;
|
|
690
|
+
sanitizerRemovedSegments = san.removedSegments;
|
|
691
|
+
if (san.removedSegments > 0) pushDebugEvent(`Sanitizer removed ${san.removedSegments} leaked block(s): ${san.notes.join("; ")}`);
|
|
692
|
+
|
|
693
|
+
// ── Adversarial gate on the standard path too (1 batched call) ──
|
|
694
|
+
const stdDomain = settings.templateEnabled && (settings.activeTemplateId === "OMEGA-SCIENCE" || settings.activeTemplateId === "NIH-GRANT-SRF") ? "science" : undefined;
|
|
695
|
+
const stdRpm = MODELS.find(m => m.id === model)?.rpm ?? 10;
|
|
696
|
+
try {
|
|
697
|
+
const report = await runAdversarialRedTeam(responseText, input, { provider, model, apiKey: requiredKey, userMessage: "", conversationHistory: [] }, {
|
|
698
|
+
domain: stdDomain, rpm: stdRpm, onDebug: (m) => pushDebugEvent(`[Adversarial] ${m}`),
|
|
699
|
+
});
|
|
700
|
+
const blocking = report.defects.filter(d => d.severity === "critical" || d.severity === "major");
|
|
701
|
+
if (blocking.length > 0) {
|
|
702
|
+
pushDebugEvent(`Adversarial found ${blocking.length} blocking defect(s) — applying one repair pass`);
|
|
703
|
+
const repairPrompt = `Revise the DRAFT to fix every listed defect. Keep what was correct. Output ONLY the corrected final answer — no commentary, no placeholders.\n\n${buildRepairBlock(report.defects)}\n\nUSER ASK: ${input}\n\nDRAFT:\n${responseText}`;
|
|
704
|
+
const repaired = await throttle(
|
|
705
|
+
() => generateSynthesizedResponse({ provider, model, apiKey: requiredKey, userMessage: repairPrompt, conversationHistory: [] }),
|
|
706
|
+
{ rpm: stdRpm, onWait: (ms) => pushDebugEvent(`[Adversarial] RPM throttle: waiting ${ms}ms`) },
|
|
707
|
+
);
|
|
708
|
+
responseText = sanitizeOutput(repaired).cleaned;
|
|
709
|
+
pushDebugEvent("Adversarial repair pass applied ✓");
|
|
710
|
+
} else {
|
|
711
|
+
pushDebugEvent("Adversarial review: no blocking defects ✓");
|
|
712
|
+
}
|
|
713
|
+
} catch (e) {
|
|
714
|
+
pushDebugEvent(`Adversarial gate skipped: ${(e as Error).message}`);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
patchTelemetry({ sanitizerStrips: sanitizerRemovedSegments });
|
|
718
|
+
|
|
719
|
+
// Source-rich refusal guard: if we have many sources but the model emits a
|
|
720
|
+
// generic "the data does not contain..." non-answer, force one targeted
|
|
721
|
+
// repair using the actual retrieved sources. This prevents the observed
|
|
722
|
+
// failure where 37-92 sources yielded an unusable negative response.
|
|
723
|
+
if (isSourceRichRefusal(responseText, retrievedWebData.length)) {
|
|
724
|
+
pushDebugEvent(`Source-rich refusal detected with ${retrievedWebData.length} sources — forcing targeted synthesis repair`);
|
|
725
|
+
const sourceDigest = retrievedWebData
|
|
726
|
+
.slice(0, 24)
|
|
727
|
+
.map((s, i) => `[Source ${i + 1}] ${s.title}\n${s.url}\n${(s.content || "").slice(0, 900)}`)
|
|
728
|
+
.join("\n\n---\n\n");
|
|
729
|
+
const repairPrompt = [
|
|
730
|
+
"The prior answer was rejected because it claimed the source context lacked relevant data despite a source-rich retrieval set.",
|
|
731
|
+
"Use the sources below to produce the requested answer directly. Do not say the data is insufficient unless you name the exact missing field and explain why every relevant source fails to support it.",
|
|
732
|
+
"For NIH grant-topic requests: produce a concrete topic, candidate awarding IC(s), rationale, Specific Aims, innovation, approach, preliminary-data requirement, safety plan if digital/clinical, analytic plan, and risks.",
|
|
733
|
+
"Do not emit raw [Source N] placeholders. Write finished prose only.",
|
|
734
|
+
"",
|
|
735
|
+
`USER ASK: ${input}`,
|
|
736
|
+
"",
|
|
737
|
+
"SOURCE DIGEST:",
|
|
738
|
+
sourceDigest,
|
|
739
|
+
].join("\n");
|
|
740
|
+
const repaired = await throttle(
|
|
741
|
+
() => generateSynthesizedResponse({ provider, model, apiKey: requiredKey, userMessage: repairPrompt, retrievedWebData: retrievedWebData.slice(0, 16), conversationHistory: [] }),
|
|
742
|
+
{ rpm: MODELS.find(m => m.id === model)?.rpm ?? 10, onWait: (ms) => pushDebugEvent(`[Source-rich repair] RPM throttle: waiting ${ms}ms`) },
|
|
743
|
+
);
|
|
744
|
+
const repairedSan = sanitizeOutput(repaired);
|
|
745
|
+
responseText = repairedSan.cleaned || repaired;
|
|
746
|
+
sanitizerRemovedSegments += repairedSan.removedSegments;
|
|
747
|
+
pushDebugEvent("Source-rich refusal repair applied ✓");
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
if (/\bVERIFICATION PLAN MODE\b|\bHypotheses:\s*\n|\bSearch Queries:\s*\n|I am now proceeding to verify/i.test(responseText)) {
|
|
751
|
+
pushDebugEvent("Verification-plan leakage detected in final answer — forcing synthesis-only repair");
|
|
752
|
+
const repairPrompt = [
|
|
753
|
+
"The previous draft leaked verification-plan text. Convert it into the FINAL answer now.",
|
|
754
|
+
"Do not include 'Verification Plan Mode', 'Hypotheses', 'Search Queries', or 'I am now proceeding'.",
|
|
755
|
+
"Produce the concrete answer requested by the user using the sources provided.",
|
|
756
|
+
"",
|
|
757
|
+
`USER ASK: ${input}`,
|
|
758
|
+
"",
|
|
759
|
+
"DRAFT TO CONVERT:",
|
|
760
|
+
responseText.slice(0, 5000),
|
|
761
|
+
].join("\n");
|
|
762
|
+
const fixed = await throttle(
|
|
763
|
+
() => generateSynthesizedResponse({ provider, model, apiKey: requiredKey, userMessage: repairPrompt, retrievedWebData: retrievedWebData.slice(0, 12), conversationHistory: [] }),
|
|
764
|
+
{ rpm: MODELS.find(m => m.id === model)?.rpm ?? 10 },
|
|
765
|
+
);
|
|
766
|
+
responseText = cleanOutputBoundary(fixed).cleaned;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// Empty-only fallback. Never replace a real short answer with a canned shell.
|
|
770
|
+
if (retrievedWebData.length > 0 && responseText.trim().length < 30) {
|
|
771
|
+
pushDebugEvent(`Empty answer detected (${responseText.trim().length} chars) — emitting minimal source pointer`);
|
|
772
|
+
const top = retrievedWebData.slice(0, 3);
|
|
773
|
+
responseText = `The model returned an empty draft. Retrieved sources are available for a grounded answer: ${top.map(s => s.title).filter(Boolean).join("; ")}. Please retry with 4-Stage or SLOOP enabled.`;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// Global final boundary pass for ALL modes (standard, 4-stage, N-Deep).
|
|
777
|
+
// This catches scratchpad / verification-plan / XML leakage even if a
|
|
778
|
+
// path only used sanitizeOutput earlier.
|
|
779
|
+
const boundary = cleanOutputBoundary(responseText);
|
|
780
|
+
if (boundary.removedSegments > 0 || boundary.cleaned !== responseText) {
|
|
781
|
+
pushDebugEvent(`Output boundary cleaner: removed ${boundary.removedSegments} leaked segment(s)${boundary.notes.length ? ` · ${boundary.notes.join("; ")}` : ""}`);
|
|
782
|
+
responseText = boundary.cleaned;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// ── Truncation Detection & Auto-Continuation ────────────────────
|
|
786
|
+
// If the model output has empty section headers or ends mid-sentence,
|
|
787
|
+
// it likely hit max_tokens. Fire ONE continuation pass to fill missing
|
|
788
|
+
// sections — but ONLY if the draft is small enough that growing it won't
|
|
789
|
+
// risk OOM. Very large drafts (SLOOP) are accepted as-is to avoid the
|
|
790
|
+
// continuation→splice→OOM crash chain reported in the logs.
|
|
791
|
+
const MAX_CONTINUATION_INPUT = 28_000; // chars; above this, skip to avoid OOM
|
|
792
|
+
const diag = diagnoseOutput(responseText);
|
|
793
|
+
if (diag.truncated && responseText.length < MAX_CONTINUATION_INPUT) {
|
|
794
|
+
pushDebugEvent(`[Truncation guard] ${diag.reason}${diag.emptySections.length ? ` · empty sections: ${diag.emptySections.slice(0, 6).join(", ")}` : ""}`);
|
|
795
|
+
try {
|
|
796
|
+
setBusyState("Phase 5.5: Continuation Pass (filling truncated sections)");
|
|
797
|
+
const contPrompt = buildContinuationPrompt(input, responseText, diag.emptySections);
|
|
798
|
+
const contRpm = MODELS.find(m => m.id === model)?.rpm ?? 10;
|
|
799
|
+
const continuation = await throttle(
|
|
800
|
+
() => generateSynthesizedResponse({
|
|
801
|
+
provider, model, apiKey: requiredKey, userMessage: contPrompt,
|
|
802
|
+
retrievedWebData: retrievedWebData.slice(0, 8),
|
|
803
|
+
conversationHistory: [],
|
|
804
|
+
}),
|
|
805
|
+
{ rpm: contRpm, onWait: (ms) => pushDebugEvent(`[Continuation] RPM throttle: waiting ${ms}ms`) },
|
|
806
|
+
);
|
|
807
|
+
if (continuation && continuation.trim().length > 100) {
|
|
808
|
+
const cleanedCont = cleanOutputBoundary(continuation).cleaned;
|
|
809
|
+
const spliced = spliceContinuation(responseText, cleanedCont);
|
|
810
|
+
// Hard cap the spliced result so post-processing can't blow the heap.
|
|
811
|
+
responseText = spliced.length > 60_000 ? spliced.slice(0, 60_000) : spliced;
|
|
812
|
+
pushDebugEvent(`[Truncation guard] Continuation spliced: ${diag.emptySections.length} sections filled · total ${responseText.length} chars`);
|
|
813
|
+
} else {
|
|
814
|
+
pushDebugEvent(`[Truncation guard] Continuation returned empty — keeping prior draft`);
|
|
815
|
+
}
|
|
816
|
+
} catch (e) {
|
|
817
|
+
pushDebugEvent(`[Truncation guard] Continuation failed: ${(e as Error).message} — keeping prior draft`);
|
|
818
|
+
}
|
|
819
|
+
} else if (diag.truncated) {
|
|
820
|
+
pushDebugEvent(`[Truncation guard] draft is ${responseText.length} chars (> ${MAX_CONTINUATION_INPUT}) — skipping continuation to prevent OOM; accepting as-is`);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// Global heap-safety cap: no final answer is allowed to exceed 60KB in
|
|
824
|
+
// working memory. This prevents the post-processing chain (calc, boundary,
|
|
825
|
+
// quality, claim extraction) from operating on a runaway SLOOP string.
|
|
826
|
+
if (responseText.length > 60_000) {
|
|
827
|
+
pushDebugEvent(`[Heap guard] response ${responseText.length} chars → capping to 60KB for safe post-processing`);
|
|
828
|
+
responseText = responseText.slice(0, 60_000);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// ── CALC-REQUEST Interceptor ───────────────────────────────────
|
|
832
|
+
// If the model emitted "CALC REQUEST: ..." or "please confirm the
|
|
833
|
+
// required clusters", run the math deterministically and rewrite the
|
|
834
|
+
// answer with verified numbers (one repair call, only if needed).
|
|
835
|
+
if (hasCalcRequest(responseText)) {
|
|
836
|
+
const calcs = interceptCalcRequests(responseText);
|
|
837
|
+
const okCalcs = calcs.filter(c => c.record.ok);
|
|
838
|
+
pushDebugEvent(`[Calc interceptor] detected un-executed calc request → ran ${okCalcs.length}/${calcs.length} deterministic compute(s)`);
|
|
839
|
+
if (okCalcs.length > 0) {
|
|
840
|
+
computeRecords = [...computeRecords, ...okCalcs.map(c => c.record)];
|
|
841
|
+
const calcBlock = renderInterceptedCalcs(okCalcs);
|
|
842
|
+
try {
|
|
843
|
+
setBusyState("Phase 5.6: Injecting deterministic calculations");
|
|
844
|
+
const rewritePrompt = [
|
|
845
|
+
"Your prior draft asked the application to perform calculations. The application has now computed them.",
|
|
846
|
+
"Rewrite the draft, replacing every 'CALC REQUEST' / 'please confirm' with the EXACT verified numbers below.",
|
|
847
|
+
"Do NOT emit any further calc requests. Keep all other content. Output only the finished answer.",
|
|
848
|
+
"",
|
|
849
|
+
calcBlock,
|
|
850
|
+
"",
|
|
851
|
+
`USER ASK: ${input.slice(0, 300)}`,
|
|
852
|
+
"",
|
|
853
|
+
"DRAFT TO FIX:",
|
|
854
|
+
responseText.slice(0, 6000),
|
|
855
|
+
].join("\n");
|
|
856
|
+
const rewritten = await throttle(
|
|
857
|
+
() => generateSynthesizedResponse({ provider, model, apiKey: requiredKey, userMessage: rewritePrompt, conversationHistory: [] }),
|
|
858
|
+
{ rpm: MODELS.find(m => m.id === model)?.rpm ?? 10 },
|
|
859
|
+
);
|
|
860
|
+
if (rewritten && rewritten.trim().length > 100) {
|
|
861
|
+
responseText = cleanOutputBoundary(rewritten).cleaned;
|
|
862
|
+
pushDebugEvent("[Calc interceptor] draft rewritten with verified numbers ✓");
|
|
863
|
+
}
|
|
864
|
+
} catch (e) {
|
|
865
|
+
pushDebugEvent(`[Calc interceptor] rewrite failed: ${(e as Error).message}`);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// Phase 6: Atomic grounding
|
|
871
|
+
setBusyState("Phase 6: Atomic Grounding");
|
|
872
|
+
pushDebugEvent("Extracting and grounding claims…");
|
|
873
|
+
const rawClaims = extractClaims(responseText);
|
|
874
|
+
const groundedClaims = rawClaims.map(claim => {
|
|
875
|
+
let matchedIndex: number | undefined;
|
|
876
|
+
const srcMatch = claim.text.match(/\[(?:Source\s*)?(\d+)\]/i);
|
|
877
|
+
if (srcMatch?.[1]) {
|
|
878
|
+
const idx = parseInt(srcMatch[1], 10) - 1;
|
|
879
|
+
if (idx >= 0 && idx < retrievedWebData.length) matchedIndex = idx;
|
|
880
|
+
}
|
|
881
|
+
if (matchedIndex === undefined) {
|
|
882
|
+
const kw = claim.searchQuery.toLowerCase().split(" ").filter(Boolean);
|
|
883
|
+
for (let i = 0; i < retrievedWebData.length; i++) {
|
|
884
|
+
const txt = `${retrievedWebData[i].title} ${retrievedWebData[i].content}`.toLowerCase();
|
|
885
|
+
if (kw.filter(k => txt.includes(k)).length >= Math.min(2, Math.max(1, kw.length))) {
|
|
886
|
+
matchedIndex = i; break;
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
return {
|
|
891
|
+
id: claim.id, text: claim.text,
|
|
892
|
+
status: (matchedIndex !== undefined ? "VERIFIED" : "UNVERIFIED") as "VERIFIED" | "UNVERIFIED",
|
|
893
|
+
sourceIndex: matchedIndex,
|
|
894
|
+
failureClass: claim.failureClass, solution: claim.solution,
|
|
895
|
+
};
|
|
896
|
+
});
|
|
897
|
+
const coherence = checkCoherence(
|
|
898
|
+
rawClaims.map(c => ({ ...c, searchQuery: "" })),
|
|
899
|
+
messages.flatMap(m => m.claims ? m.claims.map(c => ({ ...c, searchQuery: "" })) : []),
|
|
900
|
+
);
|
|
901
|
+
const verified = groundedClaims.filter(c => c.status === "VERIFIED").length;
|
|
902
|
+
pushDebugEvent(`Claims: ${groundedClaims.length} total, ${verified} verified, ${groundedClaims.length - verified} unverified`);
|
|
903
|
+
const cov = measureCoverage({
|
|
904
|
+
query: input,
|
|
905
|
+
constraints,
|
|
906
|
+
sources: retrievedWebData,
|
|
907
|
+
answer: responseText,
|
|
908
|
+
verifiedClaims: verified,
|
|
909
|
+
totalClaims: groundedClaims.length,
|
|
910
|
+
});
|
|
911
|
+
pushDebugEvent(`Measured coverage: ${cov.numerator}/${cov.denominator} facets (${(cov.coverage * 100).toFixed(0)}%)`);
|
|
912
|
+
const qualityReport = scoreAnswer({
|
|
913
|
+
query: input,
|
|
914
|
+
answer: responseText,
|
|
915
|
+
sourceCount: retrievedWebData.length,
|
|
916
|
+
verifiedClaims: verified,
|
|
917
|
+
totalClaims: groundedClaims.length,
|
|
918
|
+
computeRecords,
|
|
919
|
+
activeTemplateId: settings.templateEnabled ? settings.activeTemplateId : undefined,
|
|
920
|
+
});
|
|
921
|
+
pushDebugEvent(`Quality score: ${qualityReport.overall}/10 · ${qualityReport.items.map(i => `${i.name}=${i.score}`).join(", ")}`);
|
|
922
|
+
|
|
923
|
+
// Phase 7: REAL SSCP / Merkle receipt over the actual ledger
|
|
924
|
+
setBusyState("Phase 7: SSCP Merkle Seal");
|
|
925
|
+
patchTelemetry({ phase: "Phase 7: SSCP Merkle Seal", pipelineStage: 7, entropy: 0.15 });
|
|
926
|
+
const evidenceTier: EvidenceTier = retrievedWebData.length > 0 ? "TOOL" : "DERIV";
|
|
927
|
+
const claimLeaves: SSCPLeaf[] = groundedClaims.map(c => ({ domain: "claim", text: c.text.slice(0, 200), status: c.status }));
|
|
928
|
+
const toolLeaves: SSCPLeaf[] = retrievedWebData.map(s => ({ domain: "tool", text: s.url, status: "RETRIEVED" }));
|
|
929
|
+
const gateLeaves: SSCPLeaf[] = [
|
|
930
|
+
{ domain: "gate", text: "injection-screen", status: injection.blocked ? "BLOCKED" : "PASS" },
|
|
931
|
+
{ domain: "gate", text: "constraint-extraction", status: "PASS" },
|
|
932
|
+
{ domain: "gate", text: "temporal-anchor", status: anchor.horizonEnd ? "BOUND" : "PASS" },
|
|
933
|
+
{ domain: "gate", text: "sanitizer", status: sanitizerRemovedSegments > 0 ? `STRIPPED:${sanitizerRemovedSegments}` : "PASS" },
|
|
934
|
+
{ domain: "gate", text: "claim-grounding", status: `${verified}/${groundedClaims.length}` },
|
|
935
|
+
{ domain: "gate", text: "deterministic-compute", status: computeRecords.length > 0 ? `${computeRecords.filter(r => r.ok).length}/${computeRecords.length} verified` : "none" },
|
|
936
|
+
];
|
|
937
|
+
const evidenceLeaves: SSCPLeaf[] = [
|
|
938
|
+
...verificationPlan.map(h => ({ domain: "evidence", text: h.claim.slice(0, 120), status: h.status })),
|
|
939
|
+
...computeRecords.map(r => ({ domain: "compute", text: `${r.label}: ${JSON.stringify(r.result)}`, status: r.ok ? "VERIFIED" : "FAILED" })),
|
|
940
|
+
];
|
|
941
|
+
let receipt = null;
|
|
942
|
+
try {
|
|
943
|
+
receipt = await buildSSCPReceipt({
|
|
944
|
+
claims: claimLeaves, tools: toolLeaves, gates: gateLeaves, evidence: evidenceLeaves,
|
|
945
|
+
evidenceTier, allGatesPass: !injection.blocked,
|
|
946
|
+
});
|
|
947
|
+
setSscpReceipt(receipt);
|
|
948
|
+
pushDebugEvent(`SSCP receipt sealed: ${receipt.stateRootHash.slice(0, 16)}… (${receipt.leafCount} leaves)`);
|
|
949
|
+
} catch (e) {
|
|
950
|
+
pushDebugEvent(`SSCP seal skipped: ${(e as Error).message}`);
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
patchTelemetry({
|
|
954
|
+
claimsTotal: groundedClaims.length, claimsVerified: verified,
|
|
955
|
+
measuredCoverage: cov.coverage, coverageNumerator: cov.numerator, coverageDenominator: cov.denominator,
|
|
956
|
+
tokensOut: estTokens(responseText), elapsedMs: Date.now() - startedAt,
|
|
957
|
+
sscpHash: receipt?.stateRootHash ?? null, evidenceTier, entropy: 0.1,
|
|
958
|
+
});
|
|
959
|
+
|
|
960
|
+
const assistantMsg: ChatMessage = {
|
|
961
|
+
id: `a-${Date.now()}`,
|
|
962
|
+
role: "assistant",
|
|
963
|
+
content: responseText,
|
|
964
|
+
ts: Date.now(),
|
|
965
|
+
model,
|
|
966
|
+
toolResults: retrievedWebData.length > 0 ? retrievedWebData.slice(0, 32).map((s) => ({ ...s, content: (s.content || "").slice(0, 800) })) : undefined,
|
|
967
|
+
// Cap verificationPlan source bodies — they duplicate toolResults content
|
|
968
|
+
// and were a primary OOM vector (hypotheses × 5 sources × full content).
|
|
969
|
+
verificationPlan: verificationPlan.map(h => ({
|
|
970
|
+
...h,
|
|
971
|
+
sources: h.sources.slice(0, 3).map(s => ({ ...s, content: (s.content || "").slice(0, 200) })),
|
|
972
|
+
})),
|
|
973
|
+
claims: groundedClaims,
|
|
974
|
+
coherence,
|
|
975
|
+
constraints,
|
|
976
|
+
sanitizerRemovedSegments,
|
|
977
|
+
usedMultiPass,
|
|
978
|
+
pipelineTrace: pipelineTrace.length > 0 ? pipelineTrace.map(t => ({ ...t, data: t.data ? JSON.stringify(t.data).slice(0, 300) : undefined })) : undefined,
|
|
979
|
+
computeRecords: computeRecords.length > 0 ? computeRecords : undefined,
|
|
980
|
+
entitySheet,
|
|
981
|
+
artifactResponse,
|
|
982
|
+
qualityReport,
|
|
983
|
+
adversarialPasses: adversarialPassesForUi.length > 0 ? adversarialPassesForUi : undefined,
|
|
984
|
+
};
|
|
985
|
+
setCollapsedTools(prev => ({ ...prev, [assistantMsg.id]: true }));
|
|
986
|
+
pushMessage(assistantMsg);
|
|
987
|
+
pushDebugEvent("✓ Response delivered");
|
|
988
|
+
|
|
989
|
+
// Run ATLAS-DR adversarial gates
|
|
990
|
+
const atlasDR = runAtlasDR(input, responseText.slice(0, 200));
|
|
991
|
+
pushDebugEvent(`ATLAS-DR: Tournament winner=${atlasDR.tournament.winner.id}, Falsification=${atlasDR.falsification.rigorAudit}`);
|
|
992
|
+
|
|
993
|
+
setLastRun({
|
|
994
|
+
query: input, model, finalAnswer: responseText,
|
|
995
|
+
totalTokens: estTokens(input) + estTokens(responseText),
|
|
996
|
+
elapsedMs: Date.now() - startedAt,
|
|
997
|
+
sources: retrievedWebData.length,
|
|
998
|
+
verifiedClaims: verified,
|
|
999
|
+
totalClaims: groundedClaims.length,
|
|
1000
|
+
constraints, startedAt, finishedAt: Date.now(),
|
|
1001
|
+
pipelineTrace: pipelineTrace.length > 0 ? pipelineTrace.slice(-40).map(t => ({ ...t, data: t.data ? JSON.stringify(t.data).slice(0, 200) : undefined })) : undefined,
|
|
1002
|
+
artifactResponse,
|
|
1003
|
+
measuredCoverage: cov.coverage,
|
|
1004
|
+
coverageNumerator: cov.numerator,
|
|
1005
|
+
coverageDenominator: cov.denominator,
|
|
1006
|
+
});
|
|
1007
|
+
patchTelemetry({ running: false, phase: "complete" });
|
|
1008
|
+
} catch (err) {
|
|
1009
|
+
const msg = (err as Error)?.message || "Unknown error";
|
|
1010
|
+
pushDebugEvent(`ERROR: ${msg}`);
|
|
1011
|
+
patchTelemetry({ running: false, phase: "error" });
|
|
1012
|
+
pushMessage({ id: `a-${Date.now()}`, role: "assistant", content: `Generation Failure: ${msg}`, ts: Date.now(), model });
|
|
1013
|
+
}
|
|
1014
|
+
clearAllocationsByPrefix("pipeline.");
|
|
1015
|
+
setBusyState(null);
|
|
1016
|
+
}, [input, busyState, model, keys, persona, searchDepth, settings, messages, templates, pushMessage, setBusyState, setInput, setLastRun, pushDebugEvent, clearDebugEvents, patchTelemetry, resetTelemetry, setSscpReceipt, setBeliefState, setRelevanceGraph]);
|
|
1017
|
+
|
|
1018
|
+
useEffect(() => {
|
|
1019
|
+
if (onSubmitFromShared && onSubmitFromShared > 0) handleSubmit();
|
|
1020
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1021
|
+
}, [onSubmitFromShared]);
|
|
1022
|
+
|
|
1023
|
+
const totalClaims = messages.flatMap(m => m.claims || []).length;
|
|
1024
|
+
const totalVerified = messages.flatMap(m => m.claims || []).filter(c => c.status === "VERIFIED").length;
|
|
1025
|
+
const totalBlocked = messages.filter(m => m.injection?.blocked).length;
|
|
1026
|
+
const filteredModes = filterSuperclass ? FAILURE_MODES.filter(fm => fm.superclass === filterSuperclass) : FAILURE_MODES;
|
|
1027
|
+
|
|
1028
|
+
return (
|
|
1029
|
+
<div className="flex flex-col bg-zinc-50 text-zinc-900" style={{ minHeight: "calc(100vh - 88px)" }}>
|
|
1030
|
+
{/* Sub-header */}
|
|
1031
|
+
<header className="sticky top-0 z-30 border-b border-zinc-200 bg-white shadow-sm">
|
|
1032
|
+
<div className="mx-auto flex max-w-5xl items-center justify-between gap-3 px-4 py-2">
|
|
1033
|
+
<div className="flex items-center gap-3">
|
|
1034
|
+
<div>
|
|
1035
|
+
<h1 className="text-sm font-bold text-zinc-900">Chat</h1>
|
|
1036
|
+
<p className="text-[11px] text-zinc-500">Retrieve-first · deterministic constraints · 126 defenses</p>
|
|
1037
|
+
</div>
|
|
1038
|
+
</div>
|
|
1039
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
1040
|
+
<select
|
|
1041
|
+
value={model}
|
|
1042
|
+
onChange={e => setModel(e.target.value as ModelId)}
|
|
1043
|
+
className="rounded-md border border-zinc-300 bg-white px-2 py-1.5 text-xs font-medium text-zinc-700 shadow-sm focus:border-indigo-500 focus:outline-none"
|
|
1044
|
+
>
|
|
1045
|
+
<optgroup label="Gemma 4 / Gemini 3.x">
|
|
1046
|
+
{MODELS.filter(m => m.provider === "gemini" && (m.id.startsWith("gemini-3") || m.id.startsWith("gemma-4"))).map(m => (
|
|
1047
|
+
<option key={m.id} value={m.id}>{m.label}{m.preview ? " ⚠" : ""}</option>
|
|
1048
|
+
))}
|
|
1049
|
+
</optgroup>
|
|
1050
|
+
<optgroup label="Gemini 2.x">
|
|
1051
|
+
{MODELS.filter(m => m.provider === "gemini" && m.id.startsWith("gemini-2")).map(m => (
|
|
1052
|
+
<option key={m.id} value={m.id}>{m.label}</option>
|
|
1053
|
+
))}
|
|
1054
|
+
</optgroup>
|
|
1055
|
+
<optgroup label="Gemma 3">
|
|
1056
|
+
{MODELS.filter(m => m.provider === "gemini" && m.id.startsWith("gemma-3")).map(m => (
|
|
1057
|
+
<option key={m.id} value={m.id}>{m.label}</option>
|
|
1058
|
+
))}
|
|
1059
|
+
</optgroup>
|
|
1060
|
+
<optgroup label="Claude">{MODELS.filter(m => m.provider === "claude").map(m => <option key={m.id} value={m.id}>{m.label}</option>)}</optgroup>
|
|
1061
|
+
<optgroup label="Grok">{MODELS.filter(m => m.provider === "grok").map(m => <option key={m.id} value={m.id}>{m.label}</option>)}</optgroup>
|
|
1062
|
+
<optgroup label="DeepSeek">{MODELS.filter(m => m.provider === "deepseek").map(m => <option key={m.id} value={m.id}>{m.label}</option>)}</optgroup>
|
|
1063
|
+
</select>
|
|
1064
|
+
<button onClick={() => setShowSettings(!showSettings)} className={`rounded-md px-2.5 py-1.5 text-xs font-medium transition-colors ${showSettings ? "bg-zinc-900 text-white" : "bg-zinc-100 text-zinc-700 hover:bg-zinc-200"}`}>Keys</button>
|
|
1065
|
+
<button onClick={() => setShowModes(!showModes)} className={`rounded-md px-2.5 py-1.5 text-xs font-medium transition-colors ${showModes ? "bg-indigo-700 text-white" : "bg-indigo-50 text-indigo-700 hover:bg-indigo-100"}`}>{TOTAL_DEFENSES} Defenses</button>
|
|
1066
|
+
<button onClick={() => { setShowDebug(!showDebug); }} className={`rounded-md px-2.5 py-1.5 text-xs font-medium transition-colors ${showDebug ? "bg-emerald-700 text-white" : "bg-emerald-50 text-emerald-700 hover:bg-emerald-100"}`}>Debug</button>
|
|
1067
|
+
<label className="inline-flex cursor-pointer items-center gap-1.5 rounded-md border border-zinc-200 px-2.5 py-1.5 text-xs font-medium">
|
|
1068
|
+
<input type="checkbox" checked={settings.deepResearch} onChange={e => setSetting("deepResearch", e.target.checked)} className="h-3.5 w-3.5 rounded" />
|
|
1069
|
+
<span className={settings.deepResearch ? "text-indigo-700" : "text-zinc-600"}>4-Stage</span>
|
|
1070
|
+
</label>
|
|
1071
|
+
<label className="inline-flex cursor-pointer items-center gap-1.5 rounded-md border border-zinc-200 px-2.5 py-1.5 text-xs font-medium" title="Power user: recursive adversarial refinement until stable">
|
|
1072
|
+
<input type="checkbox" checked={settings.nDeepEnabled} onChange={e => setSetting("nDeepEnabled", e.target.checked)} className="h-3.5 w-3.5 rounded" />
|
|
1073
|
+
<span className={settings.nDeepEnabled ? "text-violet-700 font-bold" : "text-zinc-600"}>⚡ N-Deep</span>
|
|
1074
|
+
</label>
|
|
1075
|
+
{settings.nDeepEnabled && (
|
|
1076
|
+
<div className="inline-flex items-center gap-1.5 rounded-md border border-violet-200 bg-violet-50 px-2 py-1 text-[10px]" title="N-Deep max passes">
|
|
1077
|
+
<input type="range" min={1} max={20} value={settings.nDeepMaxPasses}
|
|
1078
|
+
onChange={e => setSetting("nDeepMaxPasses", parseInt(e.target.value))}
|
|
1079
|
+
className="h-1 w-14 cursor-pointer appearance-none rounded bg-violet-200 accent-violet-600" />
|
|
1080
|
+
<span className="font-mono font-bold text-violet-800">{settings.nDeepMaxPasses}</span>
|
|
1081
|
+
</div>
|
|
1082
|
+
)}
|
|
1083
|
+
<label className="inline-flex cursor-pointer items-center gap-1.5 rounded-md border border-zinc-200 px-2.5 py-1.5 text-xs font-medium" title="Run all hypothesis searches as one parallel cluster (faster)">
|
|
1084
|
+
<input type="checkbox" checked={settings.clusterSearch} onChange={e => setSetting("clusterSearch", e.target.checked)} className="h-3.5 w-3.5 rounded" />
|
|
1085
|
+
<span className={settings.clusterSearch ? "text-sky-700" : "text-zinc-600"}>⚡ Cluster</span>
|
|
1086
|
+
</label>
|
|
1087
|
+
{settings.clusterSearch && (
|
|
1088
|
+
<div className="inline-flex items-center gap-1.5 rounded-md border border-sky-200 bg-sky-50 px-2 py-1 text-[10px]" title="Parallel searches per cluster wave">
|
|
1089
|
+
<input type="range" min={2} max={16} value={settings.clusterSize}
|
|
1090
|
+
onChange={e => setSetting("clusterSize", parseInt(e.target.value))}
|
|
1091
|
+
className="h-1 w-14 cursor-pointer appearance-none rounded bg-sky-200 accent-sky-600" />
|
|
1092
|
+
<span className="font-mono font-bold text-sky-800">{settings.clusterSize}</span>
|
|
1093
|
+
</div>
|
|
1094
|
+
)}
|
|
1095
|
+
<label className="inline-flex cursor-pointer items-center gap-1.5 rounded-md border border-zinc-200 px-2.5 py-1.5 text-xs font-medium" title="SLOOP: long-form multi-page report (works on any model)">
|
|
1096
|
+
<input type="checkbox" checked={settings.forceSloop} onChange={e => setSetting("forceSloop", e.target.checked)} className="h-3.5 w-3.5 rounded" />
|
|
1097
|
+
<span className={settings.forceSloop ? "text-amber-700 font-bold" : "text-zinc-600"}>📄 SLOOP</span>
|
|
1098
|
+
</label>
|
|
1099
|
+
{settings.forceSloop && (
|
|
1100
|
+
<div className="inline-flex items-center gap-1.5 rounded-md border border-amber-200 bg-amber-50 px-2 py-1 text-[10px]" title="Target report page count">
|
|
1101
|
+
<input type="range" min={2} max={30} value={settings.sloopPages}
|
|
1102
|
+
onChange={e => setSetting("sloopPages", parseInt(e.target.value))}
|
|
1103
|
+
className="h-1 w-16 cursor-pointer appearance-none rounded bg-amber-200 accent-amber-600" />
|
|
1104
|
+
<span className="font-mono font-bold text-amber-800">{settings.sloopPages}p</span>
|
|
1105
|
+
</div>
|
|
1106
|
+
)}
|
|
1107
|
+
<MemoryMonitor />
|
|
1108
|
+
</div>
|
|
1109
|
+
</div>
|
|
1110
|
+
|
|
1111
|
+
{/* Connector status bar */}
|
|
1112
|
+
<div className="border-t border-zinc-100 bg-zinc-50/90 px-4 py-1">
|
|
1113
|
+
<div className="mx-auto flex max-w-5xl items-center justify-between text-xs">
|
|
1114
|
+
<div className="flex items-center gap-3 overflow-x-auto">
|
|
1115
|
+
<span className="shrink-0 text-[10px] font-bold uppercase tracking-wider text-zinc-400">Connectors:</span>
|
|
1116
|
+
{(["claude", "grok", "deepseek", "gemini", "jina", "prismafetch"] as (ProviderId | "jina" | "prismafetch")[]).map(conn => {
|
|
1117
|
+
const st = statuses[conn];
|
|
1118
|
+
const hasKey = conn === "prismafetch"
|
|
1119
|
+
? settings.prismafetchEnabled
|
|
1120
|
+
: !!(keys[conn as keyof typeof keys] as string);
|
|
1121
|
+
const label = conn === "jina"
|
|
1122
|
+
? "Jina"
|
|
1123
|
+
: conn === "prismafetch"
|
|
1124
|
+
? "PrismaFetch"
|
|
1125
|
+
: conn.charAt(0).toUpperCase() + conn.slice(1);
|
|
1126
|
+
return (
|
|
1127
|
+
<div key={conn} className="flex items-center gap-1 shrink-0">
|
|
1128
|
+
<span className="text-zinc-600">{label}</span>
|
|
1129
|
+
<span className={`inline-flex items-center gap-0.5 rounded px-1 py-0.5 text-[10px] font-mono font-bold ${st === "connected" ? "bg-emerald-100 text-emerald-800" : st === "error" ? "bg-rose-100 text-rose-800" : st === "testing" ? "bg-amber-100 text-amber-800 animate-pulse" : hasKey ? "bg-blue-100 text-blue-800" : "bg-zinc-100 text-zinc-500"}`}>
|
|
1130
|
+
<span className={`h-1.5 w-1.5 rounded-full ${st === "connected" ? "bg-emerald-500" : st === "error" ? "bg-rose-500" : st === "testing" ? "bg-amber-500" : hasKey ? "bg-blue-500" : "bg-zinc-400"}`} />
|
|
1131
|
+
{st === "connected" ? "OK" : st === "error" ? "ERR" : st === "testing" ? "…" : hasKey ? "RDY" : "–"}
|
|
1132
|
+
</span>
|
|
1133
|
+
</div>
|
|
1134
|
+
);
|
|
1135
|
+
})}
|
|
1136
|
+
</div>
|
|
1137
|
+
<button onClick={auditAllConnectors} className="shrink-0 text-[10px] font-bold text-indigo-600 hover:text-indigo-800 uppercase">Audit all</button>
|
|
1138
|
+
</div>
|
|
1139
|
+
</div>
|
|
1140
|
+
</header>
|
|
1141
|
+
|
|
1142
|
+
{/* Settings panel */}
|
|
1143
|
+
{showSettings && (
|
|
1144
|
+
<div className="border-b border-zinc-200 bg-white">
|
|
1145
|
+
<div className="mx-auto max-w-5xl px-4 py-4">
|
|
1146
|
+
<h2 className="mb-3 text-xs font-bold uppercase tracking-wider text-zinc-400">API Keys</h2>
|
|
1147
|
+
<div className="grid gap-3 sm:grid-cols-2 md:grid-cols-3">
|
|
1148
|
+
{([
|
|
1149
|
+
{ id: "claude", name: "Claude (Anthropic)", placeholder: "sk-ant-..." },
|
|
1150
|
+
{ id: "grok", name: "Grok (xAI)", placeholder: "xai-..." },
|
|
1151
|
+
{ id: "deepseek", name: "DeepSeek", placeholder: "sk-..." },
|
|
1152
|
+
{ id: "gemini", name: "Gemini / Gemma (Google)", placeholder: "AIzaSy..." },
|
|
1153
|
+
{ id: "jina", name: "Jina AI (cloud backup only)", placeholder: "jina_..." },
|
|
1154
|
+
{ id: "marketData", name: "Market data (Alpha Vantage optional)", placeholder: "alpha_vantage_key" },
|
|
1155
|
+
] as { id: keyof typeof keys; name: string; placeholder: string }[]).map(p => (
|
|
1156
|
+
<div key={p.id}>
|
|
1157
|
+
<div className="mb-1 flex items-center justify-between">
|
|
1158
|
+
<label className="text-xs font-semibold text-zinc-700">{p.name}</label>
|
|
1159
|
+
<button onClick={() => checkProviderStatus(p.id as ProviderId | "jina", keys[p.id] as string)} className="text-[10px] font-bold text-indigo-600 hover:text-indigo-700 uppercase">Test</button>
|
|
1160
|
+
</div>
|
|
1161
|
+
<input type="password" value={(keys[p.id] as string) ?? ""} onChange={e => updateKey(p.id, e.target.value)} placeholder={p.placeholder}
|
|
1162
|
+
className="w-full rounded-md border border-zinc-300 px-3 py-1.5 text-xs focus:border-indigo-500 focus:outline-none" />
|
|
1163
|
+
</div>
|
|
1164
|
+
))}
|
|
1165
|
+
<div className="rounded-lg border border-zinc-200 bg-zinc-50 p-3 sm:col-span-2 md:col-span-3">
|
|
1166
|
+
<div className="mb-2 flex items-center justify-between gap-2">
|
|
1167
|
+
<label className="text-xs font-semibold text-zinc-700">PrismaFetch primary scraper</label>
|
|
1168
|
+
<button onClick={() => checkProviderStatus("prismafetch", settings.prismafetchUrl)} className="text-[10px] font-bold text-indigo-600 hover:text-indigo-700 uppercase">Test</button>
|
|
1169
|
+
</div>
|
|
1170
|
+
<div className="grid gap-3 md:grid-cols-[1fr_auto_auto] md:items-center">
|
|
1171
|
+
<input
|
|
1172
|
+
type="text"
|
|
1173
|
+
value={settings.prismafetchUrl}
|
|
1174
|
+
onChange={e => setSetting("prismafetchUrl", e.target.value)}
|
|
1175
|
+
placeholder="http://127.0.0.1:8080"
|
|
1176
|
+
className="w-full rounded-md border border-zinc-300 px-3 py-1.5 text-xs focus:border-indigo-500 focus:outline-none"
|
|
1177
|
+
/>
|
|
1178
|
+
<label className="inline-flex items-center gap-2 text-xs text-zinc-700">
|
|
1179
|
+
<input type="checkbox" checked={settings.prismafetchEnabled} onChange={e => setSetting("prismafetchEnabled", e.target.checked)} className="h-3.5 w-3.5 rounded" />
|
|
1180
|
+
Enabled
|
|
1181
|
+
</label>
|
|
1182
|
+
<label className="inline-flex items-center gap-2 text-xs text-zinc-700">
|
|
1183
|
+
<input type="checkbox" checked={settings.prismafetchAutoFallback} onChange={e => setSetting("prismafetchAutoFallback", e.target.checked)} className="h-3.5 w-3.5 rounded" />
|
|
1184
|
+
Allow Jina cloud backup if both local scrapers fail
|
|
1185
|
+
</label>
|
|
1186
|
+
</div>
|
|
1187
|
+
<p className="mt-2 text-[11px] text-zinc-500">Runtime order is PrismaFetch primary, built-in OG browser scraper secondary, Jina cloud backup last.</p>
|
|
1188
|
+
</div>
|
|
1189
|
+
<div>
|
|
1190
|
+
<label className="mb-1 block text-xs font-semibold text-zinc-700">Search Depth ({searchDepth})</label>
|
|
1191
|
+
<input type="range" min="1" max="50" value={searchDepth} onChange={e => setSearchDepth(parseInt(e.target.value))} className="w-full h-2 bg-zinc-200 rounded-lg appearance-none cursor-pointer" />
|
|
1192
|
+
</div>
|
|
1193
|
+
<div className="rounded-lg border border-amber-200 bg-amber-50 p-3 sm:col-span-2 md:col-span-3">
|
|
1194
|
+
<div className="mb-2 flex items-center justify-between gap-2">
|
|
1195
|
+
<label className="text-xs font-semibold text-amber-900">SLOOP long-form report controls</label>
|
|
1196
|
+
<label className="inline-flex items-center gap-2 text-xs text-amber-900">
|
|
1197
|
+
<input type="checkbox" checked={settings.forceSloop} onChange={e => setSetting("forceSloop", e.target.checked)} className="h-3.5 w-3.5 rounded" />
|
|
1198
|
+
Force SLOOP
|
|
1199
|
+
</label>
|
|
1200
|
+
</div>
|
|
1201
|
+
<div className="grid gap-3 md:grid-cols-2 md:items-center">
|
|
1202
|
+
<label className="block text-xs text-amber-900">
|
|
1203
|
+
Target pages ({settings.sloopPages})
|
|
1204
|
+
<input type="range" min="2" max="30" value={settings.sloopPages} onChange={e => setSetting("sloopPages", parseInt(e.target.value))} className="mt-1 w-full h-2 rounded-lg bg-amber-100 accent-amber-600" />
|
|
1205
|
+
</label>
|
|
1206
|
+
<label className="block text-xs text-amber-900">
|
|
1207
|
+
Cluster search width ({settings.clusterSize})
|
|
1208
|
+
<input type="range" min="2" max="16" value={settings.clusterSize} onChange={e => setSetting("clusterSize", parseInt(e.target.value))} className="mt-1 w-full h-2 rounded-lg bg-sky-100 accent-sky-600" />
|
|
1209
|
+
</label>
|
|
1210
|
+
</div>
|
|
1211
|
+
<p className="mt-2 text-[11px] text-amber-800">SLOOP requests a complete multi-page report while keeping scraper and N-Deep memory caps active.</p>
|
|
1212
|
+
</div>
|
|
1213
|
+
</div>
|
|
1214
|
+
<p className="mt-3 text-[11px] text-zinc-500">Keys are stored in localStorage and shared across all pages. Enable "4-Stage" for the research-grade multi-pass pipeline. PrismaFetch settings are persisted here as well.</p>
|
|
1215
|
+
</div>
|
|
1216
|
+
</div>
|
|
1217
|
+
)}
|
|
1218
|
+
|
|
1219
|
+
{/* 126 Modes panel */}
|
|
1220
|
+
{showModes && (
|
|
1221
|
+
<div className="border-b border-zinc-200 bg-white">
|
|
1222
|
+
<div className="mx-auto max-w-5xl px-4 py-4">
|
|
1223
|
+
<div className="mb-3 flex flex-wrap gap-1">
|
|
1224
|
+
<button onClick={() => setFilterSuperclass(null)} className={`rounded px-2 py-1 text-xs font-medium ${!filterSuperclass ? "bg-indigo-600 text-white" : "bg-zinc-100 hover:bg-zinc-200"}`}>All ({FAILURE_MODES.length})</button>
|
|
1225
|
+
{Object.entries(SUPERCLASS_SUMMARY).map(([key, val]) => (
|
|
1226
|
+
<button key={key} onClick={() => setFilterSuperclass(filterSuperclass === key ? null : key)} className={`rounded px-2 py-1 text-xs font-medium ${filterSuperclass === key ? "bg-indigo-600 text-white" : "bg-zinc-100 hover:bg-zinc-200"}`}>
|
|
1227
|
+
{key}: {val.name}
|
|
1228
|
+
</button>
|
|
1229
|
+
))}
|
|
1230
|
+
</div>
|
|
1231
|
+
<div className="max-h-56 overflow-y-auto rounded-md border border-zinc-200">
|
|
1232
|
+
<table className="w-full text-xs">
|
|
1233
|
+
<thead className="sticky top-0 bg-zinc-100">
|
|
1234
|
+
<tr className="border-b border-zinc-200">
|
|
1235
|
+
<th className="px-3 py-2 text-left font-semibold text-zinc-700">ID</th>
|
|
1236
|
+
<th className="px-3 py-2 text-left font-semibold text-zinc-700">Failure Mode</th>
|
|
1237
|
+
<th className="px-3 py-2 text-left font-semibold text-zinc-700">Solution</th>
|
|
1238
|
+
</tr>
|
|
1239
|
+
</thead>
|
|
1240
|
+
<tbody className="divide-y divide-zinc-100">
|
|
1241
|
+
{filteredModes.map(fm => (
|
|
1242
|
+
<tr key={fm.id} className="hover:bg-zinc-50">
|
|
1243
|
+
<td className="whitespace-nowrap px-3 py-2 font-mono font-bold text-indigo-700">{fm.id}</td>
|
|
1244
|
+
<td className="px-3 py-2 font-medium text-zinc-900">{fm.name}</td>
|
|
1245
|
+
<td className="px-3 py-2 text-zinc-600">{fm.solution}</td>
|
|
1246
|
+
</tr>
|
|
1247
|
+
))}
|
|
1248
|
+
</tbody>
|
|
1249
|
+
</table>
|
|
1250
|
+
</div>
|
|
1251
|
+
</div>
|
|
1252
|
+
</div>
|
|
1253
|
+
)}
|
|
1254
|
+
|
|
1255
|
+
{/* Debug drawer */}
|
|
1256
|
+
{showDebug && (
|
|
1257
|
+
<div className="border-b border-zinc-200 bg-zinc-950">
|
|
1258
|
+
<div className="mx-auto max-w-5xl px-4 py-3">
|
|
1259
|
+
<div className="flex items-center justify-between mb-2">
|
|
1260
|
+
<span className="text-xs font-bold text-emerald-400 uppercase tracking-wider">Live debug trace</span>
|
|
1261
|
+
<button onClick={clearDebugEvents} className="text-[10px] text-zinc-500 hover:text-zinc-300 uppercase">Clear</button>
|
|
1262
|
+
</div>
|
|
1263
|
+
<div ref={debugRef} className="max-h-48 overflow-y-auto font-mono text-[11px] leading-relaxed text-emerald-100 space-y-0.5">
|
|
1264
|
+
{debugEvents.length === 0
|
|
1265
|
+
? <div className="text-zinc-500 py-2">No events yet. Run a query to see the live reasoning trace.</div>
|
|
1266
|
+
: debugEvents.map((e, i) => <div key={i} className="text-emerald-200">{e}</div>)
|
|
1267
|
+
}
|
|
1268
|
+
</div>
|
|
1269
|
+
</div>
|
|
1270
|
+
</div>
|
|
1271
|
+
)}
|
|
1272
|
+
|
|
1273
|
+
{/* Template & Persona Controls */}
|
|
1274
|
+
<div className="border-b border-zinc-200 bg-white">
|
|
1275
|
+
<div className="mx-auto max-w-5xl px-4 py-2 space-y-3">
|
|
1276
|
+
<div className="flex items-center gap-3 bg-zinc-50 border border-zinc-200 rounded-xl px-4 py-2.5">
|
|
1277
|
+
<span className="text-xs font-semibold text-zinc-700">Template</span>
|
|
1278
|
+
<select
|
|
1279
|
+
value={settings.activeTemplateId}
|
|
1280
|
+
onChange={e => setSetting("activeTemplateId", e.target.value)}
|
|
1281
|
+
className="rounded-md border border-zinc-300 bg-white px-2 py-1 text-xs text-zinc-900 focus:outline-none focus:border-indigo-500"
|
|
1282
|
+
>
|
|
1283
|
+
{templates.map(t => <option key={t.id} value={t.id}>{t.name}</option>)}
|
|
1284
|
+
</select>
|
|
1285
|
+
<label className="inline-flex items-center gap-1.5 ml-2 text-xs text-zinc-700">
|
|
1286
|
+
<input type="checkbox" checked={settings.templateEnabled} onChange={e => setSetting("templateEnabled", e.target.checked)} className="h-3.5 w-3.5 rounded" />
|
|
1287
|
+
Apply template
|
|
1288
|
+
</label>
|
|
1289
|
+
</div>
|
|
1290
|
+
<StylePersonaPanel persona={persona} onReseed={seed => reseedPersona(seed)} />
|
|
1291
|
+
</div>
|
|
1292
|
+
</div>
|
|
1293
|
+
|
|
1294
|
+
{/* Stats bar */}
|
|
1295
|
+
<div className="border-b border-zinc-200 bg-white">
|
|
1296
|
+
<div className="mx-auto flex max-w-5xl items-center gap-5 px-4 py-1.5 text-xs flex-wrap">
|
|
1297
|
+
<span className="text-zinc-600">Claims: <strong className="text-zinc-900">{totalClaims}</strong></span>
|
|
1298
|
+
<span className="text-zinc-600">Verified: <strong className="text-emerald-700">{totalVerified}</strong></span>
|
|
1299
|
+
<span className="text-zinc-600">Blocked: <strong className="text-rose-700">{totalBlocked}</strong></span>
|
|
1300
|
+
<span className="text-zinc-600">Defenses: <strong className="text-indigo-700">{TOTAL_DEFENSES}/{TOTAL_DEFENSES}</strong></span>
|
|
1301
|
+
{settings.deepResearch && <span className="rounded-full bg-indigo-100 px-2 py-0.5 text-[10px] font-bold text-indigo-800">4-Stage Pipeline ON</span>}
|
|
1302
|
+
{settings.templateEnabled && <span className="rounded-full bg-violet-100 px-2 py-0.5 text-[10px] font-bold text-violet-800">Template: {settings.activeTemplateId}</span>}
|
|
1303
|
+
<button onClick={clearMessages} className="ml-auto text-[10px] text-zinc-400 hover:text-rose-600">Clear chat</button>
|
|
1304
|
+
</div>
|
|
1305
|
+
</div>
|
|
1306
|
+
|
|
1307
|
+
{/* Live AEGIS-PHI HUD — shows real telemetry during/after any run */}
|
|
1308
|
+
{(telemetry.running || telemetry.phase !== "idle") && (
|
|
1309
|
+
<div className="border-b border-zinc-200 bg-zinc-50">
|
|
1310
|
+
<div className="mx-auto max-w-5xl px-4 py-3 grid md:grid-cols-2 gap-4">
|
|
1311
|
+
<HUD />
|
|
1312
|
+
<LiveResourceHUD />
|
|
1313
|
+
</div>
|
|
1314
|
+
</div>
|
|
1315
|
+
)}
|
|
1316
|
+
|
|
1317
|
+
{/* Debug/Reasoning Trace Panel */}
|
|
1318
|
+
<div className="border-b border-zinc-200 bg-zinc-50">
|
|
1319
|
+
<div className="mx-auto max-w-5xl px-4 py-3">
|
|
1320
|
+
<DebugTracePanel trace={telemetry.pipelineTrace || []} telemetry={telemetry} />
|
|
1321
|
+
</div>
|
|
1322
|
+
</div>
|
|
1323
|
+
|
|
1324
|
+
{/* Main chat content */}
|
|
1325
|
+
<main className="flex flex-1 flex-col">
|
|
1326
|
+
<div className="mx-auto flex w-full max-w-5xl flex-1 flex-col px-4 py-4">
|
|
1327
|
+
<div className="flex flex-1 flex-col rounded-2xl border border-zinc-200 bg-white shadow-sm overflow-hidden">
|
|
1328
|
+
{/* Message list */}
|
|
1329
|
+
<div className="flex-1 overflow-y-auto p-5 space-y-6" style={{ minHeight: 0 }}>
|
|
1330
|
+
{messages.length === 0 && (
|
|
1331
|
+
<div className="flex h-full items-center justify-center py-16">
|
|
1332
|
+
<div className="max-w-md text-center">
|
|
1333
|
+
<h2 className="mb-2 text-lg font-bold text-zinc-900">Retrieve-First, Generate-Second</h2>
|
|
1334
|
+
<p className="mb-4 text-xs text-zinc-600 leading-relaxed">
|
|
1335
|
+
Every answer is grounded in live retrieval data. Time horizons, tickers, and format hints become <strong>deterministic constraints</strong> the model must respect.
|
|
1336
|
+
Grounding uses <strong>Jina</strong> when configured and can fall back to <strong>PrismaFetch local</strong> when enabled.
|
|
1337
|
+
</p>
|
|
1338
|
+
<div className="grid gap-2 text-left text-xs">
|
|
1339
|
+
<div className="rounded-lg border border-zinc-200 bg-zinc-50 p-3"><span className="font-semibold text-indigo-700">1. Configure grounding:</span> PrismaFetch is primary, the built-in browser scraper is secondary, and Jina is cloud backup only.</div>
|
|
1340
|
+
<div className="rounded-lg border border-zinc-200 bg-zinc-50 p-3"><span className="font-semibold text-indigo-700">2. Ask a question:</span> "Best tech stock for the next 2 months?" — 2-month constraint auto-detected.</div>
|
|
1341
|
+
<div className="rounded-lg border border-zinc-200 bg-zinc-50 p-3"><span className="font-semibold text-indigo-700">3. Constraint-filtered answer:</span> 1-year targets excluded; out-of-window catalysts flagged.</div>
|
|
1342
|
+
</div>
|
|
1343
|
+
</div>
|
|
1344
|
+
</div>
|
|
1345
|
+
)}
|
|
1346
|
+
{messages.map(msg => {
|
|
1347
|
+
const isCollapsed = collapsedTools[msg.id] ?? true;
|
|
1348
|
+
return (
|
|
1349
|
+
<div key={msg.id} className={`flex ${msg.role === "user" ? "justify-end" : "justify-start"}`}>
|
|
1350
|
+
<div className={`max-w-[88%] rounded-2xl px-4 py-3.5 ${msg.role === "user" ? "bg-zinc-900" : "border border-zinc-200 bg-white"}`}>
|
|
1351
|
+
{/* Injection warning */}
|
|
1352
|
+
{msg.injection?.blocked && (
|
|
1353
|
+
<div className="mb-2 rounded-lg bg-rose-100 px-3 py-2 text-xs font-semibold text-rose-800">
|
|
1354
|
+
⚠ Prompt injection blocked: {msg.injection.patterns.join(", ")}
|
|
1355
|
+
</div>
|
|
1356
|
+
)}
|
|
1357
|
+
{/* Constraint badges */}
|
|
1358
|
+
{msg.role === "user" && msg.constraints && (msg.constraints.timeHorizon || msg.constraints.explicitComparisonTargets.length > 0 || msg.constraints.formatHints.length > 0) && (
|
|
1359
|
+
<div className="mb-2 flex flex-wrap gap-1.5">
|
|
1360
|
+
{msg.constraints.timeHorizon && (
|
|
1361
|
+
<span className="rounded-full bg-indigo-200 px-2 py-0.5 text-[10px] font-mono font-bold text-indigo-900">horizon: {msg.constraints.timeHorizon.value} {msg.constraints.timeHorizon.unit}</span>
|
|
1362
|
+
)}
|
|
1363
|
+
{msg.constraints.explicitComparisonTargets.map(t => (
|
|
1364
|
+
<span key={t} className="rounded-full bg-emerald-200 px-2 py-0.5 text-[10px] font-mono font-bold text-emerald-900">focus: {t}</span>
|
|
1365
|
+
))}
|
|
1366
|
+
{msg.constraints.domain && msg.constraints.domain !== "general" && (
|
|
1367
|
+
<span className="rounded-full bg-violet-200 px-2 py-0.5 text-[10px] font-mono font-bold text-violet-900">domain: {msg.constraints.domain}</span>
|
|
1368
|
+
)}
|
|
1369
|
+
{msg.constraints.isShortHorizon && (
|
|
1370
|
+
<span className="rounded-full bg-amber-200 px-2 py-0.5 text-[10px] font-mono font-bold text-amber-900">tactical window</span>
|
|
1371
|
+
)}
|
|
1372
|
+
</div>
|
|
1373
|
+
)}
|
|
1374
|
+
{/* Pipeline trace for assistant */}
|
|
1375
|
+
{msg.pipelineTrace && msg.pipelineTrace.length > 0 && msg.role === "assistant" && (
|
|
1376
|
+
<details className="mb-3">
|
|
1377
|
+
<summary className="cursor-pointer text-xs font-semibold text-indigo-700 hover:text-indigo-900">
|
|
1378
|
+
4-Stage pipeline trace ({msg.pipelineTrace.length} steps)
|
|
1379
|
+
</summary>
|
|
1380
|
+
<div className="mt-2 space-y-1 font-mono text-[11px] bg-zinc-50 rounded-lg p-3 border border-zinc-200">
|
|
1381
|
+
{msg.pipelineTrace.map((t, i) => (
|
|
1382
|
+
<div key={i} className={`flex gap-2 ${t.ok ? "text-emerald-700" : "text-rose-700"}`}>
|
|
1383
|
+
<span className="shrink-0 text-zinc-400">S{t.stage}</span>
|
|
1384
|
+
<span>{t.label}</span>
|
|
1385
|
+
</div>
|
|
1386
|
+
))}
|
|
1387
|
+
</div>
|
|
1388
|
+
</details>
|
|
1389
|
+
)}
|
|
1390
|
+
{/* Deterministic compute records */}
|
|
1391
|
+
{msg.computeRecords && msg.computeRecords.length > 0 && msg.role === "assistant" && (
|
|
1392
|
+
<ComputeRecordsInline records={msg.computeRecords} />
|
|
1393
|
+
)}
|
|
1394
|
+
{/* Verified entity sheet */}
|
|
1395
|
+
{msg.entitySheet && msg.role === "assistant" && (
|
|
1396
|
+
<EntitySheetInline sheet={msg.entitySheet} />
|
|
1397
|
+
)}
|
|
1398
|
+
{msg.artifactResponse && msg.role === "assistant" && (
|
|
1399
|
+
<ArtifactInline artifact={msg.artifactResponse} />
|
|
1400
|
+
)}
|
|
1401
|
+
{/* Verification plan */}
|
|
1402
|
+
{msg.verificationPlan && msg.verificationPlan.length > 0 && (
|
|
1403
|
+
<div className="mb-3 rounded-xl border border-indigo-200 bg-indigo-50/70 p-3 text-xs">
|
|
1404
|
+
<div className="mb-2 font-semibold text-indigo-900">Verification plan ({msg.verificationPlan.length} hypotheses)</div>
|
|
1405
|
+
<div className="space-y-2">
|
|
1406
|
+
{msg.verificationPlan.map((item, idx) => (
|
|
1407
|
+
<div key={idx} className="rounded-lg bg-white/80 p-2">
|
|
1408
|
+
<div className="mb-1 flex items-center gap-2">
|
|
1409
|
+
<span className={`rounded px-1.5 py-0.5 font-mono text-[10px] font-bold ${item.status === "CONFIRMED" ? "bg-emerald-100 text-emerald-800" : "bg-amber-100 text-amber-800"}`}>{item.status}</span>
|
|
1410
|
+
<span className="text-[10px] text-zinc-500">{item.sources.length} sources</span>
|
|
1411
|
+
</div>
|
|
1412
|
+
<p className="font-medium text-zinc-800">{item.claim}</p>
|
|
1413
|
+
</div>
|
|
1414
|
+
))}
|
|
1415
|
+
</div>
|
|
1416
|
+
</div>
|
|
1417
|
+
)}
|
|
1418
|
+
{/* Sources accordion */}
|
|
1419
|
+
{msg.toolResults && msg.toolResults.length > 0 && (
|
|
1420
|
+
<div className="mb-3 rounded-xl border border-zinc-200 bg-white overflow-hidden">
|
|
1421
|
+
<button onClick={() => toggleToolCollapse(msg.id)} className="flex w-full items-center justify-between bg-zinc-50 px-3 py-2 text-left text-xs font-medium text-zinc-700 hover:bg-zinc-100">
|
|
1422
|
+
<span className="flex items-center gap-1.5">
|
|
1423
|
+
<span className="text-indigo-600">🔍</span>
|
|
1424
|
+
Grounding Sources ({msg.toolResults.length} · {Array.from(new Set(msg.toolResults.map(src => src.provider ?? "unknown"))).join(" + ")})
|
|
1425
|
+
</span>
|
|
1426
|
+
<span className="text-zinc-400 font-mono text-[10px]">{isCollapsed ? "► Expand" : "▼ Collapse"}</span>
|
|
1427
|
+
</button>
|
|
1428
|
+
{!isCollapsed && (
|
|
1429
|
+
<div className="max-h-56 overflow-y-auto p-3 space-y-3 divide-y divide-zinc-100">
|
|
1430
|
+
{msg.toolResults.map((src, idx) => (
|
|
1431
|
+
<div key={idx} className={idx > 0 ? "pt-3" : ""}>
|
|
1432
|
+
<div className="flex items-baseline gap-1.5">
|
|
1433
|
+
<span className="font-mono font-bold text-xs text-indigo-600">[S{idx + 1}]</span>
|
|
1434
|
+
<a href={src.url} target="_blank" rel="noopener noreferrer" className="text-xs font-semibold text-zinc-900 hover:underline truncate max-w-xs">{src.title}</a>
|
|
1435
|
+
</div>
|
|
1436
|
+
<div className="mt-1 flex flex-wrap items-center gap-2 text-[10px] font-mono text-zinc-500">
|
|
1437
|
+
<span className="truncate max-w-[28rem]">{src.url}</span>
|
|
1438
|
+
{src.provider && <span className="rounded bg-violet-100 px-1.5 py-0.5 font-bold text-violet-800">{src.provider}</span>}
|
|
1439
|
+
{src.tier && <span className="rounded bg-zinc-100 px-1.5 py-0.5 font-bold text-zinc-700">{src.tier}</span>}
|
|
1440
|
+
</div>
|
|
1441
|
+
<p className="mt-1 text-xs text-zinc-600 line-clamp-2">{src.content.slice(0, 200)}</p>
|
|
1442
|
+
</div>
|
|
1443
|
+
))}
|
|
1444
|
+
</div>
|
|
1445
|
+
)}
|
|
1446
|
+
</div>
|
|
1447
|
+
)}
|
|
1448
|
+
{/* Message content */}
|
|
1449
|
+
{msg.role === "assistant" ? (
|
|
1450
|
+
<RichText text={msg.content} />
|
|
1451
|
+
) : (
|
|
1452
|
+
<div className="whitespace-pre-wrap text-sm leading-relaxed text-white">{msg.content}</div>
|
|
1453
|
+
)}
|
|
1454
|
+
{/* Assistant metadata */}
|
|
1455
|
+
{msg.role === "assistant" && (
|
|
1456
|
+
<div className="mt-2 flex flex-wrap items-center gap-2 text-[10px] font-mono text-zinc-400">
|
|
1457
|
+
<span>via {MODELS.find(m => m.id === msg.model)?.label ?? msg.model}</span>
|
|
1458
|
+
{msg.toolResults && msg.toolResults.length > 0 && (
|
|
1459
|
+
<span className="rounded bg-violet-100 px-1.5 py-0.5 text-violet-800 font-bold">
|
|
1460
|
+
grounding: {Array.from(new Set(msg.toolResults.map(src => src.provider ?? "unknown"))).join("+")}
|
|
1461
|
+
</span>
|
|
1462
|
+
)}
|
|
1463
|
+
{msg.usedMultiPass && <span className="rounded bg-indigo-100 px-1.5 py-0.5 text-indigo-800 font-bold">4-Stage ✓</span>}
|
|
1464
|
+
{msg.sanitizerRemovedSegments !== undefined && msg.sanitizerRemovedSegments > 0 && (
|
|
1465
|
+
<span className="rounded bg-rose-100 px-1.5 py-0.5 text-rose-700 font-bold">Sanitizer: {msg.sanitizerRemovedSegments} block(s) stripped</span>
|
|
1466
|
+
)}
|
|
1467
|
+
</div>
|
|
1468
|
+
)}
|
|
1469
|
+
{msg.qualityReport && msg.role === "assistant" && (
|
|
1470
|
+
<details className="mt-3 rounded-lg border border-violet-100 bg-violet-50/60 p-2 text-xs" open={false}>
|
|
1471
|
+
<summary className="cursor-pointer font-semibold text-violet-800">
|
|
1472
|
+
Quality / Mythos review ({msg.qualityReport.overall}/10) · kernels: {msg.qualityReport.triggeredKernels.slice(0, 3).join(", ")}
|
|
1473
|
+
</summary>
|
|
1474
|
+
<div className="mt-2 grid gap-2 md:grid-cols-2">
|
|
1475
|
+
{msg.qualityReport.items.map(item => (
|
|
1476
|
+
<div key={item.name} className="rounded border border-violet-100 bg-white p-2">
|
|
1477
|
+
<div className="flex justify-between gap-2 font-semibold text-zinc-800"><span>{item.name}</span><span>{item.score}/10</span></div>
|
|
1478
|
+
<p className="mt-1 text-[11px] text-zinc-500">{item.note}</p>
|
|
1479
|
+
</div>
|
|
1480
|
+
))}
|
|
1481
|
+
</div>
|
|
1482
|
+
{msg.qualityReport.numericAudit.length > 0 && (
|
|
1483
|
+
<div className="mt-2 rounded border border-violet-100 bg-white p-2">
|
|
1484
|
+
<div className="mb-1 font-semibold text-zinc-800">Numeric audit</div>
|
|
1485
|
+
<div className="flex flex-wrap gap-1.5">
|
|
1486
|
+
{msg.qualityReport.numericAudit.slice(0, 24).map(n => (
|
|
1487
|
+
<span key={`${n.value}-${n.status}-${n.line ?? "x"}`} title={`${n.rationale}${n.line ? ` Line ${n.line}.` : ""}${n.unit ? ` Unit: ${n.unit}.` : ""}`} className={`rounded px-1.5 py-0.5 font-mono text-[10px] ${n.status === "unverified" ? "bg-rose-100 text-rose-700" : n.status === "computed" ? "bg-emerald-100 text-emerald-700" : "bg-blue-100 text-blue-700"}`}>{n.value}{n.unit ? ` ${n.unit}` : ""} · {n.status}{n.line ? ` · L${n.line}` : ""}</span>
|
|
1488
|
+
))}
|
|
1489
|
+
</div>
|
|
1490
|
+
</div>
|
|
1491
|
+
)}
|
|
1492
|
+
<p className="mt-2 text-[11px] text-violet-700">{msg.qualityReport.mythos}</p>
|
|
1493
|
+
</details>
|
|
1494
|
+
)}
|
|
1495
|
+
{/* Coherence warning */}
|
|
1496
|
+
{msg.coherence?.drifts && msg.coherence.drifts.length > 0 && (
|
|
1497
|
+
<div className="mt-2 rounded-lg border border-amber-200 bg-amber-50 px-3 py-1.5 text-[11px] text-amber-800">⚠ Multi-turn drift: {msg.coherence.drifts[0]}</div>
|
|
1498
|
+
)}
|
|
1499
|
+
{/* Claim ledger */}
|
|
1500
|
+
{msg.claims && msg.claims.length > 0 && (
|
|
1501
|
+
<details className="mt-3 border-t border-zinc-100 pt-3">
|
|
1502
|
+
<summary className="cursor-pointer text-[11px] font-semibold text-zinc-500 uppercase tracking-wider hover:text-zinc-700">
|
|
1503
|
+
Atomic claim ledger ({msg.claims.filter(c => c.status === "VERIFIED").length}/{msg.claims.length} verified)
|
|
1504
|
+
</summary>
|
|
1505
|
+
<div className="mt-2 space-y-2">
|
|
1506
|
+
{msg.claims.map(claim => (
|
|
1507
|
+
<div key={claim.id} className="rounded-lg border border-zinc-200 bg-zinc-50 p-2.5 text-xs">
|
|
1508
|
+
<div className="flex items-center gap-2 mb-1">
|
|
1509
|
+
<span className={`rounded px-1 py-0.5 font-mono font-bold text-[10px] ${claim.status === "VERIFIED" ? "bg-emerald-100 text-emerald-800" : "bg-zinc-100 text-zinc-600"}`}>{claim.status}</span>
|
|
1510
|
+
<span className="rounded bg-indigo-50 px-1 py-0.5 font-mono text-[10px] text-indigo-700">{claim.failureClass}</span>
|
|
1511
|
+
{claim.sourceIndex !== undefined && (
|
|
1512
|
+
<span className="font-mono text-[10px] text-emerald-700 font-semibold">
|
|
1513
|
+
→ [S{claim.sourceIndex + 1}{msg.toolResults?.[claim.sourceIndex]?.provider ? ` · ${msg.toolResults[claim.sourceIndex]?.provider}` : ""}]
|
|
1514
|
+
</span>
|
|
1515
|
+
)}
|
|
1516
|
+
</div>
|
|
1517
|
+
<p className="text-zinc-800 font-medium">{claim.text}</p>
|
|
1518
|
+
<p className="mt-1 text-[10px] text-zinc-500">{claim.solution}</p>
|
|
1519
|
+
</div>
|
|
1520
|
+
))}
|
|
1521
|
+
</div>
|
|
1522
|
+
{msg.adversarialPasses && msg.adversarialPasses.length > 0 && (
|
|
1523
|
+
<details className="mt-3 rounded-lg border border-rose-100 bg-rose-50/60 p-2 text-xs" open={false}>
|
|
1524
|
+
<summary className="cursor-pointer font-semibold text-rose-800">
|
|
1525
|
+
Death certificate registry ({msg.adversarialPasses.filter(p => p.deathCertificate).length})
|
|
1526
|
+
</summary>
|
|
1527
|
+
<div className="mt-2 space-y-2">
|
|
1528
|
+
{msg.adversarialPasses.filter(p => p.deathCertificate).map(p => (
|
|
1529
|
+
<div key={`death-${p.pass}`} className="rounded border border-rose-100 bg-white p-2">
|
|
1530
|
+
<div className="font-mono text-[10px] text-zinc-500">Pass {p.pass} · {p.model}</div>
|
|
1531
|
+
<div className="mt-1 text-zinc-800">Purged draft: {p.deathCertificate?.chars} chars · hash {p.deathCertificate?.hash}</div>
|
|
1532
|
+
<div className="mt-1 text-[11px] text-zinc-500">Reason: {p.deathCertificate?.reason}</div>
|
|
1533
|
+
</div>
|
|
1534
|
+
))}
|
|
1535
|
+
</div>
|
|
1536
|
+
</details>
|
|
1537
|
+
)}
|
|
1538
|
+
</details>
|
|
1539
|
+
)}
|
|
1540
|
+
</div>
|
|
1541
|
+
</div>
|
|
1542
|
+
);
|
|
1543
|
+
})}
|
|
1544
|
+
{busyState && (
|
|
1545
|
+
<div className="flex justify-start">
|
|
1546
|
+
<div className="rounded-2xl border border-zinc-200 bg-white px-4 py-3 text-xs shadow-sm flex items-center gap-2.5">
|
|
1547
|
+
<span className="h-3 w-3 animate-spin rounded-full border-2 border-indigo-600 border-t-transparent" />
|
|
1548
|
+
<div>
|
|
1549
|
+
<span className="font-bold text-indigo-600 uppercase tracking-tight text-[10px] block">Active:</span>
|
|
1550
|
+
<span className="font-medium text-zinc-800">{busyState}</span>
|
|
1551
|
+
</div>
|
|
1552
|
+
</div>
|
|
1553
|
+
</div>
|
|
1554
|
+
)}
|
|
1555
|
+
<div ref={endRef} />
|
|
1556
|
+
</div>
|
|
1557
|
+
|
|
1558
|
+
{/* Input box */}
|
|
1559
|
+
<div className="border-t border-zinc-200 bg-zinc-50/80 p-4">
|
|
1560
|
+
<form onSubmit={e => { e.preventDefault(); handleSubmit(); }} className="flex gap-2.5">
|
|
1561
|
+
<textarea
|
|
1562
|
+
ref={taRef}
|
|
1563
|
+
value={input}
|
|
1564
|
+
onChange={e => setInput(e.target.value)}
|
|
1565
|
+
onKeyDown={e => { if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) { e.preventDefault(); handleSubmit(); } }}
|
|
1566
|
+
placeholder="Ask a question. Time horizons, tickers, format hints become deterministic constraints…"
|
|
1567
|
+
rows={1}
|
|
1568
|
+
className="min-h-[44px] flex-1 resize-none rounded-xl border border-zinc-300 bg-white px-4 py-2.5 text-sm text-zinc-900 placeholder-zinc-400 outline-none focus:border-indigo-500"
|
|
1569
|
+
/>
|
|
1570
|
+
<button type="submit" disabled={!input.trim() || busyState !== null}
|
|
1571
|
+
className="self-end rounded-xl bg-zinc-900 px-4 py-2.5 text-xs font-semibold text-white hover:bg-zinc-800 disabled:cursor-not-allowed disabled:opacity-30">
|
|
1572
|
+
{settings.deepResearch ? "Deep Answer" : "Ground & Answer"}
|
|
1573
|
+
</button>
|
|
1574
|
+
</form>
|
|
1575
|
+
<div className="mt-1.5 text-[11px] text-zinc-500">⌘↩ to send · input shared across all pages · same query on dashboard, estimator, control plane</div>
|
|
1576
|
+
</div>
|
|
1577
|
+
</div>
|
|
1578
|
+
</div>
|
|
1579
|
+
</main>
|
|
1580
|
+
</div>
|
|
1581
|
+
);
|
|
1582
|
+
}
|