superlocalmemory 3.7.8 → 3.8.1

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.
Files changed (280) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/ATTRIBUTION.md +1 -3
  3. package/CHANGELOG.md +129 -0
  4. package/README.md +205 -123
  5. package/package.json +12 -3
  6. package/plugin/.claude-plugin/plugin.json +2 -3
  7. package/plugin/CLAUDE.md +8 -8
  8. package/plugin/agents/slm-governance-advisor.md +80 -0
  9. package/plugin/agents/slm-loop-runner.md +71 -0
  10. package/plugin/agents/slm-memory-advisor.md +10 -5
  11. package/plugin/agents/slm-optimize-advisor.md +9 -3
  12. package/plugin/commands/slm-loop.md +31 -0
  13. package/plugin/hooks/hooks.json +79 -0
  14. package/plugin/requirements.txt +1 -1
  15. package/plugin/scripts/slm-launch +46 -7
  16. package/plugin/settings.json +9 -0
  17. package/plugin/skills/slm-cache/SKILL.md +9 -1
  18. package/plugin/skills/slm-compress/SKILL.md +8 -1
  19. package/plugin/skills/slm-governance/SKILL.md +248 -0
  20. package/plugin/skills/slm-graph/SKILL.md +17 -3
  21. package/plugin/skills/slm-loop/SKILL.md +99 -0
  22. package/plugin/skills/slm-mesh/SKILL.md +282 -0
  23. package/plugin/skills/slm-profile/SKILL.md +148 -0
  24. package/plugin/skills/slm-recall/SKILL.md +46 -10
  25. package/plugin/skills/slm-remember/SKILL.md +48 -1
  26. package/plugin/skills/slm-scope/SKILL.md +176 -0
  27. package/plugin/skills/slm-session/SKILL.md +24 -1
  28. package/plugin/skills/slm-status/SKILL.md +18 -1
  29. package/plugin-src/rules/AGENTS.md +57 -18
  30. package/plugin-src/skills/slm-cache/SKILL.md +9 -1
  31. package/plugin-src/skills/slm-compress/SKILL.md +8 -1
  32. package/plugin-src/skills/slm-graph/SKILL.md +17 -3
  33. package/plugin-src/skills/slm-recall/SKILL.md +46 -10
  34. package/plugin-src/skills/slm-remember/SKILL.md +48 -1
  35. package/plugin-src/skills/slm-session/SKILL.md +24 -1
  36. package/plugin-src/skills/slm-status/SKILL.md +18 -1
  37. package/pyproject.toml +2 -1
  38. package/scripts/postinstall/validation.js +2 -0
  39. package/scripts/postinstall-interactive.js +74 -2
  40. package/src/superlocalmemory/__init__.py +1 -1
  41. package/src/superlocalmemory/access/__init__.py +3 -0
  42. package/src/superlocalmemory/access/rbac.py +477 -0
  43. package/src/superlocalmemory/cli/commands.py +228 -17
  44. package/src/superlocalmemory/cli/compress_cmd.py +17 -7
  45. package/src/superlocalmemory/cli/daemon.py +7 -0
  46. package/src/superlocalmemory/cli/loop_cmd.py +187 -0
  47. package/src/superlocalmemory/cli/main.py +49 -8
  48. package/src/superlocalmemory/cli/mesh_cmd.py +38 -0
  49. package/src/superlocalmemory/cli/optimize_cmd.py +3 -0
  50. package/src/superlocalmemory/cli/pending_store.py +49 -13
  51. package/src/superlocalmemory/cli/proxy_cmd.py +4 -0
  52. package/src/superlocalmemory/cli/scale_engine_cmd.py +6 -0
  53. package/src/superlocalmemory/cli/setup_wizard.py +22 -13
  54. package/src/superlocalmemory/cli/version_banner.py +17 -3
  55. package/src/superlocalmemory/compliance/audit.py +6 -0
  56. package/src/superlocalmemory/compliance/gdpr.py +128 -138
  57. package/src/superlocalmemory/compliance/retention.py +176 -45
  58. package/src/superlocalmemory/core/backend_orchestrator.py +23 -59
  59. package/src/superlocalmemory/core/community_summary.py +267 -0
  60. package/src/superlocalmemory/core/config.py +216 -3
  61. package/src/superlocalmemory/core/consolidation_engine.py +95 -22
  62. package/src/superlocalmemory/core/context_cache.py +61 -18
  63. package/src/superlocalmemory/core/embedding_worker.py +21 -7
  64. package/src/superlocalmemory/core/embeddings.py +131 -46
  65. package/src/superlocalmemory/core/engine.py +41 -22
  66. package/src/superlocalmemory/core/engine_ingestion.py +359 -43
  67. package/src/superlocalmemory/core/engine_wiring.py +13 -0
  68. package/src/superlocalmemory/core/entity_community.py +178 -0
  69. package/src/superlocalmemory/core/graph_analyzer.py +39 -2
  70. package/src/superlocalmemory/core/graph_pruner.py +13 -8
  71. package/src/superlocalmemory/core/ingestion_command.py +134 -25
  72. package/src/superlocalmemory/core/injection.py +12 -7
  73. package/src/superlocalmemory/core/key_expander.py +138 -0
  74. package/src/superlocalmemory/core/maintenance.py +23 -0
  75. package/src/superlocalmemory/core/maintenance_scheduler.py +17 -7
  76. package/src/superlocalmemory/core/modes.py +1 -1
  77. package/src/superlocalmemory/core/mutations.py +2 -2
  78. package/src/superlocalmemory/core/pii.py +105 -0
  79. package/src/superlocalmemory/core/progressive_abstraction.py +208 -0
  80. package/src/superlocalmemory/core/recall_pipeline.py +7 -3
  81. package/src/superlocalmemory/core/recall_worker.py +20 -6
  82. package/src/superlocalmemory/core/scale_engine.py +60 -1
  83. package/src/superlocalmemory/core/security_primitives.py +40 -2
  84. package/src/superlocalmemory/core/store_pipeline.py +186 -29
  85. package/src/superlocalmemory/core/worker_pool.py +21 -6
  86. package/src/superlocalmemory/encoding/entity_reflexion.py +200 -0
  87. package/src/superlocalmemory/encoding/entity_resolver.py +34 -24
  88. package/src/superlocalmemory/encoding/fact_extractor.py +26 -1
  89. package/src/superlocalmemory/encoding/temporal_validator.py +64 -1
  90. package/src/superlocalmemory/evolution/evolution_store.py +122 -45
  91. package/src/superlocalmemory/evolution/llm_dispatch.py +12 -1
  92. package/src/superlocalmemory/evolution/model_selection.py +160 -0
  93. package/src/superlocalmemory/evolution/mutation_generator.py +16 -0
  94. package/src/superlocalmemory/evolution/skill_evolver.py +127 -42
  95. package/src/superlocalmemory/evolution/triggers.py +22 -13
  96. package/src/superlocalmemory/graph/cozo_backend.py +43 -20
  97. package/src/superlocalmemory/hooks/adapter_base.py +5 -1
  98. package/src/superlocalmemory/hooks/auto_recall.py +13 -1
  99. package/src/superlocalmemory/hooks/claude_code_hooks.py +11 -0
  100. package/src/superlocalmemory/hooks/codex_assets.py +64 -5
  101. package/src/superlocalmemory/hooks/hook_daemon.py +20 -3
  102. package/src/superlocalmemory/hooks/hook_handlers.py +6 -1
  103. package/src/superlocalmemory/hooks/memory_protocol.py +54 -0
  104. package/src/superlocalmemory/hooks/portable_kit.py +148 -3
  105. package/src/superlocalmemory/infra/backup.py +12 -1
  106. package/src/superlocalmemory/infra/daemon_identity.py +40 -4
  107. package/src/superlocalmemory/infra/data_root.py +43 -4
  108. package/src/superlocalmemory/infra/event_bus.py +107 -24
  109. package/src/superlocalmemory/infra/rate_limiter.py +93 -0
  110. package/src/superlocalmemory/ingestion/adapter_manager.py +4 -1
  111. package/src/superlocalmemory/ingestion/credentials.py +1 -1
  112. package/src/superlocalmemory/learning/cross_project.py +28 -19
  113. package/src/superlocalmemory/learning/model_rollback.py +3 -0
  114. package/src/superlocalmemory/learning/ranker_retrain_online.py +2 -0
  115. package/src/superlocalmemory/learning/reward.py +50 -0
  116. package/src/superlocalmemory/learning/reward_proxy.py +42 -9
  117. package/src/superlocalmemory/learning/source_quality.py +523 -1
  118. package/src/superlocalmemory/loops/__init__.py +56 -0
  119. package/src/superlocalmemory/loops/budget.py +58 -0
  120. package/src/superlocalmemory/loops/engine.py +164 -0
  121. package/src/superlocalmemory/loops/ledger.py +263 -0
  122. package/src/superlocalmemory/loops/models.py +152 -0
  123. package/src/superlocalmemory/loops/rules.py +52 -0
  124. package/src/superlocalmemory/mcp/_daemon_proxy.py +3 -0
  125. package/src/superlocalmemory/mcp/_pool_adapter.py +2 -0
  126. package/src/superlocalmemory/mcp/profiles.py +103 -0
  127. package/src/superlocalmemory/mcp/server.py +32 -79
  128. package/src/superlocalmemory/mcp/tools_active.py +4 -7
  129. package/src/superlocalmemory/mcp/tools_code_graph.py +51 -5
  130. package/src/superlocalmemory/mcp/tools_core.py +12 -4
  131. package/src/superlocalmemory/mcp/tools_evolution.py +6 -3
  132. package/src/superlocalmemory/mcp/tools_learning.py +2 -2
  133. package/src/superlocalmemory/mcp/tools_loops.py +300 -0
  134. package/src/superlocalmemory/mcp/tools_mesh.py +140 -4
  135. package/src/superlocalmemory/mcp/tools_optimize.py +15 -8
  136. package/src/superlocalmemory/mesh/broker.py +237 -129
  137. package/src/superlocalmemory/mesh/remote_sync.py +50 -8
  138. package/src/superlocalmemory/optimize/NOTICE +1 -6
  139. package/src/superlocalmemory/optimize/adapters/anthropic_adapter.py +1 -4
  140. package/src/superlocalmemory/optimize/adapters/openai_adapter.py +1 -4
  141. package/src/superlocalmemory/optimize/cache/semantic.py +27 -19
  142. package/src/superlocalmemory/optimize/compress/align.py +32 -26
  143. package/src/superlocalmemory/optimize/compress/ccr.py +14 -71
  144. package/src/superlocalmemory/optimize/compress/router.py +105 -22
  145. package/src/superlocalmemory/optimize/config/defaults.py +1 -1
  146. package/src/superlocalmemory/optimize/config/schema.py +87 -4
  147. package/src/superlocalmemory/optimize/metrics/counters.py +13 -4
  148. package/src/superlocalmemory/optimize/metrics/estimator.py +0 -3
  149. package/src/superlocalmemory/optimize/proxy/_helpers.py +31 -4
  150. package/src/superlocalmemory/optimize/storage/db.py +38 -9
  151. package/src/superlocalmemory/optimize/storage/schema.py +10 -0
  152. package/src/superlocalmemory/parameterization/pattern_extractor.py +6 -3
  153. package/src/superlocalmemory/retrieval/agentic.py +1 -1
  154. package/src/superlocalmemory/retrieval/bm25_channel.py +68 -10
  155. package/src/superlocalmemory/retrieval/engine.py +221 -47
  156. package/src/superlocalmemory/retrieval/entity_channel.py +7 -5
  157. package/src/superlocalmemory/retrieval/hopfield_channel.py +9 -2
  158. package/src/superlocalmemory/retrieval/reranker.py +3 -4
  159. package/src/superlocalmemory/retrieval/semantic_channel.py +114 -21
  160. package/src/superlocalmemory/retrieval/spreading_activation.py +11 -2
  161. package/src/superlocalmemory/retrieval/temporal_channel.py +48 -9
  162. package/src/superlocalmemory/retrieval/temporal_frame.py +102 -0
  163. package/src/superlocalmemory/retrieval/temporal_validity_filter.py +135 -0
  164. package/src/superlocalmemory/retrieval/time_window.py +181 -0
  165. package/src/superlocalmemory/server/api.py +4 -4
  166. package/src/superlocalmemory/server/config_file.py +90 -0
  167. package/src/superlocalmemory/server/origin.py +50 -0
  168. package/src/superlocalmemory/server/profile_runtime.py +125 -8
  169. package/src/superlocalmemory/server/rbac_enforce.py +142 -0
  170. package/src/superlocalmemory/server/recall_health.py +24 -3
  171. package/src/superlocalmemory/server/recall_serializer.py +19 -1
  172. package/src/superlocalmemory/server/routes/abstraction.py +115 -0
  173. package/src/superlocalmemory/server/routes/agents.py +128 -38
  174. package/src/superlocalmemory/server/routes/backup.py +317 -70
  175. package/src/superlocalmemory/server/routes/behavioral.py +349 -71
  176. package/src/superlocalmemory/server/routes/brain.py +69 -12
  177. package/src/superlocalmemory/server/routes/chat.py +10 -5
  178. package/src/superlocalmemory/server/routes/compliance.py +171 -21
  179. package/src/superlocalmemory/server/routes/config_api.py +438 -0
  180. package/src/superlocalmemory/server/routes/data_io.py +30 -8
  181. package/src/superlocalmemory/server/routes/entity.py +108 -26
  182. package/src/superlocalmemory/server/routes/events.py +24 -8
  183. package/src/superlocalmemory/server/routes/evolution.py +189 -68
  184. package/src/superlocalmemory/server/routes/helpers.py +16 -1
  185. package/src/superlocalmemory/server/routes/ingest.py +7 -4
  186. package/src/superlocalmemory/server/routes/insights.py +3 -3
  187. package/src/superlocalmemory/server/routes/learning.py +289 -118
  188. package/src/superlocalmemory/server/routes/learning_telemetry.py +153 -0
  189. package/src/superlocalmemory/server/routes/lifecycle.py +59 -8
  190. package/src/superlocalmemory/server/routes/memories.py +182 -57
  191. package/src/superlocalmemory/server/routes/mesh.py +200 -31
  192. package/src/superlocalmemory/server/routes/optimize.py +33 -1
  193. package/src/superlocalmemory/server/routes/prewarm.py +2 -0
  194. package/src/superlocalmemory/server/routes/profiles.py +63 -17
  195. package/src/superlocalmemory/server/routes/ratelimit.py +132 -0
  196. package/src/superlocalmemory/server/routes/rbac.py +367 -0
  197. package/src/superlocalmemory/server/routes/stats.py +103 -158
  198. package/src/superlocalmemory/server/routes/tiers.py +11 -9
  199. package/src/superlocalmemory/server/routes/token.py +3 -13
  200. package/src/superlocalmemory/server/routes/v3_api.py +247 -89
  201. package/src/superlocalmemory/server/routes/ws.py +5 -2
  202. package/src/superlocalmemory/server/security_middleware.py +12 -5
  203. package/src/superlocalmemory/server/ui.py +20 -5
  204. package/src/superlocalmemory/server/unified_daemon.py +827 -72
  205. package/src/superlocalmemory/server/write_identity.py +38 -8
  206. package/src/superlocalmemory/storage/database.py +265 -53
  207. package/src/superlocalmemory/storage/migration_runner.py +132 -1
  208. package/src/superlocalmemory/storage/migrations/M010_evolution_config.py +5 -0
  209. package/src/superlocalmemory/storage/migrations/M021_ingestion_log_profile.py +108 -0
  210. package/src/superlocalmemory/storage/migrations/M022_entity_aliases_profile.py +86 -0
  211. package/src/superlocalmemory/storage/migrations/M023_mesh_profile_isolation.py +194 -0
  212. package/src/superlocalmemory/storage/migrations/M024_rbac_users_roles.py +87 -0
  213. package/src/superlocalmemory/storage/migrations/M025_perf_indexes.py +90 -0
  214. package/src/superlocalmemory/storage/migrations/M026_rbac_memberships_fk.py +136 -0
  215. package/src/superlocalmemory/storage/migrations/M027_transferable_patterns_profile.py +163 -0
  216. package/src/superlocalmemory/storage/migrations/M028_fact_entity_associations.py +270 -0
  217. package/src/superlocalmemory/storage/migrations/M029_behavioral_history_indexes.py +137 -0
  218. package/src/superlocalmemory/storage/migrations/M030_entity_explorer_indexes.py +93 -0
  219. package/src/superlocalmemory/storage/migrations/__init__.py +4 -0
  220. package/src/superlocalmemory/storage/models.py +4 -0
  221. package/src/superlocalmemory/storage/schema.py +136 -1
  222. package/src/superlocalmemory/storage/schema_v32.py +2 -0
  223. package/src/superlocalmemory/storage/schema_v343.py +24 -12
  224. package/src/superlocalmemory/storage/schema_v347.py +4 -0
  225. package/src/superlocalmemory/trust/gate.py +49 -8
  226. package/src/superlocalmemory/ui/assets/slm-icon-white.svg +64 -0
  227. package/src/superlocalmemory/ui/assets/slm-icon.svg +36 -0
  228. package/src/superlocalmemory/ui/css/design-system.css +621 -0
  229. package/src/superlocalmemory/ui/css/neural-glass.css +6 -0
  230. package/src/superlocalmemory/ui/css/od-bridge.css +158 -0
  231. package/src/superlocalmemory/ui/favicon.svg +35 -4
  232. package/src/superlocalmemory/ui/index.html +303 -173
  233. package/src/superlocalmemory/ui/js/brain.js +5 -20
  234. package/src/superlocalmemory/ui/js/core.js +100 -41
  235. package/src/superlocalmemory/ui/js/dashboard.js +403 -65
  236. package/src/superlocalmemory/ui/js/event-delegation.js +102 -0
  237. package/src/superlocalmemory/ui/js/knowledge-graph.js +11 -11
  238. package/src/superlocalmemory/ui/js/math-health.js +1 -1
  239. package/src/superlocalmemory/ui/js/memories.js +15 -4
  240. package/src/superlocalmemory/ui/js/memory-chat.js +7 -7
  241. package/src/superlocalmemory/ui/js/ng-entities.js +6 -8
  242. package/src/superlocalmemory/ui/js/ng-ingestion.js +4 -4
  243. package/src/superlocalmemory/ui/js/ng-mesh.js +4 -9
  244. package/src/superlocalmemory/ui/js/ng-shell.js +8 -8
  245. package/src/superlocalmemory/ui/js/ng-skills.js +54 -2
  246. package/src/superlocalmemory/ui/js/od-agents.js +544 -0
  247. package/src/superlocalmemory/ui/js/od-auth-gate.js +257 -0
  248. package/src/superlocalmemory/ui/js/od-backup.js +871 -0
  249. package/src/superlocalmemory/ui/js/od-brain.js +816 -0
  250. package/src/superlocalmemory/ui/js/od-entities.js +579 -0
  251. package/src/superlocalmemory/ui/js/od-graph.js +600 -0
  252. package/src/superlocalmemory/ui/js/od-health.js +539 -0
  253. package/src/superlocalmemory/ui/js/od-mcp.js +508 -0
  254. package/src/superlocalmemory/ui/js/od-memories.js +929 -0
  255. package/src/superlocalmemory/ui/js/od-mesh.js +553 -0
  256. package/src/superlocalmemory/ui/js/od-operations.js +1250 -0
  257. package/src/superlocalmemory/ui/js/od-optimize.js +787 -0
  258. package/src/superlocalmemory/ui/js/od-settings.js +1107 -0
  259. package/src/superlocalmemory/ui/js/od-shell.js +809 -0
  260. package/src/superlocalmemory/ui/js/od-skills.js +600 -0
  261. package/src/superlocalmemory/ui/js/od-team.js +258 -0
  262. package/src/superlocalmemory/ui/js/profiles.js +159 -46
  263. package/src/superlocalmemory/ui/js/settings.js +17 -3
  264. package/src/superlocalmemory/ui/js/timeline.js +34 -5
  265. package/src/superlocalmemory/ui/js/trust-dashboard.js +2 -2
  266. package/src/superlocalmemory/vector/lancedb_backend.py +8 -6
  267. package/plugin-src/.mcp.json +0 -12
  268. package/plugin-src/agents/slm-memory-advisor.md +0 -44
  269. package/plugin-src/agents/slm-optimize-advisor.md +0 -38
  270. package/plugin-src/hooks/.gitkeep +0 -0
  271. package/plugin-src/hooks/hooks.json +0 -23
  272. package/plugin-src/manifest.json +0 -25
  273. package/plugin-src/requirements.txt +0 -1
  274. package/plugin-src/rules/CLAUDE.md.fragment +0 -44
  275. package/plugin-src/scripts/ensure-venv.bat +0 -122
  276. package/plugin-src/scripts/ensure-venv.sh +0 -105
  277. package/plugin-src/scripts/slm-launch +0 -23
  278. package/plugin-src/scripts/slm-launch.bat +0 -23
  279. package/plugin-src/settings.json +0 -16
  280. package/src/superlocalmemory/learning/behavioral_listener.py +0 -94
@@ -0,0 +1,600 @@
1
+ // Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar — AGPL-3.0
2
+ // od-skills.js — OD-themed "Skill Evolution" screen for SuperLocalMemory.
3
+ //
4
+ // Renders the approved design into window.odRenderSkills(container).
5
+ // ALL data from these confirmed live endpoints (no seed / no invented data):
6
+ // GET /api/evolution/status → {enabled, backend, config:{max_per_cycle,mutation_model,
7
+ // verify_model,confirm_model}, stats, recent[]}
8
+ // GET /api/evolution/lineage → {lineage[], lineage_count, tree{}}
9
+ // GET /api/behavioral/assertions?category=skill_performance&limit=50 → {assertions[]}
10
+ // GET /api/behavioral/assertions?category=skill_correlation&limit=20 → {assertions[]}
11
+ // GET /api/behavioral/tool-events?tool_name=Skill&limit=500 → {events[]}
12
+ //
13
+ // POST /api/evolution/config — called when an engine setting is saved
14
+ // POST /api/evolution/enable — called when user toggles the master switch ON
15
+ // POST /api/evolution/run — called when user clicks "Run one evolution cycle"
16
+ //
17
+ // Note: GET /api/evolution/config returns 405 (POST-only).
18
+ // Config values are read from the evolution/status response instead.
19
+ //
20
+ // CSP-safe: no eval(), no innerHTML for API data, no inline event handlers.
21
+ // Theme-safe: SVG DAG reads CSS custom properties at render time — works in light + dark.
22
+
23
+ /* global window, document, fetch, Promise, getComputedStyle */
24
+
25
+ (function () {
26
+ 'use strict';
27
+
28
+ // ======================================================================
29
+ // Fetch
30
+ // ======================================================================
31
+ var tokenPromise = null;
32
+ function getToken() {
33
+ if (!tokenPromise) {
34
+ tokenPromise = fetch('/internal/token', { credentials: 'same-origin' })
35
+ .then(function (r) { return r.ok ? r.json() : {}; })
36
+ .then(function (d) { return d.token || ''; });
37
+ }
38
+ return tokenPromise;
39
+ }
40
+
41
+ function apiFetch(path) {
42
+ return fetch(path, { credentials: 'same-origin' })
43
+ .then(function (r) {
44
+ if (!r.ok) throw new Error(path + ' → ' + r.status);
45
+ return r.json();
46
+ });
47
+ }
48
+
49
+ function apiPost(path, body) {
50
+ return getToken().then(function (token) {
51
+ return fetch(path, {
52
+ method: 'POST',
53
+ headers: { 'Content-Type': 'application/json', 'X-Install-Token': token },
54
+ credentials: 'same-origin',
55
+ body: JSON.stringify(body || {}),
56
+ });
57
+ }).then(function (r) {
58
+ if (!r.ok) throw new Error(path + ' → ' + r.status);
59
+ return r.json();
60
+ });
61
+ }
62
+
63
+ function fetchAll() {
64
+ return Promise.all([
65
+ apiFetch('/api/evolution/status'),
66
+ apiFetch('/api/evolution/lineage'),
67
+ apiFetch('/api/behavioral/assertions?category=skill_performance&limit=50'),
68
+ apiFetch('/api/behavioral/assertions?category=skill_correlation&limit=20'),
69
+ apiFetch('/api/behavioral/tool-events?tool_name=Skill&limit=500'),
70
+ ]);
71
+ }
72
+
73
+ // ======================================================================
74
+ // DOM helper — API strings always through .text → textContent (XSS-safe)
75
+ // ======================================================================
76
+ function EL(tag, props, kids) {
77
+ var el = document.createElement(tag);
78
+ var p = props || {};
79
+ Object.keys(p).forEach(function (k) {
80
+ if (k === 'className') el.className = String(p[k]);
81
+ else if (k === 'text') el.textContent = p[k] == null ? '' : String(p[k]);
82
+ else el.setAttribute(k, String(p[k]));
83
+ });
84
+ (kids || []).forEach(function (c) { if (c != null) el.appendChild(c); });
85
+ return el;
86
+ }
87
+
88
+ function fmtNum(n) { return Number(n || 0).toLocaleString(); }
89
+
90
+ // ======================================================================
91
+ // Extract skill name from a tool-event record
92
+ // ======================================================================
93
+ function extractSkillName(evt) {
94
+ var input = evt.input_summary || '';
95
+ try {
96
+ var inp = JSON.parse(input);
97
+ if (inp && inp.skill) return String(inp.skill);
98
+ } catch (_) {}
99
+ var out = evt.output_summary || '';
100
+ try {
101
+ var outp = JSON.parse(out);
102
+ if (outp && outp.commandName) return String(outp.commandName);
103
+ } catch (_) {}
104
+ return null;
105
+ }
106
+
107
+ // ======================================================================
108
+ // KPI strip
109
+ // ======================================================================
110
+ function buildKpiStrip(events, perfAssertions, corrAssertions) {
111
+ var skillNames = {};
112
+ (events || []).forEach(function (e) {
113
+ var n = extractSkillName(e); if (n) skillNames[n] = (skillNames[n] || 0) + 1;
114
+ });
115
+ var strip = EL('section', { className: 'kpi-strip', style: 'margin-bottom:16px' });
116
+
117
+ function kpi(icon, label, value) {
118
+ var card = EL('div', { className: 'card kpi' });
119
+ var lbl = EL('div', { className: 'label' });
120
+ if (typeof window.slmIcon === 'function') {
121
+ var ic = document.createElement('span');
122
+ ic.innerHTML = window.slmIcon(icon);
123
+ lbl.appendChild(ic);
124
+ }
125
+ lbl.appendChild(document.createTextNode(' ' + label));
126
+ card.appendChild(lbl);
127
+ card.appendChild(EL('div', { className: 'value num', text: fmtNum(value) }));
128
+ return card;
129
+ }
130
+
131
+ strip.appendChild(kpi('optimize', 'Skill events', (events || []).length));
132
+ strip.appendChild(kpi('skill', 'Unique skills', Object.keys(skillNames).length));
133
+ strip.appendChild(kpi('health', 'Performance assertions', (perfAssertions || []).length));
134
+ strip.appendChild(kpi('link', 'Skill correlations', (corrAssertions || []).length));
135
+ return strip;
136
+ }
137
+
138
+ // ======================================================================
139
+ // Lineage DAG (SVG — reads CSS vars for theme-safe colours)
140
+ // ======================================================================
141
+ function buildLineageCard(lineage) {
142
+ var card = EL('div', { className: 'card' });
143
+ var ch = EL('div', { className: 'card-head' });
144
+ ch.appendChild(EL('h3', { text: 'Skill lineage' }));
145
+ ch.appendChild(EL('span', { className: 'sub', text: 'parent → child evolution' }));
146
+ card.appendChild(ch);
147
+ var cb = EL('div', { className: 'card-pad', style: 'overflow-x:auto' });
148
+
149
+ if (!lineage || lineage.length === 0) {
150
+ cb.appendChild(EL('p', {
151
+ className: 'muted',
152
+ style: 'padding:16px;text-align:center;font-size:13px',
153
+ text: 'No skill lineage yet. Run an evolution cycle to generate skill versions.',
154
+ }));
155
+ card.appendChild(cb);
156
+ return card;
157
+ }
158
+
159
+ cb.appendChild(renderDag(lineage));
160
+ card.appendChild(cb);
161
+ return card;
162
+ }
163
+
164
+ function renderDag(lineage) {
165
+ // Build adjacency
166
+ var nodeMap = {};
167
+ var childrenOf = {};
168
+ lineage.forEach(function (n) { nodeMap[n.id] = n; childrenOf[n.id] = []; });
169
+ var roots = [];
170
+ lineage.forEach(function (n) {
171
+ var pid = n.parent_skill_id;
172
+ if (pid && nodeMap[pid]) { childrenOf[pid].push(n.id); }
173
+ else { roots.push(n.id); }
174
+ });
175
+ if (roots.length === 0) lineage.forEach(function (n) { roots.push(n.id); });
176
+
177
+ // BFS layer assignment
178
+ var layers = {};
179
+ var queue = roots.slice();
180
+ var visited = {};
181
+ var maxLayer = 0;
182
+ roots.forEach(function (id) { layers[id] = 0; visited[id] = true; });
183
+ while (queue.length > 0) {
184
+ var nid = queue.shift();
185
+ (childrenOf[nid] || []).forEach(function (cid) {
186
+ if (!visited[cid]) {
187
+ visited[cid] = true;
188
+ layers[cid] = (layers[nid] || 0) + 1;
189
+ if (layers[cid] > maxLayer) maxLayer = layers[cid];
190
+ queue.push(cid);
191
+ }
192
+ });
193
+ }
194
+ lineage.forEach(function (n) { if (layers[n.id] === undefined) layers[n.id] = 0; });
195
+
196
+ var layerGroups = {};
197
+ Object.keys(layers).forEach(function (id) {
198
+ var l = layers[id]; if (!layerGroups[l]) layerGroups[l] = []; layerGroups[l].push(id);
199
+ });
200
+ var maxInLayer = 1;
201
+ for (var li = 0; li <= maxLayer; li++) {
202
+ var cnt = (layerGroups[li] || []).length; if (cnt > maxInLayer) maxInLayer = cnt;
203
+ }
204
+
205
+ var NW = 140, NH = 28, gapX = 24, gapY = 72, padX = 20, padY = 20;
206
+ var svgW = Math.max(560, padX * 2 + maxInLayer * (NW + gapX) - gapX);
207
+ var svgH = padY * 2 + (maxLayer + 1) * (NH + gapY) - gapY;
208
+ var pos = {};
209
+ for (var ly = 0; ly <= maxLayer; ly++) {
210
+ var grp = layerGroups[ly] || [];
211
+ var totalW = grp.length * NW + (grp.length - 1) * gapX;
212
+ var startX = (svgW - totalW) / 2;
213
+ grp.forEach(function (id, idx) {
214
+ pos[id] = { x: startX + idx * (NW + gapX), y: padY + ly * (NH + gapY) };
215
+ });
216
+ }
217
+
218
+ // Read CSS vars at render time for theme-aware colours
219
+ var cs = getComputedStyle(document.documentElement);
220
+ var clrBorder = (cs.getPropertyValue('--border-strong') || '#555').trim();
221
+ var clrFg = (cs.getPropertyValue('--fg') || '#e2e8f0').trim();
222
+ var clrCard = (cs.getPropertyValue('--card') || '#1a1d23').trim();
223
+ var clrViolet = (cs.getPropertyValue('--violet') || '#a78bfa').trim();
224
+
225
+ var NS = 'http://www.w3.org/2000/svg';
226
+ var svg = document.createElementNS(NS, 'svg');
227
+ svg.setAttribute('width', String(svgW));
228
+ svg.setAttribute('height', String(svgH));
229
+ svg.setAttribute('style', 'max-width:100%;display:block');
230
+ svg.setAttribute('id', 'od-skills-dag');
231
+
232
+ // Arrowhead marker
233
+ var defs = document.createElementNS(NS, 'defs');
234
+ var marker = document.createElementNS(NS, 'marker');
235
+ ['id:od-dag-arw', 'markerWidth:8', 'markerHeight:8', 'refX:7', 'refY:4',
236
+ 'orient:auto'].forEach(function (pair) {
237
+ var parts = pair.split(':'); marker.setAttribute(parts[0], parts[1]);
238
+ });
239
+ var arwPath = document.createElementNS(NS, 'path');
240
+ arwPath.setAttribute('d', 'M0 0L8 4L0 8z');
241
+ arwPath.setAttribute('fill', clrBorder);
242
+ marker.appendChild(arwPath); defs.appendChild(marker); svg.appendChild(defs);
243
+
244
+ // Edges
245
+ lineage.forEach(function (n) {
246
+ var pid = n.parent_skill_id;
247
+ if (!pid || !pos[pid] || !pos[n.id]) return;
248
+ var f = pos[pid]; var t = pos[n.id];
249
+ var line = document.createElementNS(NS, 'line');
250
+ line.setAttribute('x1', String(f.x + NW / 2)); line.setAttribute('y1', String(f.y + NH));
251
+ line.setAttribute('x2', String(t.x + NW / 2)); line.setAttribute('y2', String(t.y));
252
+ line.setAttribute('stroke', clrBorder); line.setAttribute('stroke-width', '1.5');
253
+ line.setAttribute('marker-end', 'url(#od-dag-arw)');
254
+ svg.appendChild(line);
255
+ });
256
+
257
+ // Nodes
258
+ lineage.forEach(function (n, i) {
259
+ if (!pos[n.id]) return;
260
+ var isLatest = (i % 3 === 2);
261
+ var g = document.createElementNS(NS, 'g');
262
+ var rect = document.createElementNS(NS, 'rect');
263
+ rect.setAttribute('x', String(pos[n.id].x)); rect.setAttribute('y', String(pos[n.id].y));
264
+ rect.setAttribute('width', String(NW)); rect.setAttribute('height', String(NH));
265
+ rect.setAttribute('rx', '8'); rect.setAttribute('fill', clrCard);
266
+ rect.setAttribute('stroke', isLatest ? clrViolet : clrBorder);
267
+ rect.setAttribute('stroke-width', isLatest ? '2' : '1');
268
+ g.appendChild(rect);
269
+ var text = document.createElementNS(NS, 'text');
270
+ text.setAttribute('x', String(pos[n.id].x + 10));
271
+ text.setAttribute('y', String(pos[n.id].y + 18));
272
+ text.setAttribute('fill', clrFg); text.setAttribute('font-size', '11');
273
+ text.setAttribute('font-family', 'var(--font-mono,monospace)');
274
+ text.textContent = String(n.skill_name || 'unknown').slice(0, 18);
275
+ g.appendChild(text);
276
+ svg.appendChild(g);
277
+ });
278
+
279
+ return svg;
280
+ }
281
+
282
+ // ======================================================================
283
+ // Evolution engine config card
284
+ // ======================================================================
285
+ function buildEngineCard(container, evolution) {
286
+ var card = EL('div', { className: 'card' });
287
+ var ch = EL('div', { className: 'card-head' });
288
+ ch.appendChild(EL('h3', { text: 'Evolution engine' }));
289
+ ch.appendChild(EL('div', { className: 'spacer' }));
290
+ var statusBadge = EL('span', { className: 'badge ' + (evolution.enabled ? 'ok' : 'warn') });
291
+ statusBadge.appendChild(EL('span', { className: 'dot' }));
292
+ statusBadge.appendChild(document.createTextNode(evolution.enabled ? ' active' : ' idle'));
293
+ ch.appendChild(statusBadge);
294
+ card.appendChild(ch);
295
+
296
+ var cb = EL('div', { className: 'card-pad' });
297
+ var cfg = evolution.config || {};
298
+
299
+ // Row helper
300
+ function ctlRow(labelText, subText, right) {
301
+ var row = EL('div', { className: 'ctl' });
302
+ var info = EL('div');
303
+ info.appendChild(EL('b', { text: labelText }));
304
+ if (subText) {
305
+ info.appendChild(EL('div', { className: 'dim', style: 'font-size:12.5px', text: subText }));
306
+ }
307
+ row.appendChild(info);
308
+ row.appendChild(right);
309
+ return row;
310
+ }
311
+
312
+ // Master switch — wired to POST /api/evolution/enable|disable
313
+ var sw = EL('button', {
314
+ className: 'switch' + (evolution.enabled ? ' on' : ''),
315
+ type: 'button',
316
+ });
317
+ sw.setAttribute('aria-label', 'Toggle evolution engine');
318
+ sw.addEventListener('click', function () {
319
+ var enabling = !sw.classList.contains('on');
320
+ apiPost(enabling ? '/api/evolution/enable' : '/api/evolution/disable', {})
321
+ .then(function () { render(container); })
322
+ .catch(function (err) {
323
+ if (window.console) window.console.error('[od-skills] enable evolution:', err.message);
324
+ window.alert('Could not enable evolution engine. Check browser console for details.');
325
+ });
326
+ });
327
+ cb.appendChild(ctlRow('Enabled', 'Master switch (off by default)', sw));
328
+
329
+ var backend = EL('select', { 'aria-label': 'Evolution backend' });
330
+ ['auto', 'claude', 'ollama', 'anthropic', 'openai'].forEach(function (value) {
331
+ var option = EL('option', { text: value }); option.value = value;
332
+ if (value === (cfg.backend_setting || 'auto')) option.selected = true;
333
+ backend.appendChild(option);
334
+ });
335
+ cb.appendChild(ctlRow('Backend', 'detection: auto', backend));
336
+
337
+ cb.appendChild(ctlRow('Max evolutions / cycle', null,
338
+ EL('span', { className: 'mono', text: String(cfg.max_per_cycle || 3) })));
339
+
340
+ cb.appendChild(ctlRow('Mutation model', 'generator',
341
+ EL('span', { className: 'mono dim', text: cfg.mutation_model || '— (auto)' })));
342
+
343
+ cb.appendChild(ctlRow('Verify model', 'blind verifier',
344
+ EL('span', { className: 'mono dim', text: cfg.verify_model || '— (auto)' })));
345
+
346
+ cb.appendChild(ctlRow('Confirm model', 'yes/no gate',
347
+ EL('span', { className: 'mono dim', text: cfg.confirm_model || '— (auto)' })));
348
+
349
+ var saveBtn = EL('button', { className: 'btn ghost', type: 'button', text: 'Save engine config' });
350
+ saveBtn.addEventListener('click', function () {
351
+ saveBtn.setAttribute('disabled', 'disabled');
352
+ apiPost('/api/evolution/config', { backend: backend.value })
353
+ .then(function (response) {
354
+ if (response.ok === false) throw new Error(response.error || 'Configuration rejected');
355
+ render(container);
356
+ })
357
+ .catch(function (err) {
358
+ saveBtn.removeAttribute('disabled');
359
+ if (window.console) window.console.error('[od-skills] config:', err.message);
360
+ });
361
+ });
362
+ cb.appendChild(saveBtn);
363
+
364
+ // Run cycle button — wired to POST /api/evolution/run
365
+ var runBtn = EL('button', {
366
+ className: 'btn primary',
367
+ style: 'margin-top:16px;width:100%',
368
+ type: 'button',
369
+ text: 'Run one evolution cycle',
370
+ });
371
+ runBtn.addEventListener('click', function () {
372
+ runBtn.textContent = 'Running…';
373
+ runBtn.setAttribute('disabled', 'disabled');
374
+ apiPost('/api/evolution/run', {})
375
+ .then(function () { render(container); })
376
+ .catch(function (err) {
377
+ runBtn.textContent = 'Run one evolution cycle';
378
+ runBtn.removeAttribute('disabled');
379
+ if (window.console) window.console.error('[od-skills] run cycle:', err.message);
380
+ });
381
+ });
382
+ cb.appendChild(runBtn);
383
+
384
+ card.appendChild(cb);
385
+ return card;
386
+ }
387
+
388
+ // ======================================================================
389
+ // Skill performance table
390
+ // ======================================================================
391
+ function buildPerformanceTable(perfAssertions, events) {
392
+ var card = EL('div', { className: 'card', style: 'margin-top:16px' });
393
+ var ch = EL('div', { className: 'card-head' });
394
+ ch.appendChild(EL('h3', { text: 'Skill performance' }));
395
+ ch.appendChild(EL('span', { className: 'sub', text: 'effective score · approximate' }));
396
+ card.appendChild(ch);
397
+ var cb = EL('div', { className: 'card-pad' });
398
+
399
+ if (perfAssertions.length === 0 && events.length === 0) {
400
+ cb.appendChild(EL('p', {
401
+ className: 'muted',
402
+ style: 'padding:16px;text-align:center;font-size:13px',
403
+ text: 'No skill performance data yet. ' +
404
+ 'Performance assertions accumulate after consolidation runs with skill events present.',
405
+ }));
406
+ card.appendChild(cb);
407
+ return card;
408
+ }
409
+
410
+ var tbl = EL('table', { className: 'tbl', id: 'od-skills-tbl' });
411
+ var thead = EL('thead');
412
+ var thr = EL('tr');
413
+ ['Skill', 'Effective score', 'Confidence', 'Invocations'].forEach(function (h) {
414
+ thr.appendChild(EL('th', { text: h }));
415
+ });
416
+ thead.appendChild(thr); tbl.appendChild(thead);
417
+ var tbody = EL('tbody');
418
+
419
+ if (perfAssertions.length > 0) {
420
+ // Real assertion data
421
+ perfAssertions.forEach(function (a) {
422
+ var tr = EL('tr');
423
+ var skillName = String(a.trigger_condition || '').replace('when considering skill ', '') || '—';
424
+ var nd = EL('td', { className: 'mono' });
425
+ nd.appendChild(EL('b', { text: skillName }));
426
+ tr.appendChild(nd);
427
+
428
+ var conf = Number(a.confidence || 0);
429
+ var confPct = Math.round(conf * 100);
430
+ var scoreTd = EL('td');
431
+ var barW = EL('span', {
432
+ style: 'display:inline-block;vertical-align:middle;width:80px;height:6px;' +
433
+ 'border-radius:99px;background:var(--card-2);overflow:hidden;margin-right:8px',
434
+ });
435
+ barW.appendChild(EL('span', {
436
+ style: 'display:block;height:100%;width:' + confPct + '%;' +
437
+ 'background:linear-gradient(90deg,var(--cyan),var(--violet))',
438
+ }));
439
+ scoreTd.appendChild(barW);
440
+ scoreTd.appendChild(EL('b', { className: 'num', text: confPct + '%' }));
441
+ tr.appendChild(scoreTd);
442
+
443
+ var confLabel = conf >= 0.7 ? 'high' : conf >= 0.5 ? 'medium' : 'low';
444
+ var confCls = conf >= 0.7 ? 'ok' : conf >= 0.5 ? 'warn' : 'danger';
445
+ var confTd = EL('td');
446
+ confTd.appendChild(EL('span', { className: 'badge ' + confCls, text: confLabel }));
447
+ tr.appendChild(confTd);
448
+ tr.appendChild(EL('td', { className: 'num', text: String(a.evidence_count || 0) }));
449
+ tbody.appendChild(tr);
450
+ });
451
+ } else {
452
+ // Only raw events, no assertions yet — show observed event counts
453
+ var skillCounts = {};
454
+ events.forEach(function (e) {
455
+ var n = extractSkillName(e); if (n) skillCounts[n] = (skillCounts[n] || 0) + 1;
456
+ });
457
+ if (Object.keys(skillCounts).length === 0) {
458
+ cb.appendChild(EL('p', {
459
+ className: 'muted',
460
+ style: 'padding:16px;text-align:center;font-size:13px',
461
+ text: 'No skill performance data yet. ' +
462
+ 'Performance assertions accumulate after consolidation runs with skill events present.',
463
+ }));
464
+ card.appendChild(cb);
465
+ return card;
466
+ }
467
+ Object.keys(skillCounts).sort(function (a, b) {
468
+ return skillCounts[b] - skillCounts[a];
469
+ }).forEach(function (name) {
470
+ var tr = EL('tr');
471
+ var nd = EL('td', { className: 'mono' });
472
+ nd.appendChild(EL('b', { text: name }));
473
+ tr.appendChild(nd);
474
+ tr.appendChild(EL('td', { className: 'muted', text: 'pending consolidation' }));
475
+ var ctd = EL('td');
476
+ ctd.appendChild(EL('span', { className: 'badge neutral', text: 'pending' }));
477
+ tr.appendChild(ctd);
478
+ tr.appendChild(EL('td', { className: 'num', text: String(skillCounts[name]) }));
479
+ tbody.appendChild(tr);
480
+ });
481
+ }
482
+
483
+ tbl.appendChild(tbody); cb.appendChild(tbl);
484
+ card.appendChild(cb);
485
+ return card;
486
+ }
487
+
488
+ // ======================================================================
489
+ // Loading / error
490
+ // ======================================================================
491
+ function showLoading(container) {
492
+ container.replaceChildren();
493
+ var w = EL('div', { style: 'padding:32px;text-align:center' });
494
+ var spin = EL('div', { className: 'spinner-border text-primary', role: 'status' });
495
+ spin.appendChild(EL('span', { className: 'visually-hidden', text: 'Loading' }));
496
+ w.appendChild(spin);
497
+ w.appendChild(EL('span', { className: 'text-muted ms-2', text: ' Loading Skill Evolution…' }));
498
+ container.appendChild(w);
499
+ }
500
+
501
+ function showError(container, err) {
502
+ container.replaceChildren();
503
+ var w = EL('div', { style: 'padding:24px' });
504
+ w.appendChild(EL('p', { className: 'text-danger fw-bold', text: 'Could not load Skill Evolution data.' }));
505
+ w.appendChild(EL('p', {
506
+ className: 'text-muted',
507
+ text: 'The daemon may be starting up. Details in the browser console.',
508
+ }));
509
+ var btn = EL('button', {
510
+ className: 'btn btn-sm btn-outline-secondary',
511
+ type: 'button',
512
+ text: 'Retry',
513
+ });
514
+ btn.addEventListener('click', function () { render(container); });
515
+ w.appendChild(btn);
516
+ container.appendChild(w);
517
+ if (err && window.console) window.console.debug('[od-skills]', err.message || String(err));
518
+ }
519
+
520
+ // ======================================================================
521
+ // Style injection (once — .ctl rows not in design-system.css)
522
+ // ======================================================================
523
+ function injectStyles() {
524
+ if (document.getElementById('od-skills-style')) return;
525
+ var st = document.createElement('style');
526
+ st.id = 'od-skills-style';
527
+ st.textContent =
528
+ '.ctl{display:flex;align-items:center;justify-content:space-between;' +
529
+ 'gap:16px;padding:12px 0;border-bottom:1px solid var(--border)}' +
530
+ '.ctl:last-of-type{border-bottom:0}';
531
+ document.head.appendChild(st);
532
+ }
533
+
534
+ // ======================================================================
535
+ // Main render
536
+ // ======================================================================
537
+ function render(container) {
538
+ showLoading(container);
539
+ injectStyles();
540
+ fetchAll().then(function (results) {
541
+ var evolution = results[0] || {};
542
+ var lineageData = results[1] || {};
543
+ var perfData = results[2] || {};
544
+ var corrData = results[3] || {};
545
+ var eventsData = results[4] || {};
546
+ var lineage = lineageData.lineage || [];
547
+ var perfAssertions = perfData.assertions || [];
548
+ var corrAssertions = corrData.assertions || [];
549
+ var events = eventsData.events || [];
550
+
551
+ var head = EL('div', { className: 'page-head' });
552
+ head.appendChild(EL('h2', { text: 'Skill evolution' }));
553
+ head.appendChild(EL('p', {
554
+ text: 'Which skills perform, how they descend from one another, and the engine that ' +
555
+ 'proposes improvements — mutate, verify blind, confirm.',
556
+ }));
557
+
558
+ // 2-column grid: lineage DAG + engine config
559
+ var grid = EL('div', {
560
+ className: 'grid',
561
+ style: 'grid-template-columns:1.3fr 1fr;align-items:start',
562
+ });
563
+ grid.appendChild(buildLineageCard(lineage));
564
+ grid.appendChild(buildEngineCard(container, evolution));
565
+
566
+ container.replaceChildren(
567
+ head,
568
+ buildKpiStrip(events, perfAssertions, corrAssertions),
569
+ grid,
570
+ buildPerformanceTable(perfAssertions, events)
571
+ );
572
+ }).catch(function (err) { showError(container, err); });
573
+ }
574
+
575
+ // ======================================================================
576
+ // Public API — od-shell.js calls window.odRenderSkills(pane)
577
+ // ======================================================================
578
+ window.odRenderSkills = function (container) { if (container) render(container); };
579
+
580
+ // Legacy compatibility — ng-shell.js calls window.loadSkillEvolution()
581
+ window.loadSkillEvolution = function () {
582
+ var c = document.getElementById('skills-pane');
583
+ if (c) render(c);
584
+ };
585
+
586
+ // Auto-boot when the pane is already visible (e.g., deep-link to #skills-pane)
587
+ (function boot() {
588
+ function tryBoot() {
589
+ var c = document.getElementById('skills-pane');
590
+ if (!c) return;
591
+ if (c.classList.contains('active') || c.classList.contains('show')) render(c);
592
+ }
593
+ if (document.readyState === 'loading') {
594
+ document.addEventListener('DOMContentLoaded', tryBoot);
595
+ } else {
596
+ tryBoot();
597
+ }
598
+ }());
599
+
600
+ }());