loki-mode 6.60.0 → 6.62.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/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/app-runner.sh +34 -8
- package/autonomy/completion-council.sh +70 -32
- package/autonomy/issue-parser.sh +4 -7
- package/autonomy/loki +238 -119
- package/autonomy/notification-checker.py +49 -23
- package/autonomy/run.sh +162 -79
- package/autonomy/sandbox.sh +91 -24
- package/bin/loki-mode.js +1 -2
- package/bin/postinstall.js +10 -4
- package/dashboard/__init__.py +1 -1
- package/dashboard/control.py +46 -36
- package/dashboard/database.py +21 -4
- package/dashboard/server.py +107 -78
- package/docs/BUG-AUDIT-v6.61.0.md +957 -0
- package/docs/INSTALLATION.md +2 -2
- package/events/bus.py +129 -28
- package/events/bus.ts +41 -27
- package/events/emit.sh +1 -1
- package/integrations/openclaw/README.md +139 -0
- package/integrations/openclaw/SKILL.md +88 -0
- package/integrations/openclaw/bridge/__init__.py +1 -0
- package/integrations/openclaw/bridge/__main__.py +88 -0
- package/integrations/openclaw/bridge/schema_map.py +180 -0
- package/integrations/openclaw/bridge/watcher.py +100 -0
- package/integrations/openclaw/scripts/format-progress.sh +80 -0
- package/integrations/openclaw/scripts/poll-status.sh +74 -0
- package/integrations/vibe-kanban.md +289 -0
- package/mcp/__init__.py +1 -1
- package/mcp/server.py +96 -73
- package/memory/consolidation.py +21 -6
- package/memory/engine.py +53 -26
- package/memory/layers/index_layer.py +16 -3
- package/memory/layers/timeline_layer.py +16 -3
- package/memory/retrieval.py +4 -1
- package/memory/schemas.py +4 -2
- package/memory/storage.py +25 -4
- package/memory/token_economics.py +9 -2
- package/memory/vector_index.py +2 -2
- package/package.json +3 -1
- package/providers/cline.sh +5 -4
- package/providers/codex.sh +27 -5
- package/providers/gemini.sh +59 -23
- package/providers/loader.sh +3 -2
- package/skills/parallel-workflows.md +9 -7
- package/state/__init__.py +10 -0
- package/state/index.ts +18 -0
- package/state/manager.py +1801 -0
- package/state/manager.ts +1774 -0
- package/state/sqlite_backend.py +188 -0
- package/state/test_manager.py +703 -0
- package/state/test_manager.ts +366 -0
- package/templates/README.md +19 -4
- package/templates/dashboard.md +45 -0
- package/templates/data-pipeline.md +45 -0
- package/templates/game.md +48 -0
- package/templates/microservice.md +49 -0
- package/templates/npm-library.md +42 -0
- package/templates/rest-api.md +170 -33
- package/templates/slack-bot.md +48 -0
- package/templates/web-scraper.md +45 -0
- package/web-app/server.py +360 -191
- package/templates/saas-app.md +0 -42
|
@@ -13,8 +13,10 @@ Usage:
|
|
|
13
13
|
import argparse
|
|
14
14
|
import json
|
|
15
15
|
import os
|
|
16
|
+
import random
|
|
16
17
|
import re
|
|
17
18
|
import time
|
|
19
|
+
from collections import deque
|
|
18
20
|
from datetime import datetime, timezone
|
|
19
21
|
from pathlib import Path
|
|
20
22
|
|
|
@@ -131,8 +133,10 @@ def save_notifications(notif_dir, notifications):
|
|
|
131
133
|
|
|
132
134
|
def make_notification(trigger_id, severity, message, iteration, data=None):
|
|
133
135
|
"""Create a notification entry."""
|
|
136
|
+
# Include iteration and random suffix to prevent ID collision within same second
|
|
137
|
+
rand_suffix = f"{random.randint(0, 0xFFFF):04x}"
|
|
134
138
|
return {
|
|
135
|
-
"id": f"notif-{int(time.time())}-{trigger_id}",
|
|
139
|
+
"id": f"notif-{int(time.time())}-i{iteration}-{rand_suffix}-{trigger_id}",
|
|
136
140
|
"trigger_id": trigger_id,
|
|
137
141
|
"severity": severity,
|
|
138
142
|
"message": message,
|
|
@@ -208,11 +212,11 @@ def check_file_access(trigger, loki_dir, iteration, notifications):
|
|
|
208
212
|
if not patterns:
|
|
209
213
|
return None
|
|
210
214
|
|
|
211
|
-
# Read last 200 lines of events
|
|
215
|
+
# Read last 200 lines of events using deque to avoid loading entire file
|
|
212
216
|
results = []
|
|
213
217
|
try:
|
|
214
|
-
|
|
215
|
-
|
|
218
|
+
with open(events_file, 'r') as f:
|
|
219
|
+
recent = deque(f, maxlen=200)
|
|
216
220
|
for line in recent:
|
|
217
221
|
try:
|
|
218
222
|
event = json.loads(line)
|
|
@@ -237,11 +241,12 @@ def check_file_access(trigger, loki_dir, iteration, notifications):
|
|
|
237
241
|
|
|
238
242
|
|
|
239
243
|
def check_quality_gate(trigger, loki_dir, iteration, notifications):
|
|
240
|
-
"""Check for quality gate failures."""
|
|
244
|
+
"""Check for quality gate failures. Reports ALL failed gates, not just first."""
|
|
241
245
|
state_file = Path(loki_dir) / "dashboard-state.json"
|
|
242
246
|
if not state_file.exists():
|
|
243
|
-
return
|
|
247
|
+
return []
|
|
244
248
|
|
|
249
|
+
results = []
|
|
245
250
|
try:
|
|
246
251
|
state = json.loads(state_file.read_text())
|
|
247
252
|
gates = state.get("qualityGates", {})
|
|
@@ -249,37 +254,54 @@ def check_quality_gate(trigger, loki_dir, iteration, notifications):
|
|
|
249
254
|
for gate_name, gate_data in gates.items():
|
|
250
255
|
if isinstance(gate_data, dict) and gate_data.get("status") == "failed":
|
|
251
256
|
msg = trigger["message"].format(gate=gate_name)
|
|
252
|
-
|
|
257
|
+
results.append(make_notification(
|
|
253
258
|
trigger["id"], trigger["severity"], msg, iteration,
|
|
254
259
|
{"gate": gate_name},
|
|
255
|
-
)
|
|
260
|
+
))
|
|
256
261
|
except (json.JSONDecodeError, OSError, KeyError):
|
|
257
262
|
pass
|
|
258
|
-
return None
|
|
263
|
+
return results if results else None
|
|
259
264
|
|
|
260
265
|
|
|
261
266
|
def check_stagnation(trigger, loki_dir, iteration, notifications):
|
|
262
|
-
"""Check for stuck iterations (no git diff changes).
|
|
267
|
+
"""Check for stuck iterations (no git diff changes).
|
|
268
|
+
|
|
269
|
+
convergence.log format (pipe-delimited):
|
|
270
|
+
timestamp|iteration|files_changed|CONSECUTIVE_NO_CHANGE|DONE_SIGNALS
|
|
271
|
+
|
|
272
|
+
We parse field 4 (CONSECUTIVE_NO_CHANGE, 0-indexed field 3) from the
|
|
273
|
+
most recent line to determine if stagnation exceeds the threshold.
|
|
274
|
+
"""
|
|
263
275
|
council_file = Path(loki_dir) / "council" / "convergence.log"
|
|
264
276
|
if not council_file.exists():
|
|
265
277
|
return None
|
|
266
278
|
|
|
267
279
|
try:
|
|
268
|
-
|
|
280
|
+
# Read only the tail of the file to avoid loading everything
|
|
281
|
+
with open(council_file, 'r') as f:
|
|
282
|
+
lines = deque(f, maxlen=20)
|
|
283
|
+
|
|
269
284
|
max_no_progress = trigger.get("max_no_progress", 3)
|
|
270
|
-
|
|
271
|
-
|
|
285
|
+
|
|
286
|
+
# Parse the most recent line's CONSECUTIVE_NO_CHANGE field
|
|
272
287
|
for line in reversed(lines):
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
288
|
+
line = line.strip()
|
|
289
|
+
if not line:
|
|
290
|
+
continue
|
|
291
|
+
fields = line.split('|')
|
|
292
|
+
if len(fields) >= 4:
|
|
293
|
+
try:
|
|
294
|
+
consecutive = int(fields[3])
|
|
295
|
+
except (ValueError, IndexError):
|
|
296
|
+
consecutive = 0
|
|
297
|
+
if consecutive >= max_no_progress:
|
|
298
|
+
msg = trigger["message"].format(count=consecutive)
|
|
299
|
+
return make_notification(
|
|
300
|
+
trigger["id"], trigger["severity"], msg, iteration,
|
|
301
|
+
{"consecutive_no_progress": consecutive},
|
|
302
|
+
)
|
|
303
|
+
# Only check the most recent entry
|
|
276
304
|
break
|
|
277
|
-
if consecutive >= max_no_progress:
|
|
278
|
-
msg = trigger["message"].format(count=consecutive)
|
|
279
|
-
return make_notification(
|
|
280
|
-
trigger["id"], trigger["severity"], msg, iteration,
|
|
281
|
-
{"consecutive_no_progress": consecutive},
|
|
282
|
-
)
|
|
283
305
|
except OSError:
|
|
284
306
|
pass
|
|
285
307
|
return None
|
|
@@ -351,7 +373,11 @@ def check_triggers(loki_dir, iteration):
|
|
|
351
373
|
|
|
352
374
|
result = checker(trigger, loki_dir, iteration, notifications)
|
|
353
375
|
if result:
|
|
354
|
-
|
|
376
|
+
# Checkers may return a single notification or a list of notifications
|
|
377
|
+
if isinstance(result, list):
|
|
378
|
+
new_notifications.extend(result)
|
|
379
|
+
else:
|
|
380
|
+
new_notifications.append(result)
|
|
355
381
|
|
|
356
382
|
if new_notifications:
|
|
357
383
|
notifications.extend(new_notifications)
|