update-1.6.2a
ci / docker-build (push) Failing after 25s
ci / test (services/control) (push) Successful in 50s
ci / test (services/knowledge) (push) Successful in 52s
ci / test (platform/neuroforge) (push) Successful in 56s
ci / test (services/agent) (push) Successful in 1m1s
release-tag / release-image (push) Successful in 6m47s

This commit is contained in:
2026-09-09 14:14:05 +02:00
parent d18405ed2e
commit 9cf24dad23
10 changed files with 490 additions and 160 deletions
+22 -16
View File
@@ -2,6 +2,7 @@
079e174cd38e79421469cb5fc79ff4d886015cd5ec97c538466f97d70ce5331a ./.env.example
813c6a53b389380997fc71835ef72d3b593e1808c4fcc16dc0c309608a389f40 ./.env.subagent.example
2d49d2ee955bf352cc7e76068ec842808c0bf7bffaacb6a129b641a78bbde0e5 ./.gitea/workflows/ci.yml
ed22fda7661db8203563611dc144998161cd024e161b0471d615eaf0defeb7db ./.gitea/workflows/release-tag-gitea.yml
cefe3681ecaca9bbbf7fcc18e668c0208e12a2260b8527561d50818916cbf77a ./.gitea/workflows/release.yml
e1ff71187cc3411a85067b964264011db7bd109a585ef7b9ea5b08bda039d813 ./.gitignore
0451d06da4395dff53c5f5085bd5a78eb54a5a22ff2dbf9a717dddd13babf91a ./BUILD-AND-RELEASE.md
@@ -26,30 +27,35 @@ a2d5e268fe70979caccd895156b3d33bd61a3cc7e660ab7a5b07066849b3e248 ./VERSION
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ./backups/.gitkeep
8127e9db5e5e0af1d88770dc8fa60b381de45dbcc843262698cf9501409b4d58 ./deploy/searxng/settings.yml
316c40e929f7341b9a639d9351438d8da9d62e40783c1f05ade80ee2540ee08f ./deployments/README.md
30e63c048256d33c46931b9d9746652fd078845f446b8e395e7eb362f88b67eb ./deployments/agent/.env
0df71e0cca461b82f4991368c6f53c11bcaf62fdbdb3fda0f3aad1555e1bad36 ./deployments/agent/docker-compose.build.yml
c6c255cb0f19421618a8cc8fce3fdcefd707d39e8a371f4136b1445e3ecb7ca3 ./deployments/agent/docker-compose.yml
8a8ed00d937a8d2620e8a9713754e2b0e9c048fd2bb9b0d014edf8978bf7a961 ./deployments/combined/.env
c0f27c27b2d12919070493198d58f1efa90a0c8fa898b156400d7548d07971a7 ./deployments/combined/compose.yml
1474093f126f8cd140fcb5f45505fad114ebe37cdbc19526e1532b4d2c52b1ba ./deployments/combined/compose.yml.old
1cdd0d8f851d7f7d8533443d02496a9f368e4a4194faf39408ea67487b5f77d7 ./deployments/combined/docker-compose.build.yml
67697cc388446999f21528e19df491d86aaf1141c2cbfbc3182d0149a4958458 ./deployments/combined/docker-compose.yml
2284148fdb09db7cabba462eadfa9ba10f88a2c458279d774c5c28cb5ea31b94 ./deployments/cpu-subagent/.env
c14fe9d60e1e9c216fef80d3b596b670c56231dae72f94f9461c809ac4f24504 ./deployments/cpu-subagent/README.md
fa1f4ebcf753f7d3ba7b49a1bf9b9738d2f9f3cf1b88b3ed4403a819de196b4a ./deployments/cpu-subagent/docker-compose.yml
dead361b179b6f761b160bd302f304b62941781cd542aba29eb5cfca8ec0497c ./deployments/cpu-subagent/preflight.sh
28180b0dc3cc22d90b84ef3f2dffa30f05e4255b8dd15824d3d200d6628fbd5e ./deployments/gpu-subagent/.env
8bd16c76f2da34f50e827e131eefb260b1c56b7263f8b92b42795fdb719e9f40 ./deployments/gpu-subagent/README.md
d38f8433fb34c0d9b88b788cc95219c8b8ca96e6de93243c9a9eb8fc64946e40 ./deployments/gpu-subagent/docker-compose.yml
36d44f3bde10d228881273f04a309cbddba394c8bc559cb48f7d37ee8f2f4e3a ./deployments/gpu-subagent/preflight.sh
aee3a1b054e2f84b4f3d148067960d7d19b2edaf9eeb5dd8b02fbee4703481af ./deployments/knowledge/.env
81013b617610a4903ac7e237c231d62e0aa5caa083d73a5a838316db31d9dc55 ./deployments/knowledge/docker-compose.build.yml
6686e18e07453dcfdb5e57c0d456833ddf1e7a921fdd5f05abf324719fc69d5a ./deployments/knowledge/docker-compose.yml
966a910f90a25f5b434f386b0ea9828c5b4e270166f7101efcd582a37f9d13fe ./deployments/master/.env
16f8e8224c0c46e5c79f35b322695046edb13e608d176cb0b9f25590f11b6b01 ./deployments/master/README.md
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ./deployments/master/backups/.gitkeep
8127e9db5e5e0af1d88770dc8fa60b381de45dbcc843262698cf9501409b4d58 ./deployments/master/deploy/searxng/settings.yml
a7d49be8630c475230585db69ad977339d6b3e95c36379e75682f6ff124ab588 ./deployments/master/docker-compose.yml
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ./deployments/master/knowledge/.gitkeep
6cd2e5bdf4c2a213f548e4f7a139faeeb56ff74f54e6da72e430fbd71342d64d ./deployments/master/monitoring/README.md
0c2fc23f8f6deced618e025cc66fa5711349439814d8a2c6d0f347990b4632e1 ./deployments/master/monitoring/grafana/dashboards/neuroforge-master-subagents.json
2c7c42913333222438dc3dd9baaab21aa28bd9aff033a75f85c6981da00325be ./deployments/master/monitoring/grafana/provisioning/dashboards/dashboards.yml
083e25e212b70a4c0fb5cd8d2cd5a5735f8ae230809c7fac40187f785a8eb43d ./deployments/master/monitoring/grafana/provisioning/dashboards/neuroforge.yml
72305a9fded6b40fe740ec82dac495f006990e6e37de754d1fcd01d93da4834a ./deployments/master/monitoring/grafana/provisioning/datasources/prometheus.yml
3e42499e55aa2d5c211728809e22d713162c88a493610c2107da17891c3567ad ./deployments/master/monitoring/prometheus/alerts.yml
aac90d7e75a79256f77d730ca80fc1824e86f4299192526910b6f0226cedcbb9 ./deployments/master/monitoring/prometheus/prometheus.yml.template
5e3459ce74ef9f754e4a8439977f7da21563dc6863f760f49ef8699fe56ff603 ./deployments/master/monitoring/validate.sh
57fa3c3be7f476c791da0fd7e9985b38be82f1d9a816d13d75f645f29de57e86 ./deployments/master/preflight.sh
8dc996e535cdb7867421a02536f8b4ed0eb5d0dfdad0959ddd9db90fc04c7d4e ./deployments/ollama/.env
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ./deployments/master/staging/.gitkeep
15a8680873d1dbde2b7deca0a30b786f49f4498f499269e9cd24c33e0b243333 ./deployments/ollama/docker-compose.yml
8d4943a3e023ed3b24312ff0b406298026b4d32aac6873b0c8488812b20dba4b ./deployments/ollama/pull-models.sh
5d2ca8625ccb84cb868c3f7450a0c63fc128967aab5677363dfeab8f214f92c3 ./docker-bake.hcl
@@ -437,7 +443,7 @@ b74f901cca99eae4bed1b168f1f077a28bf1134a71c867d263c7834f6d896684 ./services/age
cfc570de023b80f9a775d82a8ad5398521a4f377a85bb86642e60d0f22333d94 ./services/agent/docker-compose.yml
a0105475dc054977223fac36618b8cd8137c55be1d11fddcd24e9a4d3074c170 ./services/agent/glpi-ai-agent-neural-brain.patch
8a7805be0da45875d45a5e5b26780fe659f671edd2d6b72c0d7da9606bf15755 ./services/agent/go.mod
e34ac0033c53effcf654689641a15a92deae1a987e3d92e9921b7b030efab3d0 ./services/agent/internal/agent/agent.go
beab3aa1f73f3b3f78f76ff43b60d2ea3d9c85353288b53e83b01bf3fd59db22 ./services/agent/internal/agent/agent.go
789e55accdbfbb99ef8179f5680aa340e67a055c71686533302558389dabfb8a ./services/agent/internal/agent/agent_test.go
a8eedc80f57b174fbad3850d787346f7b370236b4c2eef67905ef6b60a1ced64 ./services/agent/internal/agent/analysis_runs.go
46a8dfe5e80569aac48f9a098acb2c1089ea4ab9521ed6c92f53b020e0c76ccd ./services/agent/internal/agent/analysis_runs_test.go
@@ -456,8 +462,8 @@ c8da5784acc4f28d632f357dadbfc61209a37b698525dae40473d27625d9c8e2 ./services/age
fa1717032ad536bff867a07d3fdf0ae2e0a9d5d7637a27b43274832d51322eb6 ./services/agent/internal/config/config_test.go
6fe7335b0e66b60d5f6f27583b1ac11a0948719cfa657d51e77de0b337aab5d5 ./services/agent/internal/contextdata/collector.go
12e9eb6fd567b5d4173a63fa2b89be913b6d1c97e17b69048f46d119e88dce26 ./services/agent/internal/contextdata/collector_test.go
5eee0e130a9cb8452a07e9f5d4c76048ad2eb62aa411ea61c9dc6066fadff934 ./services/agent/internal/glpi/client.go
6499b2e19547d7d874dbaa64f0c905643ffe4d822329c6cd710df0a30a3a9755 ./services/agent/internal/glpi/client_test.go
cffe6bc72ac01f0eb96599a40af43941c0719bd8cb698eddb4441fbf11703c7a ./services/agent/internal/glpi/client.go
6d4846d6f4e327c8c55ebf7a83c9206bcc60364e739976c282873e5508f1789a ./services/agent/internal/glpi/client_test.go
247324ba41f3cd991a3582ed270d0b0d6c4c92c36f11e7742c20d3e59b982928 ./services/agent/internal/glpikb/sync.go
9836e2bf9153df638ead57d92c152b87f831e5a3696bcda4b1f650ff38e9ac70 ./services/agent/internal/glpikb/sync_test.go
787ab6b53623aa36ef6628f4eada11dcb4ed3011b34effd691bf07ba5d8f214d ./services/agent/internal/knowledge/category_mapping.go
@@ -467,8 +473,8 @@ df1ac214ebb88bf3ce7f68112129cf9ffc300dd725380c38661b85b04908ecdf ./services/age
63e47d86bcc89178fdc0f91e07ecb30bd01ee4b84bf29bfc660debbf26f8a40b ./services/agent/internal/knowledge/store_test.go
807996228b38e1cd97ec2516cb1ecfc4bd945b638fcde465a0991051df2f1a00 ./services/agent/internal/learning/outcomes.go
2917b3dab63783af4c92c48f269f097dc94b989008a5451f46d6c1b309ccdc9b ./services/agent/internal/learning/outcomes_test.go
cd8c7379d6ca50fed77a7eafb6ab34b12b9ce2fe56025da534f54e6db246430d ./services/agent/internal/learning/store.go
c540f2e554d66f36d79d2ce32c74a18b27b2940e96a40e89eae10abccd0bd2a5 ./services/agent/internal/learning/store_test.go
482ca79552b02a8a53bbd547318a04e01a0b41e64d4a347b00f850c0218febda ./services/agent/internal/learning/store.go
07505924d3a15046836e7e6d278d1583c6591268f2216c3be78c58e35eafc71a ./services/agent/internal/learning/store_test.go
5030ec3f51830f7decfb24c27f9b243cb64f97302a6a51df096bc957f24b3fae ./services/agent/internal/metrics/metrics.go
809c3f2833ed2093bf1f9e4466eb3d855f2a84d827cb21deb698dd0e7b63b403 ./services/agent/internal/model/model.go
975d5846fa38bf2478fa3ab9696660740c8d9a0549ce8d4f3443d4ca917c4aba ./services/agent/internal/model/reason_codes.go
@@ -489,10 +495,10 @@ ab29c5808a06f6b21a0776355eeaa030f69be5eee251e7b1dbd7e00c71045c61 ./services/age
c849df7e346e1bd1e122037a4eafc30fe461add9b1876d7b9d63a02e33d831ad ./services/agent/internal/uptimekuma/client_test.go
5f0375f33c4c3f0804e58bb7b3a8699a450a05100274fbb19762d59f4d5d0034 ./services/agent/internal/web/control_graph.go
cb68a23bef549186d251766a3d3820b611defd55e8f7386b8b87490a78b9e11d ./services/agent/internal/web/control_graph_test.go
b4f7d72022d5009fce43b5879483de92dacfee2b3bf527306902187c3d26486b ./services/agent/internal/web/server.go
1f517bcb30f95831a06dc2fe0fd9cd633a0c42fc795f67e5ce53de7a2eebd2b1 ./services/agent/internal/web/server_test.go
6b457a720d8c8c6b07b590ce827d5a51de611e50b6905ff9668ec8f3f4046fdc ./services/agent/internal/web/server.go
1edf74892fa1da9f614fee8ec47e6b900132677167f2642a625effc028f2dfa0 ./services/agent/internal/web/server_test.go
21b32b0b863ed62ae352011d7dd85f0134bf9909c23ee80b1c7e3c3605ef8cc4 ./services/agent/internal/web/templates/category-mappings.html
d0561b87acd8fe8c0ab7f1f0bb82e1df840db8342f91798e8cb0a0408064abcc ./services/agent/internal/web/templates/dashboard.html
158f91963bbcd39e19fedd6d8a3ced2b26ffc92a51f00a84d883f24557e03cb0 ./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
@@ -601,7 +607,7 @@ f1eab883370e0a40ef52a6b6d785a510d8ed48a19d25e0a2bc95f4f2cc8e329e ./services/age
895f8400aaad550ff2b96262eaa54e954d603129682e97aa4964539eb19bd7c2 ./services/agent/run.ps1
d1d4ab8145a7131c36df51d3a9fbdcdeb0cda3de134fe24c0e8d52370862f3b2 ./services/control/Dockerfile
f30ddf9251860d92717276483f7a2c2d7405f0516940ca62abc21d97ff3f3ad2 ./services/control/cmd/engineering-graph/main.go
10a365d00d56b812d3300fe08f33d1176d6819909235bc8e275606005e1b371f ./services/control/engineering-graph.json
c61a014f75ceb8c2d9365d5c053d952f80cb60faa1fbc4f3049999450b49adae ./services/control/engineering-graph.json
ba44c599b9faf861eca647614c4abd18548e2708233cf462b11eb277e96449b8 ./services/control/go.mod
da37393e58ff53847f26b1051f6d3a5270571d290b33d79360bae0dcb3513829 ./services/control/graph.go
e6a8021a44219219fe37ab5edc45d1908fc6579f653aa80c9966810d891ad27d ./services/control/graph_test.go
+1 -1
View File
@@ -1293,7 +1293,7 @@ func (s *Service) RecordCategoryFeedback(ctx context.Context, runID string, cate
}
func (s *Service) LearningExamples() []model.LearningExample {
if s.learning == nil {
return nil
return []model.LearningExample{}
}
return s.learning.List()
}
+128 -69
View File
@@ -28,6 +28,8 @@ type Client struct {
tokenExpiry time.Time
}
const maxGLPIResponseBytes = 32 << 20
func New(baseURL, version, clientID, clientSecret, username, password string, timeout time.Duration) *Client {
return &Client{baseURL: strings.TrimRight(baseURL, "/"), version: version, clientID: clientID, clientSecret: clientSecret, username: username, password: password, http: &http.Client{Timeout: timeout}}
}
@@ -104,7 +106,7 @@ func (c *Client) do(ctx context.Context, method, path string, query url.Values,
if err != nil {
return nil, nil, err
}
b, _ := io.ReadAll(io.LimitReader(resp.Body, 4<<20))
b, _ := io.ReadAll(io.LimitReader(resp.Body, maxGLPIResponseBytes))
resp.Body.Close()
if resp.StatusCode == http.StatusUnauthorized && attempt == 0 {
continue
@@ -426,70 +428,99 @@ func (c *Client) DiscoverKnowledgeBasePath(ctx context.Context, configured strin
}
return candidates[i].score > candidates[j].score
})
path := candidates[0].path
// The OpenAPI document may include /api.php/vX.Y in documented paths while
// c.do already prepends the configured API base. Keep only the route suffix.
if i := strings.Index(path, "/api.php/"); i >= 0 {
rest := path[i+len("/api.php/"):]
if slash := strings.Index(rest, "/"); slash >= 0 {
path = rest[slash:]
}
}
versionPrefix := "/" + strings.Trim(c.version, "/")
if strings.HasPrefix(path, versionPrefix+"/") {
path = strings.TrimPrefix(path, versionPrefix)
}
if !strings.HasPrefix(path, "/") {
path = "/" + path
}
return path, nil
return normalizeDocumentedAPIPath(candidates[0].path, c.version), nil
}
// ListKnowledgeBaseItems reads only items visible to the authenticated GLPI
// service account. Visibility is therefore enforced by GLPI itself; an
// optional filter can further restrict the collection on a per-instance basis.
func (c *Client) ListKnowledgeBaseItems(ctx context.Context, path string, limit int, filter string) ([]model.GLPIKnowledgeItem, error) {
q := url.Values{"limit": {strconv.Itoa(limit)}, "sort": {"date_mod"}, "order": {"DESC"}}
if strings.TrimSpace(filter) != "" {
q.Set("filter", filter)
if limit <= 0 {
return []model.GLPIKnowledgeItem{}, nil
}
b, _, err := c.do(ctx, http.MethodGet, path, q, nil)
if err != nil {
return nil, err
}
arr, err := extractArray(b)
if err != nil {
return nil, err
}
out := make([]model.GLPIKnowledgeItem, 0, len(arr))
for _, r := range arr {
id := int64Val(r["id"])
if id <= 0 {
continue
const pageSize = 100
out := make([]model.GLPIKnowledgeItem, 0, limit)
seen := make(map[int64]struct{}, limit)
start := 0
for len(out) < limit {
requestLimit := pageSize
if remaining := limit - len(out); remaining < requestLimit {
requestLimit = remaining
}
if firstString(r, "answer", "content", "text", "description") == "" {
if detail, _, e := c.do(ctx, http.MethodGet, strings.TrimRight(path, "/")+"/"+strconv.FormatInt(id, 10), nil, nil); e == nil {
q := url.Values{
"start": {strconv.Itoa(start)},
"limit": {strconv.Itoa(requestLimit)},
"sort": {"date_mod"},
"order": {"DESC"},
}
if strings.TrimSpace(filter) != "" {
q.Set("filter", filter)
}
b, headers, err := c.do(ctx, http.MethodGet, path, q, nil)
if err != nil {
return nil, err
}
arr, err := extractArray(b)
if err != nil {
return nil, err
}
if len(arr) == 0 {
break
}
newIDs := 0
for _, r := range arr {
id := int64Val(r["id"])
if id <= 0 {
continue
}
if _, duplicate := seen[id]; duplicate {
continue
}
seen[id] = struct{}{}
newIDs++
if firstString(r, "answer", "content", "text", "description") == "" {
detail, _, detailErr := c.do(ctx, http.MethodGet, strings.TrimRight(path, "/")+"/"+strconv.FormatInt(id, 10), nil, nil)
if detailErr != nil {
return nil, fmt.Errorf("fetch GLPI knowledge item %d detail: %w", id, detailErr)
}
var full map[string]any
if json.Unmarshal(detail, &full) == nil {
for k, v := range full {
r[k] = v
}
if err := json.Unmarshal(detail, &full); err != nil {
return nil, fmt.Errorf("decode GLPI knowledge item %d detail: %w", id, err)
}
for k, v := range full {
r[k] = v
}
}
title := firstString(r, "name", "title", "subject")
content := firstString(r, "answer", "content", "text", "description")
if title == "" || content == "" {
continue
}
out = append(out, model.GLPIKnowledgeItem{
ID: id,
Title: title,
Content: content,
CategoryIDs: knowledgeCategoryIDs(r),
Language: firstString(r, "language", "locale"),
ModifiedAt: firstString(r, "date_mod", "modified_at", "date_creation"),
})
if len(out) >= limit {
break
}
}
title := firstString(r, "name", "title", "subject")
content := firstString(r, "answer", "content", "text", "description")
if title == "" || content == "" {
continue
pageStart := start
start += len(arr)
moreKnown, hasMore := collectionHasMore(b, headers, pageStart, len(arr))
if len(arr) < requestLimit && (!moreKnown || !hasMore) {
break
}
out = append(out, model.GLPIKnowledgeItem{
ID: id,
Title: title,
Content: content,
CategoryIDs: knowledgeCategoryIDs(r),
Language: firstString(r, "language", "locale"),
ModifiedAt: firstString(r, "date_mod", "modified_at", "date_creation"),
})
if newIDs == 0 {
return nil, fmt.Errorf("GLPI knowledge pagination made no progress at start=%d; server may be ignoring the start parameter", pageStart)
}
}
if len(seen) > 0 && len(out) == 0 {
return nil, fmt.Errorf("GLPI returned %d knowledge records but none contained a usable title and article body", len(seen))
}
return out, nil
}
@@ -543,23 +574,7 @@ func (c *Client) ListKnowledgeBaseLinkedItems(ctx context.Context, articleIDs []
return candidates[i].score > candidates[j].score
})
chosen := candidates[0]
cleanPath := func(p string) string {
if i := strings.Index(p, "/api.php/"); i >= 0 {
rest := p[i+len("/api.php/"):]
if slash := strings.Index(rest, "/"); slash >= 0 {
p = rest[slash:]
}
}
versionPrefix := "/" + strings.Trim(c.version, "/")
if strings.HasPrefix(p, versionPrefix+"/") {
p = strings.TrimPrefix(p, versionPrefix)
}
if !strings.HasPrefix(p, "/") {
p = "/" + p
}
return p
}
chosen.path = cleanPath(chosen.path)
chosen.path = normalizeDocumentedAPIPath(chosen.path, c.version)
if limit <= 0 {
limit = 10000
}
@@ -690,6 +705,50 @@ func extractArray(b []byte) ([]map[string]any, error) {
}
return nil, fmt.Errorf("unexpected GLPI collection response: %.200s", string(b))
}
func collectionHasMore(body []byte, headers http.Header, start, count int) (known bool, more bool) {
if raw := strings.TrimSpace(headers.Get("Content-Range")); raw != "" {
if slash := strings.LastIndex(raw, "/"); slash >= 0 && slash+1 < len(raw) {
totalText := strings.TrimSpace(raw[slash+1:])
if totalText != "*" {
if total, err := strconv.Atoi(totalText); err == nil && total >= 0 {
return true, start+count < total
}
}
}
}
var obj map[string]any
if json.Unmarshal(body, &obj) == nil {
for _, key := range []string{"total", "totalcount", "total_count"} {
if total := int(int64Val(obj[key])); total > 0 {
return true, start+count < total
}
}
}
return false, false
}
var documentedVersionPrefix = regexp.MustCompile(`^/v[0-9]+(?:\.[0-9]+){0,2}/`)
func normalizeDocumentedAPIPath(path, configuredVersion string) string {
path = strings.TrimSpace(path)
if i := strings.Index(path, "/api.php/"); i >= 0 {
rest := path[i+len("/api.php/"):]
if slash := strings.Index(rest, "/"); slash >= 0 {
path = rest[slash:]
}
}
versionPrefix := "/" + strings.Trim(configuredVersion, "/")
if strings.HasPrefix(path, versionPrefix+"/") {
path = strings.TrimPrefix(path, versionPrefix)
} else if documentedVersionPrefix.MatchString(path) {
path = documentedVersionPrefix.ReplaceAllString(path, "/")
}
if !strings.HasPrefix(path, "/") {
path = "/" + path
}
return path
}
func decodeTicket(r map[string]any) model.Ticket {
t := model.Ticket{
ID: int64Val(r["id"]),
+139
View File
@@ -3,8 +3,11 @@ package glpi
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"reflect"
"strconv"
"strings"
"testing"
"time"
@@ -91,6 +94,142 @@ func TestDiscoverAndListKnowledgeBase(t *testing.T) {
}
}
func TestDiscoverKnowledgeBaseStripsPatchVersionPrefix(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/api.php/token":
_ = json.NewEncoder(w).Encode(map[string]any{"access_token": "x", "expires_in": 3600})
case "/api.php/doc.json":
_ = json.NewEncoder(w).Encode(map[string]any{"paths": map[string]any{
"/v2.3.0/Knowledge/KnowbaseItem": map[string]any{"get": map[string]any{}},
}})
default:
http.NotFound(w, r)
}
}))
defer srv.Close()
c := New(srv.URL, "v2.3", "cid", "sec", "u", "p", time.Second)
path, err := c.DiscoverKnowledgeBasePath(context.Background(), "auto")
if err != nil {
t.Fatal(err)
}
if path != "/Knowledge/KnowbaseItem" {
t.Fatalf("path=%q, want /Knowledge/KnowbaseItem", path)
}
}
func TestListKnowledgeBasePaginatesLargeCollections(t *testing.T) {
const total = 235
var starts []int
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/api.php/token":
_ = json.NewEncoder(w).Encode(map[string]any{"access_token": "x", "expires_in": 3600})
case "/api.php/v2.3/Knowledge/KnowbaseItem":
start, _ := strconv.Atoi(r.URL.Query().Get("start"))
limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
starts = append(starts, start)
if limit <= 0 || limit > 100 {
t.Fatalf("unexpected page limit %d", limit)
}
end := start + limit
if end > total {
end = total
}
rows := make([]map[string]any, 0, max(0, end-start))
for i := start; i < end; i++ {
rows = append(rows, map[string]any{
"id": i + 1, "name": fmt.Sprintf("Artikel %d", i+1), "answer": "Inhalt", "date_mod": "2026-09-09 10:00:00",
})
}
_ = json.NewEncoder(w).Encode(rows)
default:
http.NotFound(w, r)
}
}))
defer srv.Close()
c := New(srv.URL, "v2.3", "cid", "sec", "u", "p", time.Second)
items, err := c.ListKnowledgeBaseItems(context.Background(), "/Knowledge/KnowbaseItem", total, "")
if err != nil {
t.Fatal(err)
}
if len(items) != total {
t.Fatalf("items=%d, want %d", len(items), total)
}
wantStarts := []int{0, 100, 200}
if !reflect.DeepEqual(starts, wantStarts) {
t.Fatalf("starts=%v, want %v", starts, wantStarts)
}
}
func TestListKnowledgeBaseContinuesWhenServerCapsPageSize(t *testing.T) {
const total = 121
const serverCap = 37
var starts []int
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/api.php/token":
_ = json.NewEncoder(w).Encode(map[string]any{"access_token": "x", "expires_in": 3600})
case "/api.php/v2.3/Knowledge/KnowbaseItem":
start, _ := strconv.Atoi(r.URL.Query().Get("start"))
requested, _ := strconv.Atoi(r.URL.Query().Get("limit"))
starts = append(starts, start)
pageLimit := requested
if pageLimit > serverCap {
pageLimit = serverCap
}
end := start + pageLimit
if end > total {
end = total
}
rows := make([]map[string]any, 0, max(0, end-start))
for i := start; i < end; i++ {
rows = append(rows, map[string]any{"id": i + 1, "name": fmt.Sprintf("Artikel %d", i+1), "answer": "Inhalt"})
}
if len(rows) > 0 {
w.Header().Set("Content-Range", fmt.Sprintf("items %d-%d/%d", start, end-1, total))
}
_ = json.NewEncoder(w).Encode(rows)
default:
http.NotFound(w, r)
}
}))
defer srv.Close()
c := New(srv.URL, "v2.3", "cid", "sec", "u", "p", time.Second)
items, err := c.ListKnowledgeBaseItems(context.Background(), "/Knowledge/KnowbaseItem", total, "")
if err != nil {
t.Fatal(err)
}
if len(items) != total {
t.Fatalf("items=%d, want %d", len(items), total)
}
wantStarts := []int{0, 37, 74, 111}
if !reflect.DeepEqual(starts, wantStarts) {
t.Fatalf("starts=%v, want %v", starts, wantStarts)
}
}
func TestListKnowledgeBaseFailsWhenRequiredDetailCannotBeRead(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/api.php/token":
_ = json.NewEncoder(w).Encode(map[string]any{"access_token": "x", "expires_in": 3600})
case "/api.php/v2.3/Knowledge/KnowbaseItem":
_ = json.NewEncoder(w).Encode([]map[string]any{{"id": 7, "name": "Konto gesperrt"}})
case "/api.php/v2.3/Knowledge/KnowbaseItem/7":
http.Error(w, "missing right", http.StatusForbidden)
default:
http.NotFound(w, r)
}
}))
defer srv.Close()
c := New(srv.URL, "v2.3", "cid", "sec", "u", "p", time.Second)
_, err := c.ListKnowledgeBaseItems(context.Background(), "/Knowledge/KnowbaseItem", 50, "")
if err == nil || !strings.Contains(err.Error(), "knowledge item 7 detail") {
t.Fatalf("expected explicit detail-read error, got %v", err)
}
}
func TestAddFollowupPreservesRichHTML(t *testing.T) {
var gotContent string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+2 -2
View File
@@ -98,11 +98,11 @@ func (s *Store) Delete(id string) error {
func (s *Store) List() []model.LearningExample {
if s == nil {
return nil
return []model.LearningExample{}
}
s.mu.RLock()
defer s.mu.RUnlock()
out := append([]model.LearningExample(nil), s.examples...)
out := append([]model.LearningExample{}, s.examples...)
sort.SliceStable(out, func(i, j int) bool { return out[i].CreatedAt.After(out[j].CreatedAt) })
return out
}
@@ -28,3 +28,17 @@ func TestAddReplaceAndDelete(t *testing.T) {
t.Fatal("old replaced id should not exist")
}
}
func TestEmptyListIsNonNil(t *testing.T) {
s, err := Open(t.TempDir(), 10)
if err != nil {
t.Fatal(err)
}
rows := s.List()
if rows == nil {
t.Fatal("empty learning list must serialize as [] instead of null")
}
if len(rows) != 0 {
t.Fatalf("rows=%d, want 0", len(rows))
}
}
+5 -1
View File
@@ -640,7 +640,11 @@ func (s *Server) knowledgeDelete(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
}
func (s *Server) learningList(w http.ResponseWriter, r *http.Request) {
respondJSON(w, s.feedback.LearningExamples())
rows := s.feedback.LearningExamples()
if rows == nil {
rows = []model.LearningExample{}
}
respondJSON(w, rows)
}
func (s *Server) learningAdd(w http.ResponseWriter, r *http.Request) {
if !s.cfg.LearningEnabled {
@@ -51,6 +51,18 @@ func TestDashboardTemplateParses(t *testing.T) {
}
}
func TestLearningListSerializesEmptyArray(t *testing.T) {
h, _ := newKnowledgeTestServer(t)
rr := httptest.NewRecorder()
h.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/api/learning", nil))
if rr.Code != http.StatusOK {
t.Fatalf("learning list = %d: %s", rr.Code, rr.Body.String())
}
if got := strings.TrimSpace(rr.Body.String()); got != "[]" {
t.Fatalf("learning list body=%q, want []", got)
}
}
type fakeFeedback struct {
cats []model.Category
outcomeRows []model.ValidatedOutcomeEvidence
@@ -107,6 +107,7 @@ const esc=s=>String(s??'').replace(/[&<>"']/g,c=>({'&':'&amp;','<':'&lt;','>':'&
const pct=v=>`${Math.round(Number(v||0)*100)} %`; const fmtNum=v=>new Intl.NumberFormat('de-DE').format(Number(v||0));
const fmtDate=v=>{if(!v)return '';const d=new Date(v);return Number.isNaN(d.getTime())?'':d.toLocaleString('de-DE')};
const clamp=v=>Math.max(0,Math.min(1,Number(v||0))); const scoreClass=v=>Number(v)>=.8?'good':Number(v)>=.6?'warn':'bad';
const asArray=v=>Array.isArray(v)?v:[];
let statusData={},runsData=[],categories=[],kbDocs=[],learningRows=[],outcomeRows=[],currentRun=null,currentKbId='',currentLearnRun='',kbCategorySelection=new Set();
const viewMeta={overview:['Übersicht','Gesundheit, Verarbeitung und die wichtigsten Stellschrauben auf einen Blick.'],runs:['Verarbeitungen','Audit-Trail mit KI-, Policy-, RAG- und Kontextdetails.'],knowledge:['Knowledge Base','Interne und synchronisierte Wissensquellen verwalten und kontrollieren.'],learning:['Bestätigtes Lernen','Menschlich bestätigte Beispiele, die die Kategorisierung schrittweise verbessern.'],settings:['Effektive Konfiguration','Alle nicht-geheimen Laufzeitwerte zum Tuning und Troubleshooting.']};
function toast(text,kind='good'){const n=document.createElement('div');n.className=`toast ${kind}`;n.textContent=text;$('#toasts').appendChild(n);setTimeout(()=>n.remove(),5000)}
@@ -164,11 +165,11 @@ async function deleteLearning(id){if(!confirm('Lernbeispiel wirklich löschen?')
async function recordOutcome(runID,decision){const r=runsData.find(x=>x.run_id===runID);if(!r)return;let corrected='';if(decision==='corrected'){corrected=prompt('Korrigierte, fachlich bestätigte Antwort:',r.reply_proposed_text||'')||'';if(!corrected.trim())return}const note=prompt('Optionaler Prüfvermerk / Grund (kann leer bleiben):','')||'';if(!confirm(decision==='accepted'?'Diese KI-Antwort als technisch bestätigt lernen?':'Diese korrigierte Antwort als technisch bestätigt lernen?'))return;try{await api('/api/outcomes',{method:'POST',body:JSON.stringify({run_id:runID,decision,corrected_reply:corrected,note})});toast('Bestätigtes Ticket-Outcome wurde an NeuroForge übergeben.');await loadOutcomes();await loadRuns();const fresh=runsData.find(x=>x.run_id===runID);if(fresh)renderRunDrawer(fresh)}catch(e){toast(e.message,'error')}}
async function reprocessTicket(){const id=Number($('#reprocessTicketID').value||0);if(!Number.isInteger(id)||id<=0){toast('Bitte eine gültige Ticket-ID eingeben.','error');return}const live=!statusData.dry_run;if(live&&!confirm(`Ticket #${id} im LIVE-Modus neu analysieren? Konfigurierte Auto-Aktionen können ausgeführt werden.`))return;const b=$('#reprocessTicketBtn');b.disabled=true;try{await api(`/api/tickets/${id}/reprocess`,{method:'POST',body:'{}'});toast(`Ticket #${id} wurde zur Neuanalyse eingereiht.`);setTimeout(()=>refreshAll(),800)}catch(e){toast(e.message,'error')}finally{b.disabled=false}}
async function loadStatus(){statusData=await api('/api/status');renderStatusChrome();renderOverview();renderConfig();renderSourceOptions()}
async function loadRuns(){const limit=Number($('#runLimit').value||50);runsData=await api(`/api/runs?limit=${limit}`);renderRuns();renderOverview()}
async function loadKnowledge(){kbDocs=await api('/api/knowledge');renderSourceOptions();renderKB();renderOverview()}
async function loadLearning(){learningRows=await api('/api/learning');renderLearning();renderOverview()}
async function loadOutcomes(){outcomeRows=await api('/api/outcomes')}
async function loadCategories(){categories=await api('/api/categories');renderCategoryPicker($('#kbCategorySearch').value||'')}
async function loadRuns(){const limit=Number($('#runLimit').value||50);runsData=asArray(await api(`/api/runs?limit=${limit}`));renderRuns();renderOverview()}
async function loadKnowledge(){kbDocs=asArray(await api('/api/knowledge'));renderSourceOptions();renderKB();renderOverview()}
async function loadLearning(){learningRows=asArray(await api('/api/learning'));renderLearning();renderOverview()}
async function loadOutcomes(){outcomeRows=asArray(await api('/api/outcomes'))}
async function loadCategories(){categories=asArray(await api('/api/categories'));renderCategoryPicker($('#kbCategorySearch').value||'')}
async function refreshAll(showToast=false){$('#refreshBtn').disabled=true;try{await Promise.all([loadStatus(),loadRuns(),loadKnowledge(),loadLearning(),loadOutcomes(),loadCategories()]);renderSourceOptions();renderOverview();renderRuns();renderKB();renderLearning();renderConfig();if(showToast)toast('Dashboard aktualisiert.')}catch(e){toast(e.message,'error')}finally{$('#refreshBtn').disabled=false;$('#lastRefresh').textContent=new Date().toLocaleTimeString('de-DE')}}
$('#nav').addEventListener('click',e=>{const b=e.target.closest('[data-view]');if(b)setView(b.dataset.view)});document.addEventListener('click',e=>{const g=e.target.closest('[data-goto]');if(g)setView(g.dataset.goto);const row=e.target.closest('[data-run-id]');if(row){const x=runsData.find(r=>r.run_id===row.dataset.runId);if(x)renderRunDrawer(x)}const ed=e.target.closest('[data-kb-edit]');if(ed)editKB(ed.dataset.kbEdit);const del=e.target.closest('[data-kb-delete]');if(del)deleteKB(del.dataset.kbDelete);const ld=e.target.closest('[data-learning-delete]');if(ld)deleteLearning(ld.dataset.learningDelete);const lr=e.target.closest('[data-learn-run]');if(lr)openLearn(lr.dataset.learnRun,Number(lr.dataset.learnCat||0));const oa=e.target.closest('[data-outcome-accept]');if(oa)recordOutcome(oa.dataset.outcomeAccept,'accepted');const oc=e.target.closest('[data-outcome-correct]');if(oc)recordOutcome(oc.dataset.outcomeCorrect,'corrected')});
+161 -66
View File
@@ -2836,7 +2836,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": true,
"line": 35,
"line": 37,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -2848,7 +2848,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": true,
"line": 314,
"line": 316,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -2860,7 +2860,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": true,
"line": 310,
"line": 312,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -2872,7 +2872,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": true,
"line": 373,
"line": 375,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -2884,7 +2884,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": true,
"line": 124,
"line": 126,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -2896,7 +2896,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": true,
"line": 354,
"line": 356,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -2908,7 +2908,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": true,
"line": 238,
"line": 240,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -2920,7 +2920,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": true,
"line": 227,
"line": 229,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -2932,7 +2932,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": true,
"line": 327,
"line": 329,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -2944,7 +2944,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": true,
"line": 949,
"line": 1008,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -2956,7 +2956,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": true,
"line": 207,
"line": 209,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -2968,7 +2968,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": true,
"line": 451,
"line": 437,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -2980,7 +2980,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": true,
"line": 501,
"line": 532,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -2992,7 +2992,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": true,
"line": 979,
"line": 1038,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -3004,7 +3004,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": true,
"line": 187,
"line": 189,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -3016,7 +3016,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": true,
"line": 1013,
"line": 1072,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -3028,7 +3028,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": true,
"line": 120,
"line": 122,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -3040,7 +3040,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": true,
"line": 262,
"line": 264,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -3052,7 +3052,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": true,
"line": 266,
"line": 268,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -3064,7 +3064,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": true,
"line": 253,
"line": 255,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -3076,7 +3076,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": true,
"line": 257,
"line": 259,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -3088,7 +3088,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": true,
"line": 146,
"line": 148,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -3100,7 +3100,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": true,
"line": 925,
"line": 984,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -3112,7 +3112,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": false,
"line": 318,
"line": 320,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -3124,7 +3124,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": false,
"line": 36,
"line": 38,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -3136,7 +3136,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": false,
"line": 76,
"line": 78,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -3148,7 +3148,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": false,
"line": 270,
"line": 272,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -3160,7 +3160,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": true,
"line": 31,
"line": 33,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -3172,7 +3172,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": false,
"line": 792,
"line": 851,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -3184,7 +3184,19 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": false,
"line": 910,
"line": 969,
"path": "services/agent/internal/glpi/client.go"
}
},
{
"id": "function:github.com/example/glpi-ai-agent/internal/glpi:collectionHasMore",
"kind": "function",
"label": "collectionHasMore",
"group": "engineering",
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": false,
"line": 709,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -3196,7 +3208,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": false,
"line": 865,
"line": 924,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -3208,7 +3220,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": false,
"line": 693,
"line": 752,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -3220,7 +3232,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": false,
"line": 716,
"line": 775,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -3232,7 +3244,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": false,
"line": 677,
"line": 692,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -3244,7 +3256,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": false,
"line": 812,
"line": 871,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -3256,7 +3268,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": false,
"line": 762,
"line": 821,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -3268,7 +3280,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": false,
"line": 629,
"line": 644,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -3280,7 +3292,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": false,
"line": 868,
"line": 927,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -3292,7 +3304,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": false,
"line": 1054,
"line": 1113,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -3304,7 +3316,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": false,
"line": 884,
"line": 943,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -3316,7 +3328,19 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": false,
"line": 638,
"line": 653,
"path": "services/agent/internal/glpi/client.go"
}
},
{
"id": "function:github.com/example/glpi-ai-agent/internal/glpi:normalizeDocumentedAPIPath",
"kind": "function",
"label": "normalizeDocumentedAPIPath",
"group": "engineering",
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": false,
"line": 733,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -3328,7 +3352,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": false,
"line": 175,
"line": 177,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -3340,7 +3364,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": false,
"line": 878,
"line": 937,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -3352,7 +3376,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": false,
"line": 1066,
"line": 1125,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -3364,7 +3388,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": false,
"line": 901,
"line": 960,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -3376,7 +3400,7 @@
"community": "github.com/example/glpi-ai-agent/internal/glpi",
"meta": {
"exported": false,
"line": 294,
"line": 296,
"path": "services/agent/internal/glpi/client.go"
}
},
@@ -6748,7 +6772,7 @@
"community": "github.com/example/glpi-ai-agent/internal/web",
"meta": {
"exported": true,
"line": 867,
"line": 871,
"path": "services/agent/internal/web/server.go"
}
},
@@ -6784,7 +6808,7 @@
"community": "github.com/example/glpi-ai-agent/internal/web",
"meta": {
"exported": true,
"line": 870,
"line": 874,
"path": "services/agent/internal/web/server.go"
}
},
@@ -6796,7 +6820,7 @@
"community": "github.com/example/glpi-ai-agent/internal/web",
"meta": {
"exported": false,
"line": 835,
"line": 839,
"path": "services/agent/internal/web/server.go"
}
},
@@ -7072,7 +7096,7 @@
"community": "github.com/example/glpi-ai-agent/internal/web",
"meta": {
"exported": false,
"line": 645,
"line": 649,
"path": "services/agent/internal/web/server.go"
}
},
@@ -7084,7 +7108,7 @@
"community": "github.com/example/glpi-ai-agent/internal/web",
"meta": {
"exported": false,
"line": 665,
"line": 669,
"path": "services/agent/internal/web/server.go"
}
},
@@ -7108,7 +7132,7 @@
"community": "github.com/example/glpi-ai-agent/internal/web",
"meta": {
"exported": false,
"line": 706,
"line": 710,
"path": "services/agent/internal/web/server.go"
}
},
@@ -7120,7 +7144,7 @@
"community": "github.com/example/glpi-ai-agent/internal/web",
"meta": {
"exported": false,
"line": 679,
"line": 683,
"path": "services/agent/internal/web/server.go"
}
},
@@ -7132,7 +7156,7 @@
"community": "github.com/example/glpi-ai-agent/internal/web",
"meta": {
"exported": false,
"line": 676,
"line": 680,
"path": "services/agent/internal/web/server.go"
}
},
@@ -7156,7 +7180,7 @@
"community": "github.com/example/glpi-ai-agent/internal/web",
"meta": {
"exported": false,
"line": 884,
"line": 888,
"path": "services/agent/internal/web/server.go"
}
},
@@ -7180,7 +7204,7 @@
"community": "github.com/example/glpi-ai-agent/internal/web",
"meta": {
"exported": false,
"line": 720,
"line": 724,
"path": "services/agent/internal/web/server.go"
}
},
@@ -7228,7 +7252,7 @@
"community": "github.com/example/glpi-ai-agent/internal/web",
"meta": {
"exported": false,
"line": 740,
"line": 744,
"path": "services/agent/internal/web/server.go"
}
},
@@ -7324,7 +7348,7 @@
"community": "github.com/example/glpi-ai-agent/internal/web",
"meta": {
"exported": false,
"line": 768,
"line": 772,
"path": "services/agent/internal/web/server.go"
}
},
@@ -7336,7 +7360,7 @@
"community": "github.com/example/glpi-ai-agent/internal/web",
"meta": {
"exported": false,
"line": 819,
"line": 823,
"path": "services/agent/internal/web/server.go"
}
},
@@ -7360,7 +7384,7 @@
"community": "github.com/example/glpi-ai-agent/internal/web",
"meta": {
"exported": false,
"line": 858,
"line": 862,
"path": "services/agent/internal/web/server.go"
}
},
@@ -7372,7 +7396,7 @@
"community": "github.com/example/glpi-ai-agent/internal/web",
"meta": {
"exported": false,
"line": 829,
"line": 833,
"path": "services/agent/internal/web/server.go"
}
},
@@ -7384,7 +7408,7 @@
"community": "github.com/example/glpi-ai-agent/internal/web",
"meta": {
"exported": false,
"line": 830,
"line": 834,
"path": "services/agent/internal/web/server.go"
}
},
@@ -7396,7 +7420,7 @@
"community": "github.com/example/glpi-ai-agent/internal/web",
"meta": {
"exported": false,
"line": 849,
"line": 853,
"path": "services/agent/internal/web/server.go"
}
},
@@ -7408,7 +7432,7 @@
"community": "github.com/example/glpi-ai-agent/internal/web",
"meta": {
"exported": false,
"line": 781,
"line": 785,
"path": "services/agent/internal/web/server.go"
}
},
@@ -30669,6 +30693,12 @@
"to": "function:github.com/example/glpi-ai-agent/internal/glpi:boolVal",
"kind": "defines"
},
{
"id": "file:services/agent/internal/glpi/client.go-\u003efunction:github.com/example/glpi-ai-agent/internal/glpi:collectionHasMore:defines",
"from": "file:services/agent/internal/glpi/client.go",
"to": "function:github.com/example/glpi-ai-agent/internal/glpi:collectionHasMore",
"kind": "defines"
},
{
"id": "file:services/agent/internal/glpi/client.go-\u003efunction:github.com/example/glpi-ai-agent/internal/glpi:decodeFollowup:defines",
"from": "file:services/agent/internal/glpi/client.go",
@@ -30735,6 +30765,12 @@
"to": "function:github.com/example/glpi-ai-agent/internal/glpi:knowledgeCategoryIDs",
"kind": "defines"
},
{
"id": "file:services/agent/internal/glpi/client.go-\u003efunction:github.com/example/glpi-ai-agent/internal/glpi:normalizeDocumentedAPIPath:defines",
"from": "file:services/agent/internal/glpi/client.go",
"to": "function:github.com/example/glpi-ai-agent/internal/glpi:normalizeDocumentedAPIPath",
"kind": "defines"
},
{
"id": "file:services/agent/internal/glpi/client.go-\u003efunction:github.com/example/glpi-ai-agent/internal/glpi:openAPIOperations:defines",
"from": "file:services/agent/internal/glpi/client.go",
@@ -38779,6 +38815,12 @@
"to": "function:github.com/example/glpi-ai-agent/internal/glpi:New",
"kind": "calls"
},
{
"id": "function:github.com/example/glpi-ai-agent/internal/glpi:Client.DiscoverKnowledgeBasePath-\u003efunction:github.com/example/glpi-ai-agent/internal/glpi:normalizeDocumentedAPIPath:calls",
"from": "function:github.com/example/glpi-ai-agent/internal/glpi:Client.DiscoverKnowledgeBasePath",
"to": "function:github.com/example/glpi-ai-agent/internal/glpi:normalizeDocumentedAPIPath",
"kind": "calls"
},
{
"id": "function:github.com/example/glpi-ai-agent/internal/glpi:Client.DiscoverKnowledgeBasePath-\u003epackage:fmt:calls_package",
"from": "function:github.com/example/glpi-ai-agent/internal/glpi:Client.DiscoverKnowledgeBasePath",
@@ -39043,6 +39085,12 @@
"to": "function:github.com/example/glpi-ai-agent/internal/glpi:Client.do",
"kind": "calls"
},
{
"id": "function:github.com/example/glpi-ai-agent/internal/glpi:Client.ListKnowledgeBaseItems-\u003efunction:github.com/example/glpi-ai-agent/internal/glpi:collectionHasMore:calls",
"from": "function:github.com/example/glpi-ai-agent/internal/glpi:Client.ListKnowledgeBaseItems",
"to": "function:github.com/example/glpi-ai-agent/internal/glpi:collectionHasMore",
"kind": "calls"
},
{
"id": "function:github.com/example/glpi-ai-agent/internal/glpi:Client.ListKnowledgeBaseItems-\u003efunction:github.com/example/glpi-ai-agent/internal/glpi:extractArray:calls",
"from": "function:github.com/example/glpi-ai-agent/internal/glpi:Client.ListKnowledgeBaseItems",
@@ -39074,6 +39122,13 @@
"kind": "calls_package",
"label": "Unmarshal"
},
{
"id": "function:github.com/example/glpi-ai-agent/internal/glpi:Client.ListKnowledgeBaseItems-\u003epackage:fmt:calls_package",
"from": "function:github.com/example/glpi-ai-agent/internal/glpi:Client.ListKnowledgeBaseItems",
"to": "package:fmt",
"kind": "calls_package",
"label": "Errorf"
},
{
"id": "function:github.com/example/glpi-ai-agent/internal/glpi:Client.ListKnowledgeBaseItems-\u003epackage:strconv:calls_package",
"from": "function:github.com/example/glpi-ai-agent/internal/glpi:Client.ListKnowledgeBaseItems",
@@ -39124,6 +39179,12 @@
"to": "function:github.com/example/glpi-ai-agent/internal/glpi:firstString",
"kind": "calls"
},
{
"id": "function:github.com/example/glpi-ai-agent/internal/glpi:Client.ListKnowledgeBaseLinkedItems-\u003efunction:github.com/example/glpi-ai-agent/internal/glpi:normalizeDocumentedAPIPath:calls",
"from": "function:github.com/example/glpi-ai-agent/internal/glpi:Client.ListKnowledgeBaseLinkedItems",
"to": "function:github.com/example/glpi-ai-agent/internal/glpi:normalizeDocumentedAPIPath",
"kind": "calls"
},
{
"id": "function:github.com/example/glpi-ai-agent/internal/glpi:Client.ListKnowledgeBaseLinkedItems-\u003epackage:fmt:calls_package",
"from": "function:github.com/example/glpi-ai-agent/internal/glpi:Client.ListKnowledgeBaseLinkedItems",
@@ -39605,6 +39666,33 @@
"kind": "calls_package",
"label": "EqualFold"
},
{
"id": "function:github.com/example/glpi-ai-agent/internal/glpi:collectionHasMore-\u003efunction:github.com/example/glpi-ai-agent/internal/glpi:int64Val:calls",
"from": "function:github.com/example/glpi-ai-agent/internal/glpi:collectionHasMore",
"to": "function:github.com/example/glpi-ai-agent/internal/glpi:int64Val",
"kind": "calls"
},
{
"id": "function:github.com/example/glpi-ai-agent/internal/glpi:collectionHasMore-\u003epackage:encoding/json:calls_package",
"from": "function:github.com/example/glpi-ai-agent/internal/glpi:collectionHasMore",
"to": "package:encoding/json",
"kind": "calls_package",
"label": "Unmarshal"
},
{
"id": "function:github.com/example/glpi-ai-agent/internal/glpi:collectionHasMore-\u003epackage:strconv:calls_package",
"from": "function:github.com/example/glpi-ai-agent/internal/glpi:collectionHasMore",
"to": "package:strconv",
"kind": "calls_package",
"label": "Atoi"
},
{
"id": "function:github.com/example/glpi-ai-agent/internal/glpi:collectionHasMore-\u003epackage:strings:calls_package",
"from": "function:github.com/example/glpi-ai-agent/internal/glpi:collectionHasMore",
"to": "package:strings",
"kind": "calls_package",
"label": "TrimSpace"
},
{
"id": "function:github.com/example/glpi-ai-agent/internal/glpi:decodeFollowup-\u003efunction:github.com/example/glpi-ai-agent/internal/glpi:boolVal:calls",
"from": "function:github.com/example/glpi-ai-agent/internal/glpi:decodeFollowup",
@@ -39813,6 +39901,13 @@
"kind": "calls_package",
"label": "Slice"
},
{
"id": "function:github.com/example/glpi-ai-agent/internal/glpi:normalizeDocumentedAPIPath-\u003epackage:strings:calls_package",
"from": "function:github.com/example/glpi-ai-agent/internal/glpi:normalizeDocumentedAPIPath",
"to": "package:strings",
"kind": "calls_package",
"label": "TrimSpace"
},
{
"id": "function:github.com/example/glpi-ai-agent/internal/glpi:openAPIOperations-\u003epackage:strings:calls_package",
"from": "function:github.com/example/glpi-ai-agent/internal/glpi:openAPIOperations",
@@ -66907,10 +67002,10 @@
}
],
"meta": {
"edges": 7278,
"edges": 7289,
"format_version": 1,
"generator": "go-ast+compose",
"modules": 4,
"nodes": 1846
"nodes": 1848
}
}