loki-mode 7.5.16 → 7.5.27
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/README.md +10 -9
- package/SKILL.md +14 -14
- package/VERSION +1 -1
- package/autonomy/completion-council.sh +38 -9
- package/autonomy/lib/claude-flags.sh +132 -0
- package/autonomy/lib/mcp-config.sh +160 -0
- package/autonomy/lib/project-graph.sh +675 -0
- package/autonomy/lib/voter-agents.sh +356 -0
- package/autonomy/loki +72 -100
- package/autonomy/run.sh +95 -186
- package/bin/loki +10 -0
- package/dashboard/__init__.py +1 -1
- package/dashboard/requirements.txt +13 -8
- package/dashboard/server.py +53 -22
- package/dashboard/static/index.html +298 -299
- package/docs/INSTALLATION.md +54 -21
- package/docs/retrospectives/v7.5.15-fleet-postmortem.md +325 -0
- package/docs/retrospectives/v7.5.15-honesty-audit.md +136 -0
- package/docs/retrospectives/v7.5.15-llm-failure-modes.md +49 -0
- package/loki-ts/data/finding-schema.json +74 -0
- package/loki-ts/data/model-pricing.json +12 -0
- package/loki-ts/dist/loki.js +109 -108
- package/mcp/__init__.py +1 -1
- package/mcp/lsp_proxy.py +713 -0
- package/mcp/requirements.txt +9 -3
- package/mcp/tests/__init__.py +0 -0
- package/mcp/tests/test_lsp_proxy.py +377 -0
- package/memory/app_graph.py +153 -0
- package/memory/storage.py +6 -1
- package/memory/tests/test_app_graph.py +134 -0
- package/package.json +4 -3
- package/providers/claude.sh +115 -4
- package/providers/codex.sh +2 -2
- package/providers/loader.sh +4 -4
- package/providers/model_catalog.json +0 -9
- package/providers/models.sh +1 -2
- package/references/multi-provider.md +26 -35
- package/references/prompt-repetition.md +1 -1
- package/references/quality-control.md +1 -1
- package/skills/00-index.md +3 -3
- package/skills/model-selection.md +11 -14
- package/skills/providers.md +17 -57
- package/skills/quality-gates.md +2 -2
- package/skills/troubleshooting.md +1 -1
- package/src/integrations/github/action-handler.js +3 -2
- package/src/protocols/tools/start-project.js +1 -1
- package/providers/gemini.sh +0 -343
package/dashboard/server.py
CHANGED
|
@@ -810,7 +810,7 @@ async def agent_card() -> dict:
|
|
|
810
810
|
"agents": 41,
|
|
811
811
|
"swarms": 8,
|
|
812
812
|
"quality_gates": 9,
|
|
813
|
-
"providers": ["claude", "codex", "
|
|
813
|
+
"providers": ["claude", "codex", "cline", "aider"],
|
|
814
814
|
"streaming": True,
|
|
815
815
|
"pushNotifications": False,
|
|
816
816
|
"stateTransitionHistory": True,
|
|
@@ -3168,8 +3168,17 @@ def _parse_time_range(time_range: str) -> Optional[datetime]:
|
|
|
3168
3168
|
return datetime.now(timezone.utc) - delta
|
|
3169
3169
|
|
|
3170
3170
|
|
|
3171
|
-
def _read_events(time_range: str = "7d", max_events: int = 10000) -> list:
|
|
3172
|
-
"""Read events from .loki/events.jsonl with time filter and size limits.
|
|
3171
|
+
def _read_events(time_range: str = "7d", max_events: int = 10000, type_prefix: Optional[str] = None) -> list:
|
|
3172
|
+
"""Read events from .loki/events.jsonl with time filter and size limits.
|
|
3173
|
+
|
|
3174
|
+
Args:
|
|
3175
|
+
time_range: e.g. "7d", "24h", "30m". Events older than the cutoff are dropped.
|
|
3176
|
+
max_events: hard cap on the number of returned events.
|
|
3177
|
+
type_prefix: when set (non-empty), only return events whose ``type`` field
|
|
3178
|
+
starts with this prefix. Backward compatible: when None or empty,
|
|
3179
|
+
no type filtering is applied. Used by v7.5.22 Phase D for filtering
|
|
3180
|
+
``claude_hook_*`` events without adding a new endpoint.
|
|
3181
|
+
"""
|
|
3173
3182
|
events_file = _get_loki_dir() / "events.jsonl"
|
|
3174
3183
|
if not events_file.exists():
|
|
3175
3184
|
return []
|
|
@@ -3208,6 +3217,10 @@ def _read_events(time_range: str = "7d", max_events: int = 10000) -> list:
|
|
|
3208
3217
|
continue
|
|
3209
3218
|
except (ValueError, TypeError):
|
|
3210
3219
|
pass # Keep events with unparseable timestamps
|
|
3220
|
+
# Optional type-prefix filter (v7.5.22 Phase D).
|
|
3221
|
+
if type_prefix:
|
|
3222
|
+
if not str(event.get("type", "")).startswith(type_prefix):
|
|
3223
|
+
continue
|
|
3211
3224
|
events.append(event)
|
|
3212
3225
|
except json.JSONDecodeError:
|
|
3213
3226
|
pass
|
|
@@ -3401,9 +3414,6 @@ _DEFAULT_PRICING = {
|
|
|
3401
3414
|
"haiku": {"input": 1.00, "output": 5.00},
|
|
3402
3415
|
# OpenAI Codex
|
|
3403
3416
|
"gpt-5.3-codex": {"input": 1.50, "output": 12.00},
|
|
3404
|
-
# Google Gemini
|
|
3405
|
-
"gemini-3-pro": {"input": 1.25, "output": 10.00},
|
|
3406
|
-
"gemini-3-flash": {"input": 0.10, "output": 0.40},
|
|
3407
3417
|
}
|
|
3408
3418
|
|
|
3409
3419
|
# Active pricing - starts with defaults, updated from .loki/pricing.json
|
|
@@ -3616,8 +3626,6 @@ _PROVIDER_LABELS = {
|
|
|
3616
3626
|
"sonnet": "Sonnet 4.5",
|
|
3617
3627
|
"haiku": "Haiku 4.5",
|
|
3618
3628
|
"gpt-5.3-codex": "GPT-5.3 Codex",
|
|
3619
|
-
"gemini-3-pro": "Gemini 3 Pro",
|
|
3620
|
-
"gemini-3-flash": "Gemini 3 Flash",
|
|
3621
3629
|
}
|
|
3622
3630
|
|
|
3623
3631
|
_MODEL_PROVIDERS = {
|
|
@@ -3625,8 +3633,6 @@ _MODEL_PROVIDERS = {
|
|
|
3625
3633
|
"sonnet": "claude",
|
|
3626
3634
|
"haiku": "claude",
|
|
3627
3635
|
"gpt-5.3-codex": "codex",
|
|
3628
|
-
"gemini-3-pro": "gemini",
|
|
3629
|
-
"gemini-3-flash": "gemini",
|
|
3630
3636
|
"cline-default": "cline",
|
|
3631
3637
|
"aider-default": "aider",
|
|
3632
3638
|
}
|
|
@@ -3786,19 +3792,21 @@ async def force_council_review():
|
|
|
3786
3792
|
async def get_council_transcripts(
|
|
3787
3793
|
limit: int = Query(default=20, ge=1, le=200),
|
|
3788
3794
|
since: Optional[str] = Query(default=None),
|
|
3789
|
-
iter_min: Optional[int] = Query(default=None),
|
|
3795
|
+
iter_min: Optional[int] = Query(default=None, ge=0),
|
|
3796
|
+
type_prefix: Optional[str] = Query(default=None),
|
|
3790
3797
|
):
|
|
3791
3798
|
"""List council transcript records, sorted descending by iteration number.
|
|
3792
3799
|
|
|
3793
3800
|
Query params:
|
|
3794
|
-
limit
|
|
3795
|
-
since
|
|
3796
|
-
iter_min
|
|
3801
|
+
limit int, default=20, max=200
|
|
3802
|
+
since ISO8601 string (optional), filter to transcripts after this time
|
|
3803
|
+
iter_min int (optional), filter to iteration >= N
|
|
3804
|
+
type_prefix str (optional), v7.5.22 Phase D. When set, the response also
|
|
3805
|
+
includes a ``hook_events`` array of matching .loki/events.jsonl
|
|
3806
|
+
entries whose ``type`` starts with this prefix (e.g.
|
|
3807
|
+
``claude_hook_``). Unset -> behavior unchanged.
|
|
3797
3808
|
"""
|
|
3798
|
-
|
|
3799
|
-
if not transcripts_dir.exists():
|
|
3800
|
-
return {"transcripts": [], "total": 0, "latest_id": None}
|
|
3801
|
-
|
|
3809
|
+
# Validate query params before any early-return so invalid inputs always get 400.
|
|
3802
3810
|
since_dt = None
|
|
3803
3811
|
if since:
|
|
3804
3812
|
try:
|
|
@@ -3806,6 +3814,13 @@ async def get_council_transcripts(
|
|
|
3806
3814
|
except ValueError:
|
|
3807
3815
|
raise HTTPException(status_code=400, detail="Invalid 'since' timestamp format; expected ISO8601")
|
|
3808
3816
|
|
|
3817
|
+
transcripts_dir = _get_loki_dir() / "council" / "transcripts"
|
|
3818
|
+
if not transcripts_dir.exists():
|
|
3819
|
+
response: dict = {"transcripts": [], "total": 0, "latest_id": None}
|
|
3820
|
+
if type_prefix:
|
|
3821
|
+
response["hook_events"] = _read_events(type_prefix=type_prefix)
|
|
3822
|
+
return response
|
|
3823
|
+
|
|
3809
3824
|
records = []
|
|
3810
3825
|
for f in sorted(transcripts_dir.glob("iter-*.json"), reverse=True):
|
|
3811
3826
|
try:
|
|
@@ -3816,6 +3831,9 @@ async def get_council_transcripts(
|
|
|
3816
3831
|
if not isinstance(rec, dict):
|
|
3817
3832
|
logger.warning("Skipping non-object council transcript file: %s", f.name)
|
|
3818
3833
|
continue
|
|
3834
|
+
if not isinstance(rec.get("iteration_id"), str):
|
|
3835
|
+
logger.warning("Skipping transcript missing iteration_id field: %s", f.name)
|
|
3836
|
+
continue
|
|
3819
3837
|
if since_dt is not None:
|
|
3820
3838
|
ts_str = rec.get("timestamp", "")
|
|
3821
3839
|
try:
|
|
@@ -3830,11 +3848,15 @@ async def get_council_transcripts(
|
|
|
3830
3848
|
if len(records) >= limit:
|
|
3831
3849
|
break
|
|
3832
3850
|
|
|
3833
|
-
|
|
3851
|
+
response = {
|
|
3834
3852
|
"transcripts": records,
|
|
3835
3853
|
"total": len(records),
|
|
3836
|
-
"latest_id": records[0]
|
|
3854
|
+
"latest_id": records[0].get("iteration_id") if records else None,
|
|
3837
3855
|
}
|
|
3856
|
+
# v7.5.22 Phase D: opt-in hook-event passthrough via _read_events filter.
|
|
3857
|
+
if type_prefix:
|
|
3858
|
+
response["hook_events"] = _read_events(type_prefix=type_prefix)
|
|
3859
|
+
return response
|
|
3838
3860
|
|
|
3839
3861
|
|
|
3840
3862
|
@app.get("/api/council/transcripts/{iteration_id}")
|
|
@@ -3851,9 +3873,18 @@ async def get_council_transcript(iteration_id: str):
|
|
|
3851
3873
|
if not transcript_file.exists():
|
|
3852
3874
|
raise HTTPException(status_code=404, detail="Transcript not found")
|
|
3853
3875
|
try:
|
|
3854
|
-
|
|
3876
|
+
rec = json.loads(transcript_file.read_text())
|
|
3855
3877
|
except Exception:
|
|
3856
|
-
raise HTTPException(
|
|
3878
|
+
raise HTTPException(
|
|
3879
|
+
status_code=410,
|
|
3880
|
+
detail=f"Transcript file for {iteration_id} is corrupt; admin should inspect or remove it",
|
|
3881
|
+
)
|
|
3882
|
+
if not isinstance(rec, dict):
|
|
3883
|
+
raise HTTPException(
|
|
3884
|
+
status_code=410,
|
|
3885
|
+
detail=f"Transcript file for {iteration_id} is corrupt; admin should inspect or remove it",
|
|
3886
|
+
)
|
|
3887
|
+
return rec
|
|
3857
3888
|
|
|
3858
3889
|
|
|
3859
3890
|
# =============================================================================
|