diff --git a/MANIFEST.sha256 b/MANIFEST.sha256 index ecec38a..b834f37 100644 --- a/MANIFEST.sha256 +++ b/MANIFEST.sha256 @@ -1,22 +1,26 @@ 27dc46be5cbb1b171deff7fbd2f28bff1be802dff403797535fd8968bb98c8eb ./.cbmignore -6975891550d0f9343d69faaa5fa7966d016a0b84d31546e6ecb26a9ddfe23398 ./.env.example -f86e1fe23360c211f7229745743fc8728cadc9b3df4547187a0e15774a66a0b4 ./.github/workflows/release-tag.yml +962916dbc4e0953ccdb630c8d7780882aab4a2b41f0857baa529138f44b9e83a ./.env.example +ed22fda7661db8203563611dc144998161cd024e161b0471d615eaf0defeb7db ./.gitea/workflows/release-tag.yml e1ff71187cc3411a85067b964264011db7bd109a585ef7b9ea5b08bda039d813 ./.gitignore 9c18555764b03bdb004098bf6b8ca7f3eaebdccb8c17f15a11d2cebbab28cbd3 ./Makefile -546df743afd5858157b568194c3e588bd490d4438e1ffc33b91abcdb71b652c4 ./README.md +52adec38e0256b594e84ddab42cb6694bd1649bf5e12dd6e94851a8b4cc01d10 ./README.md 4858caa52c0fb6cf302a1c581d07d448e5e90e0daa797e5819610fd6223bd348 ./RELEASE-NOTES-v1.1.0.md 01163462f46314f57660677fdef407c6c2884412ea850aab13a4f650e8c29f50 ./RELEASE-NOTES-v1.2.0.md 4da388ce660aa3b7a0d8075ec066a025b5437960973397360fcb9a5d4cb58c96 ./RELEASE-NOTES-v1.3.0.md 257d6d3ea1d22b8ab97adb5d35ca5ac806702d75cea66982dc711754040fc7a2 ./RELEASE-NOTES-v1.4.0.md +0723a0151b72d492899a5c5409e1fef37de1fe4c458c3053f2f7eccd4b6e70c5 ./RELEASE-NOTES-v1.4.1.md +58923d3292f11487526b8dc4d6c0992025c0f946df8435fde5c91ef4581d46fd ./RELEASE-NOTES-v1.4.2.md 78b591400c56b7b67b8cb3b2b8a8e65e9093897f02ce0878e6b5405c68620fa7 ./VERSION e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ./backups/.gitkeep 8127e9db5e5e0af1d88770dc8fa60b381de45dbcc843262698cf9501409b4d58 ./deploy/searxng/settings.yml -6d020933eedaa08772262b861c43c50944a596ae11bef3f623041769c68cf09b ./docker-compose.yml +f95897d6e96afbbcc230f56ae1968a046f76cf24ec78d9c18aa2b22bd8335e34 ./docker-compose.yml 0f2adaa0765ff00d9c3a1133840c7a768a011f9d8f820f8a340b43600c7649d8 ./docs/ARCHITECTURE.md 9467a3c0796a87bada0eab6ad191913e628b4abc05520cbb4b73fac76d334481 ./docs/CODEBASE-MEMORY-MCP.md 765c39f0db69165a70d8175d1fe7686ff6dd512669db088a6fdf6a93de5404f6 ./docs/CONTROL-CENTER.md ea8a6d298d33ac3f23825dc58f2bc12dc369ed9487b7b96c4b28a8febf1299c0 ./docs/CONTROL-MATRIX.md -c34f750f1d06fe3b750ad7e7de0aef5cad919e389517eb0f8881eae452ac80b5 ./docs/CONTROLLED-AUTONOMY.md +323402668da4d8e1d97a29c45db753834f6c3a16739eebebf7e016f06af160bf ./docs/CONTROLLED-AUTONOMY.md +15f27a2ec770aceb3f9a4cbe82cd39953620b913171a1b63142edb3883eaab09 ./docs/ENV-MIGRATION-FROM-LEGACY.md +7b2ea6ae55361c1690aff1227dce5bb8d061cf88c545bf37fe0f499bbd494aee ./docs/ENVIRONMENT.md 2465c837c243ef03856ad1297b540d6df90005c74b9f4a9bba223066a612009f ./docs/IMPLEMENTED.md 9c38807cc12fba6f94cadc4694996de58e6d2d5396e55fc0c4bd404bb13f53ad ./docs/MIGRATION-CUTOVER.md a71d28353529906cee08cf90f35e0b0cf97bbc94ccfcef830145a2d561b21a9c ./docs/MIGRATION-MANIFEST.md @@ -135,14 +139,16 @@ cb3cde795233c0d4842dc7451c5855d1c071e459efbde3be0cabac32a837ba1b ./knowledge/14 7f1d67faf4a6cea0c41c84d7b275d8a8979b19420b52db4925d8e2cd71ead3e0 ./knowledge/16_office-aktivierung-und-lizenzierung.json f1eab883370e0a40ef52a6b6d785a510d8ed48a19d25e0a2bc95f4f2cc8e329e ./knowledge/17_serienbriefe-und-dokumentfunktionen.json 5a0b3d5d5bc712e30a67a4de3432f69070f7e43dd99931f675e72c213006b363 ./knowledge/example-vpn.json -5345a8f54fb5f1f80196eb3662332b39e9d2869e2c5dbb56fadad846eaf15293 ./mega-project.json -8531264c1fa1fb0e5067701dc874729db510fc870726c73457eabfb0d61883f4 ./patches/SHA256SUMS +e95d55331b2dfe8326ef4af50cee141d2c003a3f8a01eecaba028f1d4e6d3424 ./mega-project.json +c02d6d702401343e6d7002c3db117935964e2abd18db966b604871e59715ee05 ./patches/SHA256SUMS 47a6fa2c79bbba0c04af86dfa65d58529f492c698060fe586456c22a4eadb877 ./patches/glpi-agent-mega.diff 9b411c90d96a86c80f088ee4637046eeefaf31c62059d11aa1a999d6eb08b5b4 ./patches/glpi-knowledge-mega.diff f0491de3cb6201f98ca6be8e865237adbba4772471f7fc7165d030e0c045fdb6 ./patches/neuroforge-mega.diff 576e75ca9191bbef7f136abde90c10a1c6bf2e36450a421fd3796e988bd2af00 ./patches/v1.1.0-to-v1.2.0.diff 7a5e0ae1d09b268d3ac62a51f92bfedf6771be26b909605a347f26d47d6fa8cb ./patches/v1.2.0-to-v1.3.0.diff 163a8a990aacd986bacb385494c2b2062ac10a9a8f0a456d19a6d1746c2fc500 ./patches/v1.3.0-to-v1.4.0.diff +654de4aac7cf318680872897c3de192afbafb61c7169acb8e7514116df86efde ./patches/v1.4.0-envfix-to-v1.4.1.diff +1ebc306bd6870389500c86cf07dcd677b5bd0448c762812c7e1c0f8732f36eb1 ./patches/v1.4.1-to-v1.4.2.diff 564817f8edabde0c4e4a1a427a3aa5418aae7bf12e9463044a7e6e0f13973657 ./platform/neuroforge/.env.example 39319b6f2058e4c8d6656a9cf01675374f81a04075b956b093a2befb5e05ada4 ./platform/neuroforge/.gitignore 189486a885c7fb78e0eb878d93cda0c70ca6d7ff9bfdfb3f5f32487cf03a9688 ./platform/neuroforge/BENCHMARK-v0.5.0.md @@ -170,7 +176,7 @@ c3d34504b1b8a4ef73382df061c7b272a55c2a3a7a720435c6050254da3f22e0 ./platform/neu b4896439112f9ca0f0a43d55d43cb3a2c4c802c568b65a96d04a3ca9d841c358 ./platform/neuroforge/VALIDATION-v0.8.2.txt ceb9b2c03afb769df3b1e518520c4e1798e9a3313a1c5ffffcb054fef5d6d6fb ./platform/neuroforge/VERSION 0c5308f5a3d37ac23dce162fd5fab78ce598e41671db5dd50c9c4ae7f215d49f ./platform/neuroforge/cmd/bench/main.go -f28372e15f8e3a5a2403f50e2ddc410c0dbe37a236e74720b7a58dcc4560fc91 ./platform/neuroforge/cmd/server/main.go +445926b443baaff5df755dac1d08d5833e0d6f29a73a716b961b24b1d6293e99 ./platform/neuroforge/cmd/server/main.go 1043f1658a672f9cdfa3f68ca3d19c1b81ca5d69f240d01924ddf393613c7f75 ./platform/neuroforge/cmd/worker/main.go e3dacdaec3c629dd432f24218a50fa4de5e1698ac7f27e84b14f776b50beeb83 ./platform/neuroforge/deploy/learning-policy.example.json 3765a5faea1faeb72aad7878ff0159a56fa1b0ca1fc94a348a8c599e460b61dd ./platform/neuroforge/deploy/model-routing.example.json @@ -199,7 +205,7 @@ edfedb67b1f55b2fa8a4a4d29b809b53083b36752e8dc087fa8603f4ec26154e ./platform/neu b9bb2934e01ed2bfb6b16e4139e10e51f1387112eb8654f7f692779a2fd2d273 ./platform/neuroforge/internal/cost/cost_test.go fb66ce4ab760b979eacd4f7f17dedc41916f93c53ba582be572d250f99654695 ./platform/neuroforge/internal/httpapi/admin_app_auth_test.go 4071eb134f63f19c95bba709c505e3748fcb7613264b6876350f49e6b693d61b ./platform/neuroforge/internal/httpapi/httpapi.go -79fce5bfbeb5d39e71047b2244fcac9c26595e20ca48f47d068cae83bbf29a18 ./platform/neuroforge/internal/httpapi/index.html +c5884789a3ae17be5d61367e5dc19f89a87ac83f9516e80310f66fc9e49f5103 ./platform/neuroforge/internal/httpapi/index.html 20f4a6cc30d5ce84ceed4fbdfc9f5c57ab5f82274e0c54464817d571e0e881b0 ./platform/neuroforge/internal/httpapi/integration.go 7f6e4767dfcb948570e05baadca6917f27a406fe57ef82eeb63e3b3979ec94b6 ./platform/neuroforge/internal/httpapi/integration_api_test.go f8cfcc7a2bc781394231c25e36e75c41563f5250a1387e5687880d2591efd93b ./platform/neuroforge/internal/httpapi/integration_graph.go @@ -234,8 +240,8 @@ e890897c662b070997c3b18134bafbee5bf9b3fa5220045ac09dc6e3dfd4ac41 ./platform/neu 1858ed8045b16282de815a6668ff6027c7a3a3795e38659c753c504d0f2a8383 ./platform/neuroforge/internal/store/diskann_test.go d49925fda3cbe3ceeffde3369073bce5a5bc67a23defb6827e27cd4a7f4c59f5 ./platform/neuroforge/internal/store/index_segments.go 478efaeb260cb5e970af8b3bb99f232ef846f54c6e4ff9deb5499df6e008e1c1 ./platform/neuroforge/internal/store/index_segments_test.go -6c3e185cd9679789c10ce1b1b766045fbafa5282446efb49bfc040eb7809b8c6 ./platform/neuroforge/internal/store/knowledge.go -c9cc70e43cea30c70029fd6767308f4b2a7e7d4ba8fac749edff1f838bfb9079 ./platform/neuroforge/internal/store/knowledge_test.go +bf39c642b88d28ee03c7bcd7103b46cf3da2f1f70a1105618753229ad7159165 ./platform/neuroforge/internal/store/knowledge.go +0689a337688ce9ab8a1c2713b6ad9f2213e4846d17b2baee3ed9117009d94dc1 ./platform/neuroforge/internal/store/knowledge_test.go 43de0f541fb47633b02b25686544d44887c17b47e8d01cb23f8e57567cebdcd7 ./platform/neuroforge/internal/store/mmap_linux.go 9ce033d691157037fc6df719b5ecd8fb29d6d881d1862e2d50dc0c6c573b5f66 ./platform/neuroforge/internal/store/mmap_linux_test.go ef6135a9039fb45361e57107d3968272b4b365ac833e9e2e58b0875566bf5a07 ./platform/neuroforge/internal/store/mmap_other.go @@ -269,14 +275,14 @@ c5463c525f5ea703ea1f6df74cabc30f937f3846b1f75f237adeee2700baa977 ./platform/neu 250f3b7736f0adba4490dfe7104da2b14e3804f616a3f4cb9119d8a567ee12c7 ./platform/neuroforge/openapi.yaml 6f1d21b7aea3265a088256801aa37a00931157523d8bd834aa4f473e99511f04 ./scripts/codebase-memory-ui.sh 5abbc6b60bca94fcb880abebae19c85c6a220c9eabb9865928b23ae86246705b ./scripts/export-obsidian.sh -1295b1364f93ae4da70969716c2a99b54c248f5eb022363d15b2fbd8d6f1eaae ./scripts/generate-secrets.sh +6ffe1e8e4f69d7f366edb2b72190d155d20b68a98228cff30f81a751b4b65ad1 ./scripts/generate-secrets.sh fe35fa5db6dabf06e6266e0de4c372604160180366d4c0a8042c7ee54b7c6dcd ./scripts/propose-draft.sh 1744be0629e845c8d94dc2a06e91ca2cc9ce5928db00300b7fb894445aeda9ff ./scripts/quality-replay.py 0835b14909b5d001ef93a1678be968a87a525b6836c2c6fe8885a8bf889b4718 ./scripts/research-up.sh 457c349025a9508cc67154d571533dcd1ee77b5e0b89927de0846f0355e6a7e8 ./scripts/status.sh 2970d5f5049323b14a3f3983c2f4eb5392d745b4fea34742e3cf56e52d740f17 ./scripts/validate.sh 17d7149607bf40ccc6f64a3c4cf3e4023a8325675918c18df0be309ea9731a36 ./services/agent/.dockerignore -b0851a029d0f5634a5237c80eb6156beef46b9d71284c6616677dbf1c4e85421 ./services/agent/.env.example +d1d8c18c6f7c7c6f0b2d72f421837d010afaafc931596ae48442a826e50f0bc6 ./services/agent/.env.example 236713daf159ff0a8067e80a442ae3404fa28a5251ae6f24782f263bcfc17005 ./services/agent/.gitea/workflows/registry.yml 9ee46f2156ee805c4ff40e418b004ead5cfbc3a9b6df3968a1b772fe1b313cdc ./services/agent/.gitignore 4c893d6049499eb70ca755a333e97f0a726dd212e4ded4a9d4acabf0a25d9bd8 ./services/agent/BETRIEBSANLEITUNG_GLPI_AI_AGENT.md @@ -363,6 +369,7 @@ b4f7d72022d5009fce43b5879483de92dacfee2b3bf527306902187c3d26486b ./services/age 21b32b0b863ed62ae352011d7dd85f0134bf9909c23ee80b1c7e3c3605ef8cc4 ./services/agent/internal/web/templates/category-mappings.html d0561b87acd8fe8c0ab7f1f0bb82e1df840db8342f91798e8cb0a0408064abcc ./services/agent/internal/web/templates/dashboard.html fb5a4e2e1846046032bb878f1e878eb792497e321df6a8ce16209c07a925fdcd ./services/agent/internal/web/templates/diagnostics.html +7418ad39aa0899a85e69a9910d99ff1cb01757d5ae2edaa885c5ff7a815cb580 ./services/agent/knowledge-category-map.example.json 5a81ef73b2de4ff60c440a7a5e42da68e8ca30fab7bc62de63757157d498d72a ./services/agent/knowledge/01_active-directory.json 70b176be0117d51a35a699ab50c2ad9dc944dec807e4a193e1adbbf0ece888fc ./services/agent/knowledge/01_arbeitsplatzdrucker.json 96cf6b96e462db5e4be8345e9b18afee4e7dd5d51bcbd59e6817266725b92318 ./services/agent/knowledge/01_fachanwendung-storung.json @@ -466,7 +473,6 @@ cb3cde795233c0d4842dc7451c5855d1c071e459efbde3be0cabac32a837ba1b ./services/age 7f1d67faf4a6cea0c41c84d7b275d8a8979b19420b52db4925d8e2cd71ead3e0 ./services/agent/knowledge/16_office-aktivierung-und-lizenzierung.json f1eab883370e0a40ef52a6b6d785a510d8ed48a19d25e0a2bc95f4f2cc8e329e ./services/agent/knowledge/17_serienbriefe-und-dokumentfunktionen.json 5a0b3d5d5bc712e30a67a4de3432f69070f7e43dd99931f675e72c213006b363 ./services/agent/knowledge/example-vpn.json -7418ad39aa0899a85e69a9910d99ff1cb01757d5ae2edaa885c5ff7a815cb580 ./services/agent/knowledge-category-map.example.json 895f8400aaad550ff2b96262eaa54e954d603129682e97aa4964539eb19bd7c2 ./services/agent/run.ps1 5e2ea444f8321b723313184f8ae12219c319435cdac8904596a4481fd2be8822 ./services/control/Dockerfile f30ddf9251860d92717276483f7a2c2d7405f0516940ca62abc21d97ff3f3ad2 ./services/control/cmd/engineering-graph/main.go @@ -475,7 +481,7 @@ ba44c599b9faf861eca647614c4abd18548e2708233cf462b11eb277e96449b8 ./services/con da37393e58ff53847f26b1051f6d3a5270571d290b33d79360bae0dcb3513829 ./services/control/graph.go e6a8021a44219219fe37ab5edc45d1908fc6579f653aa80c9966810d891ad27d ./services/control/graph_test.go 0b426441ec627c2cf2d7466ce2177864593717d8550ca0b0f86d1b24f597488b ./services/control/index.html -b1832a0f0e2acb73d2dcf011b6a93ca50504cb4b27cf90d01f3b67e8151c58ea ./services/control/main.go +388ec95496667500809450e07ef2136084f25f9f37521b07d4273b1a47d3cf73 ./services/control/main.go 457fa6b4c81a62197eb1e76a5472cd571d5c69659110fcd9bc016582f1b5dfa1 ./services/knowledge/.dockerignore e82cbdb6336e424c4b9d8db5d149016d606579292184582788a9bf295895cda4 ./services/knowledge/.env.example 236713daf159ff0a8067e80a442ae3404fa28a5251ae6f24782f263bcfc17005 ./services/knowledge/.gitea/workflows/registry.yml diff --git a/README.md b/README.md index d7c7452..d92c1b8 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # GLPI NeuroForge Mega v1.4.0 +> Release: **v1.4.2** · Bugfix für leeren NeuroForge-Wissensraum-Graph. + Ein kontrolliertes Monorepo aus **GLPI AI Agent**, **GLPI AI Knowledgebase** und **NeuroForge + SQAR**. Ziel ist nicht ein untrennbarer Monolith, sondern eine gemeinsame Plattform mit klaren Zuständigkeiten, getrennten Credentials und nachvollziehbaren Failure-Modi. ## Unified Graph Explorer (v1.4.0) diff --git a/RELEASE-NOTES-v1.4.2.md b/RELEASE-NOTES-v1.4.2.md new file mode 100644 index 0000000..50fc79c --- /dev/null +++ b/RELEASE-NOTES-v1.4.2.md @@ -0,0 +1,19 @@ +# GLPI NeuroForge Mega v1.4.2 + +Bugfix-Release für den NeuroForge-WebUI-Tab **Wissensraum**. + +## Behoben + +- Leere Knowledge-Graphs werden von der Store/API-Schicht jetzt mit `nodes: []` und `edges: []` serialisiert statt mit `null`. +- Das Wissensraum-WebUI normalisiert `nodes` und `edges` defensiv, sodass auch ältere oder teilweise Antworten mit `null` keinen `.length`-Fehler mehr auslösen. +- Bei einem Graph-Ladefehler wird der Canvas auf einen definierten leeren Zustand zurückgesetzt. +- Auch Event-, Source- und Goal-Listen im NeuroForge-WebUI behandeln unerwartete `null`-Listen defensiv als `[]`. +- Regressionstest stellt sicher, dass ein leerer Knowledge-Graph nicht mehr als `null` serialisierbare Slices erzeugt. + +## Validierung + +- `go test ./...` für NeuroForge, Agent, Knowledgebase und Control Center: OK +- `go vet ./...` für alle vier Go-Module: OK +- NeuroForge Inline-JavaScript: `node --check` OK + +Keine Datenmigration und keine ENV-Änderung erforderlich. Ein Austausch/Neustart des NeuroForge-Images reicht. diff --git a/mega-project.json b/mega-project.json index 2b5d932..32d52e5 100644 --- a/mega-project.json +++ b/mega-project.json @@ -28,7 +28,7 @@ "schema": "Wiki/Schema.md", "glpi_relations": "KnowbaseItem_Item when exposed by GLPI OpenAPI" }, - "version": "1.4.0", + "version": "1.4.2", "controlled_learning": { "raw_chat_auto_learning": false, "validated_outcomes": [ diff --git a/patches/SHA256SUMS b/patches/SHA256SUMS index ba4b353..2f1095f 100644 --- a/patches/SHA256SUMS +++ b/patches/SHA256SUMS @@ -1,5 +1,8 @@ -47a6fa2c79bbba0c04af86dfa65d58529f492c698060fe586456c22a4eadb877 glpi-agent-mega.diff -9b411c90d96a86c80f088ee4637046eeefaf31c62059d11aa1a999d6eb08b5b4 glpi-knowledge-mega.diff -f0491de3cb6201f98ca6be8e865237adbba4772471f7fc7165d030e0c045fdb6 neuroforge-mega.diff -576e75ca9191bbef7f136abde90c10a1c6bf2e36450a421fd3796e988bd2af00 v1.1.0-to-v1.2.0.diff -7a5e0ae1d09b268d3ac62a51f92bfedf6771be26b909605a347f26d47d6fa8cb v1.2.0-to-v1.3.0.diff +47a6fa2c79bbba0c04af86dfa65d58529f492c698060fe586456c22a4eadb877 patches/glpi-agent-mega.diff +9b411c90d96a86c80f088ee4637046eeefaf31c62059d11aa1a999d6eb08b5b4 patches/glpi-knowledge-mega.diff +f0491de3cb6201f98ca6be8e865237adbba4772471f7fc7165d030e0c045fdb6 patches/neuroforge-mega.diff +576e75ca9191bbef7f136abde90c10a1c6bf2e36450a421fd3796e988bd2af00 patches/v1.1.0-to-v1.2.0.diff +7a5e0ae1d09b268d3ac62a51f92bfedf6771be26b909605a347f26d47d6fa8cb patches/v1.2.0-to-v1.3.0.diff +163a8a990aacd986bacb385494c2b2062ac10a9a8f0a456d19a6d1746c2fc500 patches/v1.3.0-to-v1.4.0.diff +654de4aac7cf318680872897c3de192afbafb61c7169acb8e7514116df86efde patches/v1.4.0-envfix-to-v1.4.1.diff +1ebc306bd6870389500c86cf07dcd677b5bd0448c762812c7e1c0f8732f36eb1 patches/v1.4.1-to-v1.4.2.diff diff --git a/patches/v1.4.1-to-v1.4.2.diff b/patches/v1.4.1-to-v1.4.2.diff new file mode 100644 index 0000000..d4b4847 --- /dev/null +++ b/patches/v1.4.1-to-v1.4.2.diff @@ -0,0 +1,127 @@ +diff --git a/README.md b/README.md +index d7c7452..d92c1b8 100644 +--- a/README.md ++++ b/README.md +@@ -1,5 +1,7 @@ + # GLPI NeuroForge Mega v1.4.0 + ++> Release: **v1.4.2** · Bugfix für leeren NeuroForge-Wissensraum-Graph. ++ + Ein kontrolliertes Monorepo aus **GLPI AI Agent**, **GLPI AI Knowledgebase** und **NeuroForge + SQAR**. Ziel ist nicht ein untrennbarer Monolith, sondern eine gemeinsame Plattform mit klaren Zuständigkeiten, getrennten Credentials und nachvollziehbaren Failure-Modi. + + ## Unified Graph Explorer (v1.4.0) +diff --git a/RELEASE-NOTES-v1.4.2.md b/RELEASE-NOTES-v1.4.2.md +new file mode 100644 +index 0000000..50fc79c +--- /dev/null ++++ b/RELEASE-NOTES-v1.4.2.md +@@ -0,0 +1,19 @@ ++# GLPI NeuroForge Mega v1.4.2 ++ ++Bugfix-Release für den NeuroForge-WebUI-Tab **Wissensraum**. ++ ++## Behoben ++ ++- Leere Knowledge-Graphs werden von der Store/API-Schicht jetzt mit `nodes: []` und `edges: []` serialisiert statt mit `null`. ++- Das Wissensraum-WebUI normalisiert `nodes` und `edges` defensiv, sodass auch ältere oder teilweise Antworten mit `null` keinen `.length`-Fehler mehr auslösen. ++- Bei einem Graph-Ladefehler wird der Canvas auf einen definierten leeren Zustand zurückgesetzt. ++- Auch Event-, Source- und Goal-Listen im NeuroForge-WebUI behandeln unerwartete `null`-Listen defensiv als `[]`. ++- Regressionstest stellt sicher, dass ein leerer Knowledge-Graph nicht mehr als `null` serialisierbare Slices erzeugt. ++ ++## Validierung ++ ++- `go test ./...` für NeuroForge, Agent, Knowledgebase und Control Center: OK ++- `go vet ./...` für alle vier Go-Module: OK ++- NeuroForge Inline-JavaScript: `node --check` OK ++ ++Keine Datenmigration und keine ENV-Änderung erforderlich. Ein Austausch/Neustart des NeuroForge-Images reicht. +diff --git a/mega-project.json b/mega-project.json +index 2b5d932..32d52e5 100644 +--- a/mega-project.json ++++ b/mega-project.json +@@ -28,7 +28,7 @@ + "schema": "Wiki/Schema.md", + "glpi_relations": "KnowbaseItem_Item when exposed by GLPI OpenAPI" + }, +- "version": "1.4.0", ++ "version": "1.4.2", + "controlled_learning": { + "raw_chat_auto_learning": false, + "validated_outcomes": [ +diff --git a/platform/neuroforge/internal/httpapi/index.html b/platform/neuroforge/internal/httpapi/index.html +index 6b5a22c..95b9eb6 100644 +--- a/platform/neuroforge/internal/httpapi/index.html ++++ b/platform/neuroforge/internal/httpapi/index.html +@@ -158,7 +158,7 @@ $$('[data-view]').forEach(b=>b.addEventListener('click',()=>switchView(b.dataset + + async function refreshAll(){if(!admin){$('sideStatus').textContent='Admin-Token setzen';return}try{let [s,k,g,src]=await Promise.all([req('/admin/api/status'),req('/admin/api/knowledge/summary'),req('/api/v1/goals'),req('/api/v1/sources?limit=8')]);$('sideDot').className='dot ok';$('sideStatus').textContent='bereit · rev '+s.revision;$('mMem').textContent=fmtN(s.memories);$('mSyn').textContent=fmtN(s.synapses);$('mSources').textContent=fmtN(k.sources||src.length);let active=(g||[]).filter(x=>x.status==='active');$('mGoals').textContent=active.length;$('mNextGoal').textContent=active.length?('nächster '+when(active.map(x=>x.next_cycle_at).filter(Boolean).sort()[0])):'keine aktiven Ziele';$('mTypes').textContent=Object.entries(k.by_type||{}).map(([a,b])=>a+' '+b).join(' · ');renderSystemState(s);renderEvents(k.recent_events||[],'dashEvents');renderDashboardGoals(active.slice(0,5));if(currentView==='ops')loadOps()}catch(e){$('sideDot').className='dot bad';$('sideStatus').textContent='Fehler';toast(e.message,true)}} + function renderSystemState(s){let o=s.observability||{},m=s.maintenance||{};$('systemState').innerHTML=[['Index',`${s.index_mode||o.index_mode||'–'} · HNSW ${fmtN(s.hnsw_nodes)} · PQ ${fmtN(s.disk_pq_items)}`],['Jobs',fmtN(s.pending_jobs)+' ausstehend'],['Cluster',s.cluster?.enabled?(s.cluster.role+' · term '+s.cluster.term):'deaktiviert'],['Letzter Autonomie-Lauf',m.last_autonomy_run?new Date(m.last_autonomy_run).toLocaleString():'–'],['Letzter Fehler',m.last_error||'keiner']].map(([a,b])=>`
${esc(e.summary||'')}
Noch keine Lernereignisse.
'} ++function renderEvents(xs,id){xs=Array.isArray(xs)?xs:[];$(id).innerHTML=xs.length?xs.map(e=>`${esc(e.summary||'')}
Noch keine Lernereignisse.
'} + function renderDashboardGoals(xs){$('dashGoals').innerHTML=xs.length?xs.map(g=>`Keine aktiven Ziele.
'} + + // ---- Knowledge graph with client-side LOD ---- +@@ -171,12 +171,12 @@ function lod(){return cam.z<.62?0:cam.z<1.45?1:2}function drawGraph(){let r=canv + function drawClusters(){let groups={};for(let p of graphPos.values()){let t=p.n.memory_type||'other';(groups[t]??=[]).push(p)}for(let[t,xs]of Object.entries(groups)){let x=xs.reduce((a,p)=>a+p.x,0)/xs.length,y=xs.reduce((a,p)=>a+p.y,0)/xs.length,s=worldToScreen({x,y}),rad=Math.min(58,20+Math.sqrt(xs.length)*6);ctx.fillStyle=(typeColors[t]||'#bd8cff')+'44';ctx.strokeStyle=typeColors[t]||'#bd8cff';ctx.lineWidth=2;ctx.beginPath();ctx.arc(s.x,s.y,rad,0,Math.PI*2);ctx.fill();ctx.stroke();ctx.fillStyle='#eef3fb';ctx.font='600 13px system-ui';ctx.textAlign='center';ctx.fillText(t,s.x,s.y-2);ctx.fillStyle='#a9b7cb';ctx.font='11px system-ui';ctx.fillText(xs.length+' nodes',s.x,s.y+15)}ctx.textAlign='left'} + function fitGraph(){if(!graphPos.size)return;let xs=[...graphPos.values()].map(p=>p.x),ys=[...graphPos.values()].map(p=>p.y),w=Math.max(...xs)-Math.min(...xs)+140,h=Math.max(...ys)-Math.min(...ys)+140,r=canvas.getBoundingClientRect();cam.z=Math.max(.25,Math.min(1.15,Math.min(r.width/w,r.height/h)));cam.x=cam.y=0;drawGraph()} + canvas.addEventListener('wheel',e=>{e.preventDefault();let before=screenToWorld(e.offsetX,e.offsetY),factor=Math.exp(-e.deltaY*.0012);cam.z=Math.max(.2,Math.min(5,cam.z*factor));let after=worldToScreen(before);cam.x+=e.offsetX-after.x;cam.y+=e.offsetY-after.y;drawGraph()},{passive:false});canvas.addEventListener('pointerdown',e=>{pointer.down=true;pointer.x=e.clientX;pointer.y=e.clientY;pointer.moved=false;canvas.setPointerCapture(e.pointerId)});canvas.addEventListener('pointermove',e=>{if(!pointer.down)return;let dx=e.clientX-pointer.x,dy=e.clientY-pointer.y;if(Math.hypot(dx,dy)>2)pointer.moved=true;cam.x+=dx;cam.y+=dy;pointer.x=e.clientX;pointer.y=e.clientY;drawGraph()});canvas.addEventListener('pointerup',e=>{pointer.down=false;if(pointer.moved)return;let rect=canvas.getBoundingClientRect(),x=e.clientX-rect.left,y=e.clientY-rect.top;if(lod()===0){cam.z=.8;drawGraph();return}let best=null,bd=15;for(let p of graphPos.values()){let s=worldToScreen(p),d=Math.hypot(s.x-x,s.y-y);if(dKeine Treffer.
';$$('#knowledgeHits [data-mid]').forEach(x=>x.addEventListener('click',()=>openMemory(x.dataset.mid)));if(d.hits?.[0]){selectedMemory=d.hits[0].memory.id;await loadKnowledgeGraph(selectedMemory);await openMemory(selectedMemory,false)}}catch(e){toast(e.message,true)}} + async function openMemory(id,recenter=true){selectedMemory=id;try{let d=await req('/admin/api/knowledge/memory/'+encodeURIComponent(id)),m=d.memory||{},p=m.provenance||{};$('knowledgeInspector').innerHTML=`Keine Synapsen.
'}${esc(e.summary)}
Keine Events.
'}`;$$('#knowledgeInspector [data-mid]').forEach(x=>x.addEventListener('click',()=>openMemory(x.dataset.mid)));drawGraph();if(recenter)loadKnowledgeGraph(id)}catch(e){toast(e.message,true)}} + + // ---- Sources & ingestion ---- +-async function loadSources(){try{let xs=await req('/api/v1/sources?limit=100');$('sourcesList').innerHTML=xs.length?xs.map(s=>`${esc(s.uri)}
`:''}${s.error?`${esc(s.error)}
`:''}Noch keine externen Quellen.
';$('mSources').textContent=fmtN(xs.length)}catch(e){toast(e.message,true)}}$('sourcesReloadBtn').addEventListener('click',loadSources); ++async function loadSources(){try{let xs=await req('/api/v1/sources?limit=100');xs=Array.isArray(xs)?xs:[];$('sourcesList').innerHTML=xs.length?xs.map(s=>`${esc(s.uri)}
`:''}${s.error?`${esc(s.error)}
`:''}Noch keine externen Quellen.
';$('mSources').textContent=fmtN(xs.length)}catch(e){toast(e.message,true)}}$('sourcesReloadBtn').addEventListener('click',loadSources); + const dz=$('dropZone'),fi=$('docFile');['dragenter','dragover'].forEach(n=>dz.addEventListener(n,e=>{e.preventDefault();dz.classList.add('drag')}));['dragleave','drop'].forEach(n=>dz.addEventListener(n,e=>{e.preventDefault();dz.classList.remove('drag')}));dz.addEventListener('drop',e=>{if(e.dataTransfer.files.length)fi.files=e.dataTransfer.files});$('uploadBtn').addEventListener('click',async()=>{let f=fi.files[0];if(!f)return toast('Bitte eine Datei auswählen.',true);let fd=new FormData();fd.append('file',f);fd.append('title',$('docTitle').value);fd.append('tags',$('docTags').value);fd.append('trust',$('docTrust').value);try{let r=await fetch('/api/v1/ingest/document',{method:'POST',headers:tokenHeaders(false),body:fd}),txt=await r.text(),d;try{d=JSON.parse(txt)}catch{d=txt}if(!r.ok)throw new Error(d.error||d);toast(`Dokument gelernt: ${d.chunks} Chunks, ${d.duplicates} Duplikate`);fi.value='';loadSources();refreshAll()}catch(e){toast(e.message,true)}}); + $('textLearnBtn').addEventListener('click',async()=>{try{let d=await req('/api/v1/ingest/text',{method:'POST',body:JSON.stringify({title:$('textTitle').value||'Manual text',text:$('textInput').value,source_uri:$('textURI').value,trust:Number($('textTrust').value||1),source_type:'text',memory_type:'semantic'})});toast(`Text gelernt: ${d.chunks} Chunks`);$('textInput').value='';loadSources();refreshAll()}catch(e){toast(e.message,true)}}); + +@@ -211,7 +211,7 @@ async function loadResearchHistory(){if(!researchWatch.goalId)return;try{let xs= + async function pollGoalResearch(){if(!researchWatch.goalId||currentView!=='goals')return;try{let q=`?after=${researchWatch.after}`+(researchWatch.runId?`&run_id=${encodeURIComponent(researchWatch.runId)}`:'');let d=await req('/api/v1/goals/'+encodeURIComponent(researchWatch.goalId)+'/research/live'+q);if(!d.run){researchWatch.run=null;researchWatch.events=[];researchWatch.after=0;renderResearchLive();return}if(d.reset||researchWatch.runId!==d.run.id){researchWatch.runId=d.run.id;researchWatch.after=0;researchWatch.events=[]}researchWatch.run=d.run;if(d.events?.length){researchWatch.events.push(...d.events);if(researchWatch.events.length>500)researchWatch.events=researchWatch.events.slice(-500);researchWatch.after=Math.max(researchWatch.after,...d.events.map(e=>Number(e.seq||0)))}renderResearchLive();if(d.run.status!=='running')loadResearchHistory()}catch(e){$('liveRunMeta').textContent='Live-Research konnte nicht geladen werden: '+e.message;$('livePulse').className='livePulse error'}} + function openGoalResearch(id,title){researchWatch.goalId=id;researchWatch.goalTitle=title||id;researchWatch.runId='';researchWatch.after=0;researchWatch.events=[];researchWatch.run=null;$('goalResearchLive').classList.add('show');$('liveGoalTitle').textContent='Live Research · '+researchWatch.goalTitle;renderResearchLive();loadResearchHistory();startResearchPolling();setTimeout(()=>$('goalResearchLive').scrollIntoView({behavior:'smooth',block:'start'}),20)} + $('liveCloseBtn').addEventListener('click',()=>{$('goalResearchLive').classList.remove('show');stopResearchPolling()});$('liveRefreshBtn').addEventListener('click',()=>{researchWatch.after=0;researchWatch.events=[];researchWatch.runId='';pollGoalResearch();loadResearchHistory()}); +-async function loadGoals(){try{let xs=await req('/api/v1/goals');$('goalsList').innerHTML=xs.length?xs.map(g=>{let active=g.status==='active',paused=g.status==='paused';return `${esc(g.description||'')}
Nächste Aktion: ${esc(g.next_action)}
`:''}${g.last_error?`${esc(g.last_error)}
`:''}Noch keine Ziele.
';$$('[data-goal-action]').forEach(b=>b.addEventListener('click',()=>goalAction(b)));$$('[data-research-live]').forEach(b=>b.addEventListener('click',()=>openGoalResearch(b.dataset.researchLive,b.dataset.goalTitle)));$$('[data-focus]').forEach(b=>b.addEventListener('click',()=>{switchView('knowledge');$('knowledgeQuery').value='goal:'+b.dataset.focus;searchKnowledge()}))}catch(e){toast(e.message,true)}} ++async function loadGoals(){try{let xs=await req('/api/v1/goals');xs=Array.isArray(xs)?xs:[];$('goalsList').innerHTML=xs.length?xs.map(g=>{let active=g.status==='active',paused=g.status==='paused';return `${esc(g.description||'')}
Nächste Aktion: ${esc(g.next_action)}
`:''}${g.last_error?`${esc(g.last_error)}
`:''}Noch keine Ziele.
';$$('[data-goal-action]').forEach(b=>b.addEventListener('click',()=>goalAction(b)));$$('[data-research-live]').forEach(b=>b.addEventListener('click',()=>openGoalResearch(b.dataset.researchLive,b.dataset.goalTitle)));$$('[data-focus]').forEach(b=>b.addEventListener('click',()=>{switchView('knowledge');$('knowledgeQuery').value='goal:'+b.dataset.focus;searchKnowledge()}))}catch(e){toast(e.message,true)}} + async function goalAction(b){let id=b.dataset.goalId,action=b.dataset.goalAction;if(action==='delete'&&!confirm(`Ziel „${b.dataset.goalTitle||id}“ wirklich löschen? Bereits gelerntes Wissen bleibt erhalten.`))return;if(action==='cycle')openGoalResearch(id,b.dataset.goalTitle||id);let old=b.textContent;try{b.disabled=true;b.textContent=action==='cycle'?'läuft …':action==='delete'?'lösche …':'speichere …';let path='/api/v1/goals/'+encodeURIComponent(id)+(action==='cycle'?'/cycle':action==='pause'?'/pause':action==='resume'?'/resume':'');let d=await req(path,{method:action==='delete'?'DELETE':'POST',body:action==='delete'?undefined:'{}'});if(action==='cycle')toast(`Zyklus fertig · Evaluation ${Number(d.evaluation).toFixed(2)} · ${d.sources_ingested||0} Quellen`);else if(action==='pause')toast('Ziel pausiert. Der Scheduler verarbeitet es nicht weiter.');else if(action==='resume')toast('Ziel wieder aktiv. Nächster Lauf wurde eingeplant.');else{toast('Ziel gelöscht. Bereits gelerntes Wissen bleibt erhalten.');if(researchWatch.goalId===id){$('goalResearchLive').classList.remove('show');stopResearchPolling();researchWatch.goalId=''}}await loadGoals();refreshAll()}catch(e){toast(e.message,true)}finally{b.disabled=false;b.textContent=old}} + $('goalCreateBtn').addEventListener('click',async()=>{let title=$('goalTitle').value.trim();if(!title)return toast('Titel fehlt.',true);try{let g=await req('/api/v1/goals',{method:'POST',body:JSON.stringify({title,description:$('goalDesc').value,target:$('goalTarget').value,priority:Number($('goalPriority').value||50),interval_minutes:Number($('goalInterval').value||10),status:'active'})});toast(`Ziel ${g.title} angelegt – erster Lauf ist eingeplant.`);$('goalTitle').value='';$('goalDesc').value='';loadGoals();refreshAll()}catch(e){toast(e.message,true)}});$('runAutonomyBtn').addEventListener('click',async()=>{try{let d=await req('/admin/api/autonomy',{method:'POST',body:'{}'});toast(`${d.cycles?.length||0} fällige Zyklen ausgeführt.`);loadGoals()}catch(e){toast(e.message,true)}}); + +diff --git a/platform/neuroforge/internal/store/knowledge.go b/platform/neuroforge/internal/store/knowledge.go +index cd4d2d9..817175a 100644 +--- a/platform/neuroforge/internal/store/knowledge.go ++++ b/platform/neuroforge/internal/store/knowledge.go +@@ -338,7 +338,7 @@ func (s *Store) KnowledgeGraph(center string, depth, maxNodes int) KnowledgeGrap + } + frontier = next + } +- out := KnowledgeGraph{Center: center} ++ out := KnowledgeGraph{Center: center, Nodes: make([]MemoryPreview, 0), Edges: make([]KnowledgeEdge, 0)} + for id := range selected { + if m, ok := s.fullMemoryForReadLocked(id); ok { + out.Nodes = append(out.Nodes, memoryPreview(m)) +diff --git a/platform/neuroforge/internal/store/knowledge_test.go b/platform/neuroforge/internal/store/knowledge_test.go +index fff10d6..2ce942e 100644 +--- a/platform/neuroforge/internal/store/knowledge_test.go ++++ b/platform/neuroforge/internal/store/knowledge_test.go +@@ -57,3 +57,22 @@ func TestKnowledgeEventPersistsAcrossRestart(t *testing.T) { + t.Fatalf("events=%+v", evs) + } + } ++ ++func TestKnowledgeGraphEmptyUsesEmptySlices(t *testing.T) { ++ s, err := New(t.TempDir()) ++ if err != nil { ++ t.Fatal(err) ++ } ++ defer s.Close() ++ ++ g := s.KnowledgeGraph("", 3, 600) ++ if g.Nodes == nil { ++ t.Fatal("nodes must be [] rather than null in JSON") ++ } ++ if g.Edges == nil { ++ t.Fatal("edges must be [] rather than null in JSON") ++ } ++ if len(g.Nodes) != 0 || len(g.Edges) != 0 { ++ t.Fatalf("unexpected graph contents: nodes=%d edges=%d", len(g.Nodes), len(g.Edges)) ++ } ++} diff --git a/platform/neuroforge/internal/httpapi/index.html b/platform/neuroforge/internal/httpapi/index.html index 6b5a22c..95b9eb6 100644 --- a/platform/neuroforge/internal/httpapi/index.html +++ b/platform/neuroforge/internal/httpapi/index.html @@ -158,7 +158,7 @@ $$('[data-view]').forEach(b=>b.addEventListener('click',()=>switchView(b.dataset async function refreshAll(){if(!admin){$('sideStatus').textContent='Admin-Token setzen';return}try{let [s,k,g,src]=await Promise.all([req('/admin/api/status'),req('/admin/api/knowledge/summary'),req('/api/v1/goals'),req('/api/v1/sources?limit=8')]);$('sideDot').className='dot ok';$('sideStatus').textContent='bereit · rev '+s.revision;$('mMem').textContent=fmtN(s.memories);$('mSyn').textContent=fmtN(s.synapses);$('mSources').textContent=fmtN(k.sources||src.length);let active=(g||[]).filter(x=>x.status==='active');$('mGoals').textContent=active.length;$('mNextGoal').textContent=active.length?('nächster '+when(active.map(x=>x.next_cycle_at).filter(Boolean).sort()[0])):'keine aktiven Ziele';$('mTypes').textContent=Object.entries(k.by_type||{}).map(([a,b])=>a+' '+b).join(' · ');renderSystemState(s);renderEvents(k.recent_events||[],'dashEvents');renderDashboardGoals(active.slice(0,5));if(currentView==='ops')loadOps()}catch(e){$('sideDot').className='dot bad';$('sideStatus').textContent='Fehler';toast(e.message,true)}} function renderSystemState(s){let o=s.observability||{},m=s.maintenance||{};$('systemState').innerHTML=[['Index',`${s.index_mode||o.index_mode||'–'} · HNSW ${fmtN(s.hnsw_nodes)} · PQ ${fmtN(s.disk_pq_items)}`],['Jobs',fmtN(s.pending_jobs)+' ausstehend'],['Cluster',s.cluster?.enabled?(s.cluster.role+' · term '+s.cluster.term):'deaktiviert'],['Letzter Autonomie-Lauf',m.last_autonomy_run?new Date(m.last_autonomy_run).toLocaleString():'–'],['Letzter Fehler',m.last_error||'keiner']].map(([a,b])=>`${esc(e.summary||'')}
Noch keine Lernereignisse.
'} +function renderEvents(xs,id){xs=Array.isArray(xs)?xs:[];$(id).innerHTML=xs.length?xs.map(e=>`${esc(e.summary||'')}
Noch keine Lernereignisse.
'} function renderDashboardGoals(xs){$('dashGoals').innerHTML=xs.length?xs.map(g=>`Keine aktiven Ziele.
'} // ---- Knowledge graph with client-side LOD ---- @@ -171,12 +171,12 @@ function lod(){return cam.z<.62?0:cam.z<1.45?1:2}function drawGraph(){let r=canv function drawClusters(){let groups={};for(let p of graphPos.values()){let t=p.n.memory_type||'other';(groups[t]??=[]).push(p)}for(let[t,xs]of Object.entries(groups)){let x=xs.reduce((a,p)=>a+p.x,0)/xs.length,y=xs.reduce((a,p)=>a+p.y,0)/xs.length,s=worldToScreen({x,y}),rad=Math.min(58,20+Math.sqrt(xs.length)*6);ctx.fillStyle=(typeColors[t]||'#bd8cff')+'44';ctx.strokeStyle=typeColors[t]||'#bd8cff';ctx.lineWidth=2;ctx.beginPath();ctx.arc(s.x,s.y,rad,0,Math.PI*2);ctx.fill();ctx.stroke();ctx.fillStyle='#eef3fb';ctx.font='600 13px system-ui';ctx.textAlign='center';ctx.fillText(t,s.x,s.y-2);ctx.fillStyle='#a9b7cb';ctx.font='11px system-ui';ctx.fillText(xs.length+' nodes',s.x,s.y+15)}ctx.textAlign='left'} function fitGraph(){if(!graphPos.size)return;let xs=[...graphPos.values()].map(p=>p.x),ys=[...graphPos.values()].map(p=>p.y),w=Math.max(...xs)-Math.min(...xs)+140,h=Math.max(...ys)-Math.min(...ys)+140,r=canvas.getBoundingClientRect();cam.z=Math.max(.25,Math.min(1.15,Math.min(r.width/w,r.height/h)));cam.x=cam.y=0;drawGraph()} canvas.addEventListener('wheel',e=>{e.preventDefault();let before=screenToWorld(e.offsetX,e.offsetY),factor=Math.exp(-e.deltaY*.0012);cam.z=Math.max(.2,Math.min(5,cam.z*factor));let after=worldToScreen(before);cam.x+=e.offsetX-after.x;cam.y+=e.offsetY-after.y;drawGraph()},{passive:false});canvas.addEventListener('pointerdown',e=>{pointer.down=true;pointer.x=e.clientX;pointer.y=e.clientY;pointer.moved=false;canvas.setPointerCapture(e.pointerId)});canvas.addEventListener('pointermove',e=>{if(!pointer.down)return;let dx=e.clientX-pointer.x,dy=e.clientY-pointer.y;if(Math.hypot(dx,dy)>2)pointer.moved=true;cam.x+=dx;cam.y+=dy;pointer.x=e.clientX;pointer.y=e.clientY;drawGraph()});canvas.addEventListener('pointerup',e=>{pointer.down=false;if(pointer.moved)return;let rect=canvas.getBoundingClientRect(),x=e.clientX-rect.left,y=e.clientY-rect.top;if(lod()===0){cam.z=.8;drawGraph();return}let best=null,bd=15;for(let p of graphPos.values()){let s=worldToScreen(p),d=Math.hypot(s.x-x,s.y-y);if(dKeine Treffer.
';$$('#knowledgeHits [data-mid]').forEach(x=>x.addEventListener('click',()=>openMemory(x.dataset.mid)));if(d.hits?.[0]){selectedMemory=d.hits[0].memory.id;await loadKnowledgeGraph(selectedMemory);await openMemory(selectedMemory,false)}}catch(e){toast(e.message,true)}} async function openMemory(id,recenter=true){selectedMemory=id;try{let d=await req('/admin/api/knowledge/memory/'+encodeURIComponent(id)),m=d.memory||{},p=m.provenance||{};$('knowledgeInspector').innerHTML=`Keine Synapsen.
'}${esc(e.summary)}
Keine Events.
'}`;$$('#knowledgeInspector [data-mid]').forEach(x=>x.addEventListener('click',()=>openMemory(x.dataset.mid)));drawGraph();if(recenter)loadKnowledgeGraph(id)}catch(e){toast(e.message,true)}} // ---- Sources & ingestion ---- -async function loadSources(){try{let xs=await req('/api/v1/sources?limit=100');$('sourcesList').innerHTML=xs.length?xs.map(s=>`${esc(s.uri)}
`:''}${s.error?`${esc(s.error)}
`:''}Noch keine externen Quellen.
';$('mSources').textContent=fmtN(xs.length)}catch(e){toast(e.message,true)}}$('sourcesReloadBtn').addEventListener('click',loadSources); +async function loadSources(){try{let xs=await req('/api/v1/sources?limit=100');xs=Array.isArray(xs)?xs:[];$('sourcesList').innerHTML=xs.length?xs.map(s=>`${esc(s.uri)}
`:''}${s.error?`${esc(s.error)}
`:''}Noch keine externen Quellen.
';$('mSources').textContent=fmtN(xs.length)}catch(e){toast(e.message,true)}}$('sourcesReloadBtn').addEventListener('click',loadSources); const dz=$('dropZone'),fi=$('docFile');['dragenter','dragover'].forEach(n=>dz.addEventListener(n,e=>{e.preventDefault();dz.classList.add('drag')}));['dragleave','drop'].forEach(n=>dz.addEventListener(n,e=>{e.preventDefault();dz.classList.remove('drag')}));dz.addEventListener('drop',e=>{if(e.dataTransfer.files.length)fi.files=e.dataTransfer.files});$('uploadBtn').addEventListener('click',async()=>{let f=fi.files[0];if(!f)return toast('Bitte eine Datei auswählen.',true);let fd=new FormData();fd.append('file',f);fd.append('title',$('docTitle').value);fd.append('tags',$('docTags').value);fd.append('trust',$('docTrust').value);try{let r=await fetch('/api/v1/ingest/document',{method:'POST',headers:tokenHeaders(false),body:fd}),txt=await r.text(),d;try{d=JSON.parse(txt)}catch{d=txt}if(!r.ok)throw new Error(d.error||d);toast(`Dokument gelernt: ${d.chunks} Chunks, ${d.duplicates} Duplikate`);fi.value='';loadSources();refreshAll()}catch(e){toast(e.message,true)}}); $('textLearnBtn').addEventListener('click',async()=>{try{let d=await req('/api/v1/ingest/text',{method:'POST',body:JSON.stringify({title:$('textTitle').value||'Manual text',text:$('textInput').value,source_uri:$('textURI').value,trust:Number($('textTrust').value||1),source_type:'text',memory_type:'semantic'})});toast(`Text gelernt: ${d.chunks} Chunks`);$('textInput').value='';loadSources();refreshAll()}catch(e){toast(e.message,true)}}); @@ -211,7 +211,7 @@ async function loadResearchHistory(){if(!researchWatch.goalId)return;try{let xs= async function pollGoalResearch(){if(!researchWatch.goalId||currentView!=='goals')return;try{let q=`?after=${researchWatch.after}`+(researchWatch.runId?`&run_id=${encodeURIComponent(researchWatch.runId)}`:'');let d=await req('/api/v1/goals/'+encodeURIComponent(researchWatch.goalId)+'/research/live'+q);if(!d.run){researchWatch.run=null;researchWatch.events=[];researchWatch.after=0;renderResearchLive();return}if(d.reset||researchWatch.runId!==d.run.id){researchWatch.runId=d.run.id;researchWatch.after=0;researchWatch.events=[]}researchWatch.run=d.run;if(d.events?.length){researchWatch.events.push(...d.events);if(researchWatch.events.length>500)researchWatch.events=researchWatch.events.slice(-500);researchWatch.after=Math.max(researchWatch.after,...d.events.map(e=>Number(e.seq||0)))}renderResearchLive();if(d.run.status!=='running')loadResearchHistory()}catch(e){$('liveRunMeta').textContent='Live-Research konnte nicht geladen werden: '+e.message;$('livePulse').className='livePulse error'}} function openGoalResearch(id,title){researchWatch.goalId=id;researchWatch.goalTitle=title||id;researchWatch.runId='';researchWatch.after=0;researchWatch.events=[];researchWatch.run=null;$('goalResearchLive').classList.add('show');$('liveGoalTitle').textContent='Live Research · '+researchWatch.goalTitle;renderResearchLive();loadResearchHistory();startResearchPolling();setTimeout(()=>$('goalResearchLive').scrollIntoView({behavior:'smooth',block:'start'}),20)} $('liveCloseBtn').addEventListener('click',()=>{$('goalResearchLive').classList.remove('show');stopResearchPolling()});$('liveRefreshBtn').addEventListener('click',()=>{researchWatch.after=0;researchWatch.events=[];researchWatch.runId='';pollGoalResearch();loadResearchHistory()}); -async function loadGoals(){try{let xs=await req('/api/v1/goals');$('goalsList').innerHTML=xs.length?xs.map(g=>{let active=g.status==='active',paused=g.status==='paused';return `${esc(g.description||'')}
Nächste Aktion: ${esc(g.next_action)}
`:''}${g.last_error?`${esc(g.last_error)}
`:''}Noch keine Ziele.
';$$('[data-goal-action]').forEach(b=>b.addEventListener('click',()=>goalAction(b)));$$('[data-research-live]').forEach(b=>b.addEventListener('click',()=>openGoalResearch(b.dataset.researchLive,b.dataset.goalTitle)));$$('[data-focus]').forEach(b=>b.addEventListener('click',()=>{switchView('knowledge');$('knowledgeQuery').value='goal:'+b.dataset.focus;searchKnowledge()}))}catch(e){toast(e.message,true)}} +async function loadGoals(){try{let xs=await req('/api/v1/goals');xs=Array.isArray(xs)?xs:[];$('goalsList').innerHTML=xs.length?xs.map(g=>{let active=g.status==='active',paused=g.status==='paused';return `${esc(g.description||'')}
Nächste Aktion: ${esc(g.next_action)}
`:''}${g.last_error?`${esc(g.last_error)}
`:''}Noch keine Ziele.
';$$('[data-goal-action]').forEach(b=>b.addEventListener('click',()=>goalAction(b)));$$('[data-research-live]').forEach(b=>b.addEventListener('click',()=>openGoalResearch(b.dataset.researchLive,b.dataset.goalTitle)));$$('[data-focus]').forEach(b=>b.addEventListener('click',()=>{switchView('knowledge');$('knowledgeQuery').value='goal:'+b.dataset.focus;searchKnowledge()}))}catch(e){toast(e.message,true)}} async function goalAction(b){let id=b.dataset.goalId,action=b.dataset.goalAction;if(action==='delete'&&!confirm(`Ziel „${b.dataset.goalTitle||id}“ wirklich löschen? Bereits gelerntes Wissen bleibt erhalten.`))return;if(action==='cycle')openGoalResearch(id,b.dataset.goalTitle||id);let old=b.textContent;try{b.disabled=true;b.textContent=action==='cycle'?'läuft …':action==='delete'?'lösche …':'speichere …';let path='/api/v1/goals/'+encodeURIComponent(id)+(action==='cycle'?'/cycle':action==='pause'?'/pause':action==='resume'?'/resume':'');let d=await req(path,{method:action==='delete'?'DELETE':'POST',body:action==='delete'?undefined:'{}'});if(action==='cycle')toast(`Zyklus fertig · Evaluation ${Number(d.evaluation).toFixed(2)} · ${d.sources_ingested||0} Quellen`);else if(action==='pause')toast('Ziel pausiert. Der Scheduler verarbeitet es nicht weiter.');else if(action==='resume')toast('Ziel wieder aktiv. Nächster Lauf wurde eingeplant.');else{toast('Ziel gelöscht. Bereits gelerntes Wissen bleibt erhalten.');if(researchWatch.goalId===id){$('goalResearchLive').classList.remove('show');stopResearchPolling();researchWatch.goalId=''}}await loadGoals();refreshAll()}catch(e){toast(e.message,true)}finally{b.disabled=false;b.textContent=old}} $('goalCreateBtn').addEventListener('click',async()=>{let title=$('goalTitle').value.trim();if(!title)return toast('Titel fehlt.',true);try{let g=await req('/api/v1/goals',{method:'POST',body:JSON.stringify({title,description:$('goalDesc').value,target:$('goalTarget').value,priority:Number($('goalPriority').value||50),interval_minutes:Number($('goalInterval').value||10),status:'active'})});toast(`Ziel ${g.title} angelegt – erster Lauf ist eingeplant.`);$('goalTitle').value='';$('goalDesc').value='';loadGoals();refreshAll()}catch(e){toast(e.message,true)}});$('runAutonomyBtn').addEventListener('click',async()=>{try{let d=await req('/admin/api/autonomy',{method:'POST',body:'{}'});toast(`${d.cycles?.length||0} fällige Zyklen ausgeführt.`);loadGoals()}catch(e){toast(e.message,true)}}); diff --git a/platform/neuroforge/internal/store/knowledge.go b/platform/neuroforge/internal/store/knowledge.go index cd4d2d9..817175a 100644 --- a/platform/neuroforge/internal/store/knowledge.go +++ b/platform/neuroforge/internal/store/knowledge.go @@ -338,7 +338,7 @@ func (s *Store) KnowledgeGraph(center string, depth, maxNodes int) KnowledgeGrap } frontier = next } - out := KnowledgeGraph{Center: center} + out := KnowledgeGraph{Center: center, Nodes: make([]MemoryPreview, 0), Edges: make([]KnowledgeEdge, 0)} for id := range selected { if m, ok := s.fullMemoryForReadLocked(id); ok { out.Nodes = append(out.Nodes, memoryPreview(m)) diff --git a/platform/neuroforge/internal/store/knowledge_test.go b/platform/neuroforge/internal/store/knowledge_test.go index fff10d6..2ce942e 100644 --- a/platform/neuroforge/internal/store/knowledge_test.go +++ b/platform/neuroforge/internal/store/knowledge_test.go @@ -57,3 +57,22 @@ func TestKnowledgeEventPersistsAcrossRestart(t *testing.T) { t.Fatalf("events=%+v", evs) } } + +func TestKnowledgeGraphEmptyUsesEmptySlices(t *testing.T) { + s, err := New(t.TempDir()) + if err != nil { + t.Fatal(err) + } + defer s.Close() + + g := s.KnowledgeGraph("", 3, 600) + if g.Nodes == nil { + t.Fatal("nodes must be [] rather than null in JSON") + } + if g.Edges == nil { + t.Fatal("edges must be [] rather than null in JSON") + } + if len(g.Nodes) != 0 || len(g.Edges) != 0 { + t.Fatalf("unexpected graph contents: nodes=%d edges=%d", len(g.Nodes), len(g.Edges)) + } +}