superlocalmemory 4.0.7 → 4.0.9
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/CHANGELOG.md +219 -1
- package/README.md +6 -6
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/CLAUDE.md +3 -3
- package/plugin/agents/slm-governance-advisor.md +1 -1
- package/plugin/agents/slm-loop-runner.md +1 -1
- package/plugin/agents/slm-memory-advisor.md +1 -1
- package/plugin/agents/slm-optimize-advisor.md +1 -1
- package/plugin/requirements.txt +1 -1
- package/plugin/scripts/ensure-venv.sh +1 -1
- package/plugin/skills/slm-cache/SKILL.md +1 -1
- package/plugin/skills/slm-compress/SKILL.md +1 -1
- package/plugin/skills/slm-governance/SKILL.md +1 -1
- package/plugin/skills/slm-graph/SKILL.md +1 -1
- package/plugin/skills/slm-loop/SKILL.md +1 -1
- package/plugin/skills/slm-mesh/SKILL.md +1 -1
- package/plugin/skills/slm-profile/SKILL.md +1 -1
- package/plugin/skills/slm-recall/SKILL.md +1 -1
- package/plugin/skills/slm-remember/SKILL.md +1 -1
- package/plugin/skills/slm-scope/SKILL.md +1 -1
- package/plugin/skills/slm-session/SKILL.md +1 -1
- package/plugin/skills/slm-status/SKILL.md +3 -3
- package/plugin-src/rules/AGENTS.md +1 -1
- package/plugin-src/skills/slm-status/SKILL.md +2 -2
- package/pyproject.toml +1 -1
- package/scripts/postinstall.js +4 -0
- package/src/superlocalmemory/__init__.py +1 -1
- package/src/superlocalmemory/cli/_lazy_init.py +1 -1
- package/src/superlocalmemory/cli/commands.py +119 -9
- package/src/superlocalmemory/cli/db_migrate.py +0 -2
- package/src/superlocalmemory/cli/gdpr_io.py +1 -1
- package/src/superlocalmemory/cli/main.py +5 -5
- package/src/superlocalmemory/cli/service_installer.py +2 -1
- package/src/superlocalmemory/cli/setup_wizard.py +1 -1
- package/src/superlocalmemory/cli/summary_cmd.py +23 -3
- package/src/superlocalmemory/code_graph/bridge/maintenance.py +7 -1
- package/src/superlocalmemory/core/config.py +41 -7
- package/src/superlocalmemory/core/consolidation_engine.py +14 -15
- package/src/superlocalmemory/core/context_cache.py +0 -2
- package/src/superlocalmemory/core/engine.py +371 -63
- package/src/superlocalmemory/core/evidence_bundle.py +3 -1
- package/src/superlocalmemory/core/install_detector.py +131 -0
- package/src/superlocalmemory/core/progressive_abstraction.py +1 -1
- package/src/superlocalmemory/core/recall_worker.py +4 -0
- package/src/superlocalmemory/core/security_primitives.py +3 -6
- package/src/superlocalmemory/core/store_pipeline.py +94 -26
- package/src/superlocalmemory/core/topic_signature.py +0 -2
- package/src/superlocalmemory/core/transactions/concrete_owners.py +15 -8
- package/src/superlocalmemory/dynamics/eap_scheduler.py +17 -6
- package/src/superlocalmemory/encoding/graph_builder.py +2 -2
- package/src/superlocalmemory/encoding/scene_builder.py +8 -2
- package/src/superlocalmemory/evolution/skill_evolver.py +16 -1
- package/src/superlocalmemory/hooks/adapter_base.py +0 -2
- package/src/superlocalmemory/hooks/context_payload.py +0 -2
- package/src/superlocalmemory/hooks/hook_handlers.py +38 -11
- package/src/superlocalmemory/hooks/portable_kit.py +8 -8
- package/src/superlocalmemory/hooks/post_tool_async_hook.py +0 -2
- package/src/superlocalmemory/hooks/prewarm_auth.py +0 -2
- package/src/superlocalmemory/hooks/user_prompt_hook.py +0 -2
- package/src/superlocalmemory/infra/backup.py +44 -8
- package/src/superlocalmemory/integrations/bounded_loops_mcp.py +24 -7
- package/src/superlocalmemory/learning/arm_catalog.py +0 -2
- package/src/superlocalmemory/learning/bandit.py +0 -2
- package/src/superlocalmemory/learning/bandit_cache.py +0 -2
- package/src/superlocalmemory/learning/dedup_hnsw.py +11 -11
- package/src/superlocalmemory/learning/ensemble.py +0 -2
- package/src/superlocalmemory/learning/labeler.py +0 -2
- package/src/superlocalmemory/learning/legacy_migration.py +0 -2
- package/src/superlocalmemory/learning/model_cache.py +0 -2
- package/src/superlocalmemory/learning/pattern_miner.py +12 -7
- package/src/superlocalmemory/learning/ranker.py +0 -2
- package/src/superlocalmemory/learning/reward_archive.py +6 -1
- package/src/superlocalmemory/learning/reward_proxy.py +0 -2
- package/src/superlocalmemory/learning/signal_worker.py +0 -2
- package/src/superlocalmemory/math/fisher.py +1 -1
- package/src/superlocalmemory/math/hopfield.py +4 -1
- package/src/superlocalmemory/math/langevin.py +1 -1
- package/src/superlocalmemory/math/sheaf.py +7 -3
- package/src/superlocalmemory/mcp/cli_fallback.py +1 -1
- package/src/superlocalmemory/mcp/profiles.py +11 -4
- package/src/superlocalmemory/mcp/server.py +8 -1
- package/src/superlocalmemory/mcp/tools_active.py +56 -0
- package/src/superlocalmemory/mcp/tools_core.py +1 -1
- package/src/superlocalmemory/mcp/tools_summaries.py +147 -0
- package/src/superlocalmemory/optimize/cache/manager.py +2 -2
- package/src/superlocalmemory/optimize/compress/ccr.py +1 -1
- package/src/superlocalmemory/optimize/compress/router.py +1 -1
- package/src/superlocalmemory/optimize/proxy/_helpers.py +2 -2
- package/src/superlocalmemory/optimize/proxy/server.py +1 -1
- package/src/superlocalmemory/optimize/proxy/vertex_surface.py +2 -2
- package/src/superlocalmemory/optimize/storage/db.py +2 -2
- package/src/superlocalmemory/retrieval/agentic.py +1 -1
- package/src/superlocalmemory/retrieval/ann_index.py +9 -2
- package/src/superlocalmemory/retrieval/bm25_channel.py +2 -2
- package/src/superlocalmemory/retrieval/bridge_discovery.py +2 -2
- package/src/superlocalmemory/retrieval/engine.py +272 -43
- package/src/superlocalmemory/retrieval/entity_channel.py +1 -1
- package/src/superlocalmemory/retrieval/hopfield_channel.py +8 -2
- package/src/superlocalmemory/retrieval/profile_channel.py +1 -1
- package/src/superlocalmemory/retrieval/quantization_aware_search.py +1 -1
- package/src/superlocalmemory/retrieval/remote_reranker.py +2 -2
- package/src/superlocalmemory/retrieval/reranker.py +3 -3
- package/src/superlocalmemory/retrieval/semantic_channel.py +3 -3
- package/src/superlocalmemory/retrieval/spreading_activation.py +8 -8
- package/src/superlocalmemory/retrieval/strategy.py +94 -0
- package/src/superlocalmemory/retrieval/temporal_channel.py +167 -10
- package/src/superlocalmemory/retrieval/temporal_validity_filter.py +1 -1
- package/src/superlocalmemory/retrieval/vector_store.py +88 -10
- package/src/superlocalmemory/server/consolidation_runner.py +140 -0
- package/src/superlocalmemory/server/recall_serializer.py +44 -2
- package/src/superlocalmemory/server/routes/agents.py +52 -8
- package/src/superlocalmemory/server/routes/brain.py +110 -2
- package/src/superlocalmemory/server/routes/memories.py +153 -0
- package/src/superlocalmemory/server/routes/prewarm.py +4 -4
- package/src/superlocalmemory/server/routes/v3_api.py +24 -46
- package/src/superlocalmemory/server/unified_daemon.py +566 -7
- package/src/superlocalmemory/storage/_schema_version.py +46 -3
- package/src/superlocalmemory/storage/backup.py +531 -0
- package/src/superlocalmemory/storage/database.py +11 -4
- package/src/superlocalmemory/storage/embedding_codec.py +129 -0
- package/src/superlocalmemory/storage/embedding_migrator.py +5 -3
- package/src/superlocalmemory/storage/migration_runner.py +142 -2
- package/src/superlocalmemory/storage/migrations/__init__.py +1 -1
- package/src/superlocalmemory/storage/migrations.py +15 -1
- package/src/superlocalmemory/storage/models.py +7 -0
- package/src/superlocalmemory/storage/quantized_store.py +4 -2
- package/src/superlocalmemory/summaries/base.py +159 -0
- package/src/superlocalmemory/summaries/daily_reflection.py +55 -8
- package/src/superlocalmemory/summaries/project_work_log.py +23 -7
- package/src/superlocalmemory/summaries/session_summary.py +10 -6
- package/src/superlocalmemory/ui/css/legacy-dashboard.css +1 -1
- package/src/superlocalmemory/ui/css/neural-glass.css +1 -1
- package/src/superlocalmemory/ui/index.html +9 -3
- package/src/superlocalmemory/ui/js/core.js +1 -1
- package/src/superlocalmemory/ui/js/od-boundedloops.js +324 -0
- package/src/superlocalmemory/ui/js/od-brain.js +1 -1
- package/src/superlocalmemory/ui/js/od-memories.js +337 -12
- package/src/superlocalmemory/ui/js/od-mesh.js +97 -5
- package/src/superlocalmemory/ui/js/od-operations.js +1 -150
- package/src/superlocalmemory/ui/js/od-optimize.js +36 -9
- package/src/superlocalmemory/ui/js/od-shell.js +10 -0
|
@@ -34,6 +34,9 @@ from datetime import date
|
|
|
34
34
|
from pathlib import Path
|
|
35
35
|
|
|
36
36
|
from .base import (
|
|
37
|
+
clean_llm_summary,
|
|
38
|
+
format_highlight,
|
|
39
|
+
SUMMARY_SYSTEM_PROMPT,
|
|
37
40
|
COVERAGE_FULL,
|
|
38
41
|
COVERAGE_INSUFFICIENT,
|
|
39
42
|
COVERAGE_UNAVAILABLE,
|
|
@@ -128,7 +131,7 @@ def generate_daily_reflection(
|
|
|
128
131
|
coverage = COVERAGE_FULL
|
|
129
132
|
|
|
130
133
|
# ── extractive summary (deterministic, always available) ──────────────────
|
|
131
|
-
extractive_content = _build_extractive_content(date_str, facts, fact_count)
|
|
134
|
+
extractive_content = _build_extractive_content(date_str, facts, fact_count, db_path, profile_id)
|
|
132
135
|
|
|
133
136
|
# ── LLM enrichment (optional) ─────────────────────────────────────────────
|
|
134
137
|
mode = get_mode_str(config)
|
|
@@ -162,8 +165,14 @@ def _build_extractive_content(
|
|
|
162
165
|
date_str: str,
|
|
163
166
|
facts: list[dict],
|
|
164
167
|
fact_count: int,
|
|
168
|
+
db_path: str | Path,
|
|
169
|
+
profile_id: str,
|
|
165
170
|
) -> str:
|
|
166
|
-
"""Build a deterministic extractive daily reflection.
|
|
171
|
+
"""Build a deterministic extractive daily reflection.
|
|
172
|
+
|
|
173
|
+
``db_path`` / ``profile_id`` are needed only to turn entity IDs into names
|
|
174
|
+
for the "Active entities" line — the facts themselves are already loaded.
|
|
175
|
+
"""
|
|
167
176
|
import json
|
|
168
177
|
|
|
169
178
|
lines = [
|
|
@@ -174,8 +183,7 @@ def _build_extractive_content(
|
|
|
174
183
|
]
|
|
175
184
|
for f in facts[:_BODY_FACTS]:
|
|
176
185
|
content = f.get("content", "")
|
|
177
|
-
|
|
178
|
-
content = content[:_MAX_FACT_CHARS - 3] + "..."
|
|
186
|
+
content = format_highlight(content)
|
|
179
187
|
lines.append(f" - {content}")
|
|
180
188
|
if fact_count > _BODY_FACTS:
|
|
181
189
|
lines.append(f" ... and {fact_count - _BODY_FACTS} additional facts.")
|
|
@@ -193,13 +201,50 @@ def _build_extractive_content(
|
|
|
193
201
|
|
|
194
202
|
if entity_counts:
|
|
195
203
|
top_entities = sorted(entity_counts.items(), key=lambda x: x[1], reverse=True)[:5]
|
|
196
|
-
|
|
204
|
+
# Resolve to names. canonical_entities_json stores entity IDs, so this
|
|
205
|
+
# line previously read "Active entities: 1666abc512904473 (127),
|
|
206
|
+
# 84288f2dde994afe (124)" — five 16-hex identifiers, which tell a reader
|
|
207
|
+
# nothing. They resolve to 'Fixed', 'Gateway', 'REVISED' and so on. This
|
|
208
|
+
# is the same defect 4.0.6 fixed in the Living Brain, where source
|
|
209
|
+
# quality listed internal identifiers; it survived here because this
|
|
210
|
+
# generator was never reachable to be looked at.
|
|
211
|
+
names = _entity_names(db_path, [e for e, _ in top_entities], profile_id)
|
|
212
|
+
labelled = [
|
|
213
|
+
(names.get(eid) or eid, count) for eid, count in top_entities
|
|
214
|
+
]
|
|
215
|
+
entity_str = ", ".join(f"{name} ({c})" for name, c in labelled)
|
|
197
216
|
lines.append("")
|
|
198
217
|
lines.append(f"Active entities: {entity_str}")
|
|
199
218
|
|
|
200
219
|
return "\n".join(lines)
|
|
201
220
|
|
|
202
221
|
|
|
222
|
+
def _entity_names(
|
|
223
|
+
db_path: str | Path, entity_ids: list[str], profile_id: str
|
|
224
|
+
) -> dict[str, str]:
|
|
225
|
+
"""Map entity_id → canonical_name. Missing or unreadable rows are omitted.
|
|
226
|
+
|
|
227
|
+
Fail-open: a summary is still useful with a raw id in it, so a query problem
|
|
228
|
+
must not lose the whole line. The caller falls back to the id per entity.
|
|
229
|
+
"""
|
|
230
|
+
if not entity_ids:
|
|
231
|
+
return {}
|
|
232
|
+
try:
|
|
233
|
+
conn = sqlite3.connect(f"file:{db_path}?mode=ro", uri=True)
|
|
234
|
+
try:
|
|
235
|
+
placeholders = ",".join("?" for _ in entity_ids)
|
|
236
|
+
rows = conn.execute(
|
|
237
|
+
f"SELECT entity_id, canonical_name FROM canonical_entities "
|
|
238
|
+
f"WHERE entity_id IN ({placeholders}) AND profile_id = ?",
|
|
239
|
+
(*entity_ids, profile_id),
|
|
240
|
+
).fetchall()
|
|
241
|
+
finally:
|
|
242
|
+
conn.close()
|
|
243
|
+
return {r[0]: r[1] for r in rows if r[1]}
|
|
244
|
+
except sqlite3.Error:
|
|
245
|
+
return {}
|
|
246
|
+
|
|
247
|
+
|
|
203
248
|
def _try_llm(
|
|
204
249
|
date_str: str,
|
|
205
250
|
facts: list[dict],
|
|
@@ -249,6 +294,7 @@ def _call_ollama(
|
|
|
249
294
|
payload = json.dumps({
|
|
250
295
|
"model": model,
|
|
251
296
|
"prompt": full_prompt,
|
|
297
|
+
"system": SUMMARY_SYSTEM_PROMPT,
|
|
252
298
|
"stream": False,
|
|
253
299
|
"options": {"num_predict": 300},
|
|
254
300
|
}).encode()
|
|
@@ -259,7 +305,7 @@ def _call_ollama(
|
|
|
259
305
|
)
|
|
260
306
|
resp = urllib.request.urlopen(req, timeout=timeout)
|
|
261
307
|
data = json.loads(resp.read().decode())
|
|
262
|
-
text = data.get("response", "")
|
|
308
|
+
text = clean_llm_summary(data.get("response", ""))
|
|
263
309
|
return text if text and len(text) > 20 else None
|
|
264
310
|
except Exception as exc:
|
|
265
311
|
logger.debug("Ollama daily reflection failed: %s", exc)
|
|
@@ -283,11 +329,12 @@ def _call_cloud_llm(
|
|
|
283
329
|
full_prompt = f"{prompt}\n\nFacts:\n{fact_texts}"
|
|
284
330
|
text = llm.generate(
|
|
285
331
|
prompt=full_prompt,
|
|
286
|
-
system=
|
|
332
|
+
system=SUMMARY_SYSTEM_PROMPT,
|
|
287
333
|
max_tokens=300,
|
|
288
334
|
temperature=0.1,
|
|
289
335
|
)
|
|
290
|
-
|
|
336
|
+
cleaned = clean_llm_summary(text or "")
|
|
337
|
+
return cleaned if len(cleaned) > 20 else None
|
|
291
338
|
except Exception as exc:
|
|
292
339
|
logger.debug("Cloud LLM daily reflection failed: %s", exc)
|
|
293
340
|
return None
|
|
@@ -39,8 +39,12 @@ from collections import Counter
|
|
|
39
39
|
from pathlib import Path
|
|
40
40
|
|
|
41
41
|
from .base import (
|
|
42
|
+
clean_llm_summary,
|
|
43
|
+
format_highlight,
|
|
44
|
+
SUMMARY_SYSTEM_PROMPT,
|
|
42
45
|
COVERAGE_FULL,
|
|
43
46
|
COVERAGE_INSUFFICIENT,
|
|
47
|
+
COVERAGE_PARTIAL,
|
|
44
48
|
COVERAGE_UNAVAILABLE,
|
|
45
49
|
GENERATED_BY_EXTRACTIVE,
|
|
46
50
|
GENERATED_BY_LLM_B,
|
|
@@ -121,8 +125,19 @@ def generate_project_work_log(
|
|
|
121
125
|
event_count = len(tool_rows)
|
|
122
126
|
fact_count = len(facts_rows)
|
|
123
127
|
|
|
124
|
-
|
|
125
|
-
|
|
128
|
+
# A project work log has two inputs: what was DONE (tool events) and what was
|
|
129
|
+
# LEARNED (facts). "full" means both were there.
|
|
130
|
+
#
|
|
131
|
+
# The previous rule was `events >= 1 OR facts >= 1`, which reported "full" for
|
|
132
|
+
# a project with 86 tool events and zero facts — rendered in the dashboard as
|
|
133
|
+
# "Built from 0 memories · coverage: full". Claiming full coverage of nothing
|
|
134
|
+
# is precisely the dishonest summary issue #113 exists to prevent.
|
|
135
|
+
if event_count >= _MIN_EVENTS and fact_count >= _MIN_FACTS:
|
|
136
|
+
coverage = COVERAGE_FULL
|
|
137
|
+
elif event_count >= _MIN_EVENTS or fact_count >= _MIN_FACTS:
|
|
138
|
+
coverage = COVERAGE_PARTIAL
|
|
139
|
+
else:
|
|
140
|
+
coverage = COVERAGE_INSUFFICIENT
|
|
126
141
|
|
|
127
142
|
extractive_content = _build_extractive_content(
|
|
128
143
|
project_path, tool_rows, facts_rows, event_count, fact_count
|
|
@@ -302,8 +317,7 @@ def _build_extractive_content(
|
|
|
302
317
|
lines.append("Key facts from project sessions:")
|
|
303
318
|
for f in facts_rows[:_TOP_FACTS]:
|
|
304
319
|
content = f.get("content", "")
|
|
305
|
-
|
|
306
|
-
content = content[:_MAX_FACT_CHARS - 3] + "..."
|
|
320
|
+
content = format_highlight(content)
|
|
307
321
|
lines.append(f" - {content}")
|
|
308
322
|
if fact_count > _TOP_FACTS:
|
|
309
323
|
lines.append(f" ... and {fact_count - _TOP_FACTS} more facts.")
|
|
@@ -373,6 +387,7 @@ def _call_ollama(
|
|
|
373
387
|
payload = json.dumps({
|
|
374
388
|
"model": model,
|
|
375
389
|
"prompt": full_prompt,
|
|
390
|
+
"system": SUMMARY_SYSTEM_PROMPT,
|
|
376
391
|
"stream": False,
|
|
377
392
|
"options": {"num_predict": 300},
|
|
378
393
|
}).encode()
|
|
@@ -383,7 +398,7 @@ def _call_ollama(
|
|
|
383
398
|
)
|
|
384
399
|
resp = urllib.request.urlopen(req, timeout=timeout)
|
|
385
400
|
data = json.loads(resp.read().decode())
|
|
386
|
-
text = data.get("response", "")
|
|
401
|
+
text = clean_llm_summary(data.get("response", ""))
|
|
387
402
|
return text if text and len(text) > 20 else None
|
|
388
403
|
except Exception as exc:
|
|
389
404
|
logger.debug("Ollama project work log failed: %s", exc)
|
|
@@ -414,11 +429,12 @@ def _call_cloud_llm(
|
|
|
414
429
|
)
|
|
415
430
|
text = llm.generate(
|
|
416
431
|
prompt=full_prompt,
|
|
417
|
-
system=
|
|
432
|
+
system=SUMMARY_SYSTEM_PROMPT,
|
|
418
433
|
max_tokens=300,
|
|
419
434
|
temperature=0.1,
|
|
420
435
|
)
|
|
421
|
-
|
|
436
|
+
cleaned = clean_llm_summary(text or "")
|
|
437
|
+
return cleaned if len(cleaned) > 20 else None
|
|
422
438
|
except Exception as exc:
|
|
423
439
|
logger.debug("Cloud LLM project work log failed: %s", exc)
|
|
424
440
|
return None
|
|
@@ -8,7 +8,7 @@ CRITICAL DATA HONESTY NOTE
|
|
|
8
8
|
---------------------------
|
|
9
9
|
On a real 3,294-fact store, only 127 facts (3.9%) carry a session_id.
|
|
10
10
|
A Session Summary that presents itself as "everything you did this session"
|
|
11
|
-
while silently covering 4% of the facts is the same
|
|
11
|
+
while silently covering 4% of the facts is overclaiming — the same defect
|
|
12
12
|
removed from brain/truth.py.
|
|
13
13
|
|
|
14
14
|
Coverage is ALWAYS disclosed. If the session has too few facts to summarise
|
|
@@ -37,6 +37,9 @@ from datetime import date, timezone
|
|
|
37
37
|
from pathlib import Path
|
|
38
38
|
|
|
39
39
|
from .base import (
|
|
40
|
+
clean_llm_summary,
|
|
41
|
+
format_highlight,
|
|
42
|
+
SUMMARY_SYSTEM_PROMPT,
|
|
40
43
|
COVERAGE_FULL,
|
|
41
44
|
COVERAGE_INSUFFICIENT,
|
|
42
45
|
COVERAGE_NO_SESSION,
|
|
@@ -209,8 +212,7 @@ def _build_extractive_content(
|
|
|
209
212
|
]
|
|
210
213
|
for f in facts[:_BODY_FACTS]:
|
|
211
214
|
content = f.get("content", "")
|
|
212
|
-
|
|
213
|
-
content = content[:_MAX_FACT_CHARS - 3] + "..."
|
|
215
|
+
content = format_highlight(content)
|
|
214
216
|
lines.append(f" - {content}")
|
|
215
217
|
if fact_count > _BODY_FACTS:
|
|
216
218
|
lines.append(f" ... and {fact_count - _BODY_FACTS} more facts.")
|
|
@@ -263,6 +265,7 @@ def _call_ollama(
|
|
|
263
265
|
payload = json.dumps({
|
|
264
266
|
"model": model,
|
|
265
267
|
"prompt": full_prompt,
|
|
268
|
+
"system": SUMMARY_SYSTEM_PROMPT,
|
|
266
269
|
"stream": False,
|
|
267
270
|
"options": {"num_predict": 200},
|
|
268
271
|
}).encode()
|
|
@@ -273,7 +276,7 @@ def _call_ollama(
|
|
|
273
276
|
)
|
|
274
277
|
resp = urllib.request.urlopen(req, timeout=timeout)
|
|
275
278
|
data = json.loads(resp.read().decode())
|
|
276
|
-
text = data.get("response", "")
|
|
279
|
+
text = clean_llm_summary(data.get("response", ""))
|
|
277
280
|
return text if text and len(text) > 20 else None
|
|
278
281
|
except Exception as exc:
|
|
279
282
|
logger.debug("Ollama session summary failed: %s", exc)
|
|
@@ -297,11 +300,12 @@ def _call_cloud_llm(
|
|
|
297
300
|
full_prompt = f"{prompt}\n\nFacts:\n{fact_texts}\n\nRespond in 2-4 sentences."
|
|
298
301
|
text = llm.generate(
|
|
299
302
|
prompt=full_prompt,
|
|
300
|
-
system=
|
|
303
|
+
system=SUMMARY_SYSTEM_PROMPT,
|
|
301
304
|
max_tokens=200,
|
|
302
305
|
temperature=0.1,
|
|
303
306
|
)
|
|
304
|
-
|
|
307
|
+
cleaned = clean_llm_summary(text or "")
|
|
308
|
+
return cleaned if len(cleaned) > 20 else None
|
|
305
309
|
except Exception as exc:
|
|
306
310
|
logger.debug("Cloud LLM session summary failed: %s", exc)
|
|
307
311
|
return None
|
|
@@ -1017,6 +1017,11 @@
|
|
|
1017
1017
|
<div id="skill-detail-panel" style="display:none;margin-top:24px"></div>
|
|
1018
1018
|
</div>
|
|
1019
1019
|
|
|
1020
|
+
<!-- Bounded Loops (v4.0.8) — moved out of Governance: it is a
|
|
1021
|
+
separate product SLM optionally observes, i.e. an integration.
|
|
1022
|
+
Rendered entirely by od-boundedloops.js (window.odRenderLoops). -->
|
|
1023
|
+
<div class="tab-pane fade" id="loops-pane"></div>
|
|
1024
|
+
|
|
1020
1025
|
<!-- Mesh Peers (v3.4.3 — Neural Glass) -->
|
|
1021
1026
|
<div class="tab-pane fade" id="mesh-pane">
|
|
1022
1027
|
<div class="ng-content-header">
|
|
@@ -1599,13 +1604,14 @@
|
|
|
1599
1604
|
<script src="static/js/od-ops-health.js?v=400"></script>
|
|
1600
1605
|
<script src="static/js/od-team.js?v=379"></script>
|
|
1601
1606
|
<script src="static/js/od-graph.js?v=6812bf6c"></script>
|
|
1602
|
-
<script src="static/js/od-memories.js?v=
|
|
1607
|
+
<script src="static/js/od-memories.js?v=022ff653"></script>
|
|
1603
1608
|
<script src="static/js/od-entities.js?v=379"></script>
|
|
1604
1609
|
<!-- Multi-Agent Memory pane (v3.8.0): visualises memory written by multiple agents -->
|
|
1605
1610
|
<script src="static/js/od-agents.js?v=c75ff3c2"></script>
|
|
1606
1611
|
<script src="static/js/od-skills.js?v=379"></script>
|
|
1607
|
-
<script src="static/js/od-
|
|
1608
|
-
<script src="static/js/od-
|
|
1612
|
+
<script src="static/js/od-boundedloops.js?v=b4a014a1"></script>
|
|
1613
|
+
<script src="static/js/od-mesh.js?v=a7bc694a"></script>
|
|
1614
|
+
<script src="static/js/od-optimize.js?v=1f511698"></script>
|
|
1609
1615
|
<script src="static/js/od-settings.js?v=386"></script>
|
|
1610
1616
|
<script src="static/js/od-backup.js?v=379"></script>
|
|
1611
1617
|
<!-- MCP & Integrations pane (v3.8.0): shows exposed MCP tool profile + counts -->
|
|
@@ -292,7 +292,7 @@ function showEmpty(containerId, icon, message) {
|
|
|
292
292
|
}
|
|
293
293
|
|
|
294
294
|
// ============================================================================
|
|
295
|
-
// Pane error state
|
|
295
|
+
// Pane error state
|
|
296
296
|
// showPaneError / clearPaneError / paneErrorMessage
|
|
297
297
|
// ============================================================================
|
|
298
298
|
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
// Copyright (c) 2026 Varun Pratap Bhardwaj / Qualixar
|
|
2
|
+
// Licensed under AGPL-3.0-or-later - see LICENSE file
|
|
3
|
+
// Part of SuperLocalMemory | https://qualixar.com
|
|
4
|
+
//
|
|
5
|
+
// Bounded Loops pane.
|
|
6
|
+
//
|
|
7
|
+
// Moved out of Governance in 4.0.8. Every other Governance tab governs SLM's
|
|
8
|
+
// OWN data — lifecycle, access, trust, compliance, ingestion. Bounded Loops
|
|
9
|
+
// governs none of it: it is a SEPARATE product that SLM optionally observes
|
|
10
|
+
// over the published contract bounded-loops.dev/slm-bridge/v1. Neither product
|
|
11
|
+
// depends on the other and installing either alone is complete, which makes
|
|
12
|
+
// this an integration, not a governance function.
|
|
13
|
+
//
|
|
14
|
+
// Reads GET /api/v3/bounded-loops/evidence for live bridge status and observed
|
|
15
|
+
// terminal runs. The guarantees are rendered FROM THE DATA rather than asserted
|
|
16
|
+
// in prose: every document carries eligible_for_learning=false, so the pane can
|
|
17
|
+
// show that SLM is not permitted to learn from these runs instead of merely
|
|
18
|
+
// promising that it does not.
|
|
19
|
+
|
|
20
|
+
(function () {
|
|
21
|
+
'use strict';
|
|
22
|
+
|
|
23
|
+
function esc(s) {
|
|
24
|
+
return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
|
|
25
|
+
return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c];
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function scaffold() {
|
|
30
|
+
return (
|
|
31
|
+
|
|
32
|
+
// ══════ BOUNDED LOOPS TAB ════════════════════════════════════════
|
|
33
|
+
// Static info card — no backend calls.
|
|
34
|
+
// A bounded loop advances only when an INDEPENDENT gate passes, not
|
|
35
|
+
// when the agent claims success. Laps are persisted as SLM memory
|
|
36
|
+
// (tag loop:<name>) so the full run history is queryable.
|
|
37
|
+
'<div>' +
|
|
38
|
+
|
|
39
|
+
// Live bridge status first: whether Bounded Loops is installed, the
|
|
40
|
+
// negotiated contract, and which runs were observed. The explanation
|
|
41
|
+
// below it is the same for everyone; this part is about YOUR install.
|
|
42
|
+
'<div class="card" style="margin-bottom:16px">' +
|
|
43
|
+
'<div class="card-head"><h3>Bridge status</h3>' +
|
|
44
|
+
'<span class="sub">observation only \u2014 never learns from these runs</span>' +
|
|
45
|
+
'</div>' +
|
|
46
|
+
'<div class="card-pad" id="od-bl-evidence">' +
|
|
47
|
+
'<div style="font-size:13px;color:var(--fg-2)">Checking bridge\u2026</div>' +
|
|
48
|
+
'</div>' +
|
|
49
|
+
'</div>' +
|
|
50
|
+
|
|
51
|
+
'<div class="page-head" style="margin-bottom:16px">' +
|
|
52
|
+
'<h2 style="font-size:20px;margin-bottom:6px">Bounded Loops</h2>' +
|
|
53
|
+
'<p style="font-size:13.5px">An iteration control pattern for agentic ' +
|
|
54
|
+
'frameworks — the loop advances only when an <strong>independent ' +
|
|
55
|
+
'gate</strong> passes, not when the agent claims success. Prevents ' +
|
|
56
|
+
'rationalisation: the model cannot self-certify completion.</p>' +
|
|
57
|
+
'</div>' +
|
|
58
|
+
|
|
59
|
+
// Two-column: concept + CLI reference
|
|
60
|
+
'<div class="grid" style="grid-template-columns:1fr 1fr;align-items:start;margin-bottom:16px">' +
|
|
61
|
+
|
|
62
|
+
'<div class="card">' +
|
|
63
|
+
'<div class="card-head"><h3>How it works</h3></div>' +
|
|
64
|
+
'<div class="card-pad">' +
|
|
65
|
+
'<p style="font-size:13px;line-height:1.6;margin-bottom:12px">' +
|
|
66
|
+
'Standard agentic loops let the model declare itself done — ' +
|
|
67
|
+
'a known failure mode when the model rationalises instead of verifying. ' +
|
|
68
|
+
'A bounded loop separates <em>execution</em> (the agent) from ' +
|
|
69
|
+
'<em>verification</em> (an independent gate such as a test suite, ' +
|
|
70
|
+
'linter, or judge LLM).' +
|
|
71
|
+
'</p>' +
|
|
72
|
+
'<p style="font-size:13px;line-height:1.6;margin-bottom:12px">' +
|
|
73
|
+
'The loop terminates only when the gate returns <code>DONE</code>, ' +
|
|
74
|
+
'or when a hard lap cap is reached (<code>HALT</code>). Each lap is ' +
|
|
75
|
+
'persisted as a queryable SLM memory tagged ' +
|
|
76
|
+
'<code>loop:<name></code>.' +
|
|
77
|
+
'</p>' +
|
|
78
|
+
'<div style="background:var(--card-2);border-radius:var(--r-md);' +
|
|
79
|
+
'padding:10px 14px;font-size:12.5px;line-height:1.7">' +
|
|
80
|
+
'<div><span class="badge ok" style="margin-right:8px">DONE</span>' +
|
|
81
|
+
'Gate passed — loop succeeded cleanly</div>' +
|
|
82
|
+
'<div style="margin-top:6px"><span class="badge warn" style="margin-right:8px">HALT</span>' +
|
|
83
|
+
'Lap cap reached — hard stop applied</div>' +
|
|
84
|
+
'<div style="margin-top:6px"><span class="badge cyan" style="margin-right:8px">PAUSE</span>' +
|
|
85
|
+
'Awaiting external input or approval</div>' +
|
|
86
|
+
'<div style="margin-top:6px"><span class="badge danger" style="margin-right:8px">KILLED</span>' +
|
|
87
|
+
'Manually stopped by the operator</div>' +
|
|
88
|
+
'<div style="margin-top:6px"><span class="badge neutral" style="margin-right:8px">ERROR</span>' +
|
|
89
|
+
'Unrecoverable failure during a lap</div>' +
|
|
90
|
+
'</div>' +
|
|
91
|
+
'</div>' +
|
|
92
|
+
'</div>' +
|
|
93
|
+
|
|
94
|
+
'<div class="card">' +
|
|
95
|
+
'<div class="card-head"><h3>Run it: CLI · command · MCP</h3></div>' +
|
|
96
|
+
'<div class="card-pad">' +
|
|
97
|
+
'<p style="font-size:13px;margin-bottom:14px">' +
|
|
98
|
+
'Bounded loops ship on three surfaces — the same engine and ' +
|
|
99
|
+
'the same queryable ledger behind each.' +
|
|
100
|
+
'</p>' +
|
|
101
|
+
|
|
102
|
+
'<div style="font-size:12px;font-weight:600;color:var(--fg-2);margin-bottom:6px">CLI</div>' +
|
|
103
|
+
'<div style="display:flex;flex-direction:column;gap:8px;margin-bottom:14px">' +
|
|
104
|
+
'<div class="list-row">' +
|
|
105
|
+
'<span class="mono" style="min-width:190px;font-size:13px">slm loop demo</span>' +
|
|
106
|
+
'<span style="font-size:12.5px;color:var(--fg-2)">Run a live demo bounded loop</span>' +
|
|
107
|
+
'</div>' +
|
|
108
|
+
'<div class="list-row">' +
|
|
109
|
+
'<span class="mono" style="min-width:190px;font-size:13px">slm loop history</span>' +
|
|
110
|
+
'<span style="font-size:12.5px;color:var(--fg-2)">List loop runs for this profile</span>' +
|
|
111
|
+
'</div>' +
|
|
112
|
+
'<div class="list-row">' +
|
|
113
|
+
'<span class="mono" style="min-width:190px;font-size:13px">slm loop show <run_id></span>' +
|
|
114
|
+
'<span style="font-size:12.5px;color:var(--fg-2)">Inspect a run lap-by-lap</span>' +
|
|
115
|
+
'</div>' +
|
|
116
|
+
'</div>' +
|
|
117
|
+
|
|
118
|
+
'<div style="font-size:12px;font-weight:600;color:var(--fg-2);margin-bottom:6px">Command (Claude Code / Codex)</div>' +
|
|
119
|
+
'<div style="display:flex;flex-direction:column;gap:8px;margin-bottom:14px">' +
|
|
120
|
+
'<div class="list-row">' +
|
|
121
|
+
'<span class="mono" style="min-width:190px;font-size:13px">/slm-loop</span>' +
|
|
122
|
+
'<span style="font-size:12.5px;color:var(--fg-2)">Slash command bound to the slm-loop skill + runner agent</span>' +
|
|
123
|
+
'</div>' +
|
|
124
|
+
'</div>' +
|
|
125
|
+
|
|
126
|
+
'<div style="font-size:12px;font-weight:600;color:var(--fg-2);margin-bottom:6px">MCP tools (code / full / power profiles)</div>' +
|
|
127
|
+
'<div style="display:flex;flex-direction:column;gap:8px">' +
|
|
128
|
+
'<div class="list-row">' +
|
|
129
|
+
'<span class="mono" style="min-width:190px;font-size:13px">slm_loop_run</span>' +
|
|
130
|
+
'<span style="font-size:12.5px;color:var(--fg-2)">Run a gated loop — the gate is an independent SLM recall</span>' +
|
|
131
|
+
'</div>' +
|
|
132
|
+
'<div class="list-row">' +
|
|
133
|
+
'<span class="mono" style="min-width:190px;font-size:13px">slm_loop_history</span>' +
|
|
134
|
+
'<span style="font-size:12.5px;color:var(--fg-2)">List runs (read-only)</span>' +
|
|
135
|
+
'</div>' +
|
|
136
|
+
'<div class="list-row">' +
|
|
137
|
+
'<span class="mono" style="min-width:190px;font-size:13px">slm_loop_show</span>' +
|
|
138
|
+
'<span style="font-size:12.5px;color:var(--fg-2)">Show a run lap-by-lap (read-only)</span>' +
|
|
139
|
+
'</div>' +
|
|
140
|
+
'</div>' +
|
|
141
|
+
'<div style="margin-top:18px;padding:10px 14px;background:var(--card-2);' +
|
|
142
|
+
'border-radius:var(--r-md);font-size:12.5px;line-height:1.6">' +
|
|
143
|
+
'<b>Memory tagging:</b> each lap is stored with tag ' +
|
|
144
|
+
'<code>loop:<name></code>. Recall the full history with ' +
|
|
145
|
+
'<br><code>slm recall --tag loop:my-loop-name</code>' +
|
|
146
|
+
'</div>' +
|
|
147
|
+
'</div>' +
|
|
148
|
+
'</div>' +
|
|
149
|
+
|
|
150
|
+
'</div>' + // end two-column grid
|
|
151
|
+
|
|
152
|
+
// Framework adapters note
|
|
153
|
+
'<div class="card">' +
|
|
154
|
+
'<div class="card-head"><h3>Framework adapters</h3></div>' +
|
|
155
|
+
'<div class="card-pad">' +
|
|
156
|
+
'<p style="font-size:13px;line-height:1.6;margin-bottom:14px">' +
|
|
157
|
+
'Bounded loops integrate with any agentic framework that supports ' +
|
|
158
|
+
'tool-call round-trips. The gate is an ordinary SLM memory check — ' +
|
|
159
|
+
'no special framework wiring required. Each framework stamps ' +
|
|
160
|
+
'<code>SLM_AGENT_ID</code> so lap history is attribution-aware.' +
|
|
161
|
+
'</p>' +
|
|
162
|
+
'<div style="display:flex;flex-wrap:wrap;gap:8px">' +
|
|
163
|
+
'<span style="padding:4px 12px;border-radius:99px;font-size:12px;' +
|
|
164
|
+
'background:var(--card-2);color:var(--fg-2)">CrewAI</span>' +
|
|
165
|
+
'<span style="padding:4px 12px;border-radius:99px;font-size:12px;' +
|
|
166
|
+
'background:var(--card-2);color:var(--fg-2)">LangChain</span>' +
|
|
167
|
+
'<span style="padding:4px 12px;border-radius:99px;font-size:12px;' +
|
|
168
|
+
'background:var(--card-2);color:var(--fg-2)">LangGraph</span>' +
|
|
169
|
+
'<span style="padding:4px 12px;border-radius:99px;font-size:12px;' +
|
|
170
|
+
'background:var(--card-2);color:var(--fg-2)">Semantic Kernel</span>' +
|
|
171
|
+
'<span style="padding:4px 12px;border-radius:99px;font-size:12px;' +
|
|
172
|
+
'background:var(--card-2);color:var(--fg-2)">LlamaIndex</span>' +
|
|
173
|
+
'<span style="padding:4px 12px;border-radius:99px;font-size:12px;' +
|
|
174
|
+
'background:var(--card-2);color:var(--fg-2)">Microsoft Agent Framework</span>' +
|
|
175
|
+
'<span style="padding:4px 12px;border-radius:99px;font-size:12px;' +
|
|
176
|
+
'background:var(--card-2);color:var(--fg-2)">AutoGen</span>' +
|
|
177
|
+
'<span style="padding:4px 12px;border-radius:99px;font-size:12px;' +
|
|
178
|
+
'background:var(--card-2);color:var(--fg-2)">Google ADK</span>' +
|
|
179
|
+
'<span style="padding:4px 12px;border-radius:99px;font-size:12px;' +
|
|
180
|
+
'background:var(--card-2);color:var(--fg-2)">OpenAI Agents</span>' +
|
|
181
|
+
'<span style="padding:4px 12px;border-radius:99px;font-size:12px;' +
|
|
182
|
+
'background:var(--card-2);color:var(--fg-2)">Any MCP-compatible agent</span>' +
|
|
183
|
+
'</div>' +
|
|
184
|
+
'<p style="margin-top:12px;font-size:12.5px;color:var(--fg-2)">' +
|
|
185
|
+
'Learn the full pattern: run <code>/slm-loop</code> (the slm-loop skill) ' +
|
|
186
|
+
'inside Claude Code for an interactive walkthrough with live examples.' +
|
|
187
|
+
'</p>' +
|
|
188
|
+
'</div>' +
|
|
189
|
+
'</div>' +
|
|
190
|
+
|
|
191
|
+
'</div>'
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/* Bounded Loops bridge status + observed runs.
|
|
196
|
+
*
|
|
197
|
+
* Until 4.0.8 this tab was a static essay about loop gating. It said nothing
|
|
198
|
+
* about the bridge, which is the part that concerns SLM: Bounded Loops is a
|
|
199
|
+
* SEPARATE product, and what SLM does with it is import read-only evidence
|
|
200
|
+
* over the published contract bounded-loops.dev/slm-bridge/v1.
|
|
201
|
+
*
|
|
202
|
+
* The guarantees are the point, and they must be stated from the DATA rather
|
|
203
|
+
* than asserted in prose — `eligible_for_learning` is a hard field in every
|
|
204
|
+
* document, always false in v1, so the pane can show that SLM is not allowed
|
|
205
|
+
* to learn from these runs rather than merely promising it doesn't.
|
|
206
|
+
*/
|
|
207
|
+
var _blLoaded = false;
|
|
208
|
+
function loadEvidence() {
|
|
209
|
+
var box = document.getElementById('od-bl-evidence');
|
|
210
|
+
if (!box || _blLoaded) return;
|
|
211
|
+
_blLoaded = true;
|
|
212
|
+
|
|
213
|
+
fetch('/internal/token', { credentials: 'same-origin' })
|
|
214
|
+
.then(function (r) { return r.ok ? r.json() : null; })
|
|
215
|
+
.catch(function () { return null; })
|
|
216
|
+
.then(function (t) {
|
|
217
|
+
var tok = t && (t.token || t.install_token);
|
|
218
|
+
return fetch('/api/v3/bounded-loops/evidence', {
|
|
219
|
+
credentials: 'same-origin',
|
|
220
|
+
headers: tok ? { 'X-Install-Token': tok } : {},
|
|
221
|
+
});
|
|
222
|
+
})
|
|
223
|
+
.then(function (r) { return r.ok ? r.json() : null; })
|
|
224
|
+
.then(function (d) {
|
|
225
|
+
if (!d) { box.innerHTML = _blNote('Bridge status unavailable.'); return; }
|
|
226
|
+
box.innerHTML = renderBoundedLoopsEvidence(d);
|
|
227
|
+
})
|
|
228
|
+
.catch(function () {
|
|
229
|
+
_blLoaded = false; // allow a retry on the next tab visit
|
|
230
|
+
box.innerHTML = _blNote('Could not read bridge status.');
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function _blNote(msg) {
|
|
235
|
+
return '<div style="font-size:13px;color:var(--fg-2)">' + esc(msg) + '</div>';
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function renderBoundedLoopsEvidence(d) {
|
|
239
|
+
var installed = d.installed;
|
|
240
|
+
var runs = d.runs || [];
|
|
241
|
+
var badge = installed === true
|
|
242
|
+
? '<span class="badge ok">installed</span>'
|
|
243
|
+
: (installed === false
|
|
244
|
+
? '<span class="badge neutral">not installed</span>'
|
|
245
|
+
: '<span class="badge warn">unknown</span>');
|
|
246
|
+
|
|
247
|
+
var head =
|
|
248
|
+
'<div style="display:flex;align-items:center;gap:10px;margin-bottom:10px">' +
|
|
249
|
+
badge +
|
|
250
|
+
'<span class="mono" style="font-size:12px;color:var(--fg-2)">' +
|
|
251
|
+
esc(d.contract || '') + '</span>' +
|
|
252
|
+
'</div>';
|
|
253
|
+
|
|
254
|
+
// Optional by design — "not installed" is a normal, complete state.
|
|
255
|
+
if (installed === false) {
|
|
256
|
+
return head + '<p style="font-size:13px;line-height:1.6;color:var(--fg-2)">' +
|
|
257
|
+
'Bounded Loops is a separate product and SuperLocalMemory does not ' +
|
|
258
|
+
'require it. Install it and SLM can import read-only evidence about ' +
|
|
259
|
+
'finished runs — it never sends anything back.</p>';
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
var guarantees =
|
|
263
|
+
'<div style="background:var(--card-2);border-radius:var(--r-md);' +
|
|
264
|
+
'padding:12px 14px;font-size:12.5px;line-height:1.75;margin-bottom:12px">' +
|
|
265
|
+
'<div><strong>Observation only.</strong> Evidence records what a run did. ' +
|
|
266
|
+
'It never authorises SLM to learn from it, re-rank memories, or change ' +
|
|
267
|
+
'routing — every document carries <code>eligible_for_learning: false</code>.</div>' +
|
|
268
|
+
'<div style="margin-top:6px"><strong>No paths leave the workspace.</strong> ' +
|
|
269
|
+
'Locations travel as digests, so a project directory name never reaches ' +
|
|
270
|
+
'the memory store. Gate text and artifact contents are excluded at source.</div>' +
|
|
271
|
+
'<div style="margin-top:6px"><strong>Tamper-evident, not verified.</strong> ' +
|
|
272
|
+
'Receipts form a local append-only hash chain (<code>local_hash_chain_only</code>). ' +
|
|
273
|
+
'That makes edits detectable — it is not authentication or independent audit.</div>' +
|
|
274
|
+
'</div>';
|
|
275
|
+
|
|
276
|
+
if (!runs.length) {
|
|
277
|
+
return head + guarantees +
|
|
278
|
+
'<p style="font-size:13px;color:var(--fg-2)">' +
|
|
279
|
+
'No runs observed yet. Import one with the <code>observe_bounded_loop_evidence</code> ' +
|
|
280
|
+
'tool — nothing is imported automatically.</p>';
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
var demoNote = d.demonstration_count
|
|
284
|
+
? '<div style="font-size:12px;color:var(--fg-3);margin-bottom:8px">' +
|
|
285
|
+
esc(String(d.demonstration_count)) + ' of ' + esc(String(d.total)) +
|
|
286
|
+
' observed run' + (d.total === 1 ? '' : 's') + ' ' +
|
|
287
|
+
(d.demonstration_count === 1 ? 'is a demonstration' : 'are demonstrations') +
|
|
288
|
+
' — wiring proofs, not real work.</div>'
|
|
289
|
+
: '';
|
|
290
|
+
|
|
291
|
+
var rows = runs.map(function (r) {
|
|
292
|
+
var cls = r.outcome === 'SUCCEEDED' ? 'ok'
|
|
293
|
+
: (r.outcome === 'CANCELLED' ? 'neutral' : 'danger');
|
|
294
|
+
// run_state, not just outcome: HALTED (budget/policy stop) and FAILED
|
|
295
|
+
// (gate rejected the work) are different events and both map to FAILED.
|
|
296
|
+
var state = r.run_state && r.run_state !== r.outcome
|
|
297
|
+
? ' <span style="color:var(--fg-3)">(' + esc(r.run_state) + ')</span>' : '';
|
|
298
|
+
// min-width:0 + ellipsis: a run ref is a single unbroken token, and in a
|
|
299
|
+
// narrow pane `flex:1` alone shreds it one character per line
|
|
300
|
+
// ("demo / — / proo / f"). Truncate with the full value in the tooltip.
|
|
301
|
+
return '<div class="list-row" style="align-items:baseline;gap:8px">' +
|
|
302
|
+
'<span class="mono" title="' + esc(r.run_ref || r.run_id) + '" ' +
|
|
303
|
+
'style="flex:1;min-width:0;font-size:12.5px;overflow:hidden;' +
|
|
304
|
+
'text-overflow:ellipsis;white-space:nowrap">' +
|
|
305
|
+
esc(r.run_ref || r.run_id) + '</span>' +
|
|
306
|
+
(r.demonstration
|
|
307
|
+
? '<span class="badge neutral" style="margin-right:8px">demo</span>' : '') +
|
|
308
|
+
'<span class="badge ' + cls + '" style="margin-right:8px">' + esc(r.outcome) + '</span>' +
|
|
309
|
+
'<span style="font-size:12px;color:var(--fg-2)">' + state + '</span>' +
|
|
310
|
+
'<span style="font-size:12px;color:var(--fg-3);margin-left:10px">seq ' +
|
|
311
|
+
esc(String(r.receipt_sequence)) + '</span>' +
|
|
312
|
+
'</div>';
|
|
313
|
+
}).join('');
|
|
314
|
+
|
|
315
|
+
return head + guarantees + demoNote + rows;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
window.odRenderLoops = function (container) {
|
|
319
|
+
if (!container) return;
|
|
320
|
+
container.innerHTML = scaffold();
|
|
321
|
+
_blLoaded = false;
|
|
322
|
+
loadEvidence();
|
|
323
|
+
};
|
|
324
|
+
})();
|