loki-mode 7.102.0 → 7.104.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/loki +29 -15
- package/autonomy/run.sh +65 -9
- package/autonomy/verify.sh +214 -6
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +137 -24
- package/dashboard/static/index.html +57 -5
- package/docs/INSTALLATION.md +1 -1
- package/docs/SONNET5-DEFAULT-PLAN.md +157 -0
- package/loki-ts/data/model-pricing.json +3 -3
- package/loki-ts/dist/loki.js +2 -2
- package/mcp/__init__.py +1 -1
- package/package.json +2 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/providers/claude.sh +15 -9
- package/providers/model_catalog.json +20 -19
- package/vendor/autonomi-verify/LICENSE +39 -0
- package/vendor/autonomi-verify/README.md +58 -0
- package/vendor/autonomi-verify/embed.js +8 -0
package/dashboard/server.py
CHANGED
|
@@ -2549,6 +2549,26 @@ class FocusRequest(BaseModel):
|
|
|
2549
2549
|
# 2x Opus cost; the UI shows that. `None`/empty clears the override.
|
|
2550
2550
|
_SESSION_MODEL_ALLOWLIST = ("haiku", "sonnet", "opus", "fable")
|
|
2551
2551
|
|
|
2552
|
+
# Start-time execution-model + advisor-model allowlist. Deliberately NARROWER
|
|
2553
|
+
# than _SESSION_MODEL_ALLOWLIST: fable is excluded on BOTH the start-time model
|
|
2554
|
+
# picker and the advisor knob. Fable is a 2x-Opus advisory-only model and is not
|
|
2555
|
+
# available as a Claude API dispatch model (the runner collapses it to opus), so
|
|
2556
|
+
# offering it as a start-time execution model or advisor judge would be a cost/
|
|
2557
|
+
# behavior surprise. The mid-run POST /api/session/model path keeps fable (that
|
|
2558
|
+
# allowlist is unchanged) because it is an explicit live-run control.
|
|
2559
|
+
_START_MODEL_ALLOWLIST = ("haiku", "sonnet", "opus")
|
|
2560
|
+
|
|
2561
|
+
|
|
2562
|
+
def _normalize_start_model(raw: str | None) -> str:
|
|
2563
|
+
"""Normalize a start-time model / advisor alias (haiku|sonnet|opus, no fable).
|
|
2564
|
+
|
|
2565
|
+
Same trim + lowercase + exact-match rule as _normalize_session_model, but on
|
|
2566
|
+
the narrower _START_MODEL_ALLOWLIST. Returns "" for absent/invalid/fable so
|
|
2567
|
+
callers can treat empty as "no selection" (engine uses its own default).
|
|
2568
|
+
"""
|
|
2569
|
+
val = (raw or "").strip().lower()
|
|
2570
|
+
return val if val in _START_MODEL_ALLOWLIST else ""
|
|
2571
|
+
|
|
2552
2572
|
|
|
2553
2573
|
class SessionModelRequest(BaseModel):
|
|
2554
2574
|
"""Schema for setting (or clearing) the live run's model override."""
|
|
@@ -2667,22 +2687,27 @@ def _provider_model_fast() -> str:
|
|
|
2667
2687
|
|
|
2668
2688
|
|
|
2669
2689
|
def _provider_model_development() -> str:
|
|
2670
|
-
# claude.sh:
|
|
2690
|
+
# claude.sh:61 -> LOKI_CLAUDE_MODEL_DEVELOPMENT > LOKI_MODEL_DEVELOPMENT > default.
|
|
2691
|
+
# v7.104.0: CLAUDE_DEFAULT_DEVELOPMENT is now "sonnet" (was opus) on BOTH the
|
|
2692
|
+
# stock and the LOKI_ALLOW_HAIKU path (claude.sh:61,65), so the default is
|
|
2693
|
+
# unconditionally sonnet. Keeping this mirror in sync with the sibling
|
|
2694
|
+
# claude.sh change is what stops GET /api/session/model reporting opus on the
|
|
2695
|
+
# default path while the run actually dispatches sonnet.
|
|
2671
2696
|
return (
|
|
2672
2697
|
os.environ.get("LOKI_CLAUDE_MODEL_DEVELOPMENT")
|
|
2673
2698
|
or os.environ.get("LOKI_MODEL_DEVELOPMENT")
|
|
2674
|
-
or
|
|
2699
|
+
or "sonnet"
|
|
2675
2700
|
)
|
|
2676
2701
|
|
|
2677
2702
|
|
|
2678
2703
|
def _provider_model_planning() -> str:
|
|
2679
|
-
# claude.sh:
|
|
2680
|
-
# CLAUDE_DEFAULT_PLANNING is
|
|
2681
|
-
#
|
|
2704
|
+
# claude.sh:60 -> LOKI_CLAUDE_MODEL_PLANNING > LOKI_MODEL_PLANNING > default.
|
|
2705
|
+
# v7.104.0: CLAUDE_DEFAULT_PLANNING is now "sonnet" (was opus) -- Sonnet 5 is
|
|
2706
|
+
# the default execution model. LOKI_ALLOW_HAIKU does not change planning.
|
|
2682
2707
|
return (
|
|
2683
2708
|
os.environ.get("LOKI_CLAUDE_MODEL_PLANNING")
|
|
2684
2709
|
or os.environ.get("LOKI_MODEL_PLANNING")
|
|
2685
|
-
or "
|
|
2710
|
+
or "sonnet"
|
|
2686
2711
|
)
|
|
2687
2712
|
|
|
2688
2713
|
|
|
@@ -2719,23 +2744,37 @@ def _resolve_session_pin(alias: str) -> str:
|
|
|
2719
2744
|
"""Resolve a session-pin alias the way the runner's NO-OVERRIDE path does.
|
|
2720
2745
|
|
|
2721
2746
|
The runner does NOT feed a session pin straight to --model. It maps the alias
|
|
2722
|
-
to an abstract TIER (run.sh:12331 --
|
|
2723
|
-
|
|
2747
|
+
to an abstract TIER (run.sh:12331 -- sonnet->development, haiku->fast,
|
|
2748
|
+
fable->fable; opus is special-cased below) and resolves that tier through
|
|
2724
2749
|
resolve_model_for_tier (claude.sh:353), then applies
|
|
2725
|
-
loki_apply_max_tier_clamp(model, REAL_tier).
|
|
2726
|
-
|
|
2727
|
-
dispatches
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2750
|
+
loki_apply_max_tier_clamp(model, REAL_tier). v7.104.0: with the Sonnet-5
|
|
2751
|
+
default (PROVIDER_MODEL_DEVELOPMENT=sonnet on stock config), a 'sonnet' SESSION
|
|
2752
|
+
pin now dispatches SONNET via the development tier -- matching a 'sonnet'
|
|
2753
|
+
OVERRIDE file. An 'opus' SESSION pin is special-cased to dispatch opus directly
|
|
2754
|
+
(no tier resolves to opus post-flip), clamped by LOKI_MAX_TIER. Use this for
|
|
2755
|
+
the no-override `default`/`effective` derivation so the dashboard reports the
|
|
2756
|
+
model the run actually dispatches on the default path.
|
|
2731
2757
|
|
|
2732
2758
|
SYNC: byte-faithful with run.sh's session-pin case + claude.sh
|
|
2733
2759
|
resolve_model_for_tier + loki_apply_max_tier_clamp, and with the estimator's
|
|
2734
2760
|
_resolve_session_pin in autonomy/loki. Locked by the session-pin parity matrix
|
|
2735
2761
|
in tests/test-model-override.sh.
|
|
2736
2762
|
"""
|
|
2763
|
+
_alias_norm = (alias or "").strip().lower()
|
|
2764
|
+
# v7.104.0 opus-pin fix: mirror run.sh + estimator. Post the Sonnet-5 default
|
|
2765
|
+
# flip, NO tier resolves to opus, so an opus SESSION pin must dispatch opus
|
|
2766
|
+
# directly (not route through planning->sonnet), clamped by LOKI_MAX_TIER.
|
|
2767
|
+
# sonnet/haiku pins stay on the tier route (ALLOW_HAIKU gate preserved).
|
|
2768
|
+
if _alias_norm == "opus":
|
|
2769
|
+
_mt = (os.environ.get("LOKI_MAX_TIER") or "").strip().lower()
|
|
2770
|
+
if not _mt:
|
|
2771
|
+
return "opus"
|
|
2772
|
+
if _mt == "haiku":
|
|
2773
|
+
return _provider_model_fast()
|
|
2774
|
+
if _mt == "sonnet":
|
|
2775
|
+
return _provider_model_development()
|
|
2776
|
+
return "opus"
|
|
2737
2777
|
pin_tier = {
|
|
2738
|
-
"opus": "planning",
|
|
2739
2778
|
"sonnet": "development",
|
|
2740
2779
|
"haiku": "fast",
|
|
2741
2780
|
"fable": "fable",
|
|
@@ -2746,7 +2785,7 @@ def _resolve_session_pin(alias: str) -> str:
|
|
|
2746
2785
|
"planning": "planning",
|
|
2747
2786
|
"development": "development",
|
|
2748
2787
|
"fast": "fast",
|
|
2749
|
-
}.get(
|
|
2788
|
+
}.get(_alias_norm, "development")
|
|
2750
2789
|
if pin_tier == "planning":
|
|
2751
2790
|
model = _provider_model_planning()
|
|
2752
2791
|
elif pin_tier == "fast":
|
|
@@ -2791,10 +2830,11 @@ async def get_session_model():
|
|
|
2791
2830
|
loki_apply_max_tier_clamp(alias, alias). `effective` = _clamp_to_max_tier
|
|
2792
2831
|
(the override-path clamp). A "sonnet" override dispatches sonnet.
|
|
2793
2832
|
- NO override (session pin): the runner maps the pin through a tier
|
|
2794
|
-
(
|
|
2795
|
-
through PROVIDER_MODEL_* (then the cost-ceiling clamp).
|
|
2796
|
-
_resolve_session_pin.
|
|
2797
|
-
PROVIDER_MODEL_DEVELOPMENT=
|
|
2833
|
+
(sonnet->development, haiku->fast; opus special-cased to opus) and
|
|
2834
|
+
resolves the tier through PROVIDER_MODEL_* (then the cost-ceiling clamp).
|
|
2835
|
+
`effective` = _resolve_session_pin. v7.104.0: a "sonnet" pin dispatches
|
|
2836
|
+
SONNET (development tier -> PROVIDER_MODEL_DEVELOPMENT=sonnet on stock
|
|
2837
|
+
config); an "opus" pin dispatches opus (special-cased, clamped).
|
|
2798
2838
|
|
|
2799
2839
|
Both routes resolve through the SAME provider config the runner uses
|
|
2800
2840
|
(LOKI_ALLOW_HAIKU plus the LOKI_CLAUDE_MODEL_PLANNING/FAST/DEVELOPMENT and
|
|
@@ -3044,6 +3084,24 @@ class StartBuildRequest(BaseModel):
|
|
|
3044
3084
|
# The path is path-guarded against ALLOWED_WORKSPACE_ROOTS (see
|
|
3045
3085
|
# _validate_workspace) -- it is NOT a free-form filesystem write target.
|
|
3046
3086
|
workspace: Optional[str] = None
|
|
3087
|
+
# Start-time execution model (haiku|sonnet|opus; fable NOT accepted). When a
|
|
3088
|
+
# valid alias is supplied, the run is pinned to EXACTLY that model for every
|
|
3089
|
+
# iteration via the LOKI_CLAUDE_MODEL_{PLANNING,DEVELOPMENT,FAST} env triple
|
|
3090
|
+
# (see start_build). Absent/invalid -> no pin, the engine uses its own
|
|
3091
|
+
# default (Sonnet 5 as of v7.104.0). This is an EXACT-model pin, NOT the
|
|
3092
|
+
# session-pin tier route: it does not remap through tiers, so an "opus" pick
|
|
3093
|
+
# dispatches opus (the tier route would resolve opus->planning->sonnet on the
|
|
3094
|
+
# v7.104.0 stock config and thus lie). The mid-flight .loki/state/model-override
|
|
3095
|
+
# file is NOT usable at start time (run.sh clears it at ITERATION_COUNT==0 as a
|
|
3096
|
+
# deliberate session-scope reset, run.sh:15623), which is why start-time uses
|
|
3097
|
+
# env, not the override file.
|
|
3098
|
+
model: Optional[str] = None
|
|
3099
|
+
# Advisor / reviewer model (opt-in Opus judge). haiku|sonnet|opus; fable NOT
|
|
3100
|
+
# accepted. Exported as LOKI_ADVISOR_MODEL into the run env; run.sh reads it at
|
|
3101
|
+
# the reviewer-dispatch site (run.sh:10199) to pin the code-review judge while
|
|
3102
|
+
# execution stays on the chosen/default execution model. Absent/invalid -> no
|
|
3103
|
+
# advisor pin (reviewers use the account default).
|
|
3104
|
+
advisor_model: Optional[str] = None
|
|
3047
3105
|
|
|
3048
3106
|
def validate_provider(self) -> None:
|
|
3049
3107
|
"""Validate provider is from the supported list.
|
|
@@ -3331,11 +3389,38 @@ async def start_build(request: Request, body: StartBuildRequest):
|
|
|
3331
3389
|
# engine's own .loki. Setting both LOKI_DIR and LOKI_TARGET_DIR makes the
|
|
3332
3390
|
# workspace authoritative. When no workspace is given, env=None (inherit) so
|
|
3333
3391
|
# behavior is byte-identical to before this feature.
|
|
3392
|
+
# Normalize the optional start-time execution model + advisor model here so we
|
|
3393
|
+
# know whether we need a custom env at all. Both are narrowed to
|
|
3394
|
+
# haiku|sonnet|opus (no fable); invalid/absent -> "" -> no pin.
|
|
3395
|
+
start_model = _normalize_start_model(body.model)
|
|
3396
|
+
advisor_model = _normalize_start_model(body.advisor_model)
|
|
3397
|
+
|
|
3398
|
+
# Build a custom env only when we actually need to change something
|
|
3399
|
+
# (workspace pin, start-time model pin, or advisor pin). When nothing is set,
|
|
3400
|
+
# env stays None (inherit) so behavior is byte-identical to before.
|
|
3334
3401
|
popen_env = None
|
|
3335
|
-
if workspace_dir is not None:
|
|
3402
|
+
if workspace_dir is not None or start_model or advisor_model:
|
|
3336
3403
|
popen_env = dict(os.environ)
|
|
3404
|
+
if workspace_dir is not None:
|
|
3337
3405
|
popen_env["LOKI_TARGET_DIR"] = str(workspace_dir)
|
|
3338
3406
|
popen_env["LOKI_DIR"] = str(loki_dir)
|
|
3407
|
+
if start_model:
|
|
3408
|
+
# EXACT-model pin (not the session-pin tier route): set all three tier
|
|
3409
|
+
# models to the chosen alias so resolve_model_for_tier returns the alias
|
|
3410
|
+
# for every tier and every iteration dispatches exactly the picked model.
|
|
3411
|
+
# This is the honest start-time equivalent of the mid-flight override
|
|
3412
|
+
# file, which run.sh clears at iteration 0. LOKI_SESSION_MODEL is set too
|
|
3413
|
+
# for internal coherence (the run's own tier accounting/logging), but the
|
|
3414
|
+
# env triple is the load-bearing dispatch-honesty mechanism: on the
|
|
3415
|
+
# v7.104.0 stock config the session pin alone would remap opus->planning->
|
|
3416
|
+
# sonnet and haiku->fast->sonnet, dispatching sonnet for both.
|
|
3417
|
+
popen_env["LOKI_CLAUDE_MODEL_PLANNING"] = start_model
|
|
3418
|
+
popen_env["LOKI_CLAUDE_MODEL_DEVELOPMENT"] = start_model
|
|
3419
|
+
popen_env["LOKI_CLAUDE_MODEL_FAST"] = start_model
|
|
3420
|
+
popen_env["LOKI_SESSION_MODEL"] = start_model
|
|
3421
|
+
if advisor_model:
|
|
3422
|
+
# Opt-in Opus (or other) judge for code review; execution model unchanged.
|
|
3423
|
+
popen_env["LOKI_ADVISOR_MODEL"] = advisor_model
|
|
3339
3424
|
try:
|
|
3340
3425
|
process = subprocess.Popen(
|
|
3341
3426
|
args,
|
|
@@ -3384,6 +3469,9 @@ async def start_build(request: Request, body: StartBuildRequest):
|
|
|
3384
3469
|
"provider": body.provider,
|
|
3385
3470
|
"spec": str(spec_file),
|
|
3386
3471
|
"pid": process.pid,
|
|
3472
|
+
# Empty string when no pin was requested (engine uses its default).
|
|
3473
|
+
"model": start_model,
|
|
3474
|
+
"advisor_model": advisor_model,
|
|
3387
3475
|
},
|
|
3388
3476
|
ip_address=request.client.host if request.client else None,
|
|
3389
3477
|
)
|
|
@@ -3416,6 +3504,10 @@ async def start_build(request: Request, body: StartBuildRequest):
|
|
|
3416
3504
|
# The trust run_id if already minted, else "" (see note above). When "",
|
|
3417
3505
|
# derive it from the workspace's .loki/state/trust-run-id.
|
|
3418
3506
|
"run_id": run_id,
|
|
3507
|
+
# The pinned execution + advisor models (empty == engine default). Echoed
|
|
3508
|
+
# so the UI can confirm what the run was actually pinned to.
|
|
3509
|
+
"model": start_model,
|
|
3510
|
+
"advisor_model": advisor_model,
|
|
3419
3511
|
}
|
|
3420
3512
|
|
|
3421
3513
|
|
|
@@ -6731,12 +6823,27 @@ async def get_trust_trajectory():
|
|
|
6731
6823
|
# =============================================================================
|
|
6732
6824
|
|
|
6733
6825
|
_PROVIDER_LABELS = {
|
|
6734
|
-
|
|
6735
|
-
|
|
6826
|
+
# v7.104.0: current model IDs (model_catalog.json): opus=claude-opus-4-8,
|
|
6827
|
+
# sonnet=claude-sonnet-5 (the default execution model), haiku=claude-haiku-4-5.
|
|
6828
|
+
"opus": "Opus 4.8",
|
|
6829
|
+
"sonnet": "Sonnet 5",
|
|
6736
6830
|
"haiku": "Haiku 4.5",
|
|
6737
6831
|
"gpt-5.3-codex": "GPT-5.3 Codex",
|
|
6738
6832
|
}
|
|
6739
6833
|
|
|
6834
|
+
# Display-only pricing notes, keyed by model. These annotate the pricing table in
|
|
6835
|
+
# the UI WITHOUT changing any computed cost number. The Sonnet 5 launch carries an
|
|
6836
|
+
# intro price ($2/$10 per MTok through Aug 31 2026), but the cost estimator keeps
|
|
6837
|
+
# quoting the standard $3/$15 rate: over-estimating the display is the safe
|
|
6838
|
+
# direction (a bill can only come in lower than quoted, never higher). This note
|
|
6839
|
+
# tells the user why the quote is conservative during the intro window.
|
|
6840
|
+
_MODEL_PRICING_NOTES = {
|
|
6841
|
+
"sonnet": (
|
|
6842
|
+
"Intro pricing: $2 / $10 per MTok through Aug 31 2026. "
|
|
6843
|
+
"Estimates use the standard $3 / $15 rate (conservative)."
|
|
6844
|
+
),
|
|
6845
|
+
}
|
|
6846
|
+
|
|
6740
6847
|
_MODEL_PROVIDERS = {
|
|
6741
6848
|
"opus": "claude",
|
|
6742
6849
|
"sonnet": "claude",
|
|
@@ -6775,12 +6882,18 @@ async def get_pricing():
|
|
|
6775
6882
|
pricing_table = _get_model_pricing()
|
|
6776
6883
|
models = {}
|
|
6777
6884
|
for model_key, rates in pricing_table.items():
|
|
6778
|
-
|
|
6885
|
+
entry = {
|
|
6779
6886
|
"input": rates["input"],
|
|
6780
6887
|
"output": rates["output"],
|
|
6781
6888
|
"label": _PROVIDER_LABELS.get(model_key, model_key),
|
|
6782
6889
|
"provider": _MODEL_PROVIDERS.get(model_key, "unknown"),
|
|
6783
6890
|
}
|
|
6891
|
+
# Display-only note (e.g. Sonnet 5 intro pricing). Present only when a note
|
|
6892
|
+
# exists; the input/output numbers above are unchanged (conservative).
|
|
6893
|
+
note = _MODEL_PRICING_NOTES.get(model_key)
|
|
6894
|
+
if note:
|
|
6895
|
+
entry["note"] = note
|
|
6896
|
+
models[model_key] = entry
|
|
6784
6897
|
|
|
6785
6898
|
return {
|
|
6786
6899
|
"provider": provider,
|
|
@@ -2295,7 +2295,7 @@ var LokiDashboard=(()=>{var Re=Object.defineProperty;var _t=Object.getOwnPropert
|
|
|
2295
2295
|
}
|
|
2296
2296
|
|
|
2297
2297
|
${V}
|
|
2298
|
-
`}getAriaPattern(e){return Ie[e]||{}}applyAriaPattern(e,t){let i=this.getAriaPattern(t);for(let[a,s]of Object.entries(i))if(a==="role")e.setAttribute("role",s);else{let r=a.replace(/([A-Z])/g,"-$1").toLowerCase();e.setAttribute(r,s)}}render(){}};var I={realtime:1e3,normal:2e3,background:5e3,offline:1e4},et={vscode:I.normal,browser:I.realtime,cli:I.background},tt={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},f={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"},D=class D extends EventTarget{static getInstance(e={}){let t=e.baseUrl||tt.baseUrl;return D._instances.has(t)||D._instances.set(t,new D(e)),D._instances.get(t)}static clearInstances(){D._instances.forEach(e=>e.disconnect()),D._instances.clear()}constructor(e={}){super(),this.config={...tt,...e},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=et[this._context]||I.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 I}_setupAdaptivePolling(){typeof document>"u"||(this._visibilityChangeHandler=()=>{document.hidden?this._setPollInterval(I.background):this._setPollInterval(et[this._context]||I.normal)},document.addEventListener("visibilitychange",this._visibilityChangeHandler))}_setPollInterval(e){this._currentPollInterval=e,this._pollInterval&&(this.stopPolling(),this.startPolling(null,e))}setPollMode(e){let t=I[e];t&&this._setPollInterval(t)}_setupVSCodeBridge(){if(!(typeof acquireVsCodeApi>"u")){try{this._vscodeApi=acquireVsCodeApi()}catch{console.warn("VS Code API already acquired or unavailable");return}this._messageHandler=e=>{let t=e.data;if(!(!t||!t.type))switch(t.type){case"updateStatus":this._emit(f.STATUS_UPDATE,t.data);break;case"updateTasks":this._emit(f.TASK_UPDATED,t.data);break;case"taskCreated":this._emit(f.TASK_CREATED,t.data);break;case"taskDeleted":this._emit(f.TASK_DELETED,t.data);break;case"projectCreated":this._emit(f.PROJECT_CREATED,t.data);break;case"projectUpdated":this._emit(f.PROJECT_UPDATED,t.data);break;case"agentUpdate":this._emit(f.AGENT_UPDATE,t.data);break;case"logMessage":this._emit(f.LOG_MESSAGE,t.data);break;case"memoryUpdate":this._emit(f.MEMORY_UPDATE,t.data);break;case"connected":this._connected=!0,this._emit(f.CONNECTED,t.data);break;case"disconnected":this._connected=!1,this._emit(f.DISCONNECTED,t.data);break;case"error":this._emit(f.ERROR,t.data);break;case"setPollMode":this.setPollMode(t.data.mode);break;default:this._emit(`api:${t.type}`,t.data)}},window.addEventListener("message",this._messageHandler)}}get isVSCode(){return this._context==="vscode"}postToVSCode(e,t={}){this._vscodeApi&&this._vscodeApi.postMessage({type:e,data:t})}requestRefresh(){this.postToVSCode("requestRefresh")}notifyVSCode(e,t={}){this.postToVSCode("userAction",{action:e,...t})}get baseUrl(){return this.config.baseUrl}set baseUrl(e){this.config.baseUrl=e,this.config.wsUrl=e.replace(/^http/,"ws")+"/ws"}get isConnected(){return this._connected}async connect(){if(!(this._ws&&this._ws.readyState===WebSocket.OPEN))return new Promise((e,t)=>{try{this._ws=new WebSocket(this.config.wsUrl),this._ws.onopen=()=>{this._connected=!0,this._reconnectAttempts=0,this._emit(f.CONNECTED),e()},this._ws.onclose=()=>{this._connected=!1,this._emit(f.DISCONNECTED),this._scheduleReconnect()},this._ws.onerror=i=>{this._emit(f.ERROR,{error:i}),t(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){t(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(f.ERROR,{error:"Max reconnect attempts reached"});return}let e=Math.min(this.config.retryDelay*Math.pow(2,this._reconnectAttempts),3e4);this._reconnectAttempts++,this._reconnectTimeout=setTimeout(()=>{this._reconnectTimeout=null,this.connect().catch(()=>{})},e)}_handleMessage(e){if(e.type==="ping"){this._ws&&this._ws.readyState===WebSocket.OPEN&&this._ws.send(JSON.stringify({type:"pong"}));return}let i={connected:f.CONNECTED,status_update:f.STATUS_UPDATE,task_created:f.TASK_CREATED,task_updated:f.TASK_UPDATED,task_deleted:f.TASK_DELETED,task_moved:f.TASK_UPDATED,project_created:f.PROJECT_CREATED,project_updated:f.PROJECT_UPDATED,agent_update:f.AGENT_UPDATE,log:f.LOG_MESSAGE}[e.type]||`api:${e.type}`;this._emit(i,e.data)}_emit(e,t={}){this.dispatchEvent(new CustomEvent(e,{detail:t}))}async _request(e,t={}){let i=`${this.config.baseUrl}${e}`,a=new AbortController,s=t&&typeof t.timeout=="number"?t.timeout:this.config.timeout,r=setTimeout(()=>a.abort(),s);try{let o=await fetch(i,{...t,signal:a.signal,credentials:"include",headers:{"Content-Type":"application/json",...t.headers}});if(clearTimeout(r),!o.ok){let n=await o.text().catch(()=>""),l=o.statusText||`HTTP ${o.status}`;if(n)try{let c=JSON.parse(n);l=c.detail||c.error||c.message||l}catch{l=n.length>200?n.slice(0,200)+"...":n}throw new Error(l)}return o.status===204?null:await o.json()}catch(o){throw clearTimeout(r),o.name==="AbortError"?new Error("Request timeout"):o}}async _get(e,t=!1){if(t&&this._cache.has(e)){let a=this._cache.get(e);if(Date.now()-a.timestamp<this._cacheTimeout)return a.data}let i=await this._request(e);return t&&this._cache.set(e,{data:i,timestamp:Date.now()}),i}async _post(e,t,i={}){return this._request(e,{method:"POST",body:JSON.stringify(t),...i})}async _put(e,t){return this._request(e,{method:"PUT",body:JSON.stringify(t)})}async _delete(e){return this._request(e,{method:"DELETE"})}async get(e){return this._get(e)}async getStatus(){return this._get("/api/status")}async healthCheck(){return this._get("/health")}async listProjects(e=null){let t=e?`?status=${e}`:"";return this._get(`/api/projects${t}`)}async getProject(e){return this._get(`/api/projects/${e}`)}async createProject(e){return this._post("/api/projects",e)}async updateProject(e,t){return this._put(`/api/projects/${e}`,t)}async deleteProject(e){return this._delete(`/api/projects/${e}`)}async listTasks(e={}){let t=new URLSearchParams;e.projectId&&t.append("project_id",e.projectId),e.status&&t.append("status",e.status),e.priority&&t.append("priority",e.priority);let i=t.toString()?`?${t}`:"";return this._get(`/api/tasks${i}`)}async getTask(e){return this._get(`/api/tasks/${e}`)}async createTask(e){return this._post("/api/tasks",e)}async updateTask(e,t){return this._put(`/api/tasks/${e}`,t)}async moveTask(e,t,i){return this._post(`/api/tasks/${e}/move`,{status:t,position:i})}async deleteTask(e){return this._delete(`/api/tasks/${e}`)}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(e={}){let t=new URLSearchParams(e).toString();return this._get(`/api/memory/episodes${t?"?"+t:""}`)}async getEpisode(e){return this._get(`/api/memory/episodes/${e}`)}async listPatterns(e={}){let t=new URLSearchParams(e).toString();return this._get(`/api/memory/patterns${t?"?"+t:""}`)}async getPattern(e){return this._get(`/api/memory/patterns/${e}`)}async listSkills(){return this._get("/api/memory/skills")}async getSkill(e){return this._get(`/api/memory/skills/${e}`)}async retrieveMemories(e,t=null,i=5){return this._post("/api/memory/retrieve",{query:e,taskType:t,topK:i},{timeout:3e4})}async consolidateMemory(e=24){return this._post("/api/memory/consolidate",{sinceHours:e},{timeout:12e4})}async getTokenEconomics(){return this._get("/api/memory/economics")}async searchMemory(e,t="all",i=20){let a=new URLSearchParams({q:e,collection:t,limit:String(i)});return this._get(`/api/memory/search?${a}`)}async getMemoryStats(){return this._get("/api/memory/stats",!0)}async listRegisteredProjects(e=!1){return this._get(`/api/registry/projects?include_inactive=${e}`)}async registerProject(e,t=null,i=null){return this._post("/api/registry/projects",{path:e,name:t,alias:i})}async discoverProjects(e=3){return this._get(`/api/registry/discover?max_depth=${e}`)}async syncRegistry(){return this._post("/api/registry/sync",{},{timeout:45e3})}async getCrossProjectTasks(e=null){let t=e?`?project_ids=${e.join(",")}`:"";return this._get(`/api/registry/tasks${t}`)}async getLearningMetrics(e={}){let t=new URLSearchParams;e.timeRange&&t.append("timeRange",e.timeRange),e.signalType&&t.append("signalType",e.signalType),e.source&&t.append("source",e.source);let i=t.toString()?`?${t}`:"";return this._get(`/api/learning/metrics${i}`)}async getLearningTrends(e={}){let t=new URLSearchParams;e.timeRange&&t.append("timeRange",e.timeRange),e.signalType&&t.append("signalType",e.signalType),e.source&&t.append("source",e.source);let i=t.toString()?`?${t}`:"";return this._get(`/api/learning/trends${i}`)}async getLearningSignals(e={}){let t=new URLSearchParams;e.timeRange&&t.append("timeRange",e.timeRange),e.signalType&&t.append("signalType",e.signalType),e.source&&t.append("source",e.source),e.limit&&t.append("limit",String(e.limit)),e.offset&&t.append("offset",String(e.offset));let i=t.toString()?`?${t}`:"";return this._get(`/api/learning/signals${i}`)}async getLatestAggregation(){return this._get("/api/learning/aggregation")}async triggerAggregation(e={}){return this._post("/api/learning/aggregate",e,{timeout:6e4})}async getAggregatedPreferences(e=20){return this._get(`/api/learning/preferences?limit=${e}`)}async getAggregatedErrors(e=20){return this._get(`/api/learning/errors?limit=${e}`)}async getAggregatedSuccessPatterns(e=20){return this._get(`/api/learning/success?limit=${e}`)}async getToolEfficiency(e=20){return this._get(`/api/learning/tools?limit=${e}`)}async getCost(){return this._get("/api/cost")}async getPricing(){return this._get("/api/pricing")}async getCouncilState(){return this._get("/api/council/state")}async getCouncilVerdicts(e=20){return this._get(`/api/council/verdicts?limit=${e}`)}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(e,t){let i=new URLSearchParams;e&&i.set("severity",e),t&&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(e){return this._put("/api/notifications/triggers",{triggers:e})}async acknowledgeNotification(e){return this._post("/api/notifications/"+encodeURIComponent(e)+"/acknowledge",{})}async startSession(e,t={}){let i={provider:t.provider||"claude",parallel:!!t.parallel};return t.prdPath?i.prd_path=t.prdPath:i.prd_text=e||"",this._post("/api/control/start",i)}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 getSessionModel(){return this._get("/api/session/model")}async setSessionModel(e){return this._post("/api/session/model",{model:e||null})}async getLogs(e=100){return this._get(`/api/logs?lines=${e}`)}async getChecklist(){return this._get("/api/checklist")}async getChecklistSummary(){return this._get("/api/checklist/summary")}async getPrdObservations(){let e=await fetch(`${this.baseUrl}/api/prd-observations`,{credentials:"include"});if(!e.ok)throw new Error(`HTTP ${e.status}`);return e.text()}async getChecklistWaivers(){return this._get("/api/checklist/waivers")}async addChecklistWaiver(e,t,i="dashboard"){return this._post("/api/checklist/waivers",{item_id:e,reason:t,waived_by:i})}async removeChecklistWaiver(e){return this._delete(`/api/checklist/waivers/${encodeURIComponent(e)}`)}async getCouncilGate(){return this._get("/api/council/gate")}async getAppRunnerStatus(){return this._get("/api/app-runner/status")}async getAppRunnerLogs(e=100){return this._get(`/api/app-runner/logs?lines=${e}`)}async getAppRunnerErrors(e=50){return this._get(`/api/app-runner/errors?lines=${e}`)}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(e,t=null){if(this._pollInterval)return;this._pollCallback=e;let i=async()=>{try{let s=await this.getStatus();this._connected=!0,this._pollCallback&&this._pollCallback(s),this._emit(f.STATUS_UPDATE,s),this._vscodeApi&&this.postToVSCode("pollSuccess",{timestamp:Date.now()})}catch(s){this._connected=!1,this._emit(f.ERROR,{error:s}),this._vscodeApi&&this.postToVSCode("pollError",{error:s.message})}};i();let a=t||this._currentPollInterval||this.config.pollInterval;this._pollInterval=setInterval(i,a)}stopPolling(){this._pollInterval&&(clearInterval(this._pollInterval),this._pollInterval=null)}};S(D,"_instances",new Map);var N=D;function it(d={}){return new N(d)}function h(d={}){return N.getInstance(d)}var Ue="loki-state-change",je={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}},T=class T extends EventTarget{static getInstance(){return T._instance||(T._instance=new T),T._instance}constructor(){super(),this._state=this._loadState(),this._subscribers=new Map,this._batchUpdates=[],this._batchTimeout=null}_loadState(){try{let e=localStorage.getItem(T.STORAGE_KEY);if(e){let t=JSON.parse(e);return this._mergeState(je,t)}}catch(e){console.warn("Failed to load state from localStorage:",e)}return{...je}}_mergeState(e,t){let i={...e};for(let a of Object.keys(t))a in e&&typeof e[a]=="object"&&!Array.isArray(e[a])?i[a]=this._mergeState(e[a],t[a]):i[a]=t[a];return i}_saveState(){try{let e={ui:this._state.ui,localTasks:this._state.localTasks,preferences:this._state.preferences};localStorage.setItem(T.STORAGE_KEY,JSON.stringify(e))}catch(e){console.warn("Failed to save state to localStorage:",e)}}get(e=null){if(!e)return{...this._state};let t=e.split("."),i=this._state;for(let a of t){if(i==null)return;i=i[a]}return i}set(e,t,i=!0){let a=e.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]=t,i&&this._saveState(),this._notifyChange(e,t,o)}update(e,t=!0){let i=[];for(let[a,s]of Object.entries(e)){let r=this.get(a);this.set(a,s,!1),i.push({path:a,value:s,oldValue:r})}t&&this._saveState();for(let a of i)this._notifyChange(a.path,a.value,a.oldValue)}_notifyChange(e,t,i){this.dispatchEvent(new CustomEvent(Ue,{detail:{path:e,value:t,oldValue:i}}));let a=this._subscribers.get(e)||[];for(let r of a)try{r(t,i,e)}catch(o){console.error("State subscriber error:",o)}let s=e.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(e,t){return this._subscribers.has(e)||this._subscribers.set(e,[]),this._subscribers.get(e).push(t),()=>{let i=this._subscribers.get(e),a=i.indexOf(t);a>-1&&i.splice(a,1)}}reset(e=null){if(e){let t=e.split("."),i=je;for(let a of t)i=i?.[a];this.set(e,i)}else this._state={...je},this._saveState(),this.dispatchEvent(new CustomEvent(Ue,{detail:{path:null,value:this._state,oldValue:null}}))}addLocalTask(e){let t=this.get("localTasks")||[],i={id:`local-${Date.now()}-${Math.random().toString(36).substr(2,9)}`,createdAt:new Date().toISOString(),status:"pending",...e};return this.set("localTasks",[...t,i]),i}updateLocalTask(e,t){let i=this.get("localTasks")||[],a=i.findIndex(r=>r.id===e);if(a===-1)return null;let s={...i[a],...t,updatedAt:new Date().toISOString()};return i[a]=s,this.set("localTasks",[...i]),s}deleteLocalTask(e){let t=this.get("localTasks")||[];this.set("localTasks",t.filter(i=>i.id!==e))}moveLocalTask(e,t,i=null){let s=(this.get("localTasks")||[]).find(r=>r.id===e);return s?this.updateLocalTask(e,{status:t,position:i??s.position}):null}updateSession(e){this.update(Object.fromEntries(Object.entries(e).map(([t,i])=>[`session.${t}`,i])),!1)}updateCache(e){this.update({"cache.projects":e.projects??this.get("cache.projects"),"cache.tasks":e.tasks??this.get("cache.tasks"),"cache.agents":e.agents??this.get("cache.agents"),"cache.memory":e.memory??this.get("cache.memory"),"cache.lastFetch":new Date().toISOString()},!1)}getMergedTasks(){let e=this.get("cache.tasks")||[],i=(this.get("localTasks")||[]).map(a=>({...a,isLocal:!0}));return[...e,...i]}getTasksByStatus(e){return this.getMergedTasks().filter(t=>t.status===e)}};S(T,"STORAGE_KEY","loki-dashboard-state"),S(T,"_instance",null);var Y=T;function U(){return Y.getInstance()}function at(d){let e=U();return{get:()=>e.get(d),set:t=>e.set(d,t),subscribe:t=>e.subscribe(d,t)}}function Oe(d){return String(d??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}function C(d){if(d==null||d==="")return"";let e=String(d).replace(/\r\n/g,`
|
|
2298
|
+
`}getAriaPattern(e){return Ie[e]||{}}applyAriaPattern(e,t){let i=this.getAriaPattern(t);for(let[a,s]of Object.entries(i))if(a==="role")e.setAttribute("role",s);else{let r=a.replace(/([A-Z])/g,"-$1").toLowerCase();e.setAttribute(r,s)}}render(){}};var I={realtime:1e3,normal:2e3,background:5e3,offline:1e4},et={vscode:I.normal,browser:I.realtime,cli:I.background},tt={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},f={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"},D=class D extends EventTarget{static getInstance(e={}){let t=e.baseUrl||tt.baseUrl;return D._instances.has(t)||D._instances.set(t,new D(e)),D._instances.get(t)}static clearInstances(){D._instances.forEach(e=>e.disconnect()),D._instances.clear()}constructor(e={}){super(),this.config={...tt,...e},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=et[this._context]||I.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 I}_setupAdaptivePolling(){typeof document>"u"||(this._visibilityChangeHandler=()=>{document.hidden?this._setPollInterval(I.background):this._setPollInterval(et[this._context]||I.normal)},document.addEventListener("visibilitychange",this._visibilityChangeHandler))}_setPollInterval(e){this._currentPollInterval=e,this._pollInterval&&(this.stopPolling(),this.startPolling(null,e))}setPollMode(e){let t=I[e];t&&this._setPollInterval(t)}_setupVSCodeBridge(){if(!(typeof acquireVsCodeApi>"u")){try{this._vscodeApi=acquireVsCodeApi()}catch{console.warn("VS Code API already acquired or unavailable");return}this._messageHandler=e=>{let t=e.data;if(!(!t||!t.type))switch(t.type){case"updateStatus":this._emit(f.STATUS_UPDATE,t.data);break;case"updateTasks":this._emit(f.TASK_UPDATED,t.data);break;case"taskCreated":this._emit(f.TASK_CREATED,t.data);break;case"taskDeleted":this._emit(f.TASK_DELETED,t.data);break;case"projectCreated":this._emit(f.PROJECT_CREATED,t.data);break;case"projectUpdated":this._emit(f.PROJECT_UPDATED,t.data);break;case"agentUpdate":this._emit(f.AGENT_UPDATE,t.data);break;case"logMessage":this._emit(f.LOG_MESSAGE,t.data);break;case"memoryUpdate":this._emit(f.MEMORY_UPDATE,t.data);break;case"connected":this._connected=!0,this._emit(f.CONNECTED,t.data);break;case"disconnected":this._connected=!1,this._emit(f.DISCONNECTED,t.data);break;case"error":this._emit(f.ERROR,t.data);break;case"setPollMode":this.setPollMode(t.data.mode);break;default:this._emit(`api:${t.type}`,t.data)}},window.addEventListener("message",this._messageHandler)}}get isVSCode(){return this._context==="vscode"}postToVSCode(e,t={}){this._vscodeApi&&this._vscodeApi.postMessage({type:e,data:t})}requestRefresh(){this.postToVSCode("requestRefresh")}notifyVSCode(e,t={}){this.postToVSCode("userAction",{action:e,...t})}get baseUrl(){return this.config.baseUrl}set baseUrl(e){this.config.baseUrl=e,this.config.wsUrl=e.replace(/^http/,"ws")+"/ws"}get isConnected(){return this._connected}async connect(){if(!(this._ws&&this._ws.readyState===WebSocket.OPEN))return new Promise((e,t)=>{try{this._ws=new WebSocket(this.config.wsUrl),this._ws.onopen=()=>{this._connected=!0,this._reconnectAttempts=0,this._emit(f.CONNECTED),e()},this._ws.onclose=()=>{this._connected=!1,this._emit(f.DISCONNECTED),this._scheduleReconnect()},this._ws.onerror=i=>{this._emit(f.ERROR,{error:i}),t(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){t(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(f.ERROR,{error:"Max reconnect attempts reached"});return}let e=Math.min(this.config.retryDelay*Math.pow(2,this._reconnectAttempts),3e4);this._reconnectAttempts++,this._reconnectTimeout=setTimeout(()=>{this._reconnectTimeout=null,this.connect().catch(()=>{})},e)}_handleMessage(e){if(e.type==="ping"){this._ws&&this._ws.readyState===WebSocket.OPEN&&this._ws.send(JSON.stringify({type:"pong"}));return}let i={connected:f.CONNECTED,status_update:f.STATUS_UPDATE,task_created:f.TASK_CREATED,task_updated:f.TASK_UPDATED,task_deleted:f.TASK_DELETED,task_moved:f.TASK_UPDATED,project_created:f.PROJECT_CREATED,project_updated:f.PROJECT_UPDATED,agent_update:f.AGENT_UPDATE,log:f.LOG_MESSAGE}[e.type]||`api:${e.type}`;this._emit(i,e.data)}_emit(e,t={}){this.dispatchEvent(new CustomEvent(e,{detail:t}))}async _request(e,t={}){let i=`${this.config.baseUrl}${e}`,a=new AbortController,s=t&&typeof t.timeout=="number"?t.timeout:this.config.timeout,r=setTimeout(()=>a.abort(),s);try{let o=await fetch(i,{...t,signal:a.signal,credentials:"include",headers:{"Content-Type":"application/json",...t.headers}});if(clearTimeout(r),!o.ok){let n=await o.text().catch(()=>""),l=o.statusText||`HTTP ${o.status}`;if(n)try{let c=JSON.parse(n);l=c.detail||c.error||c.message||l}catch{l=n.length>200?n.slice(0,200)+"...":n}throw new Error(l)}return o.status===204?null:await o.json()}catch(o){throw clearTimeout(r),o.name==="AbortError"?new Error("Request timeout"):o}}async _get(e,t=!1){if(t&&this._cache.has(e)){let a=this._cache.get(e);if(Date.now()-a.timestamp<this._cacheTimeout)return a.data}let i=await this._request(e);return t&&this._cache.set(e,{data:i,timestamp:Date.now()}),i}async _post(e,t,i={}){return this._request(e,{method:"POST",body:JSON.stringify(t),...i})}async _put(e,t){return this._request(e,{method:"PUT",body:JSON.stringify(t)})}async _delete(e){return this._request(e,{method:"DELETE"})}async get(e){return this._get(e)}async getStatus(){return this._get("/api/status")}async healthCheck(){return this._get("/health")}async listProjects(e=null){let t=e?`?status=${e}`:"";return this._get(`/api/projects${t}`)}async getProject(e){return this._get(`/api/projects/${e}`)}async createProject(e){return this._post("/api/projects",e)}async updateProject(e,t){return this._put(`/api/projects/${e}`,t)}async deleteProject(e){return this._delete(`/api/projects/${e}`)}async listTasks(e={}){let t=new URLSearchParams;e.projectId&&t.append("project_id",e.projectId),e.status&&t.append("status",e.status),e.priority&&t.append("priority",e.priority);let i=t.toString()?`?${t}`:"";return this._get(`/api/tasks${i}`)}async getTask(e){return this._get(`/api/tasks/${e}`)}async createTask(e){return this._post("/api/tasks",e)}async updateTask(e,t){return this._put(`/api/tasks/${e}`,t)}async moveTask(e,t,i){return this._post(`/api/tasks/${e}/move`,{status:t,position:i})}async deleteTask(e){return this._delete(`/api/tasks/${e}`)}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(e={}){let t=new URLSearchParams(e).toString();return this._get(`/api/memory/episodes${t?"?"+t:""}`)}async getEpisode(e){return this._get(`/api/memory/episodes/${e}`)}async listPatterns(e={}){let t=new URLSearchParams(e).toString();return this._get(`/api/memory/patterns${t?"?"+t:""}`)}async getPattern(e){return this._get(`/api/memory/patterns/${e}`)}async listSkills(){return this._get("/api/memory/skills")}async getSkill(e){return this._get(`/api/memory/skills/${e}`)}async retrieveMemories(e,t=null,i=5){return this._post("/api/memory/retrieve",{query:e,taskType:t,topK:i},{timeout:3e4})}async consolidateMemory(e=24){return this._post("/api/memory/consolidate",{sinceHours:e},{timeout:12e4})}async getTokenEconomics(){return this._get("/api/memory/economics")}async searchMemory(e,t="all",i=20){let a=new URLSearchParams({q:e,collection:t,limit:String(i)});return this._get(`/api/memory/search?${a}`)}async getMemoryStats(){return this._get("/api/memory/stats",!0)}async listRegisteredProjects(e=!1){return this._get(`/api/registry/projects?include_inactive=${e}`)}async registerProject(e,t=null,i=null){return this._post("/api/registry/projects",{path:e,name:t,alias:i})}async discoverProjects(e=3){return this._get(`/api/registry/discover?max_depth=${e}`)}async syncRegistry(){return this._post("/api/registry/sync",{},{timeout:45e3})}async getCrossProjectTasks(e=null){let t=e?`?project_ids=${e.join(",")}`:"";return this._get(`/api/registry/tasks${t}`)}async getLearningMetrics(e={}){let t=new URLSearchParams;e.timeRange&&t.append("timeRange",e.timeRange),e.signalType&&t.append("signalType",e.signalType),e.source&&t.append("source",e.source);let i=t.toString()?`?${t}`:"";return this._get(`/api/learning/metrics${i}`)}async getLearningTrends(e={}){let t=new URLSearchParams;e.timeRange&&t.append("timeRange",e.timeRange),e.signalType&&t.append("signalType",e.signalType),e.source&&t.append("source",e.source);let i=t.toString()?`?${t}`:"";return this._get(`/api/learning/trends${i}`)}async getLearningSignals(e={}){let t=new URLSearchParams;e.timeRange&&t.append("timeRange",e.timeRange),e.signalType&&t.append("signalType",e.signalType),e.source&&t.append("source",e.source),e.limit&&t.append("limit",String(e.limit)),e.offset&&t.append("offset",String(e.offset));let i=t.toString()?`?${t}`:"";return this._get(`/api/learning/signals${i}`)}async getLatestAggregation(){return this._get("/api/learning/aggregation")}async triggerAggregation(e={}){return this._post("/api/learning/aggregate",e,{timeout:6e4})}async getAggregatedPreferences(e=20){return this._get(`/api/learning/preferences?limit=${e}`)}async getAggregatedErrors(e=20){return this._get(`/api/learning/errors?limit=${e}`)}async getAggregatedSuccessPatterns(e=20){return this._get(`/api/learning/success?limit=${e}`)}async getToolEfficiency(e=20){return this._get(`/api/learning/tools?limit=${e}`)}async getCost(){return this._get("/api/cost")}async getPricing(){return this._get("/api/pricing")}async getCouncilState(){return this._get("/api/council/state")}async getCouncilVerdicts(e=20){return this._get(`/api/council/verdicts?limit=${e}`)}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(e,t){let i=new URLSearchParams;e&&i.set("severity",e),t&&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(e){return this._put("/api/notifications/triggers",{triggers:e})}async acknowledgeNotification(e){return this._post("/api/notifications/"+encodeURIComponent(e)+"/acknowledge",{})}async startSession(e,t={}){let i={provider:t.provider||"claude",parallel:!!t.parallel};return t.prdPath?i.prd_path=t.prdPath:i.prd_text=e||"",t.model&&(i.model=t.model),t.advisorModel&&(i.advisor_model=t.advisorModel),this._post("/api/control/start",i)}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 getSessionModel(){return this._get("/api/session/model")}async setSessionModel(e){return this._post("/api/session/model",{model:e||null})}async getLogs(e=100){return this._get(`/api/logs?lines=${e}`)}async getChecklist(){return this._get("/api/checklist")}async getChecklistSummary(){return this._get("/api/checklist/summary")}async getPrdObservations(){let e=await fetch(`${this.baseUrl}/api/prd-observations`,{credentials:"include"});if(!e.ok)throw new Error(`HTTP ${e.status}`);return e.text()}async getChecklistWaivers(){return this._get("/api/checklist/waivers")}async addChecklistWaiver(e,t,i="dashboard"){return this._post("/api/checklist/waivers",{item_id:e,reason:t,waived_by:i})}async removeChecklistWaiver(e){return this._delete(`/api/checklist/waivers/${encodeURIComponent(e)}`)}async getCouncilGate(){return this._get("/api/council/gate")}async getAppRunnerStatus(){return this._get("/api/app-runner/status")}async getAppRunnerLogs(e=100){return this._get(`/api/app-runner/logs?lines=${e}`)}async getAppRunnerErrors(e=50){return this._get(`/api/app-runner/errors?lines=${e}`)}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(e,t=null){if(this._pollInterval)return;this._pollCallback=e;let i=async()=>{try{let s=await this.getStatus();this._connected=!0,this._pollCallback&&this._pollCallback(s),this._emit(f.STATUS_UPDATE,s),this._vscodeApi&&this.postToVSCode("pollSuccess",{timestamp:Date.now()})}catch(s){this._connected=!1,this._emit(f.ERROR,{error:s}),this._vscodeApi&&this.postToVSCode("pollError",{error:s.message})}};i();let a=t||this._currentPollInterval||this.config.pollInterval;this._pollInterval=setInterval(i,a)}stopPolling(){this._pollInterval&&(clearInterval(this._pollInterval),this._pollInterval=null)}};S(D,"_instances",new Map);var N=D;function it(d={}){return new N(d)}function h(d={}){return N.getInstance(d)}var Ue="loki-state-change",je={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}},T=class T extends EventTarget{static getInstance(){return T._instance||(T._instance=new T),T._instance}constructor(){super(),this._state=this._loadState(),this._subscribers=new Map,this._batchUpdates=[],this._batchTimeout=null}_loadState(){try{let e=localStorage.getItem(T.STORAGE_KEY);if(e){let t=JSON.parse(e);return this._mergeState(je,t)}}catch(e){console.warn("Failed to load state from localStorage:",e)}return{...je}}_mergeState(e,t){let i={...e};for(let a of Object.keys(t))a in e&&typeof e[a]=="object"&&!Array.isArray(e[a])?i[a]=this._mergeState(e[a],t[a]):i[a]=t[a];return i}_saveState(){try{let e={ui:this._state.ui,localTasks:this._state.localTasks,preferences:this._state.preferences};localStorage.setItem(T.STORAGE_KEY,JSON.stringify(e))}catch(e){console.warn("Failed to save state to localStorage:",e)}}get(e=null){if(!e)return{...this._state};let t=e.split("."),i=this._state;for(let a of t){if(i==null)return;i=i[a]}return i}set(e,t,i=!0){let a=e.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]=t,i&&this._saveState(),this._notifyChange(e,t,o)}update(e,t=!0){let i=[];for(let[a,s]of Object.entries(e)){let r=this.get(a);this.set(a,s,!1),i.push({path:a,value:s,oldValue:r})}t&&this._saveState();for(let a of i)this._notifyChange(a.path,a.value,a.oldValue)}_notifyChange(e,t,i){this.dispatchEvent(new CustomEvent(Ue,{detail:{path:e,value:t,oldValue:i}}));let a=this._subscribers.get(e)||[];for(let r of a)try{r(t,i,e)}catch(o){console.error("State subscriber error:",o)}let s=e.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(e,t){return this._subscribers.has(e)||this._subscribers.set(e,[]),this._subscribers.get(e).push(t),()=>{let i=this._subscribers.get(e),a=i.indexOf(t);a>-1&&i.splice(a,1)}}reset(e=null){if(e){let t=e.split("."),i=je;for(let a of t)i=i?.[a];this.set(e,i)}else this._state={...je},this._saveState(),this.dispatchEvent(new CustomEvent(Ue,{detail:{path:null,value:this._state,oldValue:null}}))}addLocalTask(e){let t=this.get("localTasks")||[],i={id:`local-${Date.now()}-${Math.random().toString(36).substr(2,9)}`,createdAt:new Date().toISOString(),status:"pending",...e};return this.set("localTasks",[...t,i]),i}updateLocalTask(e,t){let i=this.get("localTasks")||[],a=i.findIndex(r=>r.id===e);if(a===-1)return null;let s={...i[a],...t,updatedAt:new Date().toISOString()};return i[a]=s,this.set("localTasks",[...i]),s}deleteLocalTask(e){let t=this.get("localTasks")||[];this.set("localTasks",t.filter(i=>i.id!==e))}moveLocalTask(e,t,i=null){let s=(this.get("localTasks")||[]).find(r=>r.id===e);return s?this.updateLocalTask(e,{status:t,position:i??s.position}):null}updateSession(e){this.update(Object.fromEntries(Object.entries(e).map(([t,i])=>[`session.${t}`,i])),!1)}updateCache(e){this.update({"cache.projects":e.projects??this.get("cache.projects"),"cache.tasks":e.tasks??this.get("cache.tasks"),"cache.agents":e.agents??this.get("cache.agents"),"cache.memory":e.memory??this.get("cache.memory"),"cache.lastFetch":new Date().toISOString()},!1)}getMergedTasks(){let e=this.get("cache.tasks")||[],i=(this.get("localTasks")||[]).map(a=>({...a,isLocal:!0}));return[...e,...i]}getTasksByStatus(e){return this.getMergedTasks().filter(t=>t.status===e)}};S(T,"STORAGE_KEY","loki-dashboard-state"),S(T,"_instance",null);var Y=T;function U(){return Y.getInstance()}function at(d){let e=U();return{get:()=>e.get(d),set:t=>e.set(d,t),subscribe:t=>e.subscribe(d,t)}}function Oe(d){return String(d??"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}function C(d){if(d==null||d==="")return"";let e=String(d).replace(/\r\n/g,`
|
|
2299
2299
|
`),t=[];e=e.replace(/```[^\n]*\n([\s\S]*?)```/g,(u,m)=>{let g=t.length;return t.push(`<pre class="md-code"><code>${Oe(m.replace(/\n$/,""))}</code></pre>`),`\0CODEBLOCK${g}\0`});let i=Oe(e);i=i.replace(/`([^`\n]+)`/g,(u,m)=>`<code class="md-inline-code">${m}</code>`),i=i.replace(/\[([^\]\n]+)\]\(([^)\s]+)\)/g,(u,m,g)=>/^(https?:|mailto:|\/|#|\.)/.test(g)?`<a class="md-link" href="${g}" target="_blank" rel="noopener noreferrer">${m}</a>`:u);let a=i.split(`
|
|
2300
2300
|
`),s=[],r=[],o=[],n=()=>{r.length&&(s.push(`<p class="md-p">${r.join("<br>")}</p>`),r=[])},l=u=>{for(;o.length&&o[o.length-1].indent>=u;)s.push(`</${o.pop().tag}>`)},c=()=>{for(;o.length;)s.push(`</${o.pop().tag}>`)};for(let u of a){let m=u,g=m.trim(),x=g.match(/^CODEBLOCK(\d+)$/);if(x){n(),c(),s.push(t[Number(x[1])]);continue}if(g===""){n(),c();continue}let k=g.match(/^(#{1,6})\s+(.+)$/);if(k){n(),c();let M=Math.min(k[1].length+1,6);s.push(`<h${M} class="md-h${M}">${k[2]}</h${M}>`);continue}if(/^(-{3,}|\*{3,}|_{3,})$/.test(g)){n(),c(),s.push('<hr class="md-hr">');continue}let _=g.match(/^>\s?(.*)$/);if(_){n(),c(),s.push(`<blockquote class="md-quote">${_[1]}</blockquote>`);continue}let H=m.match(/^(\s*)/)[1].replace(/\t/g," ").length,J=g.match(/^([-*+])\s+(.+)$/),G=g.match(/^(\d+)[.)]\s+(.+)$/);if(J||G){n();let M=G?"ol":"ul",xt=J?J[2]:G[2];!o.length||H>o[o.length-1].indent?(s.push(`<${M} class="md-list">`),o.push({tag:M,indent:H})):(l(H+1),o.length||(s.push(`<${M} class="md-list">`),o.push({tag:M,indent:H}))),s.push(`<li>${xt}</li>`);continue}c(),r.push(m.trim())}n(),c();let p=s.join(`
|
|
2301
2301
|
`);return p=p.replace(/\*\*([^*\n]+)\*\*/g,"<strong>$1</strong>"),p=p.replace(/__([^_\n]+)__/g,"<strong>$1</strong>"),p=p.replace(/(^|[^*])\*([^*\n]+)\*/g,"$1<em>$2</em>"),p}var L=`
|
|
@@ -3755,7 +3755,7 @@ var LokiDashboard=(()=>{var Re=Object.defineProperty;var _t=Object.getOwnPropert
|
|
|
3755
3755
|
</div>
|
|
3756
3756
|
`}).join("")}
|
|
3757
3757
|
</div>
|
|
3758
|
-
`}}_attachEventListeners(){let e=this.shadowRoot.getElementById("refresh-btn");e&&e.addEventListener("click",()=>this._loadTasks());let t=this.shadowRoot.getElementById("bulk-toggle-btn");t&&t.addEventListener("click",()=>this._toggleBulkMode()),this.shadowRoot.querySelectorAll(".filter-pill").forEach(r=>{r.addEventListener("click",()=>this._setFilter(r.dataset.filter))});let i=this.shadowRoot.getElementById("task-search");i&&i.addEventListener("input",r=>this._setSearch(r.target.value)),this.shadowRoot.querySelectorAll(".show-more-btn").forEach(r=>{r.addEventListener("click",()=>this._showMore(r.dataset.showMore))}),this.shadowRoot.querySelectorAll(".bulk-btn").forEach(r=>{r.addEventListener("click",()=>{let o=r.dataset.bulkAction;o==="delete"?this._bulkDelete():this._bulkMove(o)})}),this.shadowRoot.querySelectorAll(".add-task-btn").forEach(r=>{r.addEventListener("click",()=>{this._openAddTaskModal(r.dataset.status)})}),this.shadowRoot.querySelectorAll(".task-checkbox").forEach(r=>{r.addEventListener("click",o=>{o.stopPropagation(),this._toggleTaskSelection(r.dataset.checkId,o)})}),this.shadowRoot.querySelectorAll(".expand-toggle").forEach(r=>{r.addEventListener("click",o=>{o.stopPropagation(),this._toggleCardExpand(r.dataset.expandId)})}),this.shadowRoot.querySelectorAll(".task-card").forEach(r=>{let o=r.dataset.taskId,n=this._tasks.find(l=>l.id.toString()===o);n&&(r.addEventListener("click",l=>{if(this._bulkMode){this._toggleTaskSelection(o,l);return}this._openTaskDetail(n)}),r.addEventListener("keydown",l=>{l.key==="Enter"||l.key===" "?(l.preventDefault(),this._bulkMode?this._toggleTaskSelection(o,l):this._openTaskDetail(n)):(l.key==="ArrowDown"||l.key==="ArrowUp")&&(l.preventDefault(),this._navigateTaskCards(r,l.key==="ArrowDown"?"next":"prev"))}),r.classList.contains("draggable")&&(r.addEventListener("dragstart",l=>this._handleDragStart(l,n)),r.addEventListener("dragend",l=>this._handleDragEnd(l))))}),this.shadowRoot.querySelectorAll(".kanban-tasks").forEach(r=>{r.addEventListener("dragover",o=>this._handleDragOver(o)),r.addEventListener("dragenter",o=>this._handleDragEnter(o)),r.addEventListener("dragleave",o=>this._handleDragLeave(o)),r.addEventListener("drop",o=>this._handleDrop(o,r.dataset.status))});let a=this.shadowRoot.getElementById("modal-close-btn");a&&a.addEventListener("click",()=>this._closeTaskDetail());let s=this.shadowRoot.getElementById("task-detail-overlay");s&&s.addEventListener("click",r=>{r.target===s&&this._closeTaskDetail()})}_escapeHtml(e){let t=document.createElement("div");return t.textContent=e,t.innerHTML}_navigateTaskCards(e,t){let i=Array.from(this.shadowRoot.querySelectorAll(".task-card")),a=i.indexOf(e);if(a===-1)return;let s=t==="next"?a+1:a-1;s>=0&&s<i.length&&i[s].focus()}};customElements.get("loki-task-board")||customElements.define("loki-task-board",Q);var X=class extends v{static get observedAttributes(){return["api-url","theme","compact"]}constructor(){super(),this._status={mode:"offline",phase:null,iteration:null,complexity:null,connected:!1,version:null,uptime:0,activeAgents:0,pendingTasks:0},this._model={override:null,default:"sonnet",effective:"sonnet",notice:""},this._modelBusy=!1,this._startBusy=!1,this._startNotice="",this._specText="",this._api=null,this._state=U(),this._statusUpdateHandler=null,this._connectedHandler=null,this._disconnectedHandler=null}connectedCallback(){super.connectedCallback(),this._setupApi(),this._loadStatus(),this._loadModel(),this._startPolling()}disconnectedCallback(){super.disconnectedCallback(),this._stopPolling(),this._teardownApiListeners()}_teardownApiListeners(){this._api&&(this._statusUpdateHandler&&this._api.removeEventListener(f.STATUS_UPDATE,this._statusUpdateHandler),this._connectedHandler&&this._api.removeEventListener(f.CONNECTED,this._connectedHandler),this._disconnectedHandler&&this._api.removeEventListener(f.DISCONNECTED,this._disconnectedHandler))}attributeChangedCallback(e,t,i){t!==i&&(e==="api-url"&&this._api&&(this._teardownApiListeners(),this._setupApi(),this._loadStatus(),this._loadModel()),e==="theme"&&this._applyTheme(),e==="compact"&&this.render())}_setupApi(){let e=this.getAttribute("api-url")||window.location.origin;this._api=h({baseUrl:e}),this._statusUpdateHandler=t=>this._updateFromStatus(t.detail),this._connectedHandler=()=>{this._status.connected=!0,this.render()},this._disconnectedHandler=()=>{this._status.connected=!1,this._status.mode="offline",this.render()},this._api.addEventListener(f.STATUS_UPDATE,this._statusUpdateHandler),this._api.addEventListener(f.CONNECTED,this._connectedHandler),this._api.addEventListener(f.DISCONNECTED,this._disconnectedHandler)}async _loadStatus(){let e=this._api;try{let t=await e.getStatus();if(e!==this._api)return;this._updateFromStatus(t)}catch{if(e!==this._api)return;this._status.connected=!1,this._status.mode="offline",this.render()}}_updateFromStatus(e){e&&(this._status={...this._status,connected:!0,mode:e.status||"running",version:e.version,uptime:e.uptime_seconds||0,activeAgents:e.running_agents||0,pendingTasks:e.pending_tasks||0,phase:e.phase,iteration:e.iteration,complexity:e.complexity},this._state.updateSession({connected:!0,mode:this._status.mode,lastSync:new Date().toISOString()}),this.render())}_startPolling(){this._poll=b({loadFn:()=>this._loadStatus(),intervalMs:3e3,sectionId:null,immediate:!1})}_stopPolling(){this._poll&&(this._poll.stop(),this._poll=null)}_formatUptime(e){if(!e||e<0)return"--";let t=Math.floor(e/3600),i=Math.floor(e%3600/60),a=Math.floor(e%60);return t>0?`${t}h ${i}m`:i>0?`${i}m ${a}s`:`${a}s`}_escapeHtml(e){let t=document.createElement("div");return t.textContent=String(e??""),t.innerHTML}_getStatusClass(){switch(this._status.mode){case"running":case"autonomous":return"active";case"paused":return"paused";case"stopped":return"stopped";case"error":return"error";default:return"offline"}}_getStatusLabel(){switch(this._status.mode){case"running":case"autonomous":return"AUTONOMOUS";case"paused":return"PAUSED";case"stopped":return"STOPPED";case"error":return"ERROR";default:return"OFFLINE"}}async _triggerStart(){if(this._startBusy)return;let e=(this._specText||"").trim();if(!e){this._startNotice="Enter a spec or one-line brief to start a build.",this.render();return}if(!this._api||typeof this._api.startSession!="function"){this._startNotice="Start is not available on this server.",this.render();return}this._startBusy=!0,this._startNotice="Starting build...",this.render();try{let t=await this._api.startSession(e,{provider:this._status.provider||"claude"});if(t&&t.error)throw new Error(t.error);this._startBusy=!1,this._startNotice="",this._specText="",this._status.mode="running",this._status.connected=!0,this.render(),this._loadStatus(),this.dispatchEvent(new CustomEvent("session-start",{detail:{...this._status,pid:t&&t.pid,spec:t&&t.spec}}))}catch(t){console.error("Failed to start build:",t),this._startBusy=!1,this._startNotice=t&&t.message?`Could not start: ${t.message}`:"Could not start the build. Try again.",this.render()}}_onSpecInput(e){this._specText=e}async _triggerPause(){try{let e=await this._api.pauseSession();if(e&&e.error)throw new Error(e.error);this._status.mode="paused",this.render(),this.dispatchEvent(new CustomEvent("session-pause",{detail:this._status}))}catch(e){console.error("Failed to pause session:",e),this.render()}}async _triggerResume(){try{let e=await this._api.resumeSession();if(e&&e.error)throw new Error(e.error);this._status.mode="running",this.render(),this.dispatchEvent(new CustomEvent("session-resume",{detail:this._status}))}catch(e){console.error("Failed to resume session:",e),this.render()}}async _triggerStop(){try{let e=await this._api.stopSession();if(e&&e.error)throw new Error(e.error);this._status.mode="stopped",this.render(),this.dispatchEvent(new CustomEvent("session-stop",{detail:this._status}))}catch(e){console.error("Failed to stop session:",e),this.render()}}async _loadModel(){if(!this._api||typeof this._api.getSessionModel!="function")return;let e=this._api;try{let t=await e.getSessionModel();if(e!==this._api)return;t&&!t.error&&(this._model={...this._model,override:t.override??null,default:t.default||"sonnet",effective:t.effective||t.default||"sonnet"},this.render())}catch{}}async _onModelChange(e){if(this._modelBusy)return;this._modelBusy=!0;let t=e===""?null:e;try{let i=await this._api.setSessionModel(t);if(i&&i.error)throw new Error(i.error);this._model.override=t,this._model.notice=t?`Switching to ${t}. Applies from the next iteration, for the current run only.`:"Override cleared. Reverts to the tier mapping from the next iteration.",this._modelBusy=!1,await this._loadModel()}catch(i){console.error("Failed to set session model:",i),this._model.notice="Could not change the model. Try again.",this._modelBusy=!1,this.render()}}_renderModelControl(){let e=this._model.override||"",i=[{value:"",label:`Default (tier: ${this._escapeHtml(this._model.default)})`},{value:"haiku",label:"Haiku (fastest, cheapest)"},{value:"sonnet",label:"Sonnet (balanced)"},{value:"opus",label:"Opus (top coding)"},{value:"fable",label:"Fable 5 (2x Opus cost: $10/$50 per MTok)"}].map(s=>{let r=s.value===e?" selected":"";return`<option value="${this._escapeHtml(s.value)}"${r}>${this._escapeHtml(s.label)}</option>`}).join(""),a=this._model.effective==="fable";return`
|
|
3758
|
+
`}}_attachEventListeners(){let e=this.shadowRoot.getElementById("refresh-btn");e&&e.addEventListener("click",()=>this._loadTasks());let t=this.shadowRoot.getElementById("bulk-toggle-btn");t&&t.addEventListener("click",()=>this._toggleBulkMode()),this.shadowRoot.querySelectorAll(".filter-pill").forEach(r=>{r.addEventListener("click",()=>this._setFilter(r.dataset.filter))});let i=this.shadowRoot.getElementById("task-search");i&&i.addEventListener("input",r=>this._setSearch(r.target.value)),this.shadowRoot.querySelectorAll(".show-more-btn").forEach(r=>{r.addEventListener("click",()=>this._showMore(r.dataset.showMore))}),this.shadowRoot.querySelectorAll(".bulk-btn").forEach(r=>{r.addEventListener("click",()=>{let o=r.dataset.bulkAction;o==="delete"?this._bulkDelete():this._bulkMove(o)})}),this.shadowRoot.querySelectorAll(".add-task-btn").forEach(r=>{r.addEventListener("click",()=>{this._openAddTaskModal(r.dataset.status)})}),this.shadowRoot.querySelectorAll(".task-checkbox").forEach(r=>{r.addEventListener("click",o=>{o.stopPropagation(),this._toggleTaskSelection(r.dataset.checkId,o)})}),this.shadowRoot.querySelectorAll(".expand-toggle").forEach(r=>{r.addEventListener("click",o=>{o.stopPropagation(),this._toggleCardExpand(r.dataset.expandId)})}),this.shadowRoot.querySelectorAll(".task-card").forEach(r=>{let o=r.dataset.taskId,n=this._tasks.find(l=>l.id.toString()===o);n&&(r.addEventListener("click",l=>{if(this._bulkMode){this._toggleTaskSelection(o,l);return}this._openTaskDetail(n)}),r.addEventListener("keydown",l=>{l.key==="Enter"||l.key===" "?(l.preventDefault(),this._bulkMode?this._toggleTaskSelection(o,l):this._openTaskDetail(n)):(l.key==="ArrowDown"||l.key==="ArrowUp")&&(l.preventDefault(),this._navigateTaskCards(r,l.key==="ArrowDown"?"next":"prev"))}),r.classList.contains("draggable")&&(r.addEventListener("dragstart",l=>this._handleDragStart(l,n)),r.addEventListener("dragend",l=>this._handleDragEnd(l))))}),this.shadowRoot.querySelectorAll(".kanban-tasks").forEach(r=>{r.addEventListener("dragover",o=>this._handleDragOver(o)),r.addEventListener("dragenter",o=>this._handleDragEnter(o)),r.addEventListener("dragleave",o=>this._handleDragLeave(o)),r.addEventListener("drop",o=>this._handleDrop(o,r.dataset.status))});let a=this.shadowRoot.getElementById("modal-close-btn");a&&a.addEventListener("click",()=>this._closeTaskDetail());let s=this.shadowRoot.getElementById("task-detail-overlay");s&&s.addEventListener("click",r=>{r.target===s&&this._closeTaskDetail()})}_escapeHtml(e){let t=document.createElement("div");return t.textContent=e,t.innerHTML}_navigateTaskCards(e,t){let i=Array.from(this.shadowRoot.querySelectorAll(".task-card")),a=i.indexOf(e);if(a===-1)return;let s=t==="next"?a+1:a-1;s>=0&&s<i.length&&i[s].focus()}};customElements.get("loki-task-board")||customElements.define("loki-task-board",Q);var X=class extends v{static get observedAttributes(){return["api-url","theme","compact"]}constructor(){super(),this._status={mode:"offline",phase:null,iteration:null,complexity:null,connected:!1,version:null,uptime:0,activeAgents:0,pendingTasks:0},this._model={override:null,default:"sonnet",effective:"sonnet",notice:""},this._modelBusy=!1,this._startBusy=!1,this._startNotice="",this._specText="",this._startModel="",this._advisorModel="",this._api=null,this._state=U(),this._statusUpdateHandler=null,this._connectedHandler=null,this._disconnectedHandler=null}connectedCallback(){super.connectedCallback(),this._setupApi(),this._loadStatus(),this._loadModel(),this._startPolling()}disconnectedCallback(){super.disconnectedCallback(),this._stopPolling(),this._teardownApiListeners()}_teardownApiListeners(){this._api&&(this._statusUpdateHandler&&this._api.removeEventListener(f.STATUS_UPDATE,this._statusUpdateHandler),this._connectedHandler&&this._api.removeEventListener(f.CONNECTED,this._connectedHandler),this._disconnectedHandler&&this._api.removeEventListener(f.DISCONNECTED,this._disconnectedHandler))}attributeChangedCallback(e,t,i){t!==i&&(e==="api-url"&&this._api&&(this._teardownApiListeners(),this._setupApi(),this._loadStatus(),this._loadModel()),e==="theme"&&this._applyTheme(),e==="compact"&&this.render())}_setupApi(){let e=this.getAttribute("api-url")||window.location.origin;this._api=h({baseUrl:e}),this._statusUpdateHandler=t=>this._updateFromStatus(t.detail),this._connectedHandler=()=>{this._status.connected=!0,this.render()},this._disconnectedHandler=()=>{this._status.connected=!1,this._status.mode="offline",this.render()},this._api.addEventListener(f.STATUS_UPDATE,this._statusUpdateHandler),this._api.addEventListener(f.CONNECTED,this._connectedHandler),this._api.addEventListener(f.DISCONNECTED,this._disconnectedHandler)}async _loadStatus(){let e=this._api;try{let t=await e.getStatus();if(e!==this._api)return;this._updateFromStatus(t)}catch{if(e!==this._api)return;this._status.connected=!1,this._status.mode="offline",this.render()}}_updateFromStatus(e){e&&(this._status={...this._status,connected:!0,mode:e.status||"running",version:e.version,uptime:e.uptime_seconds||0,activeAgents:e.running_agents||0,pendingTasks:e.pending_tasks||0,phase:e.phase,iteration:e.iteration,complexity:e.complexity},this._state.updateSession({connected:!0,mode:this._status.mode,lastSync:new Date().toISOString()}),this.render())}_startPolling(){this._poll=b({loadFn:()=>this._loadStatus(),intervalMs:3e3,sectionId:null,immediate:!1})}_stopPolling(){this._poll&&(this._poll.stop(),this._poll=null)}_formatUptime(e){if(!e||e<0)return"--";let t=Math.floor(e/3600),i=Math.floor(e%3600/60),a=Math.floor(e%60);return t>0?`${t}h ${i}m`:i>0?`${i}m ${a}s`:`${a}s`}_escapeHtml(e){let t=document.createElement("div");return t.textContent=String(e??""),t.innerHTML}_getStatusClass(){switch(this._status.mode){case"running":case"autonomous":return"active";case"paused":return"paused";case"stopped":return"stopped";case"error":return"error";default:return"offline"}}_getStatusLabel(){switch(this._status.mode){case"running":case"autonomous":return"AUTONOMOUS";case"paused":return"PAUSED";case"stopped":return"STOPPED";case"error":return"ERROR";default:return"OFFLINE"}}async _triggerStart(){if(this._startBusy)return;let e=(this._specText||"").trim();if(!e){this._startNotice="Enter a spec or one-line brief to start a build.",this.render();return}if(!this._api||typeof this._api.startSession!="function"){this._startNotice="Start is not available on this server.",this.render();return}this._startBusy=!0,this._startNotice="Starting build...",this.render();try{let t=await this._api.startSession(e,{provider:this._status.provider||"claude",model:this._startModel||"",advisorModel:this._advisorModel||""});if(t&&t.error)throw new Error(t.error);this._startBusy=!1,this._startNotice="",this._specText="",this._status.mode="running",this._status.connected=!0,this.render(),this._loadStatus(),this.dispatchEvent(new CustomEvent("session-start",{detail:{...this._status,pid:t&&t.pid,spec:t&&t.spec}}))}catch(t){console.error("Failed to start build:",t),this._startBusy=!1,this._startNotice=t&&t.message?`Could not start: ${t.message}`:"Could not start the build. Try again.",this.render()}}_onSpecInput(e){this._specText=e}async _triggerPause(){try{let e=await this._api.pauseSession();if(e&&e.error)throw new Error(e.error);this._status.mode="paused",this.render(),this.dispatchEvent(new CustomEvent("session-pause",{detail:this._status}))}catch(e){console.error("Failed to pause session:",e),this.render()}}async _triggerResume(){try{let e=await this._api.resumeSession();if(e&&e.error)throw new Error(e.error);this._status.mode="running",this.render(),this.dispatchEvent(new CustomEvent("session-resume",{detail:this._status}))}catch(e){console.error("Failed to resume session:",e),this.render()}}async _triggerStop(){try{let e=await this._api.stopSession();if(e&&e.error)throw new Error(e.error);this._status.mode="stopped",this.render(),this.dispatchEvent(new CustomEvent("session-stop",{detail:this._status}))}catch(e){console.error("Failed to stop session:",e),this.render()}}async _loadModel(){if(!this._api||typeof this._api.getSessionModel!="function")return;let e=this._api;try{let t=await e.getSessionModel();if(e!==this._api)return;t&&!t.error&&(this._model={...this._model,override:t.override??null,default:t.default||"sonnet",effective:t.effective||t.default||"sonnet"},this.render())}catch{}}async _onModelChange(e){if(this._modelBusy)return;this._modelBusy=!0;let t=e===""?null:e;try{let i=await this._api.setSessionModel(t);if(i&&i.error)throw new Error(i.error);this._model.override=t,this._model.notice=t?`Switching to ${t}. Applies from the next iteration, for the current run only.`:"Override cleared. Reverts to the tier mapping from the next iteration.",this._modelBusy=!1,await this._loadModel()}catch(i){console.error("Failed to set session model:",i),this._model.notice="Could not change the model. Try again.",this._modelBusy=!1,this.render()}}_renderModelControl(){let e=this._model.override||"",i=[{value:"",label:`Default (tier: ${this._escapeHtml(this._model.default)})`},{value:"haiku",label:"Haiku (fastest, cheapest)"},{value:"sonnet",label:"Sonnet 5 (balanced)"},{value:"opus",label:"Opus (top coding)"},{value:"fable",label:"Fable 5 (2x Opus cost: $10/$50 per MTok)"}].map(s=>{let r=s.value===e?" selected":"";return`<option value="${this._escapeHtml(s.value)}"${r}>${this._escapeHtml(s.label)}</option>`}).join(""),a=this._model.effective==="fable";return`
|
|
3759
3759
|
<div class="model-control">
|
|
3760
3760
|
<div class="model-row">
|
|
3761
3761
|
<label for="model-select">Model</label>
|
|
@@ -3767,7 +3767,7 @@ var LokiDashboard=(()=>{var Re=Object.defineProperty;var _t=Object.getOwnPropert
|
|
|
3767
3767
|
<div class="model-disclosure">Model changes apply from the next iteration, for the current run only.</div>
|
|
3768
3768
|
${this._model.notice?`<div class="model-notice">${this._escapeHtml(this._model.notice)}</div>`:""}
|
|
3769
3769
|
</div>
|
|
3770
|
-
`}_renderStartControl(){return`
|
|
3770
|
+
`}_renderStartControl(){let t=[{value:"",label:"Sonnet 5 (default)"},{value:"haiku",label:"Haiku (fastest, cheapest)"},{value:"sonnet",label:"Sonnet 5 (balanced)"},{value:"opus",label:"Opus (top coding, priciest)"}].map(s=>{let r=s.value===this._startModel?" selected":"";return`<option value="${this._escapeHtml(s.value)}"${r}>${this._escapeHtml(s.label)}</option>`}).join(""),a=[{value:"",label:"Account default"},{value:"opus",label:"Opus (stronger judge)"}].map(s=>{let r=s.value===this._advisorModel?" selected":"";return`<option value="${this._escapeHtml(s.value)}"${r}>${this._escapeHtml(s.label)}</option>`}).join("");return`
|
|
3771
3771
|
<div class="start-control">
|
|
3772
3772
|
<label class="start-label" for="spec-input">Start a build from a spec</label>
|
|
3773
3773
|
<textarea
|
|
@@ -3777,6 +3777,30 @@ var LokiDashboard=(()=>{var Re=Object.defineProperty;var _t=Object.getOwnPropert
|
|
|
3777
3777
|
placeholder="Paste a PRD or type a one-line brief (e.g. 'a CLI todo app in Go with JSON storage')"
|
|
3778
3778
|
aria-label="Spec or one-line brief"
|
|
3779
3779
|
${this._startBusy?"disabled":""}>${this._escapeHtml(this._specText)}</textarea>
|
|
3780
|
+
|
|
3781
|
+
<div class="start-field">
|
|
3782
|
+
<label for="start-model-select">Model</label>
|
|
3783
|
+
<select
|
|
3784
|
+
class="model-select"
|
|
3785
|
+
id="start-model-select"
|
|
3786
|
+
aria-label="Execution model for this build"
|
|
3787
|
+
${this._startBusy?"disabled":""}>
|
|
3788
|
+
${t}
|
|
3789
|
+
</select>
|
|
3790
|
+
</div>
|
|
3791
|
+
|
|
3792
|
+
<div class="start-field">
|
|
3793
|
+
<label for="advisor-select">Advisor</label>
|
|
3794
|
+
<select
|
|
3795
|
+
class="model-select"
|
|
3796
|
+
id="advisor-select"
|
|
3797
|
+
aria-label="Advisor (code-review judge) model"
|
|
3798
|
+
${this._startBusy?"disabled":""}>
|
|
3799
|
+
${a}
|
|
3800
|
+
</select>
|
|
3801
|
+
</div>
|
|
3802
|
+
<div class="start-hint" id="advisor-hint">Advisor judges the code-review gate; execution stays on the model above.</div>
|
|
3803
|
+
|
|
3780
3804
|
<button class="control-btn start" id="start-btn" aria-label="Start build" ${this._startBusy?"disabled":""}>
|
|
3781
3805
|
<svg viewBox="0 0 24 24" aria-hidden="true"><polygon points="5 3 19 12 5 21 5 3"/></svg>
|
|
3782
3806
|
${this._startBusy?"Starting...":"Start Build"}
|
|
@@ -4037,6 +4061,26 @@ var LokiDashboard=(()=>{var Re=Object.defineProperty;var _t=Object.getOwnPropert
|
|
|
4037
4061
|
color: var(--loki-text-secondary);
|
|
4038
4062
|
}
|
|
4039
4063
|
|
|
4064
|
+
.start-field {
|
|
4065
|
+
display: flex;
|
|
4066
|
+
align-items: center;
|
|
4067
|
+
gap: 8px;
|
|
4068
|
+
}
|
|
4069
|
+
|
|
4070
|
+
.start-field label {
|
|
4071
|
+
font-size: 11px;
|
|
4072
|
+
color: var(--loki-text-secondary);
|
|
4073
|
+
white-space: nowrap;
|
|
4074
|
+
/* Fixed label column so the two selects line up. */
|
|
4075
|
+
flex: 0 0 48px;
|
|
4076
|
+
}
|
|
4077
|
+
|
|
4078
|
+
.start-hint {
|
|
4079
|
+
font-size: 10px;
|
|
4080
|
+
color: var(--loki-text-muted);
|
|
4081
|
+
line-height: 1.4;
|
|
4082
|
+
}
|
|
4083
|
+
|
|
4040
4084
|
.spec-input {
|
|
4041
4085
|
width: 100%;
|
|
4042
4086
|
box-sizing: border-box;
|
|
@@ -4161,7 +4205,7 @@ var LokiDashboard=(()=>{var Re=Object.defineProperty;var _t=Object.getOwnPropert
|
|
|
4161
4205
|
`,c=this.shadowRoot.activeElement,p=c&&c.id==="spec-input",u=p?c.selectionStart:null,m=p?c.selectionEnd:null;if(this.shadowRoot.innerHTML=`
|
|
4162
4206
|
${o}
|
|
4163
4207
|
${e?n:l}
|
|
4164
|
-
`,this._attachEventListeners(),p){let g=this.shadowRoot.getElementById("spec-input");if(g&&!g.disabled){g.focus();try{g.setSelectionRange(u,m)}catch{}}}}_attachEventListeners(){let e=this.shadowRoot.getElementById("pause-btn"),t=this.shadowRoot.getElementById("resume-btn"),i=this.shadowRoot.getElementById("stop-btn"),a=this.shadowRoot.getElementById("start-btn");e&&e.addEventListener("click",()=>this._triggerPause()),t&&t.addEventListener("click",()=>this._triggerResume()),i&&i.addEventListener("click",()=>this._triggerStop()),a&&a.addEventListener("click",()=>this._triggerStart());let s=this.shadowRoot.getElementById("model-select");if(s){let
|
|
4208
|
+
`,this._attachEventListeners(),p){let g=this.shadowRoot.getElementById("spec-input");if(g&&!g.disabled){g.focus();try{g.setSelectionRange(u,m)}catch{}}}}_attachEventListeners(){let e=this.shadowRoot.getElementById("pause-btn"),t=this.shadowRoot.getElementById("resume-btn"),i=this.shadowRoot.getElementById("stop-btn"),a=this.shadowRoot.getElementById("start-btn");e&&e.addEventListener("click",()=>this._triggerPause()),t&&t.addEventListener("click",()=>this._triggerResume()),i&&i.addEventListener("click",()=>this._triggerStop()),a&&a.addEventListener("click",()=>this._triggerStart());let s=this.shadowRoot.getElementById("model-select");if(s){let l=()=>{this._modelInteracting=!1,this._renderPending&&(this._renderPending=!1,this.render())};s.addEventListener("mousedown",()=>{this._modelInteracting=!0}),s.addEventListener("focus",()=>{this._modelInteracting=!0}),s.addEventListener("blur",l),s.addEventListener("change",c=>{this._modelInteracting=!1,this._renderPending=!1,this._onModelChange(c.target.value)})}let r=this.shadowRoot.getElementById("spec-input");r&&r.addEventListener("input",l=>this._onSpecInput(l.target.value));let o=this.shadowRoot.getElementById("start-model-select");o&&(o.addEventListener("mousedown",()=>{this._modelInteracting=!0}),o.addEventListener("focus",()=>{this._modelInteracting=!0}),o.addEventListener("blur",()=>{this._modelInteracting=!1,this._renderPending&&(this._renderPending=!1,this.render())}),o.addEventListener("change",l=>{this._modelInteracting=!1,this._renderPending=!1,this._startModel=l.target.value}));let n=this.shadowRoot.getElementById("advisor-select");n&&(n.addEventListener("mousedown",()=>{this._modelInteracting=!0}),n.addEventListener("focus",()=>{this._modelInteracting=!0}),n.addEventListener("blur",()=>{this._modelInteracting=!1,this._renderPending&&(this._renderPending=!1,this.render())}),n.addEventListener("change",l=>{this._modelInteracting=!1,this._renderPending=!1,this._advisorModel=l.target.value}))}};customElements.get("loki-session-control")||customElements.define("loki-session-control",X);var rt={info:{color:"var(--loki-blue)",label:"INFO"},success:{color:"var(--loki-green)",label:"SUCCESS"},warning:{color:"var(--loki-yellow)",label:"WARN"},error:{color:"var(--loki-red)",label:"ERROR"},step:{color:"var(--loki-purple)",label:"STEP"},agent:{color:"var(--loki-accent)",label:"AGENT"},debug:{color:"var(--loki-text-muted)",label:"DEBUG"}},Z=class extends v{static get observedAttributes(){return["api-url","max-lines","auto-scroll","theme","log-file"]}constructor(){super(),this._logs=[],this._maxLines=500,this._autoScroll=!0,this._filter="",this._levelFilter="all",this._api=null,this._pollInterval=null,this._logMessageHandler=null,this._apiLastCount=0,this._apiLastSig=null,this._fileLastSize=0,this._fileLastSig=null}connectedCallback(){super.connectedCallback(),this._maxLines=parseInt(this.getAttribute("max-lines"))||500,this._autoScroll=this.hasAttribute("auto-scroll"),this._setupApi(),this._startLogPolling()}disconnectedCallback(){super.disconnectedCallback(),this._stopLogPolling(),this._teardownApiListeners()}_teardownApiListeners(){this._api&&this._logMessageHandler&&this._api.removeEventListener(f.LOG_MESSAGE,this._logMessageHandler)}attributeChangedCallback(e,t,i){if(t!==i)switch(e){case"api-url":this._api&&(this._teardownApiListeners(),this._logs=[],this._apiLastCount=0,this._apiLastSig=null,this._setupApi(),this.render());break;case"max-lines":this._maxLines=parseInt(i)||500,this._trimLogs(),this.render();break;case"auto-scroll":this._autoScroll=this.hasAttribute("auto-scroll"),this.render();break;case"theme":this._applyTheme();break}}_setupApi(){let e=this.getAttribute("api-url")||window.location.origin;this._api=h({baseUrl:e}),this._logMessageHandler=t=>this._addLog(t.detail),this._api.addEventListener(f.LOG_MESSAGE,this._logMessageHandler)}_startLogPolling(){let e=this.getAttribute("log-file");e?this._pollLogFile(e):this._pollApiLogs()}async _pollApiLogs(){await this._apiLogPollTick(),this._poll=b({loadFn:()=>this._apiLogPollTick(),intervalMs:5e3,element:this,immediate:!1})}async _apiLogPollTick(){try{let e=await this._api.getLogs(200);if(!Array.isArray(e))return;let t=e.length?`${e.length}:${e[e.length-1]?.timestamp||""}:${e[e.length-1]?.message||""}`:"0";if(t===this._apiLastSig)return;if(this._apiLastSig=t,e.length>this._apiLastCount){let i=e.slice(this._apiLastCount);for(let a of i)a.message&&a.message.trim()&&this._addLog({message:a.message,level:a.level||"info",timestamp:a.timestamp||new Date().toLocaleTimeString()})}else e.length<this._apiLastCount&&(this._apiLastCount=0);this._apiLastCount=e.length}catch{}}async _pollLogFile(e){await this._fileLogPollTick(e),this._poll=b({loadFn:()=>this._fileLogPollTick(e),intervalMs:1e3,element:this,immediate:!1})}async _fileLogPollTick(e){try{let t=await fetch(`${e}?t=${Date.now()}`,{credentials:"include"});if(!t.ok)return;let i=await t.text(),a=`${i.length}`;if(a===this._fileLastSig)return;this._fileLastSig=a;let s=i.split(`
|
|
4165
4209
|
`);if(s.length>this._fileLastSize){let r=s.slice(this._fileLastSize);for(let o of r)o.trim()&&this._addLog(this._parseLine(o))}else s.length<this._fileLastSize&&(this._fileLastSize=0);this._fileLastSize=s.length}catch{}}_stopLogPolling(){this._poll&&(this._poll.stop(),this._poll=null)}_parseLine(e){let t=e.match(/^\[([^\]]+)\]\s*\[([^\]]+)\]\s*(.+)$/);if(t)return{timestamp:t[1],level:t[2].toLowerCase(),message:t[3]};let i=e.match(/^(\d{2}:\d{2}:\d{2})\s+(\w+)\s+(.+)$/);return i?{timestamp:i[1],level:i[2].toLowerCase(),message:i[3]}:{timestamp:new Date().toLocaleTimeString(),level:"info",message:e}}_addLog(e){if(!e)return;let t={id:Date.now()+Math.random(),timestamp:e.timestamp||new Date().toLocaleTimeString(),level:(e.level||"info").toLowerCase(),message:e.message||e};this._logs.push(t),this._trimLogs(),this.dispatchEvent(new CustomEvent("log-received",{detail:t})),this._renderLogs(),this._autoScroll&&this._scrollToBottom()}_trimLogs(){this._logs.length>this._maxLines&&(this._logs=this._logs.slice(-this._maxLines))}_clearLogs(){this._logs=[],this.dispatchEvent(new CustomEvent("logs-cleared")),this._renderLogs()}_toggleAutoScroll(){this._autoScroll=!this._autoScroll,this.render(),this._autoScroll&&this._scrollToBottom()}_scrollToBottom(){requestAnimationFrame(()=>{let e=this.shadowRoot.getElementById("log-output");e&&(e.scrollTop=e.scrollHeight)})}_downloadLogs(){let e=this._logs.map(s=>`[${s.timestamp}] [${s.level.toUpperCase()}] ${s.message}`).join(`
|
|
4166
4210
|
`),t=new Blob([e],{type:"text/plain"}),i=URL.createObjectURL(t),a=document.createElement("a");a.href=i,a.download=`loki-logs-${new Date().toISOString().split("T")[0]}.txt`,a.click(),URL.revokeObjectURL(i)}_setFilter(e){this._filter=e.toLowerCase(),this._renderLogs()}_setLevelFilter(e){this._levelFilter=e,this._renderLogs()}_getFilteredLogs(){return this._logs.filter(e=>!(this._levelFilter!=="all"&&e.level!==this._levelFilter||this._filter&&!e.message.toLowerCase().includes(this._filter)))}_renderLogs(){let e=this.shadowRoot.getElementById("log-output");if(!e)return;let t=this._getFilteredLogs();if(t.length===0){e.innerHTML='<div class="log-empty">No log output yet. Terminal will update when Loki Mode is running.</div>';return}e.innerHTML=t.map(i=>{let a=rt[i.level]||rt.info;return`
|
|
4167
4211
|
<div class="log-line">
|
|
@@ -7557,7 +7601,7 @@ var LokiDashboard=(()=>{var Re=Object.defineProperty;var _t=Object.getOwnPropert
|
|
|
7557
7601
|
</button>
|
|
7558
7602
|
${this._detailsOpen?`<div class="details-body">${s}</div>`:""}
|
|
7559
7603
|
</div>
|
|
7560
|
-
`}_attachEventListeners(){let e=this.shadowRoot;if(!e)return;let t=(i,a)=>{let s=e.querySelector(i);s&&s.addEventListener("click",a)};e.querySelectorAll('[data-action="restart"]').forEach(i=>i.addEventListener("click",()=>this._handleRestart())),e.querySelectorAll('[data-action="open-external"]').forEach(i=>i.addEventListener("click",()=>this._handleOpenExternal())),e.querySelectorAll('[data-action="retry-frame"]').forEach(i=>i.addEventListener("click",()=>this._handleRetryFrame())),t('[data-action="refresh"]',()=>this._handleRefresh()),t('[data-action="toggle-details"]',()=>this._toggleDetails()),e.querySelectorAll('[data-action="select-service"]').forEach(i=>i.addEventListener("click",()=>this._handleSelectService(i.getAttribute("data-service-key"))))}_escapeHtml(e){return e==null?"":String(e).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}};customElements.define("loki-app-preview",re);var Bt={opus:{input:5,output:25,label:"Opus 4.6",provider:"claude"},sonnet:{input:3,output:15,label:"Sonnet
|
|
7604
|
+
`}_attachEventListeners(){let e=this.shadowRoot;if(!e)return;let t=(i,a)=>{let s=e.querySelector(i);s&&s.addEventListener("click",a)};e.querySelectorAll('[data-action="restart"]').forEach(i=>i.addEventListener("click",()=>this._handleRestart())),e.querySelectorAll('[data-action="open-external"]').forEach(i=>i.addEventListener("click",()=>this._handleOpenExternal())),e.querySelectorAll('[data-action="retry-frame"]').forEach(i=>i.addEventListener("click",()=>this._handleRetryFrame())),t('[data-action="refresh"]',()=>this._handleRefresh()),t('[data-action="toggle-details"]',()=>this._toggleDetails()),e.querySelectorAll('[data-action="select-service"]').forEach(i=>i.addEventListener("click",()=>this._handleSelectService(i.getAttribute("data-service-key"))))}_escapeHtml(e){return e==null?"":String(e).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}};customElements.define("loki-app-preview",re);var Bt={opus:{input:5,output:25,label:"Opus 4.6",provider:"claude"},sonnet:{input:3,output:15,label:"Sonnet 5",provider:"claude"},haiku:{input:1,output:5,label:"Haiku 4.5",provider:"claude"},"gpt-5.3-codex":{input:1.5,output:12,label:"GPT-5.3 Codex",provider:"codex"}},oe=class extends v{static get observedAttributes(){return["api-url","theme"]}constructor(){super(),this._data={total_input_tokens:0,total_output_tokens:0,estimated_cost_usd:0,by_phase:{},by_model:{},budget_limit:null,budget_used:0,budget_remaining:null,connected:!1},this._api=null,this._pollInterval=null,this._modelPricing={...Bt}}connectedCallback(){super.connectedCallback(),this._setupApi(),this._loadPricing(),this._loadCost(),this._startPolling()}disconnectedCallback(){super.disconnectedCallback(),this._stopPolling()}attributeChangedCallback(e,t,i){t!==i&&(e==="api-url"&&this._api&&(this._api=h({baseUrl:i}),this._loadCost()),e==="theme"&&this._applyTheme())}_setupApi(){let e=this.getAttribute("api-url")||window.location.origin;this._api=h({baseUrl:e})}async _loadPricing(){let e=this._api;try{let t=await e.getPricing();if(e!==this._api)return;if(t&&t.models){let i={};for(let[a,s]of Object.entries(t.models))i[a]={input:s.input,output:s.output,label:s.label||a,provider:s.provider||"unknown",note:s.note||""};this._modelPricing=i,this._pricingSource=t.source||"api",this._pricingDate=t.updated||"",this._activeProvider=t.provider||"claude",this.render()}}catch{}}async _loadCost(){let e=this._api;try{let t=await e.getCost();if(e!==this._api)return;this._updateFromCost(t)}catch{if(e!==this._api)return;this._data.connected=!1,this.render()}}_updateFromCost(e){e&&(this._data={...this._data,connected:!0,total_input_tokens:e.total_input_tokens||0,total_output_tokens:e.total_output_tokens||0,estimated_cost_usd:e.estimated_cost_usd||0,by_phase:e.by_phase||{},by_model:e.by_model||{},budget_limit:e.budget_limit,budget_used:e.budget_used||0,budget_remaining:e.budget_remaining},this.render())}_startPolling(){this._poll=b({loadFn:()=>this._loadCost(),intervalMs:5e3,element:this,immediate:!1})}_stopPolling(){this._poll&&(this._poll.stop(),this._poll=null)}_formatTokens(e){return!e||e===0?"0":e>=1e6?(e/1e6).toFixed(2)+"M":e>=1e3?(e/1e3).toFixed(1)+"K":String(e)}_formatUSD(e){return!e||e===0?"$0.00":e<.01?"<$0.01":"$"+e.toFixed(2)}_getBudgetPercent(){return!this._data.budget_limit||this._data.budget_limit<=0?0:Math.min(100,this._data.budget_used/this._data.budget_limit*100)}_getBudgetStatusClass(){let e=this._getBudgetPercent();return e>=90?"critical":e>=70?"warning":"ok"}_renderPhaseRows(){let e=this._data.by_phase;return!e||Object.keys(e).length===0?'<tr><td colspan="4" class="empty-cell">No phase data yet</td></tr>':Object.entries(e).map(([t,i])=>{let a=i.input_tokens||0,s=i.output_tokens||0,r=i.cost_usd||0;return`
|
|
7561
7605
|
<tr>
|
|
7562
7606
|
<td class="phase-name">${this._escapeHTML(t)}</td>
|
|
7563
7607
|
<td class="mono-cell">${this._formatTokens(a)}</td>
|
|
@@ -7854,6 +7898,13 @@ var LokiDashboard=(()=>{var Re=Object.defineProperty;var _t=Object.getOwnPropert
|
|
|
7854
7898
|
line-height: 1.5;
|
|
7855
7899
|
}
|
|
7856
7900
|
|
|
7901
|
+
.pricing-note {
|
|
7902
|
+
font-size: 10px;
|
|
7903
|
+
color: var(--loki-text-secondary);
|
|
7904
|
+
margin-top: 4px;
|
|
7905
|
+
line-height: 1.4;
|
|
7906
|
+
}
|
|
7907
|
+
|
|
7857
7908
|
/* Offline state */
|
|
7858
7909
|
.offline-notice {
|
|
7859
7910
|
text-align: center;
|
|
@@ -7955,6 +8006,7 @@ var LokiDashboard=(()=>{var Re=Object.defineProperty;var _t=Object.getOwnPropert
|
|
|
7955
8006
|
<div class="pricing-item">
|
|
7956
8007
|
<div class="pricing-model ${this._getPricingColorClass(t,i)}">${this._escapeHTML(i.label||t)}</div>
|
|
7957
8008
|
<div class="pricing-rates">In: $${Number(i.input??0).toFixed(2)} / Out: $${Number(i.output??0).toFixed(2)}</div>
|
|
8009
|
+
${i.note?`<div class="pricing-note">${this._escapeHTML(i.note)}</div>`:""}
|
|
7958
8010
|
</div>`).join("")}
|
|
7959
8011
|
</div>
|
|
7960
8012
|
</div>
|
package/docs/INSTALLATION.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The flagship product of [Autonomi](https://www.autonomi.dev/). Loki Mode is a spec-driven autonomous builder with a built-in trust layer that takes any spec to a deployed product and verifies completion with evidence (quality gates plus a completion council), not just a "done" claim. Complete installation instructions for all platforms and use cases.
|
|
4
4
|
|
|
5
|
-
**Version:** v7.
|
|
5
|
+
**Version:** v7.104.0
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|