loki-mode 6.36.1 → 6.36.3
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 +2 -2
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/loki +56 -8
- package/autonomy/run.sh +21 -8
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +107 -2
- package/dashboard/static/index.html +1 -1
- package/docs/INSTALLATION.md +1 -1
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -10,11 +10,11 @@
|
|
|
10
10
|
[](https://www.autonomi.dev/)
|
|
11
11
|
[](https://hub.docker.com/r/asklokesh/loki-mode)
|
|
12
12
|
|
|
13
|
-
**Current Version: v6.
|
|
13
|
+
**Current Version: v6.36.1**
|
|
14
14
|
|
|
15
15
|
### Traction
|
|
16
16
|
|
|
17
|
-
**
|
|
17
|
+
**741 stars** | **149 forks** | **11,400+ Docker pulls** | **477 npm downloads (last 7d)** | **606 commits** | **263 releases published** | **20+ releases in 72 hours (March 17-19, 2026)**
|
|
18
18
|
|
|
19
19
|
---
|
|
20
20
|
|
package/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: loki-mode
|
|
|
3
3
|
description: Multi-agent autonomous startup system. Triggers on "Loki Mode". Takes PRD to deployed product with minimal human intervention. Requires --dangerously-skip-permissions flag.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Loki Mode v6.36.
|
|
6
|
+
# Loki Mode v6.36.3
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -267,4 +267,4 @@ The following features are documented in skill modules but not yet fully automat
|
|
|
267
267
|
| Quality gates 3-reviewer system | Implemented (v5.35.0) | 5 specialist reviewers in `skills/quality-gates.md`; execution in run.sh |
|
|
268
268
|
| Benchmarks (HumanEval, SWE-bench) | Infrastructure only | Runner scripts and datasets exist in `benchmarks/`; no published results |
|
|
269
269
|
|
|
270
|
-
**v6.36.
|
|
270
|
+
**v6.36.3 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
6.36.
|
|
1
|
+
6.36.3
|
package/autonomy/loki
CHANGED
|
@@ -219,7 +219,7 @@ require_jq() {
|
|
|
219
219
|
echo " brew install jq (macOS)"
|
|
220
220
|
echo " apt install jq (Debian/Ubuntu)"
|
|
221
221
|
echo " yum install jq (RHEL/CentOS)"
|
|
222
|
-
|
|
222
|
+
return 1
|
|
223
223
|
fi
|
|
224
224
|
}
|
|
225
225
|
|
|
@@ -551,6 +551,10 @@ cmd_start() {
|
|
|
551
551
|
;;
|
|
552
552
|
--provider)
|
|
553
553
|
if [[ -n "${2:-}" ]]; then
|
|
554
|
+
if [[ "$2" == --* ]]; then
|
|
555
|
+
echo -e "${RED}Missing value for --provider flag${NC}"
|
|
556
|
+
exit 1
|
|
557
|
+
fi
|
|
554
558
|
provider="$2"
|
|
555
559
|
args+=("--provider" "$provider")
|
|
556
560
|
shift 2
|
|
@@ -1450,7 +1454,7 @@ cmd_status() {
|
|
|
1450
1454
|
esac
|
|
1451
1455
|
done
|
|
1452
1456
|
|
|
1453
|
-
require_jq
|
|
1457
|
+
require_jq || return 1
|
|
1454
1458
|
|
|
1455
1459
|
if [ ! -d "$LOKI_DIR" ]; then
|
|
1456
1460
|
echo -e "${BOLD}Loki Mode Status${NC}"
|
|
@@ -3451,7 +3455,7 @@ cmd_issue_parse() {
|
|
|
3451
3455
|
|
|
3452
3456
|
# View parsed GitHub issue details
|
|
3453
3457
|
cmd_issue_view() {
|
|
3454
|
-
require_jq
|
|
3458
|
+
require_jq || return 1
|
|
3455
3459
|
|
|
3456
3460
|
local issue_ref="${1:-}"
|
|
3457
3461
|
|
|
@@ -3566,7 +3570,7 @@ cmd_issue_view() {
|
|
|
3566
3570
|
#===============================================================================
|
|
3567
3571
|
|
|
3568
3572
|
cmd_run() {
|
|
3569
|
-
require_jq
|
|
3573
|
+
require_jq || return 1
|
|
3570
3574
|
|
|
3571
3575
|
local issue_ref=""
|
|
3572
3576
|
local dry_run=false
|
|
@@ -3731,6 +3735,22 @@ cmd_run() {
|
|
|
3731
3735
|
esac
|
|
3732
3736
|
done
|
|
3733
3737
|
|
|
3738
|
+
# Validate git prerequisites for --ship and --pr flags
|
|
3739
|
+
if $create_pr || $auto_merge; then
|
|
3740
|
+
if ! command -v git &>/dev/null; then
|
|
3741
|
+
echo -e "${RED}Error: --pr/--ship requires git but it is not installed.${NC}"
|
|
3742
|
+
return 1
|
|
3743
|
+
fi
|
|
3744
|
+
if ! git rev-parse --git-dir &>/dev/null 2>&1; then
|
|
3745
|
+
echo -e "${RED}Error: --pr/--ship requires a git repository but the current directory is not one.${NC}"
|
|
3746
|
+
return 1
|
|
3747
|
+
fi
|
|
3748
|
+
if [[ -z "$(git remote 2>/dev/null)" ]]; then
|
|
3749
|
+
echo -e "${RED}Error: --pr/--ship requires a git remote but none is configured.${NC}"
|
|
3750
|
+
return 1
|
|
3751
|
+
fi
|
|
3752
|
+
fi
|
|
3753
|
+
|
|
3734
3754
|
# Add --parallel once if worktree mode is enabled (not per-flag)
|
|
3735
3755
|
if $use_worktree; then
|
|
3736
3756
|
start_args+=("--parallel")
|
|
@@ -3994,7 +4014,7 @@ cmd_issue() {
|
|
|
3994
4014
|
echo -e "${YELLOW} 'loki run' supports GitHub, GitLab, Jira, and Azure DevOps issues.${NC}" >&2
|
|
3995
4015
|
echo "" >&2
|
|
3996
4016
|
|
|
3997
|
-
require_jq
|
|
4017
|
+
require_jq || return 1
|
|
3998
4018
|
|
|
3999
4019
|
local issue_ref=""
|
|
4000
4020
|
local repo=""
|
|
@@ -6595,6 +6615,19 @@ QPRDEOF
|
|
|
6595
6615
|
|
|
6596
6616
|
# Project scaffolding (v6.28.0)
|
|
6597
6617
|
cmd_init() {
|
|
6618
|
+
# Guard: check if .loki/ already exists to avoid overwriting active session
|
|
6619
|
+
if [[ -d ".loki" ]]; then
|
|
6620
|
+
if [[ -f ".loki/loki.pid" ]]; then
|
|
6621
|
+
local pid
|
|
6622
|
+
pid=$(cat ".loki/loki.pid" 2>/dev/null)
|
|
6623
|
+
if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then
|
|
6624
|
+
echo -e "${RED}Cannot initialize: active session running. Stop it first with 'loki stop'.${NC}"
|
|
6625
|
+
return 1
|
|
6626
|
+
fi
|
|
6627
|
+
fi
|
|
6628
|
+
echo -e "${YELLOW}Reinitializing existing .loki/ directory${NC}"
|
|
6629
|
+
fi
|
|
6630
|
+
|
|
6598
6631
|
local version=$(get_version)
|
|
6599
6632
|
local project_name=""
|
|
6600
6633
|
local template_name=""
|
|
@@ -10269,10 +10302,25 @@ cmd_worktree() {
|
|
|
10269
10302
|
return 1
|
|
10270
10303
|
fi
|
|
10271
10304
|
|
|
10305
|
+
# Validate JSON is parseable and contains required fields
|
|
10306
|
+
if ! python3 -c "
|
|
10307
|
+
import json, sys
|
|
10308
|
+
try:
|
|
10309
|
+
data = json.load(open('$signal_file'))
|
|
10310
|
+
assert data.get('branch'), 'missing branch'
|
|
10311
|
+
assert data.get('worktree'), 'missing worktree'
|
|
10312
|
+
except Exception as e:
|
|
10313
|
+
print(str(e), file=sys.stderr)
|
|
10314
|
+
sys.exit(1)
|
|
10315
|
+
" 2>/dev/null; then
|
|
10316
|
+
echo -e "${RED}Invalid or corrupted merge signal file${NC}"
|
|
10317
|
+
exit 1
|
|
10318
|
+
fi
|
|
10319
|
+
|
|
10272
10320
|
local branch
|
|
10273
|
-
branch=$(python3 -c "import json; print(json.load(open('$signal_file'))['branch'])"
|
|
10321
|
+
branch=$(python3 -c "import json; print(json.load(open('$signal_file'))['branch'])")
|
|
10274
10322
|
local worktree_path
|
|
10275
|
-
worktree_path=$(python3 -c "import json; print(json.load(open('$signal_file'))['worktree'])"
|
|
10323
|
+
worktree_path=$(python3 -c "import json; print(json.load(open('$signal_file'))['worktree'])")
|
|
10276
10324
|
|
|
10277
10325
|
if git merge --no-ff "$branch" -m "merge($name): auto-merge from parallel worktree"; then
|
|
10278
10326
|
echo -e "${GREEN}Merge successful${NC}"
|
|
@@ -10785,7 +10833,7 @@ print()
|
|
|
10785
10833
|
|
|
10786
10834
|
# Reset session state
|
|
10787
10835
|
cmd_reset() {
|
|
10788
|
-
require_jq
|
|
10836
|
+
require_jq || return 1
|
|
10789
10837
|
|
|
10790
10838
|
local subcommand="${1:-all}"
|
|
10791
10839
|
|
package/autonomy/run.sh
CHANGED
|
@@ -1212,13 +1212,17 @@ detect_complexity() {
|
|
|
1212
1212
|
fi
|
|
1213
1213
|
|
|
1214
1214
|
# Count files in project (excluding common non-source dirs)
|
|
1215
|
-
local file_count
|
|
1215
|
+
local file_count=0
|
|
1216
|
+
file_count=$(find "$target_dir" -type f \
|
|
1216
1217
|
\( -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.jsx" \
|
|
1217
1218
|
-o -name "*.py" -o -name "*.go" -o -name "*.rs" -o -name "*.java" \
|
|
1218
1219
|
-o -name "*.rb" -o -name "*.php" -o -name "*.swift" -o -name "*.kt" \) \
|
|
1219
1220
|
! -path "*/node_modules/*" ! -path "*/.git/*" ! -path "*/vendor/*" \
|
|
1220
1221
|
! -path "*/dist/*" ! -path "*/build/*" ! -path "*/__pycache__/*" \
|
|
1221
1222
|
2>/dev/null | wc -l | tr -d ' ')
|
|
1223
|
+
# Validate file_count is numeric (guard against empty/malformed pipeline output)
|
|
1224
|
+
file_count="${file_count:-0}"
|
|
1225
|
+
file_count="${file_count//[^0-9]/}"
|
|
1222
1226
|
|
|
1223
1227
|
# Check for external integrations
|
|
1224
1228
|
local has_external=false
|
|
@@ -7901,8 +7905,10 @@ build_prompt() {
|
|
|
7901
7905
|
# Load existing context if resuming
|
|
7902
7906
|
local context_injection=""
|
|
7903
7907
|
if [ $retry -gt 0 ]; then
|
|
7904
|
-
local ledger
|
|
7905
|
-
|
|
7908
|
+
local ledger=""
|
|
7909
|
+
type -t load_ledger_context &>/dev/null && ledger=$(load_ledger_context)
|
|
7910
|
+
local handoff=""
|
|
7911
|
+
type -t load_handoff_context &>/dev/null && handoff=$(load_handoff_context)
|
|
7906
7912
|
|
|
7907
7913
|
if [ -n "$ledger" ]; then
|
|
7908
7914
|
context_injection="PREVIOUS_LEDGER_STATE: $ledger"
|
|
@@ -8432,11 +8438,15 @@ run_autonomous() {
|
|
|
8432
8438
|
if [ -n "$found_prd" ]; then
|
|
8433
8439
|
log_info "Found existing PRD: $found_prd"
|
|
8434
8440
|
prd_path="$found_prd"
|
|
8441
|
+
# Warn if a generated PRD also exists (user file takes precedence)
|
|
8442
|
+
if [ -f ".loki/generated-prd.md" ] || [ -f ".loki/generated-prd.json" ]; then
|
|
8443
|
+
log_warn "Using user PRD ($found_prd) instead of generated PRD (.loki/generated-prd.md). Remove generated PRD if no longer needed."
|
|
8444
|
+
fi
|
|
8435
8445
|
elif [ -f ".loki/generated-prd.md" ]; then
|
|
8436
|
-
log_info "Using previously generated PRD: .loki/generated-prd.md"
|
|
8446
|
+
log_info "No user PRD found. Using previously generated PRD: .loki/generated-prd.md"
|
|
8437
8447
|
prd_path=".loki/generated-prd.md"
|
|
8438
8448
|
elif [ -f ".loki/generated-prd.json" ]; then
|
|
8439
|
-
log_info "Using previously generated PRD: .loki/generated-prd.json"
|
|
8449
|
+
log_info "No user PRD found. Using previously generated PRD: .loki/generated-prd.json"
|
|
8440
8450
|
prd_path=".loki/generated-prd.json"
|
|
8441
8451
|
else
|
|
8442
8452
|
log_info "No PRD found - will analyze codebase and generate one"
|
|
@@ -9194,8 +9204,9 @@ check_human_intervention() {
|
|
|
9194
9204
|
log_warn "PAUSE file created by budget limit - NOT auto-clearing in perpetual mode"
|
|
9195
9205
|
log_warn "Budget limit reached. Remove .loki/signals/BUDGET_EXCEEDED and .loki/PAUSE to continue."
|
|
9196
9206
|
notify_intervention_needed "Budget limit reached - execution paused" 2>/dev/null || true
|
|
9207
|
+
local pause_result
|
|
9197
9208
|
handle_pause
|
|
9198
|
-
|
|
9209
|
+
pause_result=$?
|
|
9199
9210
|
rm -f "$loki_dir/PAUSE"
|
|
9200
9211
|
if [ "$pause_result" -eq 1 ]; then
|
|
9201
9212
|
return 2
|
|
@@ -9211,8 +9222,9 @@ check_human_intervention() {
|
|
|
9211
9222
|
fi
|
|
9212
9223
|
log_warn "PAUSE file detected - pausing execution"
|
|
9213
9224
|
notify_intervention_needed "Execution paused via PAUSE file"
|
|
9225
|
+
local pause_result
|
|
9214
9226
|
handle_pause
|
|
9215
|
-
|
|
9227
|
+
pause_result=$?
|
|
9216
9228
|
rm -f "$loki_dir/PAUSE"
|
|
9217
9229
|
if [ "$pause_result" -eq 1 ]; then
|
|
9218
9230
|
# STOP was requested during pause
|
|
@@ -9228,8 +9240,9 @@ check_human_intervention() {
|
|
|
9228
9240
|
rm -f "$loki_dir/PAUSE_AT_CHECKPOINT"
|
|
9229
9241
|
notify_intervention_needed "Execution paused at checkpoint"
|
|
9230
9242
|
touch "$loki_dir/PAUSE"
|
|
9243
|
+
local pause_result
|
|
9231
9244
|
handle_pause
|
|
9232
|
-
|
|
9245
|
+
pause_result=$?
|
|
9233
9246
|
rm -f "$loki_dir/PAUSE"
|
|
9234
9247
|
if [ "$pause_result" -eq 1 ]; then
|
|
9235
9248
|
return 2
|
package/dashboard/__init__.py
CHANGED
package/dashboard/server.py
CHANGED
|
@@ -289,14 +289,98 @@ start_time = datetime.now(timezone.utc)
|
|
|
289
289
|
_dashboard_start_time = time.time()
|
|
290
290
|
|
|
291
291
|
|
|
292
|
+
async def _push_loki_state_loop() -> None:
|
|
293
|
+
"""Background loop: push .loki/ state changes to all WebSocket clients.
|
|
294
|
+
|
|
295
|
+
Reads dashboard-state.json every 2s when running (30s when idle) and
|
|
296
|
+
broadcasts a state_update message so clients don't rely solely on polling.
|
|
297
|
+
"""
|
|
298
|
+
last_mtime: float = 0.0
|
|
299
|
+
while True:
|
|
300
|
+
try:
|
|
301
|
+
if not manager.active_connections:
|
|
302
|
+
await asyncio.sleep(5)
|
|
303
|
+
continue
|
|
304
|
+
|
|
305
|
+
loki_dir = _get_loki_dir()
|
|
306
|
+
state_file = loki_dir / "dashboard-state.json"
|
|
307
|
+
|
|
308
|
+
if state_file.exists():
|
|
309
|
+
try:
|
|
310
|
+
mtime = state_file.stat().st_mtime
|
|
311
|
+
except OSError:
|
|
312
|
+
mtime = 0.0
|
|
313
|
+
|
|
314
|
+
# Only broadcast if file changed
|
|
315
|
+
if mtime != last_mtime:
|
|
316
|
+
last_mtime = mtime
|
|
317
|
+
try:
|
|
318
|
+
raw = json.loads(state_file.read_text())
|
|
319
|
+
# Transform to StatusResponse-compatible format
|
|
320
|
+
agents_list = raw.get("agents", [])
|
|
321
|
+
running_agents = len(agents_list) if isinstance(agents_list, list) else 0
|
|
322
|
+
tasks = raw.get("tasks", {})
|
|
323
|
+
pending = tasks.get("pending", [])
|
|
324
|
+
in_prog = tasks.get("inProgress", [])
|
|
325
|
+
status_str = raw.get("mode", "autonomous")
|
|
326
|
+
if status_str == "paused":
|
|
327
|
+
status_str = "paused"
|
|
328
|
+
elif status_str in ("stopped", ""):
|
|
329
|
+
status_str = "stopped"
|
|
330
|
+
else:
|
|
331
|
+
status_str = "running"
|
|
332
|
+
|
|
333
|
+
payload = {
|
|
334
|
+
"status": status_str,
|
|
335
|
+
"phase": raw.get("phase", ""),
|
|
336
|
+
"iteration": raw.get("iteration", 0),
|
|
337
|
+
"complexity": raw.get("complexity", "standard"),
|
|
338
|
+
"mode": raw.get("mode", ""),
|
|
339
|
+
"provider": raw.get("provider", "claude"),
|
|
340
|
+
"running_agents": running_agents,
|
|
341
|
+
"pending_tasks": len(pending) if isinstance(pending, list) else 0,
|
|
342
|
+
"current_task": in_prog[0].get("payload", {}).get("action", "") if isinstance(in_prog, list) and in_prog else "",
|
|
343
|
+
"version": raw.get("version", ""),
|
|
344
|
+
}
|
|
345
|
+
await manager.broadcast({
|
|
346
|
+
"type": "status_update",
|
|
347
|
+
"data": payload,
|
|
348
|
+
})
|
|
349
|
+
except (json.JSONDecodeError, OSError, KeyError):
|
|
350
|
+
pass
|
|
351
|
+
|
|
352
|
+
# Poll faster when a session is running
|
|
353
|
+
pid_file = loki_dir / "loki.pid"
|
|
354
|
+
is_running = False
|
|
355
|
+
if pid_file.exists():
|
|
356
|
+
try:
|
|
357
|
+
pid = int(pid_file.read_text().strip())
|
|
358
|
+
os.kill(pid, 0)
|
|
359
|
+
is_running = True
|
|
360
|
+
except (ValueError, OSError, ProcessLookupError):
|
|
361
|
+
pass
|
|
362
|
+
|
|
363
|
+
await asyncio.sleep(2.0 if is_running else 30.0)
|
|
364
|
+
except asyncio.CancelledError:
|
|
365
|
+
return
|
|
366
|
+
except Exception:
|
|
367
|
+
await asyncio.sleep(5)
|
|
368
|
+
|
|
369
|
+
|
|
292
370
|
@asynccontextmanager
|
|
293
371
|
async def lifespan(app: FastAPI):
|
|
294
372
|
"""Application lifespan handler."""
|
|
295
373
|
# Startup
|
|
296
374
|
await init_db()
|
|
297
375
|
_telemetry.send_telemetry("dashboard_start")
|
|
376
|
+
push_task = asyncio.create_task(_push_loki_state_loop())
|
|
298
377
|
yield
|
|
299
378
|
# Shutdown
|
|
379
|
+
push_task.cancel()
|
|
380
|
+
try:
|
|
381
|
+
await push_task
|
|
382
|
+
except (asyncio.CancelledError, Exception):
|
|
383
|
+
pass
|
|
300
384
|
await close_db()
|
|
301
385
|
|
|
302
386
|
|
|
@@ -1573,8 +1657,29 @@ async def get_audit_summary(days: int = 7):
|
|
|
1573
1657
|
# =============================================================================
|
|
1574
1658
|
|
|
1575
1659
|
def _get_loki_dir() -> _Path:
|
|
1576
|
-
"""Get LOKI_DIR, refreshing from env on each call for consistency.
|
|
1577
|
-
|
|
1660
|
+
"""Get LOKI_DIR, refreshing from env on each call for consistency.
|
|
1661
|
+
|
|
1662
|
+
Resolution order:
|
|
1663
|
+
1. LOKI_DIR env var (set by run.sh during active sessions)
|
|
1664
|
+
2. .loki/ in current working directory
|
|
1665
|
+
3. ~/.loki/ as global fallback
|
|
1666
|
+
"""
|
|
1667
|
+
env_dir = os.environ.get("LOKI_DIR")
|
|
1668
|
+
if env_dir:
|
|
1669
|
+
return _Path(env_dir)
|
|
1670
|
+
|
|
1671
|
+
# Check CWD first
|
|
1672
|
+
cwd_loki = _Path.cwd() / ".loki"
|
|
1673
|
+
if cwd_loki.is_dir():
|
|
1674
|
+
return cwd_loki
|
|
1675
|
+
|
|
1676
|
+
# Check home directory fallback
|
|
1677
|
+
home_loki = _Path.home() / ".loki"
|
|
1678
|
+
if home_loki.is_dir():
|
|
1679
|
+
return home_loki
|
|
1680
|
+
|
|
1681
|
+
# Default: relative .loki/ (will be created when session starts)
|
|
1682
|
+
return _Path(".loki")
|
|
1578
1683
|
|
|
1579
1684
|
|
|
1580
1685
|
_SAFE_ID_RE = re.compile(r'^[a-zA-Z0-9_-]+$')
|
|
@@ -1237,7 +1237,7 @@ var LokiDashboard=(()=>{var pt=Object.defineProperty;var Pt=Object.getOwnPropert
|
|
|
1237
1237
|
}
|
|
1238
1238
|
|
|
1239
1239
|
${B}
|
|
1240
|
-
`}getAriaPattern(t){return gt[t]||{}}applyAriaPattern(t,e){let i=this.getAriaPattern(e);for(let[a,s]of Object.entries(i))if(a==="role")t.setAttribute("role",s);else{let r=a.replace(/([A-Z])/g,"-$1").toLowerCase();t.setAttribute(r,s)}}render(){}};var L={realtime:1e3,normal:2e3,background:5e3,offline:1e4},_t={vscode:L.normal,browser:L.realtime,cli:L.background},yt={baseUrl:typeof window<"u"?window.location.origin:"http://localhost:57374",wsUrl:typeof window<"u"?`${window.location.protocol==="https:"?"wss:":"ws:"}//${window.location.host}/ws`:"ws://localhost:57374/ws",pollInterval:2e3,timeout:1e4,retryAttempts:3,retryDelay:1e3},u={CONNECTED:"api:connected",DISCONNECTED:"api:disconnected",ERROR:"api:error",STATUS_UPDATE:"api:status-update",TASK_CREATED:"api:task-created",TASK_UPDATED:"api:task-updated",TASK_DELETED:"api:task-deleted",PROJECT_CREATED:"api:project-created",PROJECT_UPDATED:"api:project-updated",AGENT_UPDATE:"api:agent-update",LOG_MESSAGE:"api:log-message",MEMORY_UPDATE:"api:memory-update",CHECKLIST_UPDATE:"api:checklist-update"},C=class C extends EventTarget{static getInstance(t={}){let e=t.baseUrl||yt.baseUrl;return C._instances.has(e)||C._instances.set(e,new C(t)),C._instances.get(e)}static clearInstances(){C._instances.forEach(t=>t.disconnect()),C._instances.clear()}constructor(t={}){super(),this.config={...yt,...t},this._ws=null,this._connected=!1,this._pollInterval=null,this._reconnectTimeout=null,this._reconnectAttempts=0,this._maxReconnectAttempts=20,this._cache=new Map,this._cacheTimeout=5e3,this._vscodeApi=null,this._context=this._detectContext(),this._currentPollInterval=_t[this._context]||L.normal,this._visibilityChangeHandler=null,this._messageHandler=null,this._setupAdaptivePolling(),this._setupVSCodeBridge()}_detectContext(){return typeof acquireVsCodeApi<"u"?"vscode":typeof window<"u"&&window.location?"browser":"cli"}get context(){return this._context}static get POLL_INTERVALS(){return L}_setupAdaptivePolling(){typeof document>"u"||(this._visibilityChangeHandler=()=>{document.hidden?this._setPollInterval(L.background):this._setPollInterval(_t[this._context]||L.normal)},document.addEventListener("visibilitychange",this._visibilityChangeHandler))}_setPollInterval(t){this._currentPollInterval=t,this._pollInterval&&(this.stopPolling(),this.startPolling(null,t))}setPollMode(t){let e=L[t];e&&this._setPollInterval(e)}_setupVSCodeBridge(){if(!(typeof acquireVsCodeApi>"u")){try{this._vscodeApi=acquireVsCodeApi()}catch{console.warn("VS Code API already acquired or unavailable");return}this._messageHandler=t=>{let e=t.data;if(!(!e||!e.type))switch(e.type){case"updateStatus":this._emit(u.STATUS_UPDATE,e.data);break;case"updateTasks":this._emit(u.TASK_UPDATED,e.data);break;case"taskCreated":this._emit(u.TASK_CREATED,e.data);break;case"taskDeleted":this._emit(u.TASK_DELETED,e.data);break;case"projectCreated":this._emit(u.PROJECT_CREATED,e.data);break;case"projectUpdated":this._emit(u.PROJECT_UPDATED,e.data);break;case"agentUpdate":this._emit(u.AGENT_UPDATE,e.data);break;case"logMessage":this._emit(u.LOG_MESSAGE,e.data);break;case"memoryUpdate":this._emit(u.MEMORY_UPDATE,e.data);break;case"connected":this._connected=!0,this._emit(u.CONNECTED,e.data);break;case"disconnected":this._connected=!1,this._emit(u.DISCONNECTED,e.data);break;case"error":this._emit(u.ERROR,e.data);break;case"setPollMode":this.setPollMode(e.data.mode);break;default:this._emit(`api:${e.type}`,e.data)}},window.addEventListener("message",this._messageHandler)}}get isVSCode(){return this._context==="vscode"}postToVSCode(t,e={}){this._vscodeApi&&this._vscodeApi.postMessage({type:t,data:e})}requestRefresh(){this.postToVSCode("requestRefresh")}notifyVSCode(t,e={}){this.postToVSCode("userAction",{action:t,...e})}get baseUrl(){return this.config.baseUrl}set baseUrl(t){this.config.baseUrl=t,this.config.wsUrl=t.replace(/^http/,"ws")+"/ws"}get isConnected(){return this._connected}async connect(){if(!(this._ws&&this._ws.readyState===WebSocket.OPEN))return new Promise((t,e)=>{try{this._ws=new WebSocket(this.config.wsUrl),this._ws.onopen=()=>{this._connected=!0,this._reconnectAttempts=0,this._emit(u.CONNECTED),t()},this._ws.onclose=()=>{this._connected=!1,this._emit(u.DISCONNECTED),this._scheduleReconnect()},this._ws.onerror=i=>{this._emit(u.ERROR,{error:i}),e(i)},this._ws.onmessage=i=>{try{let a=JSON.parse(i.data);this._handleMessage(a)}catch(a){console.error("Failed to parse WebSocket message:",a)}}}catch(i){e(i)}})}disconnect(){this._ws&&(this._ws.close(),this._ws=null),this._pollInterval&&(clearInterval(this._pollInterval),this._pollInterval=null),this._reconnectTimeout&&(clearTimeout(this._reconnectTimeout),this._reconnectTimeout=null),this._connected=!1,this._cleanupGlobalListeners()}_cleanupGlobalListeners(){this._visibilityChangeHandler&&typeof document<"u"&&(document.removeEventListener("visibilitychange",this._visibilityChangeHandler),this._visibilityChangeHandler=null),this._messageHandler&&typeof window<"u"&&(window.removeEventListener("message",this._messageHandler),this._messageHandler=null)}destroy(){this.disconnect()}_scheduleReconnect(){if(this._reconnectTimeout)return;if(this._reconnectAttempts>=this._maxReconnectAttempts){console.warn("WebSocket max reconnect attempts reached, giving up"),this._emit(u.ERROR,{error:"Max reconnect attempts reached"});return}let t=Math.min(this.config.retryDelay*Math.pow(2,this._reconnectAttempts),3e4);this._reconnectAttempts++,this._reconnectTimeout=setTimeout(()=>{this._reconnectTimeout=null,this.connect().catch(()=>{})},t)}_handleMessage(t){let i={connected:u.CONNECTED,status_update:u.STATUS_UPDATE,task_created:u.TASK_CREATED,task_updated:u.TASK_UPDATED,task_deleted:u.TASK_DELETED,task_moved:u.TASK_UPDATED,project_created:u.PROJECT_CREATED,project_updated:u.PROJECT_UPDATED,agent_update:u.AGENT_UPDATE,log:u.LOG_MESSAGE}[t.type]||`api:${t.type}`;this._emit(i,t.data)}_emit(t,e={}){this.dispatchEvent(new CustomEvent(t,{detail:e}))}async _request(t,e={}){let i=`${this.config.baseUrl}${t}`,a=new AbortController,s=setTimeout(()=>a.abort(),this.config.timeout);try{let r=await fetch(i,{...e,signal:a.signal,headers:{"Content-Type":"application/json",...e.headers}});if(clearTimeout(s),!r.ok){let o=await r.json().catch(()=>({detail:r.statusText}));throw new Error(o.detail||`HTTP ${r.status}`)}return r.status===204?null:await r.json()}catch(r){throw clearTimeout(s),r.name==="AbortError"?new Error("Request timeout"):r}}async _get(t,e=!1){if(e&&this._cache.has(t)){let a=this._cache.get(t);if(Date.now()-a.timestamp<this._cacheTimeout)return a.data}let i=await this._request(t);return e&&this._cache.set(t,{data:i,timestamp:Date.now()}),i}async _post(t,e){return this._request(t,{method:"POST",body:JSON.stringify(e)})}async _put(t,e){return this._request(t,{method:"PUT",body:JSON.stringify(e)})}async _delete(t){return this._request(t,{method:"DELETE"})}async getStatus(){return this._get("/api/status")}async healthCheck(){return this._get("/health")}async listProjects(t=null){let e=t?`?status=${t}`:"";return this._get(`/api/projects${e}`)}async getProject(t){return this._get(`/api/projects/${t}`)}async createProject(t){return this._post("/api/projects",t)}async updateProject(t,e){return this._put(`/api/projects/${t}`,e)}async deleteProject(t){return this._delete(`/api/projects/${t}`)}async listTasks(t={}){let e=new URLSearchParams;t.projectId&&e.append("project_id",t.projectId),t.status&&e.append("status",t.status),t.priority&&e.append("priority",t.priority);let i=e.toString()?`?${e}`:"";return this._get(`/api/tasks${i}`)}async getTask(t){return this._get(`/api/tasks/${t}`)}async createTask(t){return this._post("/api/tasks",t)}async updateTask(t,e){return this._put(`/api/tasks/${t}`,e)}async moveTask(t,e,i){return this._post(`/api/tasks/${t}/move`,{status:e,position:i})}async deleteTask(t){return this._delete(`/api/tasks/${t}`)}async getMemorySummary(){return this._get("/api/memory/summary",!0)}async getMemoryIndex(){return this._get("/api/memory/index",!0)}async getMemoryTimeline(){return this._get("/api/memory/timeline")}async listEpisodes(t={}){let e=new URLSearchParams(t).toString();return this._get(`/api/memory/episodes${e?"?"+e:""}`)}async getEpisode(t){return this._get(`/api/memory/episodes/${t}`)}async listPatterns(t={}){let e=new URLSearchParams(t).toString();return this._get(`/api/memory/patterns${e?"?"+e:""}`)}async getPattern(t){return this._get(`/api/memory/patterns/${t}`)}async listSkills(){return this._get("/api/memory/skills")}async getSkill(t){return this._get(`/api/memory/skills/${t}`)}async retrieveMemories(t,e=null,i=5){return this._post("/api/memory/retrieve",{query:t,taskType:e,topK:i})}async consolidateMemory(t=24){return this._post("/api/memory/consolidate",{sinceHours:t})}async getTokenEconomics(){return this._get("/api/memory/economics")}async searchMemory(t,e="all",i=20){let a=new URLSearchParams({q:t,collection:e,limit:String(i)});return this._get(`/api/memory/search?${a}`)}async getMemoryStats(){return this._get("/api/memory/stats",!0)}async listRegisteredProjects(t=!1){return this._get(`/api/registry/projects?include_inactive=${t}`)}async registerProject(t,e=null,i=null){return this._post("/api/registry/projects",{path:t,name:e,alias:i})}async discoverProjects(t=3){return this._get(`/api/registry/discover?max_depth=${t}`)}async syncRegistry(){return this._post("/api/registry/sync",{})}async getCrossProjectTasks(t=null){let e=t?`?project_ids=${t.join(",")}`:"";return this._get(`/api/registry/tasks${e}`)}async getLearningMetrics(t={}){let e=new URLSearchParams;t.timeRange&&e.append("timeRange",t.timeRange),t.signalType&&e.append("signalType",t.signalType),t.source&&e.append("source",t.source);let i=e.toString()?`?${e}`:"";return this._get(`/api/learning/metrics${i}`)}async getLearningTrends(t={}){let e=new URLSearchParams;t.timeRange&&e.append("timeRange",t.timeRange),t.signalType&&e.append("signalType",t.signalType),t.source&&e.append("source",t.source);let i=e.toString()?`?${e}`:"";return this._get(`/api/learning/trends${i}`)}async getLearningSignals(t={}){let e=new URLSearchParams;t.timeRange&&e.append("timeRange",t.timeRange),t.signalType&&e.append("signalType",t.signalType),t.source&&e.append("source",t.source),t.limit&&e.append("limit",String(t.limit)),t.offset&&e.append("offset",String(t.offset));let i=e.toString()?`?${e}`:"";return this._get(`/api/learning/signals${i}`)}async getLatestAggregation(){return this._get("/api/learning/aggregation")}async triggerAggregation(t={}){return this._post("/api/learning/aggregate",t)}async getAggregatedPreferences(t=20){return this._get(`/api/learning/preferences?limit=${t}`)}async getAggregatedErrors(t=20){return this._get(`/api/learning/errors?limit=${t}`)}async getAggregatedSuccessPatterns(t=20){return this._get(`/api/learning/success?limit=${t}`)}async getToolEfficiency(t=20){return this._get(`/api/learning/tools?limit=${t}`)}async getCost(){return this._get("/api/cost")}async getPricing(){return this._get("/api/pricing")}async getCouncilState(){return this._get("/api/council/state")}async getCouncilVerdicts(t=20){return this._get(`/api/council/verdicts?limit=${t}`)}async getCouncilConvergence(){return this._get("/api/council/convergence")}async getCouncilReport(){return this._get("/api/council/report")}async forceCouncilReview(){return this._post("/api/council/force-review",{})}async getContext(){return this._get("/api/context")}async getNotifications(t,e){let i=new URLSearchParams;t&&i.set("severity",t),e&&i.set("unread_only","true");let a=i.toString();return this._get("/api/notifications"+(a?"?"+a:""))}async getNotificationTriggers(){return this._get("/api/notifications/triggers")}async updateNotificationTriggers(t){return this._put("/api/notifications/triggers",{triggers:t})}async acknowledgeNotification(t){return this._post("/api/notifications/"+encodeURIComponent(t)+"/acknowledge",{})}async pauseSession(){return this._post("/api/control/pause",{})}async resumeSession(){return this._post("/api/control/resume",{})}async stopSession(){return this._post("/api/control/stop",{})}async getLogs(t=100){return this._get(`/api/logs?lines=${t}`)}async getChecklist(){return this._get("/api/checklist")}async getChecklistSummary(){return this._get("/api/checklist/summary")}async getPrdObservations(){let t=await fetch(`${this.baseUrl}/api/prd-observations`);if(!t.ok)throw new Error(`HTTP ${t.status}`);return t.text()}async getChecklistWaivers(){return this._get("/api/checklist/waivers")}async addChecklistWaiver(t,e,i="dashboard"){return this._post("/api/checklist/waivers",{item_id:t,reason:e,waived_by:i})}async removeChecklistWaiver(t){return this._delete(`/api/checklist/waivers/${encodeURIComponent(t)}`)}async getCouncilGate(){return this._get("/api/council/gate")}async getAppRunnerStatus(){return this._get("/api/app-runner/status")}async getAppRunnerLogs(t=100){return this._get(`/api/app-runner/logs?lines=${t}`)}async restartApp(){return this._post("/api/control/app-restart",{})}async stopApp(){return this._post("/api/control/app-stop",{})}async getPlaywrightResults(){return this._get("/api/playwright/results")}async getPlaywrightScreenshot(){return this._get("/api/playwright/screenshot")}startPolling(t,e=null){if(this._pollInterval)return;this._pollCallback=t;let i=async()=>{try{let s=await this.getStatus();this._connected=!0,this._pollCallback&&this._pollCallback(s),this._emit(u.STATUS_UPDATE,s),this._vscodeApi&&this.postToVSCode("pollSuccess",{timestamp:Date.now()})}catch(s){this._connected=!1,this._emit(u.ERROR,{error:s}),this._vscodeApi&&this.postToVSCode("pollError",{error:s.message})}};i();let a=e||this._currentPollInterval||this.config.pollInterval;this._pollInterval=setInterval(i,a)}stopPolling(){this._pollInterval&&(clearInterval(this._pollInterval),this._pollInterval=null)}};w(C,"_instances",new Map);var R=C;function wt(d={}){return new R(d)}function g(d={}){return R.getInstance(d)}var bt="loki-state-change",mt={ui:{theme:"light",sidebarCollapsed:!1,activeSection:"kanban",terminalAutoScroll:!0},session:{connected:!1,lastSync:null,mode:"offline",phase:null,iteration:null},localTasks:[],cache:{projects:[],tasks:[],agents:[],memory:null,lastFetch:null},preferences:{pollInterval:2e3,notifications:!0,soundEnabled:!1}},$=class $ extends EventTarget{static getInstance(){return $._instance||($._instance=new $),$._instance}constructor(){super(),this._state=this._loadState(),this._subscribers=new Map,this._batchUpdates=[],this._batchTimeout=null}_loadState(){try{let t=localStorage.getItem($.STORAGE_KEY);if(t){let e=JSON.parse(t);return this._mergeState(mt,e)}}catch(t){console.warn("Failed to load state from localStorage:",t)}return{...mt}}_mergeState(t,e){let i={...t};for(let a of Object.keys(e))a in t&&typeof t[a]=="object"&&!Array.isArray(t[a])?i[a]=this._mergeState(t[a],e[a]):i[a]=e[a];return i}_saveState(){try{let t={ui:this._state.ui,localTasks:this._state.localTasks,preferences:this._state.preferences};localStorage.setItem($.STORAGE_KEY,JSON.stringify(t))}catch(t){console.warn("Failed to save state to localStorage:",t)}}get(t=null){if(!t)return{...this._state};let e=t.split("."),i=this._state;for(let a of e){if(i==null)return;i=i[a]}return i}set(t,e,i=!0){let a=t.split("."),s=a.pop(),r=this._state;for(let n of a)n in r||(r[n]={}),r=r[n];let o=r[s];r[s]=e,i&&this._saveState(),this._notifyChange(t,e,o)}update(t,e=!0){let i=[];for(let[a,s]of Object.entries(t)){let r=this.get(a);this.set(a,s,!1),i.push({path:a,value:s,oldValue:r})}e&&this._saveState();for(let a of i)this._notifyChange(a.path,a.value,a.oldValue)}_notifyChange(t,e,i){this.dispatchEvent(new CustomEvent(bt,{detail:{path:t,value:e,oldValue:i}}));let a=this._subscribers.get(t)||[];for(let r of a)try{r(e,i,t)}catch(o){console.error("State subscriber error:",o)}let s=t.split(".");for(;s.length>1;){s.pop();let r=s.join("."),o=this._subscribers.get(r)||[];for(let n of o)try{n(this.get(r),null,r)}catch(l){console.error("State subscriber error:",l)}}}subscribe(t,e){return this._subscribers.has(t)||this._subscribers.set(t,[]),this._subscribers.get(t).push(e),()=>{let i=this._subscribers.get(t),a=i.indexOf(e);a>-1&&i.splice(a,1)}}reset(t=null){if(t){let e=t.split("."),i=mt;for(let a of e)i=i?.[a];this.set(t,i)}else this._state={...mt},this._saveState(),this.dispatchEvent(new CustomEvent(bt,{detail:{path:null,value:this._state,oldValue:null}}))}addLocalTask(t){let e=this.get("localTasks")||[],i={id:`local-${Date.now()}-${Math.random().toString(36).substr(2,9)}`,createdAt:new Date().toISOString(),status:"pending",...t};return this.set("localTasks",[...e,i]),i}updateLocalTask(t,e){let i=this.get("localTasks")||[],a=i.findIndex(r=>r.id===t);if(a===-1)return null;let s={...i[a],...e,updatedAt:new Date().toISOString()};return i[a]=s,this.set("localTasks",[...i]),s}deleteLocalTask(t){let e=this.get("localTasks")||[];this.set("localTasks",e.filter(i=>i.id!==t))}moveLocalTask(t,e,i=null){let s=(this.get("localTasks")||[]).find(r=>r.id===t);return s?this.updateLocalTask(t,{status:e,position:i??s.position}):null}updateSession(t){this.update(Object.fromEntries(Object.entries(t).map(([e,i])=>[`session.${e}`,i])),!1)}updateCache(t){this.update({"cache.projects":t.projects??this.get("cache.projects"),"cache.tasks":t.tasks??this.get("cache.tasks"),"cache.agents":t.agents??this.get("cache.agents"),"cache.memory":t.memory??this.get("cache.memory"),"cache.lastFetch":new Date().toISOString()},!1)}getMergedTasks(){let t=this.get("cache.tasks")||[],i=(this.get("localTasks")||[]).map(a=>({...a,isLocal:!0}));return[...t,...i]}getTasksByStatus(t){return this.getMergedTasks().filter(e=>e.status===t)}};w($,"STORAGE_KEY","loki-dashboard-state"),w($,"_instance",null);var F=$;function z(){return F.getInstance()}function $t(d){let t=z();return{get:()=>t.get(d),set:e=>t.set(d,e),subscribe:e=>t.subscribe(d,e)}}var j=class extends h{static get observedAttributes(){return["api-url","theme"]}constructor(){super(),this._data={status:"offline",phase:null,iteration:null,provider:null,running_agents:0,pending_tasks:null,uptime_seconds:0,complexity:null,connected:!1},this._api=null,this._pollInterval=null,this._statusUpdateHandler=null,this._connectedHandler=null,this._disconnectedHandler=null,this._checklistSummary=null,this._appRunnerStatus=null,this._playwrightResults=null,this._gateStatus=null}connectedCallback(){super.connectedCallback(),this._setupApi(),this._loadStatus(),this._startPolling()}disconnectedCallback(){super.disconnectedCallback(),this._stopPolling(),this._api&&(this._statusUpdateHandler&&this._api.removeEventListener(u.STATUS_UPDATE,this._statusUpdateHandler),this._connectedHandler&&this._api.removeEventListener(u.CONNECTED,this._connectedHandler),this._disconnectedHandler&&this._api.removeEventListener(u.DISCONNECTED,this._disconnectedHandler))}attributeChangedCallback(t,e,i){e!==i&&(t==="api-url"&&this._api&&(this._api.baseUrl=i,this._loadStatus()),t==="theme"&&this._applyTheme())}_setupApi(){let t=this.getAttribute("api-url")||window.location.origin;this._api=g({baseUrl:t}),this._statusUpdateHandler=e=>this._updateFromStatus(e.detail),this._connectedHandler=()=>{this._data.connected=!0,this.render()},this._disconnectedHandler=()=>{this._data.connected=!1,this._data.status="offline",this.render()},this._api.addEventListener(u.STATUS_UPDATE,this._statusUpdateHandler),this._api.addEventListener(u.CONNECTED,this._connectedHandler),this._api.addEventListener(u.DISCONNECTED,this._disconnectedHandler)}async _loadStatus(){try{let[t,e,i,a,s]=await Promise.allSettled([this._api.getStatus(),this._api.getChecklistSummary(),this._api.getAppRunnerStatus(),this._api.getPlaywrightResults(),this._api.getCouncilGate()]);t.status==="fulfilled"?this._updateFromStatus(t.value):(this._data.connected=!1,this._data.status="offline"),e.status==="fulfilled"&&(this._checklistSummary=e.value?.summary||null),i.status==="fulfilled"&&(this._appRunnerStatus=i.value),a.status==="fulfilled"&&(this._playwrightResults=a.value),s.status==="fulfilled"&&(this._gateStatus=s.value),this.render()}catch{this._data.connected=!1,this._data.status="offline",this.render()}}_updateFromStatus(t){t&&(this._data={...this._data,connected:!0,status:t.status||"offline",phase:t.phase||null,iteration:t.iteration!=null?t.iteration:null,provider:t.provider||null,running_agents:t.running_agents||0,pending_tasks:t.pending_tasks!=null?t.pending_tasks:null,uptime_seconds:t.uptime_seconds||0,complexity:t.complexity||null})}_startPolling(){this._pollInterval=setInterval(async()=>{try{await this._loadStatus()}catch{this._data.connected=!1,this._data.status="offline",this.render()}},5e3)}_stopPolling(){this._pollInterval&&(clearInterval(this._pollInterval),this._pollInterval=null)}_formatUptime(t){if(!t||t<0)return"--";let e=Math.floor(t/3600),i=Math.floor(t%3600/60),a=Math.floor(t%60);return e>0?`${e}h ${i}m`:i>0?`${i}m ${a}s`:`${a}s`}_getStatusDotClass(){switch(this._data.status){case"running":case"autonomous":return"active";case"paused":return"paused";case"stopped":return"stopped";case"error":return"error";default:return"offline"}}_escapeHtml(t){return t?String(t).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,"""):""}_renderAppRunnerCard(){let t=this._appRunnerStatus;if(!t||t.status==="not_initialized")return`
|
|
1240
|
+
`}getAriaPattern(t){return gt[t]||{}}applyAriaPattern(t,e){let i=this.getAriaPattern(e);for(let[a,s]of Object.entries(i))if(a==="role")t.setAttribute("role",s);else{let r=a.replace(/([A-Z])/g,"-$1").toLowerCase();t.setAttribute(r,s)}}render(){}};var L={realtime:1e3,normal:2e3,background:5e3,offline:1e4},_t={vscode:L.normal,browser:L.realtime,cli:L.background},yt={baseUrl:typeof window<"u"?window.location.origin:"http://localhost:57374",wsUrl:typeof window<"u"?`${window.location.protocol==="https:"?"wss:":"ws:"}//${window.location.host}/ws`:"ws://localhost:57374/ws",pollInterval:2e3,timeout:1e4,retryAttempts:3,retryDelay:1e3},u={CONNECTED:"api:connected",DISCONNECTED:"api:disconnected",ERROR:"api:error",STATUS_UPDATE:"api:status-update",TASK_CREATED:"api:task-created",TASK_UPDATED:"api:task-updated",TASK_DELETED:"api:task-deleted",PROJECT_CREATED:"api:project-created",PROJECT_UPDATED:"api:project-updated",AGENT_UPDATE:"api:agent-update",LOG_MESSAGE:"api:log-message",MEMORY_UPDATE:"api:memory-update",CHECKLIST_UPDATE:"api:checklist-update"},C=class C extends EventTarget{static getInstance(t={}){let e=t.baseUrl||yt.baseUrl;return C._instances.has(e)||C._instances.set(e,new C(t)),C._instances.get(e)}static clearInstances(){C._instances.forEach(t=>t.disconnect()),C._instances.clear()}constructor(t={}){super(),this.config={...yt,...t},this._ws=null,this._connected=!1,this._pollInterval=null,this._reconnectTimeout=null,this._reconnectAttempts=0,this._maxReconnectAttempts=20,this._cache=new Map,this._cacheTimeout=5e3,this._vscodeApi=null,this._context=this._detectContext(),this._currentPollInterval=_t[this._context]||L.normal,this._visibilityChangeHandler=null,this._messageHandler=null,this._setupAdaptivePolling(),this._setupVSCodeBridge()}_detectContext(){return typeof acquireVsCodeApi<"u"?"vscode":typeof window<"u"&&window.location?"browser":"cli"}get context(){return this._context}static get POLL_INTERVALS(){return L}_setupAdaptivePolling(){typeof document>"u"||(this._visibilityChangeHandler=()=>{document.hidden?this._setPollInterval(L.background):this._setPollInterval(_t[this._context]||L.normal)},document.addEventListener("visibilitychange",this._visibilityChangeHandler))}_setPollInterval(t){this._currentPollInterval=t,this._pollInterval&&(this.stopPolling(),this.startPolling(null,t))}setPollMode(t){let e=L[t];e&&this._setPollInterval(e)}_setupVSCodeBridge(){if(!(typeof acquireVsCodeApi>"u")){try{this._vscodeApi=acquireVsCodeApi()}catch{console.warn("VS Code API already acquired or unavailable");return}this._messageHandler=t=>{let e=t.data;if(!(!e||!e.type))switch(e.type){case"updateStatus":this._emit(u.STATUS_UPDATE,e.data);break;case"updateTasks":this._emit(u.TASK_UPDATED,e.data);break;case"taskCreated":this._emit(u.TASK_CREATED,e.data);break;case"taskDeleted":this._emit(u.TASK_DELETED,e.data);break;case"projectCreated":this._emit(u.PROJECT_CREATED,e.data);break;case"projectUpdated":this._emit(u.PROJECT_UPDATED,e.data);break;case"agentUpdate":this._emit(u.AGENT_UPDATE,e.data);break;case"logMessage":this._emit(u.LOG_MESSAGE,e.data);break;case"memoryUpdate":this._emit(u.MEMORY_UPDATE,e.data);break;case"connected":this._connected=!0,this._emit(u.CONNECTED,e.data);break;case"disconnected":this._connected=!1,this._emit(u.DISCONNECTED,e.data);break;case"error":this._emit(u.ERROR,e.data);break;case"setPollMode":this.setPollMode(e.data.mode);break;default:this._emit(`api:${e.type}`,e.data)}},window.addEventListener("message",this._messageHandler)}}get isVSCode(){return this._context==="vscode"}postToVSCode(t,e={}){this._vscodeApi&&this._vscodeApi.postMessage({type:t,data:e})}requestRefresh(){this.postToVSCode("requestRefresh")}notifyVSCode(t,e={}){this.postToVSCode("userAction",{action:t,...e})}get baseUrl(){return this.config.baseUrl}set baseUrl(t){this.config.baseUrl=t,this.config.wsUrl=t.replace(/^http/,"ws")+"/ws"}get isConnected(){return this._connected}async connect(){if(!(this._ws&&this._ws.readyState===WebSocket.OPEN))return new Promise((t,e)=>{try{this._ws=new WebSocket(this.config.wsUrl),this._ws.onopen=()=>{this._connected=!0,this._reconnectAttempts=0,this._emit(u.CONNECTED),t()},this._ws.onclose=()=>{this._connected=!1,this._emit(u.DISCONNECTED),this._scheduleReconnect()},this._ws.onerror=i=>{this._emit(u.ERROR,{error:i}),e(i)},this._ws.onmessage=i=>{try{let a=JSON.parse(i.data);this._handleMessage(a)}catch(a){console.error("Failed to parse WebSocket message:",a)}}}catch(i){e(i)}})}disconnect(){this._ws&&(this._ws.close(),this._ws=null),this._pollInterval&&(clearInterval(this._pollInterval),this._pollInterval=null),this._reconnectTimeout&&(clearTimeout(this._reconnectTimeout),this._reconnectTimeout=null),this._connected=!1,this._cleanupGlobalListeners()}_cleanupGlobalListeners(){this._visibilityChangeHandler&&typeof document<"u"&&(document.removeEventListener("visibilitychange",this._visibilityChangeHandler),this._visibilityChangeHandler=null),this._messageHandler&&typeof window<"u"&&(window.removeEventListener("message",this._messageHandler),this._messageHandler=null)}destroy(){this.disconnect()}_scheduleReconnect(){if(this._reconnectTimeout)return;if(this._reconnectAttempts>=this._maxReconnectAttempts){console.warn("WebSocket max reconnect attempts reached, giving up"),this._emit(u.ERROR,{error:"Max reconnect attempts reached"});return}let t=Math.min(this.config.retryDelay*Math.pow(2,this._reconnectAttempts),3e4);this._reconnectAttempts++,this._reconnectTimeout=setTimeout(()=>{this._reconnectTimeout=null,this.connect().catch(()=>{})},t)}_handleMessage(t){let i={connected:u.CONNECTED,status_update:u.STATUS_UPDATE,task_created:u.TASK_CREATED,task_updated:u.TASK_UPDATED,task_deleted:u.TASK_DELETED,task_moved:u.TASK_UPDATED,project_created:u.PROJECT_CREATED,project_updated:u.PROJECT_UPDATED,agent_update:u.AGENT_UPDATE,log:u.LOG_MESSAGE}[t.type]||`api:${t.type}`;this._emit(i,t.data)}_emit(t,e={}){this.dispatchEvent(new CustomEvent(t,{detail:e}))}async _request(t,e={}){let i=`${this.config.baseUrl}${t}`,a=new AbortController,s=setTimeout(()=>a.abort(),this.config.timeout);try{let r=await fetch(i,{...e,signal:a.signal,headers:{"Content-Type":"application/json",...e.headers}});if(clearTimeout(s),!r.ok){let o=await r.json().catch(()=>({detail:r.statusText}));throw new Error(o.detail||`HTTP ${r.status}`)}return r.status===204?null:await r.json()}catch(r){throw clearTimeout(s),r.name==="AbortError"?new Error("Request timeout"):r}}async _get(t,e=!1){if(e&&this._cache.has(t)){let a=this._cache.get(t);if(Date.now()-a.timestamp<this._cacheTimeout)return a.data}let i=await this._request(t);return e&&this._cache.set(t,{data:i,timestamp:Date.now()}),i}async _post(t,e){return this._request(t,{method:"POST",body:JSON.stringify(e)})}async _put(t,e){return this._request(t,{method:"PUT",body:JSON.stringify(e)})}async _delete(t){return this._request(t,{method:"DELETE"})}async getStatus(){return this._get("/api/status")}async healthCheck(){return this._get("/health")}async listProjects(t=null){let e=t?`?status=${t}`:"";return this._get(`/api/projects${e}`)}async getProject(t){return this._get(`/api/projects/${t}`)}async createProject(t){return this._post("/api/projects",t)}async updateProject(t,e){return this._put(`/api/projects/${t}`,e)}async deleteProject(t){return this._delete(`/api/projects/${t}`)}async listTasks(t={}){let e=new URLSearchParams;t.projectId&&e.append("project_id",t.projectId),t.status&&e.append("status",t.status),t.priority&&e.append("priority",t.priority);let i=e.toString()?`?${e}`:"";return this._get(`/api/tasks${i}`)}async getTask(t){return this._get(`/api/tasks/${t}`)}async createTask(t){return this._post("/api/tasks",t)}async updateTask(t,e){return this._put(`/api/tasks/${t}`,e)}async moveTask(t,e,i){return this._post(`/api/tasks/${t}/move`,{status:e,position:i})}async deleteTask(t){return this._delete(`/api/tasks/${t}`)}async getMemorySummary(){return this._get("/api/memory/summary",!0)}async getMemoryIndex(){return this._get("/api/memory/index",!0)}async getMemoryTimeline(){return this._get("/api/memory/timeline")}async listEpisodes(t={}){let e=new URLSearchParams(t).toString();return this._get(`/api/memory/episodes${e?"?"+e:""}`)}async getEpisode(t){return this._get(`/api/memory/episodes/${t}`)}async listPatterns(t={}){let e=new URLSearchParams(t).toString();return this._get(`/api/memory/patterns${e?"?"+e:""}`)}async getPattern(t){return this._get(`/api/memory/patterns/${t}`)}async listSkills(){return this._get("/api/memory/skills")}async getSkill(t){return this._get(`/api/memory/skills/${t}`)}async retrieveMemories(t,e=null,i=5){return this._post("/api/memory/retrieve",{query:t,taskType:e,topK:i})}async consolidateMemory(t=24){return this._post("/api/memory/consolidate",{sinceHours:t})}async getTokenEconomics(){return this._get("/api/memory/economics")}async searchMemory(t,e="all",i=20){let a=new URLSearchParams({q:t,collection:e,limit:String(i)});return this._get(`/api/memory/search?${a}`)}async getMemoryStats(){return this._get("/api/memory/stats",!0)}async listRegisteredProjects(t=!1){return this._get(`/api/registry/projects?include_inactive=${t}`)}async registerProject(t,e=null,i=null){return this._post("/api/registry/projects",{path:t,name:e,alias:i})}async discoverProjects(t=3){return this._get(`/api/registry/discover?max_depth=${t}`)}async syncRegistry(){return this._post("/api/registry/sync",{})}async getCrossProjectTasks(t=null){let e=t?`?project_ids=${t.join(",")}`:"";return this._get(`/api/registry/tasks${e}`)}async getLearningMetrics(t={}){let e=new URLSearchParams;t.timeRange&&e.append("timeRange",t.timeRange),t.signalType&&e.append("signalType",t.signalType),t.source&&e.append("source",t.source);let i=e.toString()?`?${e}`:"";return this._get(`/api/learning/metrics${i}`)}async getLearningTrends(t={}){let e=new URLSearchParams;t.timeRange&&e.append("timeRange",t.timeRange),t.signalType&&e.append("signalType",t.signalType),t.source&&e.append("source",t.source);let i=e.toString()?`?${e}`:"";return this._get(`/api/learning/trends${i}`)}async getLearningSignals(t={}){let e=new URLSearchParams;t.timeRange&&e.append("timeRange",t.timeRange),t.signalType&&e.append("signalType",t.signalType),t.source&&e.append("source",t.source),t.limit&&e.append("limit",String(t.limit)),t.offset&&e.append("offset",String(t.offset));let i=e.toString()?`?${e}`:"";return this._get(`/api/learning/signals${i}`)}async getLatestAggregation(){return this._get("/api/learning/aggregation")}async triggerAggregation(t={}){return this._post("/api/learning/aggregate",t)}async getAggregatedPreferences(t=20){return this._get(`/api/learning/preferences?limit=${t}`)}async getAggregatedErrors(t=20){return this._get(`/api/learning/errors?limit=${t}`)}async getAggregatedSuccessPatterns(t=20){return this._get(`/api/learning/success?limit=${t}`)}async getToolEfficiency(t=20){return this._get(`/api/learning/tools?limit=${t}`)}async getCost(){return this._get("/api/cost")}async getPricing(){return this._get("/api/pricing")}async getCouncilState(){return this._get("/api/council/state")}async getCouncilVerdicts(t=20){return this._get(`/api/council/verdicts?limit=${t}`)}async getCouncilConvergence(){return this._get("/api/council/convergence")}async getCouncilReport(){return this._get("/api/council/report")}async forceCouncilReview(){return this._post("/api/council/force-review",{})}async getContext(){return this._get("/api/context")}async getNotifications(t,e){let i=new URLSearchParams;t&&i.set("severity",t),e&&i.set("unread_only","true");let a=i.toString();return this._get("/api/notifications"+(a?"?"+a:""))}async getNotificationTriggers(){return this._get("/api/notifications/triggers")}async updateNotificationTriggers(t){return this._put("/api/notifications/triggers",{triggers:t})}async acknowledgeNotification(t){return this._post("/api/notifications/"+encodeURIComponent(t)+"/acknowledge",{})}async pauseSession(){return this._post("/api/control/pause",{})}async resumeSession(){return this._post("/api/control/resume",{})}async stopSession(){return this._post("/api/control/stop",{})}async getLogs(t=100){return this._get(`/api/logs?lines=${t}`)}async getChecklist(){return this._get("/api/checklist")}async getChecklistSummary(){return this._get("/api/checklist/summary")}async getPrdObservations(){let t=await fetch(`${this.baseUrl}/api/prd-observations`);if(!t.ok)throw new Error(`HTTP ${t.status}`);return t.text()}async getChecklistWaivers(){return this._get("/api/checklist/waivers")}async addChecklistWaiver(t,e,i="dashboard"){return this._post("/api/checklist/waivers",{item_id:t,reason:e,waived_by:i})}async removeChecklistWaiver(t){return this._delete(`/api/checklist/waivers/${encodeURIComponent(t)}`)}async getCouncilGate(){return this._get("/api/council/gate")}async getAppRunnerStatus(){return this._get("/api/app-runner/status")}async getAppRunnerLogs(t=100){return this._get(`/api/app-runner/logs?lines=${t}`)}async restartApp(){return this._post("/api/control/app-restart",{})}async stopApp(){return this._post("/api/control/app-stop",{})}async getPlaywrightResults(){return this._get("/api/playwright/results")}async getPlaywrightScreenshot(){return this._get("/api/playwright/screenshot")}startPolling(t,e=null){if(this._pollInterval)return;this._pollCallback=t;let i=async()=>{try{let s=await this.getStatus();this._connected=!0,this._pollCallback&&this._pollCallback(s),this._emit(u.STATUS_UPDATE,s),this._vscodeApi&&this.postToVSCode("pollSuccess",{timestamp:Date.now()})}catch(s){this._connected=!1,this._emit(u.ERROR,{error:s}),this._vscodeApi&&this.postToVSCode("pollError",{error:s.message})}};i();let a=e||this._currentPollInterval||this.config.pollInterval;this._pollInterval=setInterval(i,a)}stopPolling(){this._pollInterval&&(clearInterval(this._pollInterval),this._pollInterval=null)}};w(C,"_instances",new Map);var R=C;function wt(d={}){return new R(d)}function g(d={}){return R.getInstance(d)}var bt="loki-state-change",mt={ui:{theme:"light",sidebarCollapsed:!1,activeSection:"kanban",terminalAutoScroll:!0},session:{connected:!1,lastSync:null,mode:"offline",phase:null,iteration:null},localTasks:[],cache:{projects:[],tasks:[],agents:[],memory:null,lastFetch:null},preferences:{pollInterval:2e3,notifications:!0,soundEnabled:!1}},$=class $ extends EventTarget{static getInstance(){return $._instance||($._instance=new $),$._instance}constructor(){super(),this._state=this._loadState(),this._subscribers=new Map,this._batchUpdates=[],this._batchTimeout=null}_loadState(){try{let t=localStorage.getItem($.STORAGE_KEY);if(t){let e=JSON.parse(t);return this._mergeState(mt,e)}}catch(t){console.warn("Failed to load state from localStorage:",t)}return{...mt}}_mergeState(t,e){let i={...t};for(let a of Object.keys(e))a in t&&typeof t[a]=="object"&&!Array.isArray(t[a])?i[a]=this._mergeState(t[a],e[a]):i[a]=e[a];return i}_saveState(){try{let t={ui:this._state.ui,localTasks:this._state.localTasks,preferences:this._state.preferences};localStorage.setItem($.STORAGE_KEY,JSON.stringify(t))}catch(t){console.warn("Failed to save state to localStorage:",t)}}get(t=null){if(!t)return{...this._state};let e=t.split("."),i=this._state;for(let a of e){if(i==null)return;i=i[a]}return i}set(t,e,i=!0){let a=t.split("."),s=a.pop(),r=this._state;for(let n of a)n in r||(r[n]={}),r=r[n];let o=r[s];r[s]=e,i&&this._saveState(),this._notifyChange(t,e,o)}update(t,e=!0){let i=[];for(let[a,s]of Object.entries(t)){let r=this.get(a);this.set(a,s,!1),i.push({path:a,value:s,oldValue:r})}e&&this._saveState();for(let a of i)this._notifyChange(a.path,a.value,a.oldValue)}_notifyChange(t,e,i){this.dispatchEvent(new CustomEvent(bt,{detail:{path:t,value:e,oldValue:i}}));let a=this._subscribers.get(t)||[];for(let r of a)try{r(e,i,t)}catch(o){console.error("State subscriber error:",o)}let s=t.split(".");for(;s.length>1;){s.pop();let r=s.join("."),o=this._subscribers.get(r)||[];for(let n of o)try{n(this.get(r),null,r)}catch(l){console.error("State subscriber error:",l)}}}subscribe(t,e){return this._subscribers.has(t)||this._subscribers.set(t,[]),this._subscribers.get(t).push(e),()=>{let i=this._subscribers.get(t),a=i.indexOf(e);a>-1&&i.splice(a,1)}}reset(t=null){if(t){let e=t.split("."),i=mt;for(let a of e)i=i?.[a];this.set(t,i)}else this._state={...mt},this._saveState(),this.dispatchEvent(new CustomEvent(bt,{detail:{path:null,value:this._state,oldValue:null}}))}addLocalTask(t){let e=this.get("localTasks")||[],i={id:`local-${Date.now()}-${Math.random().toString(36).substr(2,9)}`,createdAt:new Date().toISOString(),status:"pending",...t};return this.set("localTasks",[...e,i]),i}updateLocalTask(t,e){let i=this.get("localTasks")||[],a=i.findIndex(r=>r.id===t);if(a===-1)return null;let s={...i[a],...e,updatedAt:new Date().toISOString()};return i[a]=s,this.set("localTasks",[...i]),s}deleteLocalTask(t){let e=this.get("localTasks")||[];this.set("localTasks",e.filter(i=>i.id!==t))}moveLocalTask(t,e,i=null){let s=(this.get("localTasks")||[]).find(r=>r.id===t);return s?this.updateLocalTask(t,{status:e,position:i??s.position}):null}updateSession(t){this.update(Object.fromEntries(Object.entries(t).map(([e,i])=>[`session.${e}`,i])),!1)}updateCache(t){this.update({"cache.projects":t.projects??this.get("cache.projects"),"cache.tasks":t.tasks??this.get("cache.tasks"),"cache.agents":t.agents??this.get("cache.agents"),"cache.memory":t.memory??this.get("cache.memory"),"cache.lastFetch":new Date().toISOString()},!1)}getMergedTasks(){let t=this.get("cache.tasks")||[],i=(this.get("localTasks")||[]).map(a=>({...a,isLocal:!0}));return[...t,...i]}getTasksByStatus(t){return this.getMergedTasks().filter(e=>e.status===t)}};w($,"STORAGE_KEY","loki-dashboard-state"),w($,"_instance",null);var F=$;function z(){return F.getInstance()}function $t(d){let t=z();return{get:()=>t.get(d),set:e=>t.set(d,e),subscribe:e=>t.subscribe(d,e)}}var j=class extends h{static get observedAttributes(){return["api-url","theme"]}constructor(){super(),this._data={status:"offline",phase:null,iteration:null,provider:null,running_agents:0,pending_tasks:null,uptime_seconds:0,complexity:null,connected:!1},this._api=null,this._pollInterval=null,this._statusUpdateHandler=null,this._connectedHandler=null,this._disconnectedHandler=null,this._checklistSummary=null,this._appRunnerStatus=null,this._playwrightResults=null,this._gateStatus=null}connectedCallback(){super.connectedCallback(),this._setupApi(),this._loadStatus(),this._startPolling(),this._api.connect().catch(()=>{})}disconnectedCallback(){super.disconnectedCallback(),this._stopPolling(),this._api&&(this._statusUpdateHandler&&this._api.removeEventListener(u.STATUS_UPDATE,this._statusUpdateHandler),this._connectedHandler&&this._api.removeEventListener(u.CONNECTED,this._connectedHandler),this._disconnectedHandler&&this._api.removeEventListener(u.DISCONNECTED,this._disconnectedHandler))}attributeChangedCallback(t,e,i){e!==i&&(t==="api-url"&&this._api&&(this._api.baseUrl=i,this._loadStatus()),t==="theme"&&this._applyTheme())}_setupApi(){let t=this.getAttribute("api-url")||window.location.origin;this._api=g({baseUrl:t}),this._statusUpdateHandler=e=>this._updateFromStatus(e.detail),this._connectedHandler=()=>{this._data.connected=!0,this.render()},this._disconnectedHandler=()=>{this._data.connected=!1,this._data.status="offline",this.render()},this._api.addEventListener(u.STATUS_UPDATE,this._statusUpdateHandler),this._api.addEventListener(u.CONNECTED,this._connectedHandler),this._api.addEventListener(u.DISCONNECTED,this._disconnectedHandler)}async _loadStatus(){try{let[t,e,i,a,s]=await Promise.allSettled([this._api.getStatus(),this._api.getChecklistSummary(),this._api.getAppRunnerStatus(),this._api.getPlaywrightResults(),this._api.getCouncilGate()]);t.status==="fulfilled"?this._updateFromStatus(t.value):(this._data.connected=!1,this._data.status="offline"),e.status==="fulfilled"&&(this._checklistSummary=e.value?.summary||null),i.status==="fulfilled"&&(this._appRunnerStatus=i.value),a.status==="fulfilled"&&(this._playwrightResults=a.value),s.status==="fulfilled"&&(this._gateStatus=s.value),this.render()}catch{this._data.connected=!1,this._data.status="offline",this.render()}}_updateFromStatus(t){t&&(this._data={...this._data,connected:!0,status:t.status||"offline",phase:t.phase||null,iteration:t.iteration!=null?t.iteration:null,provider:t.provider||null,running_agents:t.running_agents||0,pending_tasks:t.pending_tasks!=null?t.pending_tasks:null,uptime_seconds:t.uptime_seconds||0,complexity:t.complexity||null})}_startPolling(){this._pollInterval=setInterval(async()=>{try{await this._loadStatus()}catch{this._data.connected=!1,this._data.status="offline",this.render()}},5e3)}_stopPolling(){this._pollInterval&&(clearInterval(this._pollInterval),this._pollInterval=null)}_formatUptime(t){if(!t||t<0)return"--";let e=Math.floor(t/3600),i=Math.floor(t%3600/60),a=Math.floor(t%60);return e>0?`${e}h ${i}m`:i>0?`${i}m ${a}s`:`${a}s`}_getStatusDotClass(){switch(this._data.status){case"running":case"autonomous":return"active";case"paused":return"paused";case"stopped":return"stopped";case"error":return"error";default:return"offline"}}_escapeHtml(t){return t?String(t).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,"""):""}_renderAppRunnerCard(){let t=this._appRunnerStatus;if(!t||t.status==="not_initialized")return`
|
|
1241
1241
|
<div class="overview-card">
|
|
1242
1242
|
<div class="card-label">App Runner</div>
|
|
1243
1243
|
<div class="card-value small-text">${this._data.status==="running"||this._data.status==="autonomous"?"Waiting...":"Not started"}</div>
|
package/docs/INSTALLATION.md
CHANGED
package/mcp/__init__.py
CHANGED