superlocalmemory 3.7.7 → 3.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/ATTRIBUTION.md +1 -3
- package/CHANGELOG.md +85 -0
- package/README.md +199 -29
- package/package.json +4 -2
- package/plugin/.claude-plugin/plugin.json +2 -2
- package/plugin/CLAUDE.md +8 -8
- package/plugin/agents/slm-governance-advisor.md +80 -0
- package/plugin/agents/slm-loop-runner.md +71 -0
- package/plugin/agents/slm-memory-advisor.md +10 -5
- package/plugin/agents/slm-optimize-advisor.md +9 -3
- package/plugin/commands/slm-loop.md +31 -0
- package/plugin/hooks/hooks.json +79 -0
- package/plugin/requirements.txt +1 -1
- package/plugin/scripts/slm-launch +46 -7
- package/plugin/settings.json +9 -0
- package/plugin/skills/slm-cache/SKILL.md +9 -1
- package/plugin/skills/slm-compress/SKILL.md +8 -1
- package/plugin/skills/slm-governance/SKILL.md +248 -0
- package/plugin/skills/slm-graph/SKILL.md +17 -3
- package/plugin/skills/slm-loop/SKILL.md +99 -0
- package/plugin/skills/slm-mesh/SKILL.md +282 -0
- package/plugin/skills/slm-profile/SKILL.md +148 -0
- package/plugin/skills/slm-recall/SKILL.md +46 -10
- package/plugin/skills/slm-remember/SKILL.md +48 -1
- package/plugin/skills/slm-scope/SKILL.md +176 -0
- package/plugin/skills/slm-session/SKILL.md +24 -1
- package/plugin/skills/slm-status/SKILL.md +18 -1
- package/plugin-src/agents/slm-governance-advisor.md +80 -0
- package/plugin-src/agents/slm-loop-runner.md +71 -0
- package/plugin-src/agents/slm-memory-advisor.md +10 -5
- package/plugin-src/agents/slm-optimize-advisor.md +9 -3
- package/plugin-src/commands/slm-loop.md +31 -0
- package/plugin-src/hooks/hooks.json +79 -0
- package/plugin-src/manifest.json +7 -2
- package/plugin-src/requirements.txt +1 -1
- package/plugin-src/rules/AGENTS.md +57 -18
- package/plugin-src/rules/CLAUDE.md.fragment +8 -8
- package/plugin-src/scripts/slm-launch +46 -7
- package/plugin-src/settings.json +9 -0
- package/plugin-src/skills/slm-cache/SKILL.md +9 -1
- package/plugin-src/skills/slm-compress/SKILL.md +8 -1
- package/plugin-src/skills/slm-governance/SKILL.md +248 -0
- package/plugin-src/skills/slm-graph/SKILL.md +17 -3
- package/plugin-src/skills/slm-loop/SKILL.md +99 -0
- package/plugin-src/skills/slm-mesh/SKILL.md +282 -0
- package/plugin-src/skills/slm-profile/SKILL.md +148 -0
- package/plugin-src/skills/slm-recall/SKILL.md +46 -10
- package/plugin-src/skills/slm-remember/SKILL.md +48 -1
- package/plugin-src/skills/slm-scope/SKILL.md +176 -0
- package/plugin-src/skills/slm-session/SKILL.md +24 -1
- package/plugin-src/skills/slm-status/SKILL.md +18 -1
- package/pyproject.toml +1 -2
- package/scripts/postinstall/validation.js +2 -0
- package/scripts/postinstall-interactive.js +74 -2
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/access/__init__.py +3 -0
- package/src/superlocalmemory/access/rbac.py +477 -0
- package/src/superlocalmemory/cli/commands.py +96 -12
- package/src/superlocalmemory/cli/compress_cmd.py +17 -7
- package/src/superlocalmemory/cli/loop_cmd.py +192 -0
- package/src/superlocalmemory/cli/main.py +39 -4
- package/src/superlocalmemory/cli/mesh_cmd.py +38 -0
- package/src/superlocalmemory/cli/optimize_cmd.py +3 -0
- package/src/superlocalmemory/cli/pending_store.py +49 -13
- package/src/superlocalmemory/cli/proxy_cmd.py +4 -0
- package/src/superlocalmemory/cli/scale_engine_cmd.py +6 -0
- package/src/superlocalmemory/cli/setup_wizard.py +22 -13
- package/src/superlocalmemory/compliance/audit.py +6 -0
- package/src/superlocalmemory/compliance/gdpr.py +128 -138
- package/src/superlocalmemory/compliance/retention.py +176 -45
- package/src/superlocalmemory/core/backend_orchestrator.py +5 -43
- package/src/superlocalmemory/core/community_summary.py +267 -0
- package/src/superlocalmemory/core/config.py +216 -3
- package/src/superlocalmemory/core/consolidation_engine.py +95 -22
- package/src/superlocalmemory/core/context_cache.py +61 -18
- package/src/superlocalmemory/core/embedding_worker.py +17 -2
- package/src/superlocalmemory/core/embeddings.py +12 -1
- package/src/superlocalmemory/core/engine.py +17 -1
- package/src/superlocalmemory/core/engine_ingestion.py +29 -0
- package/src/superlocalmemory/core/engine_wiring.py +13 -0
- package/src/superlocalmemory/core/entity_community.py +178 -0
- package/src/superlocalmemory/core/graph_analyzer.py +39 -2
- package/src/superlocalmemory/core/graph_pruner.py +13 -8
- package/src/superlocalmemory/core/key_expander.py +138 -0
- package/src/superlocalmemory/core/maintenance.py +23 -0
- package/src/superlocalmemory/core/modes.py +1 -1
- package/src/superlocalmemory/core/mutations.py +2 -2
- package/src/superlocalmemory/core/pii.py +105 -0
- package/src/superlocalmemory/core/progressive_abstraction.py +208 -0
- package/src/superlocalmemory/core/recall_pipeline.py +2 -0
- package/src/superlocalmemory/core/recall_worker.py +20 -6
- package/src/superlocalmemory/core/scale_engine.py +60 -1
- package/src/superlocalmemory/core/security_primitives.py +40 -2
- package/src/superlocalmemory/core/store_pipeline.py +35 -11
- package/src/superlocalmemory/core/worker_pool.py +21 -6
- package/src/superlocalmemory/encoding/entity_reflexion.py +200 -0
- package/src/superlocalmemory/encoding/entity_resolver.py +34 -24
- package/src/superlocalmemory/encoding/fact_extractor.py +26 -1
- package/src/superlocalmemory/encoding/temporal_validator.py +64 -1
- package/src/superlocalmemory/evolution/evolution_store.py +122 -45
- package/src/superlocalmemory/evolution/llm_dispatch.py +12 -1
- package/src/superlocalmemory/evolution/model_selection.py +160 -0
- package/src/superlocalmemory/evolution/mutation_generator.py +16 -0
- package/src/superlocalmemory/evolution/skill_evolver.py +127 -42
- package/src/superlocalmemory/evolution/triggers.py +22 -13
- package/src/superlocalmemory/graph/cozo_backend.py +43 -20
- package/src/superlocalmemory/hooks/adapter_base.py +5 -1
- package/src/superlocalmemory/hooks/auto_recall.py +13 -1
- package/src/superlocalmemory/hooks/claude_code_hooks.py +11 -0
- package/src/superlocalmemory/hooks/codex_assets.py +64 -5
- package/src/superlocalmemory/hooks/hook_daemon.py +20 -3
- package/src/superlocalmemory/hooks/memory_protocol.py +54 -0
- package/src/superlocalmemory/hooks/portable_kit.py +114 -1
- package/src/superlocalmemory/infra/auth_middleware.py +28 -0
- package/src/superlocalmemory/infra/backup.py +12 -1
- package/src/superlocalmemory/infra/daemon_identity.py +40 -4
- package/src/superlocalmemory/infra/data_root.py +43 -4
- package/src/superlocalmemory/infra/event_bus.py +107 -24
- package/src/superlocalmemory/infra/rate_limiter.py +93 -0
- package/src/superlocalmemory/ingestion/adapter_manager.py +4 -1
- package/src/superlocalmemory/ingestion/credentials.py +1 -1
- package/src/superlocalmemory/learning/cross_project.py +28 -19
- package/src/superlocalmemory/learning/reward_proxy.py +42 -9
- package/src/superlocalmemory/loops/__init__.py +56 -0
- package/src/superlocalmemory/loops/budget.py +58 -0
- package/src/superlocalmemory/loops/engine.py +164 -0
- package/src/superlocalmemory/loops/ledger.py +243 -0
- package/src/superlocalmemory/loops/models.py +152 -0
- package/src/superlocalmemory/loops/rules.py +52 -0
- package/src/superlocalmemory/mcp/_daemon_proxy.py +3 -0
- package/src/superlocalmemory/mcp/_pool_adapter.py +2 -0
- package/src/superlocalmemory/mcp/profiles.py +103 -0
- package/src/superlocalmemory/mcp/server.py +21 -49
- package/src/superlocalmemory/mcp/tools_active.py +4 -7
- package/src/superlocalmemory/mcp/tools_code_graph.py +51 -5
- package/src/superlocalmemory/mcp/tools_core.py +50 -5
- package/src/superlocalmemory/mcp/tools_evolution.py +6 -3
- package/src/superlocalmemory/mcp/tools_loops.py +300 -0
- package/src/superlocalmemory/mcp/tools_mesh.py +140 -4
- package/src/superlocalmemory/mcp/tools_optimize.py +15 -8
- package/src/superlocalmemory/mesh/broker.py +237 -129
- package/src/superlocalmemory/mesh/remote_sync.py +50 -8
- package/src/superlocalmemory/optimize/NOTICE +1 -6
- package/src/superlocalmemory/optimize/adapters/anthropic_adapter.py +1 -4
- package/src/superlocalmemory/optimize/adapters/openai_adapter.py +1 -4
- package/src/superlocalmemory/optimize/cache/semantic.py +27 -19
- package/src/superlocalmemory/optimize/compress/align.py +32 -26
- package/src/superlocalmemory/optimize/compress/ccr.py +14 -71
- package/src/superlocalmemory/optimize/compress/router.py +105 -22
- package/src/superlocalmemory/optimize/config/defaults.py +1 -1
- package/src/superlocalmemory/optimize/config/schema.py +87 -4
- package/src/superlocalmemory/optimize/metrics/counters.py +13 -4
- package/src/superlocalmemory/optimize/metrics/estimator.py +0 -3
- package/src/superlocalmemory/optimize/proxy/_helpers.py +31 -4
- package/src/superlocalmemory/optimize/storage/db.py +38 -9
- package/src/superlocalmemory/optimize/storage/schema.py +10 -0
- package/src/superlocalmemory/parameterization/pattern_extractor.py +6 -3
- package/src/superlocalmemory/retrieval/agentic.py +1 -1
- package/src/superlocalmemory/retrieval/bm25_channel.py +68 -10
- package/src/superlocalmemory/retrieval/engine.py +168 -26
- package/src/superlocalmemory/retrieval/entity_channel.py +7 -5
- package/src/superlocalmemory/retrieval/hopfield_channel.py +9 -2
- package/src/superlocalmemory/retrieval/semantic_channel.py +114 -21
- package/src/superlocalmemory/retrieval/spreading_activation.py +11 -2
- package/src/superlocalmemory/retrieval/temporal_channel.py +48 -9
- package/src/superlocalmemory/retrieval/temporal_frame.py +102 -0
- package/src/superlocalmemory/retrieval/temporal_validity_filter.py +135 -0
- package/src/superlocalmemory/retrieval/time_window.py +181 -0
- package/src/superlocalmemory/server/api.py +21 -4
- package/src/superlocalmemory/server/profile_runtime.py +125 -8
- package/src/superlocalmemory/server/rbac_enforce.py +142 -0
- package/src/superlocalmemory/server/recall_health.py +24 -3
- package/src/superlocalmemory/server/recall_serializer.py +19 -1
- package/src/superlocalmemory/server/routes/abstraction.py +115 -0
- package/src/superlocalmemory/server/routes/agents.py +128 -38
- package/src/superlocalmemory/server/routes/backup.py +34 -10
- package/src/superlocalmemory/server/routes/behavioral.py +13 -12
- package/src/superlocalmemory/server/routes/brain.py +21 -5
- package/src/superlocalmemory/server/routes/chat.py +72 -16
- package/src/superlocalmemory/server/routes/compliance.py +171 -21
- package/src/superlocalmemory/server/routes/config_api.py +436 -0
- package/src/superlocalmemory/server/routes/data_io.py +30 -8
- package/src/superlocalmemory/server/routes/entity.py +9 -4
- package/src/superlocalmemory/server/routes/events.py +24 -8
- package/src/superlocalmemory/server/routes/evolution.py +135 -17
- package/src/superlocalmemory/server/routes/helpers.py +16 -1
- package/src/superlocalmemory/server/routes/ingest.py +7 -4
- package/src/superlocalmemory/server/routes/insights.py +3 -3
- package/src/superlocalmemory/server/routes/learning.py +14 -14
- package/src/superlocalmemory/server/routes/lifecycle.py +59 -8
- package/src/superlocalmemory/server/routes/memories.py +221 -49
- package/src/superlocalmemory/server/routes/mesh.py +95 -15
- package/src/superlocalmemory/server/routes/optimize.py +33 -1
- package/src/superlocalmemory/server/routes/prewarm.py +2 -0
- package/src/superlocalmemory/server/routes/profiles.py +63 -17
- package/src/superlocalmemory/server/routes/ratelimit.py +124 -0
- package/src/superlocalmemory/server/routes/rbac.py +367 -0
- package/src/superlocalmemory/server/routes/stats.py +13 -6
- package/src/superlocalmemory/server/routes/tiers.py +11 -9
- package/src/superlocalmemory/server/routes/v3_api.py +194 -81
- package/src/superlocalmemory/server/routes/ws.py +5 -2
- package/src/superlocalmemory/server/security_middleware.py +12 -5
- package/src/superlocalmemory/server/ui.py +30 -5
- package/src/superlocalmemory/server/unified_daemon.py +431 -75
- package/src/superlocalmemory/server/write_identity.py +38 -8
- package/src/superlocalmemory/storage/database.py +265 -53
- package/src/superlocalmemory/storage/migration_runner.py +53 -0
- package/src/superlocalmemory/storage/migrations/M021_ingestion_log_profile.py +108 -0
- package/src/superlocalmemory/storage/migrations/M022_entity_aliases_profile.py +86 -0
- package/src/superlocalmemory/storage/migrations/M023_mesh_profile_isolation.py +194 -0
- package/src/superlocalmemory/storage/migrations/M024_rbac_users_roles.py +87 -0
- package/src/superlocalmemory/storage/migrations/M025_perf_indexes.py +90 -0
- package/src/superlocalmemory/storage/migrations/M026_rbac_memberships_fk.py +136 -0
- package/src/superlocalmemory/storage/migrations/M027_transferable_patterns_profile.py +163 -0
- package/src/superlocalmemory/storage/models.py +4 -0
- package/src/superlocalmemory/storage/schema.py +87 -0
- package/src/superlocalmemory/storage/schema_v32.py +0 -9
- package/src/superlocalmemory/storage/schema_v343.py +24 -12
- package/src/superlocalmemory/trust/gate.py +49 -8
- package/src/superlocalmemory/ui/assets/slm-icon-white.svg +64 -0
- package/src/superlocalmemory/ui/assets/slm-icon.svg +36 -0
- package/src/superlocalmemory/ui/css/design-system.css +621 -0
- package/src/superlocalmemory/ui/css/neural-glass.css +6 -0
- package/src/superlocalmemory/ui/css/od-bridge.css +158 -0
- package/src/superlocalmemory/ui/favicon.svg +35 -4
- package/src/superlocalmemory/ui/index.html +306 -173
- package/src/superlocalmemory/ui/js/brain.js +5 -20
- package/src/superlocalmemory/ui/js/core.js +47 -31
- package/src/superlocalmemory/ui/js/dashboard.js +314 -63
- package/src/superlocalmemory/ui/js/event-delegation.js +102 -0
- package/src/superlocalmemory/ui/js/knowledge-graph.js +11 -11
- package/src/superlocalmemory/ui/js/math-health.js +1 -1
- package/src/superlocalmemory/ui/js/memories.js +15 -4
- package/src/superlocalmemory/ui/js/memory-chat.js +7 -7
- package/src/superlocalmemory/ui/js/ng-entities.js +6 -8
- package/src/superlocalmemory/ui/js/ng-ingestion.js +4 -4
- package/src/superlocalmemory/ui/js/ng-mesh.js +4 -9
- package/src/superlocalmemory/ui/js/ng-shell.js +8 -8
- package/src/superlocalmemory/ui/js/ng-skills.js +54 -2
- package/src/superlocalmemory/ui/js/od-agents.js +544 -0
- package/src/superlocalmemory/ui/js/od-auth-gate.js +257 -0
- package/src/superlocalmemory/ui/js/od-backup.js +780 -0
- package/src/superlocalmemory/ui/js/od-brain.js +779 -0
- package/src/superlocalmemory/ui/js/od-entities.js +579 -0
- package/src/superlocalmemory/ui/js/od-graph.js +593 -0
- package/src/superlocalmemory/ui/js/od-health.js +539 -0
- package/src/superlocalmemory/ui/js/od-mcp.js +508 -0
- package/src/superlocalmemory/ui/js/od-memories.js +887 -0
- package/src/superlocalmemory/ui/js/od-mesh.js +539 -0
- package/src/superlocalmemory/ui/js/od-operations.js +1250 -0
- package/src/superlocalmemory/ui/js/od-optimize.js +787 -0
- package/src/superlocalmemory/ui/js/od-settings.js +1053 -0
- package/src/superlocalmemory/ui/js/od-shell.js +593 -0
- package/src/superlocalmemory/ui/js/od-skills.js +573 -0
- package/src/superlocalmemory/ui/js/od-team.js +258 -0
- package/src/superlocalmemory/ui/js/profiles.js +159 -46
- package/src/superlocalmemory/ui/js/settings.js +2 -2
- package/src/superlocalmemory/ui/js/timeline.js +34 -5
- package/src/superlocalmemory/ui/js/trust-dashboard.js +2 -2
- package/src/superlocalmemory/vector/lancedb_backend.py +8 -6
- package/src/superlocalmemory/learning/behavioral_listener.py +0 -94
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
// SuperLocalMemory — od-agents.js v1.0
|
|
2
|
+
// OD-styled Multi-Agent Memory pane. Self-contained, CSP-safe.
|
|
3
|
+
// Renders into the "agents-pane" tab container via the shell's triggerTabLoad.
|
|
4
|
+
// Exposes window.odRenderAgents(container).
|
|
5
|
+
//
|
|
6
|
+
// Endpoint:
|
|
7
|
+
// GET /api/agents/memory-activity?limit=20 →
|
|
8
|
+
// { ok, profile_id, total_memories, agent_count,
|
|
9
|
+
// agents: [{agent_id, count, last_active, source_types:[..]}], // desc by count
|
|
10
|
+
// recent: [{agent_id, content, created_at, source_type, session_id}] }
|
|
11
|
+
//
|
|
12
|
+
// CRIT fixes baked in:
|
|
13
|
+
// C1 — all agent_id / content / source_type values through escapeHtml() before innerHTML
|
|
14
|
+
// C2 — empty state for zero agents / zero memories (adapter guide shown)
|
|
15
|
+
// C3 — fetch failure shows error card with Retry; never throws, never silent
|
|
16
|
+
// C4 — a 404 (route absent) reads as "not connected / restart the daemon",
|
|
17
|
+
// never a raw "HTTP 404"; the no-agents case is a friendly empty state
|
|
18
|
+
//
|
|
19
|
+
// Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar — AGPL-3.0
|
|
20
|
+
|
|
21
|
+
(function () {
|
|
22
|
+
'use strict';
|
|
23
|
+
|
|
24
|
+
var API_URL = '/api/agents/memory-activity?limit=20';
|
|
25
|
+
var REFRESH_MS = 15000;
|
|
26
|
+
|
|
27
|
+
// ── Module state ──────────────────────────────────────────────────────────
|
|
28
|
+
var _container = null;
|
|
29
|
+
var _timer = null;
|
|
30
|
+
var _observer = null;
|
|
31
|
+
var _allRecent = []; // full recent list — client-side filter only
|
|
32
|
+
|
|
33
|
+
// ── Helpers ───────────────────────────────────────────────────────────────
|
|
34
|
+
// CRIT C1: every API-derived string must pass through this before innerHTML.
|
|
35
|
+
// Same implementation as od-optimize.js and od-mesh.js.
|
|
36
|
+
function escapeHtml(s) {
|
|
37
|
+
return String(s == null ? '' : s)
|
|
38
|
+
.replace(/&/g, '&')
|
|
39
|
+
.replace(/</g, '<')
|
|
40
|
+
.replace(/>/g, '>')
|
|
41
|
+
.replace(/"/g, '"')
|
|
42
|
+
.replace(/'/g, ''');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function timeAgo(iso) {
|
|
46
|
+
if (!iso) return 'never';
|
|
47
|
+
var d = (Date.now() - new Date(iso).getTime()) / 1000;
|
|
48
|
+
if (d < 5) return 'just now';
|
|
49
|
+
if (d < 60) return Math.floor(d) + 's ago';
|
|
50
|
+
if (d < 3600) return Math.floor(d / 60) + 'm ago';
|
|
51
|
+
if (d < 86400) return Math.floor(d / 3600) + 'h ago';
|
|
52
|
+
return Math.floor(d / 86400) + 'd ago';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// An agent is "active" only if it wrote memory recently. This page's data is
|
|
56
|
+
// last-write time, not a live connection, so most agents are historical.
|
|
57
|
+
var ACTIVE_WINDOW_MS = 10 * 60 * 1000; // 10 minutes
|
|
58
|
+
function isRecent(iso) {
|
|
59
|
+
if (!iso) return false;
|
|
60
|
+
var t = new Date(iso).getTime();
|
|
61
|
+
return !isNaN(t) && (Date.now() - t) < ACTIVE_WINDOW_MS;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function fmtDate(iso) {
|
|
65
|
+
if (!iso) return '—';
|
|
66
|
+
try {
|
|
67
|
+
var d = new Date(iso);
|
|
68
|
+
return d.toLocaleDateString() + ' ' + d.toTimeString().slice(0, 5);
|
|
69
|
+
} catch (e) { return String(iso); }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Render source_type chips (array of strings)
|
|
73
|
+
function fmtChips(types) {
|
|
74
|
+
if (!Array.isArray(types) || types.length === 0) {
|
|
75
|
+
return '<span style="color:var(--fg-3)">—</span>';
|
|
76
|
+
}
|
|
77
|
+
return types.map(function (t) {
|
|
78
|
+
return (
|
|
79
|
+
'<span style="display:inline-block;padding:1px 7px;border-radius:99px;' +
|
|
80
|
+
'font-size:11px;background:var(--card-2);color:var(--fg-2);margin-right:4px">' +
|
|
81
|
+
escapeHtml(t) + '</span>'
|
|
82
|
+
);
|
|
83
|
+
}).join('');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Deterministic avatar color from agent_id string
|
|
87
|
+
function avatarColor(id) {
|
|
88
|
+
var palette = ['var(--violet)', 'var(--cyan)', 'var(--ok)', 'var(--warn)'];
|
|
89
|
+
var s = String(id || '?');
|
|
90
|
+
var h = 0;
|
|
91
|
+
for (var i = 0; i < s.length; i++) {
|
|
92
|
+
h = (h * 31 + s.charCodeAt(i)) & 0x7fffffff;
|
|
93
|
+
}
|
|
94
|
+
return palette[h % palette.length];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ── Build layout HTML ─────────────────────────────────────────────────────
|
|
98
|
+
function buildHTML() {
|
|
99
|
+
return (
|
|
100
|
+
'<div id="od-agents-root">' +
|
|
101
|
+
|
|
102
|
+
// Page head
|
|
103
|
+
'<div class="page-head">' +
|
|
104
|
+
'<h2>Multi-Agent Memory</h2>' +
|
|
105
|
+
'<p>Memory written by agents across CrewAI, LangChain, LangGraph, ' +
|
|
106
|
+
'Semantic Kernel, and other agentic frameworks. Each agent stamps ' +
|
|
107
|
+
'<code>SLM_AGENT_ID</code> on every write — this page visualises ' +
|
|
108
|
+
'that activity across all agents sharing this SLM instance.</p>' +
|
|
109
|
+
'</div>' +
|
|
110
|
+
|
|
111
|
+
// Error band — CRIT C3
|
|
112
|
+
'<div id="od-agents-err"' +
|
|
113
|
+
' style="display:none;background:var(--warn-soft);border:1px solid var(--warn);' +
|
|
114
|
+
'border-radius:var(--r-md);padding:12px 16px;margin-bottom:16px;' +
|
|
115
|
+
'align-items:center;justify-content:space-between;gap:12px">' +
|
|
116
|
+
'<span style="color:var(--warn);font-size:13.5px" id="od-agents-err-msg">' +
|
|
117
|
+
'Could not load agent memory data' +
|
|
118
|
+
'</span>' +
|
|
119
|
+
'<button id="od-agents-retry"' +
|
|
120
|
+
' style="padding:5px 12px;font-size:12.5px;border-radius:var(--r-md);' +
|
|
121
|
+
'border:1px solid var(--warn);background:transparent;' +
|
|
122
|
+
'color:var(--warn);cursor:pointer;font-weight:600">' +
|
|
123
|
+
'Retry' +
|
|
124
|
+
'</button>' +
|
|
125
|
+
'</div>' +
|
|
126
|
+
|
|
127
|
+
// KPI strip
|
|
128
|
+
'<section class="kpi-strip" style="margin-bottom:16px">' +
|
|
129
|
+
'<div class="card kpi">' +
|
|
130
|
+
'<div class="label"><span data-ic="memories"></span> Total memories</div>' +
|
|
131
|
+
'<div class="value num" id="od-agents-kpi-total">—</div>' +
|
|
132
|
+
'<div class="delta" id="od-agents-kpi-profile"> </div>' +
|
|
133
|
+
'</div>' +
|
|
134
|
+
'<div class="card kpi">' +
|
|
135
|
+
'<div class="label"><span data-ic="mesh"></span> Contributing agents</div>' +
|
|
136
|
+
'<div class="value num" id="od-agents-kpi-count">—</div>' +
|
|
137
|
+
'<div class="delta" id="od-agents-kpi-active"> </div>' +
|
|
138
|
+
'</div>' +
|
|
139
|
+
'</section>' +
|
|
140
|
+
|
|
141
|
+
// Per-agent cards
|
|
142
|
+
'<div class="card" style="margin-bottom:16px">' +
|
|
143
|
+
'<div class="card-head">' +
|
|
144
|
+
'<h3>Agent activity</h3>' +
|
|
145
|
+
'<span class="sub">memories written per agent, ranked by volume</span>' +
|
|
146
|
+
'</div>' +
|
|
147
|
+
'<div class="launch-grid" id="od-agents-grid"' +
|
|
148
|
+
' style="grid-template-columns:repeat(auto-fill,minmax(220px,1fr));' +
|
|
149
|
+
'padding:16px 20px;gap:14px">' +
|
|
150
|
+
'<div style="grid-column:1/-1;text-align:center;padding:40px;color:var(--fg-2);' +
|
|
151
|
+
'font-size:14px">Loading agent data…</div>' +
|
|
152
|
+
'</div>' +
|
|
153
|
+
'</div>' +
|
|
154
|
+
|
|
155
|
+
// Recent memories table with client-side agent filter
|
|
156
|
+
'<div class="card">' +
|
|
157
|
+
'<div class="card-head">' +
|
|
158
|
+
'<h3>Recent memories</h3>' +
|
|
159
|
+
'<span class="sub">newest first</span>' +
|
|
160
|
+
'<div class="spacer"></div>' +
|
|
161
|
+
'<select id="od-agents-filter"' +
|
|
162
|
+
' style="font-size:12.5px;padding:3px 8px;border-radius:var(--r-sm);' +
|
|
163
|
+
'background:var(--card-2);border:1px solid var(--border);' +
|
|
164
|
+
'color:var(--fg-1)">' +
|
|
165
|
+
'<option value="">All agents</option>' +
|
|
166
|
+
'</select>' +
|
|
167
|
+
'</div>' +
|
|
168
|
+
'<div class="card-pad" style="overflow-x:auto">' +
|
|
169
|
+
'<div id="od-agents-table">' +
|
|
170
|
+
'<div style="text-align:center;padding:32px;color:var(--fg-3);font-size:13px">' +
|
|
171
|
+
'Loading recent memories…' +
|
|
172
|
+
'</div>' +
|
|
173
|
+
'</div>' +
|
|
174
|
+
'</div>' +
|
|
175
|
+
'</div>' +
|
|
176
|
+
|
|
177
|
+
'</div>' // od-agents-root
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ── Render: KPI strip ─────────────────────────────────────────────────────
|
|
182
|
+
function renderKpi(data) {
|
|
183
|
+
var totEl = document.getElementById('od-agents-kpi-total');
|
|
184
|
+
var cntEl = document.getElementById('od-agents-kpi-count');
|
|
185
|
+
var profEl = document.getElementById('od-agents-kpi-profile');
|
|
186
|
+
if (totEl) totEl.textContent = Number(data.total_memories || 0).toLocaleString();
|
|
187
|
+
if (cntEl) cntEl.textContent = Number(data.agent_count || 0).toLocaleString();
|
|
188
|
+
if (profEl) profEl.textContent = 'profile: ' + escapeHtml(data.profile_id || 'default');
|
|
189
|
+
var actEl = document.getElementById('od-agents-kpi-active');
|
|
190
|
+
if (actEl) {
|
|
191
|
+
var agents = Array.isArray(data.agents) ? data.agents : [];
|
|
192
|
+
var live = agents.filter(function (a) { return isRecent(a.last_active); }).length;
|
|
193
|
+
actEl.textContent = live + (live === 1 ? ' active in last 10m' : ' active in last 10m');
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ── Render: per-agent cards ───────────────────────────────────────────────
|
|
198
|
+
function renderAgentCards(agents) {
|
|
199
|
+
var grid = document.getElementById('od-agents-grid');
|
|
200
|
+
if (!grid) return;
|
|
201
|
+
|
|
202
|
+
// CRIT C2: empty state when no agent has written memory yet
|
|
203
|
+
if (!agents || agents.length === 0) {
|
|
204
|
+
grid.innerHTML =
|
|
205
|
+
'<div class="card launch-card"' +
|
|
206
|
+
' style="grid-column:1/-1;display:flex;flex-direction:column;' +
|
|
207
|
+
'align-items:center;justify-content:center;' +
|
|
208
|
+
'padding:48px 24px;text-align:center;cursor:default">' +
|
|
209
|
+
'<div style="width:44px;height:44px;border-radius:12px;' +
|
|
210
|
+
'background:var(--card-2);display:flex;align-items:center;' +
|
|
211
|
+
'justify-content:center;margin-bottom:16px;color:var(--fg-3)">' +
|
|
212
|
+
(typeof window.slmIcon === 'function' ? window.slmIcon('mesh') : '') +
|
|
213
|
+
'</div>' +
|
|
214
|
+
'<h3 style="font-size:15px;margin-bottom:6px">No multi-agent memory yet</h3>' +
|
|
215
|
+
'<p style="font-size:12.5px;color:var(--fg-2);max-width:44ch;line-height:1.55">' +
|
|
216
|
+
'Connect agents via the CrewAI, LangChain, LangGraph, Semantic Kernel, ' +
|
|
217
|
+
'or Agent Framework adapters. Each stamps <code>SLM_AGENT_ID</code> on ' +
|
|
218
|
+
'every write so their activity appears here.' +
|
|
219
|
+
'</p>' +
|
|
220
|
+
'</div>';
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
var html = '';
|
|
225
|
+
agents.forEach(function (a) {
|
|
226
|
+
var rawId = String(a.agent_id || 'unknown');
|
|
227
|
+
var id = escapeHtml(rawId);
|
|
228
|
+
var count = Number(a.count || 0);
|
|
229
|
+
var letter = escapeHtml(rawId.charAt(0).toUpperCase()); // SEC: escape before innerHTML
|
|
230
|
+
var bg = avatarColor(rawId);
|
|
231
|
+
var ago = escapeHtml(timeAgo(a.last_active));
|
|
232
|
+
var live = isRecent(a.last_active);
|
|
233
|
+
var dot = '<span style="display:inline-block;width:7px;height:7px;border-radius:50%;' +
|
|
234
|
+
'margin-right:5px;vertical-align:middle;background:' +
|
|
235
|
+
(live ? 'var(--ok)' : 'var(--fg-3)') + '"></span>';
|
|
236
|
+
|
|
237
|
+
// Clickable: card drills into this agent's memories (sets the filter).
|
|
238
|
+
html +=
|
|
239
|
+
'<div class="card launch-card slm-agent-card" data-agent="' + id + '" ' +
|
|
240
|
+
'style="cursor:pointer" title="View ' + id + '’s recent memories">' +
|
|
241
|
+
'<div style="display:flex;align-items:center;gap:11px;margin-bottom:12px">' +
|
|
242
|
+
'<span class="avatar" style="background:' + bg + '">' + letter + '</span>' +
|
|
243
|
+
'<div style="flex:1;min-width:0">' +
|
|
244
|
+
'<h3 style="font-size:15px;white-space:nowrap;overflow:hidden;' +
|
|
245
|
+
'text-overflow:ellipsis" title="' + id + '">' + id + '</h3>' +
|
|
246
|
+
'<span class="mono dim" style="font-size:11px">' + dot +
|
|
247
|
+
(live ? 'active ' : 'wrote ') + ago + '</span>' +
|
|
248
|
+
'</div>' +
|
|
249
|
+
'<span class="badge ok">' + count.toLocaleString() + '</span>' +
|
|
250
|
+
'</div>' +
|
|
251
|
+
'<div style="font-size:12px;color:var(--fg-2);margin-bottom:6px">' +
|
|
252
|
+
'<span class="dim">source types</span>' +
|
|
253
|
+
'</div>' +
|
|
254
|
+
'<div>' + fmtChips(a.source_types) + '</div>' +
|
|
255
|
+
'<div style="margin-top:10px;font-size:11.5px;color:var(--violet)">View memories →</div>' +
|
|
256
|
+
'</div>';
|
|
257
|
+
});
|
|
258
|
+
grid.innerHTML = html;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// ── Populate agent filter dropdown ────────────────────────────────────────
|
|
262
|
+
function populateFilter(agents) {
|
|
263
|
+
var sel = document.getElementById('od-agents-filter');
|
|
264
|
+
if (!sel) return;
|
|
265
|
+
// Drop all options except the first "All agents" placeholder
|
|
266
|
+
while (sel.options.length > 1) sel.remove(1);
|
|
267
|
+
if (!agents) return;
|
|
268
|
+
agents.forEach(function (a) {
|
|
269
|
+
var opt = document.createElement('option');
|
|
270
|
+
opt.value = String(a.agent_id || '');
|
|
271
|
+
opt.textContent = String(a.agent_id || 'unknown');
|
|
272
|
+
sel.appendChild(opt);
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// ── Render: recent memories table ────────────────────────────────────────
|
|
277
|
+
function renderRecentTable(records) {
|
|
278
|
+
var wrap = document.getElementById('od-agents-table');
|
|
279
|
+
if (!wrap) return;
|
|
280
|
+
|
|
281
|
+
if (!records || records.length === 0) {
|
|
282
|
+
wrap.innerHTML =
|
|
283
|
+
'<div style="text-align:center;padding:32px;color:var(--fg-3);font-size:13px">' +
|
|
284
|
+
'No memories match the current filter.' +
|
|
285
|
+
'</div>';
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
var rows = records.map(function (r) {
|
|
290
|
+
// CRIT C1: all three user-derived fields escaped
|
|
291
|
+
var rawId = String(r.agent_id || '—');
|
|
292
|
+
var rawContent = String(r.content || '—');
|
|
293
|
+
var rawSrc = String(r.source_type || '—');
|
|
294
|
+
var rawSess = String(r.session_id || '—');
|
|
295
|
+
|
|
296
|
+
var agId = escapeHtml(rawId);
|
|
297
|
+
var srcType = escapeHtml(rawSrc);
|
|
298
|
+
var dt = escapeHtml(fmtDate(r.created_at));
|
|
299
|
+
|
|
300
|
+
// Truncate content for display; keep full version in title attribute
|
|
301
|
+
var dispContent = rawContent.length > 120 ? rawContent.slice(0, 120) + '…' : rawContent;
|
|
302
|
+
var content = escapeHtml(dispContent);
|
|
303
|
+
var contentFull = escapeHtml(rawContent);
|
|
304
|
+
|
|
305
|
+
var dispSess = rawSess.length > 12 ? rawSess.slice(0, 12) + '…' : rawSess;
|
|
306
|
+
var sess = escapeHtml(dispSess);
|
|
307
|
+
var sessFull = escapeHtml(rawSess);
|
|
308
|
+
|
|
309
|
+
var avatarBg = avatarColor(rawId);
|
|
310
|
+
var letter = escapeHtml(rawId.charAt(0).toUpperCase()); // SEC: escape before innerHTML
|
|
311
|
+
|
|
312
|
+
return (
|
|
313
|
+
'<tr>' +
|
|
314
|
+
'<td style="white-space:nowrap;padding:7px 12px 7px 0">' +
|
|
315
|
+
'<span style="display:inline-flex;align-items:center;gap:6px">' +
|
|
316
|
+
'<span class="avatar"' +
|
|
317
|
+
' style="width:22px;height:22px;font-size:10px;background:' + avatarBg + '">' +
|
|
318
|
+
letter +
|
|
319
|
+
'</span>' +
|
|
320
|
+
'<span class="mono" style="font-size:12px">' + agId + '</span>' +
|
|
321
|
+
'</span>' +
|
|
322
|
+
'</td>' +
|
|
323
|
+
'<td style="max-width:340px;font-size:13px;padding:7px 12px 7px 0"' +
|
|
324
|
+
' title="' + contentFull + '">' + content + '</td>' +
|
|
325
|
+
'<td style="white-space:nowrap;padding:7px 12px 7px 0">' +
|
|
326
|
+
'<span style="padding:1px 7px;border-radius:99px;font-size:11px;' +
|
|
327
|
+
'background:var(--card-2);color:var(--fg-2)">' + srcType + '</span>' +
|
|
328
|
+
'</td>' +
|
|
329
|
+
'<td class="mono dim" style="font-size:11px;white-space:nowrap;padding:7px 12px 7px 0">' +
|
|
330
|
+
dt +
|
|
331
|
+
'</td>' +
|
|
332
|
+
'<td class="mono dim" style="font-size:11px;white-space:nowrap;padding:7px 0"' +
|
|
333
|
+
' title="' + sessFull + '">' + sess + '</td>' +
|
|
334
|
+
'</tr>'
|
|
335
|
+
);
|
|
336
|
+
}).join('');
|
|
337
|
+
|
|
338
|
+
wrap.innerHTML =
|
|
339
|
+
'<table style="width:100%;border-collapse:collapse;font-size:13px">' +
|
|
340
|
+
'<thead>' +
|
|
341
|
+
'<tr style="border-bottom:1px solid var(--border)">' +
|
|
342
|
+
'<th style="text-align:left;padding:4px 12px 8px 0;font-weight:600;' +
|
|
343
|
+
'font-size:12px;color:var(--fg-2)">Agent</th>' +
|
|
344
|
+
'<th style="text-align:left;padding:4px 12px 8px 0;font-weight:600;' +
|
|
345
|
+
'font-size:12px;color:var(--fg-2)">Memory</th>' +
|
|
346
|
+
'<th style="text-align:left;padding:4px 12px 8px 0;font-weight:600;' +
|
|
347
|
+
'font-size:12px;color:var(--fg-2)">Source</th>' +
|
|
348
|
+
'<th style="text-align:left;padding:4px 12px 8px 0;font-weight:600;' +
|
|
349
|
+
'font-size:12px;color:var(--fg-2)">Written</th>' +
|
|
350
|
+
'<th style="text-align:left;padding:4px 0 8px 0;font-weight:600;' +
|
|
351
|
+
'font-size:12px;color:var(--fg-2)">Session</th>' +
|
|
352
|
+
'</tr>' +
|
|
353
|
+
'</thead>' +
|
|
354
|
+
'<tbody>' + rows + '</tbody>' +
|
|
355
|
+
'</table>';
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// ── Error/retry UI ─────────────────────────────────────────────────────────
|
|
359
|
+
// C4: turn a fetch failure into an actionable sentence. A 404 means the
|
|
360
|
+
// multi-agent route isn't on the running daemon (typically a daemon that
|
|
361
|
+
// predates this build) — say "not connected", point at `slm restart`, and
|
|
362
|
+
// never surface a bare status code.
|
|
363
|
+
function errorMessage(err) {
|
|
364
|
+
var status = err && err.httpStatus;
|
|
365
|
+
if (status === 404) {
|
|
366
|
+
return 'Multi-agent memory isn’t connected on this daemon. This view ' +
|
|
367
|
+
'ships with SLM 3.8.0 — if you just upgraded, restart the daemon ' +
|
|
368
|
+
'(run “slm restart”) to load it, then retry.';
|
|
369
|
+
}
|
|
370
|
+
if (status === 401 || status === 403) {
|
|
371
|
+
return 'Not authorised to read agent memory. Sign in to the dashboard, then retry.';
|
|
372
|
+
}
|
|
373
|
+
if (status) {
|
|
374
|
+
return 'The SLM daemon returned an error (status ' + status + '). Retry, ' +
|
|
375
|
+
'or check the daemon logs.';
|
|
376
|
+
}
|
|
377
|
+
// No status → the request never reached the daemon.
|
|
378
|
+
return 'Could not reach the SLM daemon. Check that it is running, then retry.';
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function showError(msg) {
|
|
382
|
+
var band = document.getElementById('od-agents-err');
|
|
383
|
+
var msgEl = document.getElementById('od-agents-err-msg');
|
|
384
|
+
if (band) band.style.display = 'flex';
|
|
385
|
+
if (msgEl) msgEl.textContent = msg || 'Could not load agent memory data';
|
|
386
|
+
|
|
387
|
+
// Degrade gracefully — the actionable message lives in the band above;
|
|
388
|
+
// the sub-panels just stop showing a stale "Loading…" state.
|
|
389
|
+
var grid = document.getElementById('od-agents-grid');
|
|
390
|
+
if (grid) {
|
|
391
|
+
grid.innerHTML =
|
|
392
|
+
'<div style="grid-column:1/-1;text-align:center;padding:32px;' +
|
|
393
|
+
'color:var(--fg-3);font-size:13px">Agent activity unavailable — see the message above.</div>';
|
|
394
|
+
}
|
|
395
|
+
var table = document.getElementById('od-agents-table');
|
|
396
|
+
if (table) {
|
|
397
|
+
table.innerHTML =
|
|
398
|
+
'<div style="text-align:center;padding:32px;color:var(--fg-3);font-size:13px">' +
|
|
399
|
+
'Recent memories unavailable — see the message above.' +
|
|
400
|
+
'</div>';
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
function hideError() {
|
|
405
|
+
var band = document.getElementById('od-agents-err');
|
|
406
|
+
if (band) band.style.display = 'none';
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// ── Data loader ───────────────────────────────────────────────────────────
|
|
410
|
+
function loadAll() {
|
|
411
|
+
fetch(API_URL, { credentials: 'same-origin' })
|
|
412
|
+
.then(function (r) {
|
|
413
|
+
if (!r.ok) {
|
|
414
|
+
var e = new Error('HTTP ' + r.status);
|
|
415
|
+
e.httpStatus = r.status; // C4: preserve status for friendly messaging
|
|
416
|
+
throw e;
|
|
417
|
+
}
|
|
418
|
+
return r.json();
|
|
419
|
+
})
|
|
420
|
+
.then(function (data) {
|
|
421
|
+
if (!data || !data.ok) throw new Error('API returned error');
|
|
422
|
+
hideError();
|
|
423
|
+
_allRecent = Array.isArray(data.recent) ? data.recent : [];
|
|
424
|
+
|
|
425
|
+
renderKpi(data);
|
|
426
|
+
renderAgentCards(Array.isArray(data.agents) ? data.agents : []);
|
|
427
|
+
populateFilter(Array.isArray(data.agents) ? data.agents : []);
|
|
428
|
+
renderRecentTable(_allRecent);
|
|
429
|
+
})
|
|
430
|
+
.catch(function (err) {
|
|
431
|
+
// CRIT C3/C4: never throw; translate the failure into a message that
|
|
432
|
+
// tells the operator what to actually do, never a bare "HTTP 404".
|
|
433
|
+
showError(errorMessage(err));
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// ── Client-side filter: re-render recent table by selected agent ──────────
|
|
438
|
+
function applyFilter() {
|
|
439
|
+
var sel = document.getElementById('od-agents-filter');
|
|
440
|
+
var filt = sel ? sel.value : '';
|
|
441
|
+
var subset = filt
|
|
442
|
+
? _allRecent.filter(function (r) { return r.agent_id === filt; })
|
|
443
|
+
: _allRecent;
|
|
444
|
+
renderRecentTable(subset);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// ── Event wiring (CSP-safe, no onXxx= attributes) ─────────────────────────
|
|
448
|
+
function wireEvents(container) {
|
|
449
|
+
var sel = container.querySelector('#od-agents-filter');
|
|
450
|
+
if (sel) sel.addEventListener('change', applyFilter);
|
|
451
|
+
|
|
452
|
+
var retryBtn = container.querySelector('#od-agents-retry');
|
|
453
|
+
if (retryBtn) {
|
|
454
|
+
retryBtn.addEventListener('click', function () {
|
|
455
|
+
hideError();
|
|
456
|
+
loadAll();
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Click an agent card → filter Recent memories to that agent + scroll to it.
|
|
461
|
+
var grid = container.querySelector('#od-agents-grid');
|
|
462
|
+
if (grid) {
|
|
463
|
+
grid.addEventListener('click', function (e) {
|
|
464
|
+
var card = e.target && e.target.closest ? e.target.closest('.slm-agent-card') : null;
|
|
465
|
+
if (!card) return;
|
|
466
|
+
var agent = card.getAttribute('data-agent');
|
|
467
|
+
if (!agent) return;
|
|
468
|
+
var f = document.getElementById('od-agents-filter');
|
|
469
|
+
if (f) { f.value = agent; applyFilter(); }
|
|
470
|
+
var tbl = document.getElementById('od-agents-table');
|
|
471
|
+
if (tbl && tbl.scrollIntoView) tbl.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// ── Refresh management: MutationObserver re-activates on tab show ─────────
|
|
477
|
+
function startRefreshObserver() {
|
|
478
|
+
var pane = document.getElementById('agents-pane');
|
|
479
|
+
if (!pane || _observer) return;
|
|
480
|
+
_observer = new MutationObserver(function (mutations) {
|
|
481
|
+
mutations.forEach(function (m) {
|
|
482
|
+
if (m.type !== 'attributes' || m.attributeName !== 'class') return;
|
|
483
|
+
var t = m.target;
|
|
484
|
+
if (t.classList.contains('active') && t.classList.contains('show')) {
|
|
485
|
+
loadAll();
|
|
486
|
+
if (_timer) clearInterval(_timer);
|
|
487
|
+
_timer = setInterval(function () {
|
|
488
|
+
if (t.classList.contains('active')) {
|
|
489
|
+
loadAll();
|
|
490
|
+
} else {
|
|
491
|
+
clearInterval(_timer);
|
|
492
|
+
_timer = null;
|
|
493
|
+
}
|
|
494
|
+
}, REFRESH_MS);
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
});
|
|
498
|
+
_observer.observe(pane, { attributes: true });
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// ── Public API ─────────────────────────────────────────────────────────────
|
|
502
|
+
window.odRenderAgents = function (container) {
|
|
503
|
+
if (!container) return;
|
|
504
|
+
_container = container;
|
|
505
|
+
|
|
506
|
+
// Idempotent: reload data if already rendered
|
|
507
|
+
if (container.querySelector('#od-agents-root')) {
|
|
508
|
+
loadAll();
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
container.innerHTML = buildHTML();
|
|
513
|
+
|
|
514
|
+
// Fill data-ic SVG placeholders (icons in KPI strip)
|
|
515
|
+
if (typeof window.slmIcon === 'function') {
|
|
516
|
+
container.querySelectorAll('[data-ic]').forEach(function (el) {
|
|
517
|
+
el.innerHTML = window.slmIcon(el.getAttribute('data-ic'));
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
wireEvents(container);
|
|
522
|
+
loadAll();
|
|
523
|
+
startRefreshObserver();
|
|
524
|
+
|
|
525
|
+
// Polling timer — self-stops when pane becomes inactive
|
|
526
|
+
if (_timer) clearInterval(_timer);
|
|
527
|
+
_timer = setInterval(function () {
|
|
528
|
+
var pane = document.getElementById('agents-pane');
|
|
529
|
+
if (pane && pane.classList.contains('active')) {
|
|
530
|
+
loadAll();
|
|
531
|
+
} else {
|
|
532
|
+
clearInterval(_timer);
|
|
533
|
+
_timer = null;
|
|
534
|
+
}
|
|
535
|
+
}, REFRESH_MS);
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
// Auto-run at DOMContentLoaded if the pane is already in the DOM
|
|
539
|
+
document.addEventListener('DOMContentLoaded', function () {
|
|
540
|
+
var pane = document.getElementById('agents-pane');
|
|
541
|
+
if (pane) window.odRenderAgents(pane);
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
}());
|