loki-mode 9.22.12 → 9.24.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.
Files changed (92) hide show
  1. package/SKILL.md +3 -3
  2. package/VERSION +1 -1
  3. package/autonomy/lib/claude-flags.sh +69 -5
  4. package/autonomy/lib/contract-scaffold/scaffold.sh +1 -1
  5. package/autonomy/loki +324 -74
  6. package/autonomy/run.sh +176 -40
  7. package/autonomy/verify.sh +16 -3
  8. package/bin/loki +25 -1
  9. package/dashboard/__init__.py +1 -1
  10. package/dashboard/control.py +88 -4
  11. package/dashboard/server.py +50 -48
  12. package/docs/INSTALLATION.md +2 -2
  13. package/docs/RARV-C-100X-PLAN.md +9 -1
  14. package/docs/enterprise/performance.md +51 -9
  15. package/docs/exit-codes.md +4 -0
  16. package/loki-ts/dist/cockpit.js +1 -1
  17. package/loki-ts/dist/loki.js +634 -549
  18. package/loki-ts/package.json +2 -2
  19. package/mcp/__init__.py +1 -1
  20. package/package.json +6 -5
  21. package/plugins/loki-mode/.claude-plugin/plugin.json +2 -3
  22. package/providers/loader.sh +2 -2
  23. package/providers/model_catalog.json +26 -8
  24. package/providers/models.sh +1 -1
  25. package/providers/opencode.sh +1 -1
  26. package/skills/providers.md +42 -14
  27. package/tools/normalize-package-permissions.mjs +34 -0
  28. package/web-app/dist/assets/{AdminPage-DLhWX6si.js → AdminPage-PyyVXg4b.js} +1 -1
  29. package/web-app/dist/assets/{Avatar-CqyDcUxl.js → Avatar-CTUlr_Xf.js} +1 -1
  30. package/web-app/dist/assets/{Badge-BKGSt1n2.js → Badge-BQeXvmg8.js} +1 -1
  31. package/web-app/dist/assets/{Button-DuuKHXre.js → Button-Dc6-P6pz.js} +1 -1
  32. package/web-app/dist/assets/{CockpitPage-DdClYERC.js → CockpitPage-BspOJ3Fv.js} +1 -1
  33. package/web-app/dist/assets/{ComparePage-CUWlqBxv.js → ComparePage-CFXIM--Z.js} +1 -1
  34. package/web-app/dist/assets/{ErrorBoundary-RwfGKyRJ.js → ErrorBoundary-C7vQYDW9.js} +1 -1
  35. package/web-app/dist/assets/{EvidenceReceiptPanel-CzIgk9sg.js → EvidenceReceiptPanel-CqjsjTOs.js} +1 -1
  36. package/web-app/dist/assets/{GitHubIssuesPanel-CsXVKuyJ.js → GitHubIssuesPanel-DyuNDMgD.js} +1 -1
  37. package/web-app/dist/assets/{GitHubPRsPanel-CYRp1eKS.js → GitHubPRsPanel-BtAq20xF.js} +1 -1
  38. package/web-app/dist/assets/{HomePage-CVJcI5pQ.js → HomePage-BuVFOrO8.js} +1 -1
  39. package/web-app/dist/assets/{LoginPage-BBJNV9Qb.js → LoginPage-CuhhrxGb.js} +1 -1
  40. package/web-app/dist/assets/{MagicPage-CIi9P8jK.js → MagicPage-D2KZPX3F.js} +1 -1
  41. package/web-app/dist/assets/{MetricsPage-Bm34ipiy.js → MetricsPage-DFEYJl9z.js} +1 -1
  42. package/web-app/dist/assets/{NotFoundPage-DpTbcC2P.js → NotFoundPage-CTnnaWlL.js} +1 -1
  43. package/web-app/dist/assets/{ProjectPage-CY2SMGx9.js → ProjectPage-uJFaoM8-.js} +55 -55
  44. package/web-app/dist/assets/{ProjectsPage-DSrXUUKJ.js → ProjectsPage-8lKAOBIW.js} +1 -1
  45. package/web-app/dist/assets/{SettingsPage-jvDyOqYi.js → SettingsPage-D1BBd5qy.js} +1 -1
  46. package/web-app/dist/assets/{ShowcasePage-C8tFWKau.js → ShowcasePage-CmQ0n8iO.js} +1 -1
  47. package/web-app/dist/assets/{SystemSettingsPage-DH4FTH3K.js → SystemSettingsPage-CSHaVyuD.js} +1 -1
  48. package/web-app/dist/assets/{TeamsPage-C1fQYauF.js → TeamsPage-BS0TtVUa.js} +1 -1
  49. package/web-app/dist/assets/{TemplatesPage-CSTFt-4P.js → TemplatesPage-C2zPnnf2.js} +1 -1
  50. package/web-app/dist/assets/{TerminalOutput-Cu4NsyN6.js → TerminalOutput-Dle1klqq.js} +1 -1
  51. package/web-app/dist/assets/{activity-CtjsDzmh.js → activity-DQp5U9f9.js} +1 -1
  52. package/web-app/dist/assets/{bell-C5es1xAs.js → bell-BqkTmgL7.js} +1 -1
  53. package/web-app/dist/assets/{bot-DE04U5Lc.js → bot-DILhaIGH.js} +1 -1
  54. package/web-app/dist/assets/{check-DSKKA9EU.js → check-BC9x7oPm.js} +1 -1
  55. package/web-app/dist/assets/{chevron-left-DJ13W5AR.js → chevron-left--yqvAfjx.js} +1 -1
  56. package/web-app/dist/assets/{circle-alert-CS0BfqAd.js → circle-alert-DOH6PcgZ.js} +1 -1
  57. package/web-app/dist/assets/{clock-D1Y5nruk.js → clock-DKSDFsE6.js} +1 -1
  58. package/web-app/dist/assets/{cloud-nU6LBoZL.js → cloud-DRBU3JYs.js} +1 -1
  59. package/web-app/dist/assets/{code-xml-BZfvIdFo.js → code-xml-D2LqszR9.js} +1 -1
  60. package/web-app/dist/assets/{copy-CK_2O1Xv.js → copy-BKW4U4Qr.js} +1 -1
  61. package/web-app/dist/assets/{database-DJiwznAm.js → database-D7ceedlh.js} +1 -1
  62. package/web-app/dist/assets/{dollar-sign-pO20A3QY.js → dollar-sign-D0A47egY.js} +1 -1
  63. package/web-app/dist/assets/{file-code-corner-Besy6vek.js → file-code-corner-CH_Qbtjo.js} +1 -1
  64. package/web-app/dist/assets/{file-plus-DeaswxYY.js → file-plus-CUNMjHk9.js} +1 -1
  65. package/web-app/dist/assets/{folder-open-cr1UvQLI.js → folder-open-CQ_SVq94.js} +1 -1
  66. package/web-app/dist/assets/{git-commit-horizontal-BLxnfbSn.js → git-commit-horizontal-DQUBe0oD.js} +1 -1
  67. package/web-app/dist/assets/{globe-DrhG1NfX.js → globe-gZnaGg8N.js} +1 -1
  68. package/web-app/dist/assets/{hammer-CeZtPBfE.js → hammer-BhJC5DMK.js} +1 -1
  69. package/web-app/dist/assets/{index-CLvjO22Z.js → index-BMmYYws0.js} +2 -2
  70. package/web-app/dist/assets/{layers-fldADzyE.js → layers-glXeoUbJ.js} +1 -1
  71. package/web-app/dist/assets/{lightbulb-CznOfsPb.js → lightbulb-gXn8H0Lf.js} +1 -1
  72. package/web-app/dist/assets/{loader-circle---lPB1Fs.js → loader-circle-Do2ke8xm.js} +1 -1
  73. package/web-app/dist/assets/{lock-D5jVF-tO.js → lock-Bgplb1_n.js} +1 -1
  74. package/web-app/dist/assets/{mail-D7HkLnlW.js → mail-j4Lo8hfB.js} +1 -1
  75. package/web-app/dist/assets/{minus-C-dT19Ct.js → minus-dhrzKSK5.js} +1 -1
  76. package/web-app/dist/assets/{package-Cm9B-cKE.js → package-CInWQQ97.js} +1 -1
  77. package/web-app/dist/assets/{plus-CQwOffLD.js → plus-BpYvoLlZ.js} +1 -1
  78. package/web-app/dist/assets/{refresh-cw-CvWJZLbN.js → refresh-cw-F6koqEgF.js} +1 -1
  79. package/web-app/dist/assets/{rotate-ccw-C2Q1LYgM.js → rotate-ccw-CQ2W5S8f.js} +1 -1
  80. package/web-app/dist/assets/{save-BP0MvpoQ.js → save-DMCYGm06.js} +1 -1
  81. package/web-app/dist/assets/{server-4i4VZXix.js → server-Bj8EX4Z1.js} +1 -1
  82. package/web-app/dist/assets/{shield-alert-D2_bgb7j.js → shield-alert-CiYuulCU.js} +1 -1
  83. package/web-app/dist/assets/{thumbs-up-vACbzeXj.js → thumbs-up-BqEh1puP.js} +1 -1
  84. package/web-app/dist/assets/{trash-2-EYjdESLs.js → trash-2-CCwouZJJ.js} +1 -1
  85. package/web-app/dist/assets/{trending-down-BWBdcvE3.js → trending-down-Cx1vhE4o.js} +1 -1
  86. package/web-app/dist/assets/{trending-up-Bix6V_-Y.js → trending-up-BUhVYJ2T.js} +1 -1
  87. package/web-app/dist/assets/{upload-B7hh6ERF.js → upload-D5IsZeFM.js} +1 -1
  88. package/web-app/dist/assets/{usePolling-b_e-k-IN.js → usePolling-CJbfhFcH.js} +1 -1
  89. package/web-app/dist/assets/{user-pmjZKWEg.js → user-BRLhezsD.js} +1 -1
  90. package/web-app/dist/index.html +1 -1
  91. package/web-app/requirements.txt +1 -0
  92. package/web-app/server.py +160 -31
@@ -58,7 +58,12 @@ from . import audit
58
58
  from . import app_secrets as secrets_mod
59
59
  from . import telemetry as _telemetry
60
60
  from . import build_supervisor as _build_execution
61
- from .control import atomic_write_json, find_skill_dir, is_process_running
61
+ from .control import (
62
+ atomic_write_json,
63
+ browser_mutation_origin_allowed,
64
+ find_skill_dir,
65
+ is_process_running,
66
+ )
62
67
  from .activity_logger import get_activity_logger
63
68
  from .api_v2 import (
64
69
  TenantContext,
@@ -187,53 +192,46 @@ def _rate_key(base: str, request: Optional[Request]) -> str:
187
192
  logger = logging.getLogger(__name__)
188
193
 
189
194
 
190
- # Reads that expose operational or credential-adjacent state. These stay open
191
- # to a LOCAL caller (zero-config use is the point) but must not be readable by
192
- # an anonymous remote caller when the dashboard is bound to 0.0.0.0.
195
+ # Public API metadata needed before a caller can authenticate or before the UI
196
+ # can choose a provider. Everything else under an API namespace is
197
+ # state-bearing unless it is explicitly admitted here.
193
198
  #
194
- # Measured before this list existed, from a routable remote address with auth
195
- # off: /api/logs, /api/secrets/status, /api/github/status, /api/tasks,
196
- # /api/council/transcripts and /api/proofs all returned 200.
199
+ # This is deliberately an ALLOWLIST, not the old list of sensitive prefixes.
200
+ # That list silently started every new read family public. Measured from a
201
+ # routable remote address with auth off, routes added after the list --
202
+ # /api/context, /api/notifications, /api/agents, /api/usage,
203
+ # /api/prd-observations and every /api/v2/* read -- all reached their handlers
204
+ # and returned project, tenant or audit state.
197
205
  #
198
- # /health and /metrics are deliberately ABSENT: a container health probe and a
199
- # Prometheus scrape must keep working with no configuration, and neither
200
- # carries workspace content.
201
- _SENSITIVE_READ_PREFIXES = (
202
- "/api/logs",
203
- "/api/secrets",
204
- "/api/github",
205
- "/api/tasks",
206
- "/api/projects",
207
- "/api/council",
208
- "/api/proofs",
209
- "/api/phases",
210
- "/api/memory",
211
- "/api/learnings",
212
- "/api/learning",
213
- "/api/escalations",
214
- "/api/spec",
215
- "/api/checkpoints",
216
- "/api/enterprise",
217
- "/api/collab",
218
- "/api/cost",
219
- "/api/budget",
220
- "/api/findings",
221
- "/api/operator",
222
- "/api/fleet",
223
- "/api/registry",
224
- "/api/wiki",
225
- "/api/activity",
226
- "/api/session",
227
- "/api/failures",
228
- "/api/prompt",
229
- "/api/quality",
230
- "/api/migration",
231
- "/api/managed",
232
- "/api/app-runner",
233
- "/api/playwright",
234
- "/api/checklist",
235
- "/api/control",
236
- )
206
+ # /health, /metrics, /.well-known/*, docs and static/UI routes are outside the
207
+ # API namespaces and remain public. The three entries below contain only
208
+ # capability/auth bootstrap metadata and are already pinned as public by the
209
+ # dashboard auth inventory tests.
210
+ _PUBLIC_API_GET_PATHS = frozenset({
211
+ "/api/auth/info",
212
+ "/api/enterprise/status",
213
+ "/api/providers/models",
214
+ })
215
+
216
+
217
+ def _is_state_bearing_get(path: str) -> bool:
218
+ """Classify dashboard reads at the namespace boundary.
219
+
220
+ A future GET under /api or the mounted Purple Lab's /lab/api namespace is
221
+ private by default. Exact public metadata is admitted above; probes,
222
+ discovery documents and UI/static files live outside these namespaces.
223
+ Normalize one trailing slash so FastAPI's redirect spelling cannot turn a
224
+ public metadata request into a remote-only failure.
225
+ """
226
+ normalized = path.rstrip("/") or "/"
227
+ if normalized in _PUBLIC_API_GET_PATHS:
228
+ return False
229
+ return (
230
+ normalized == "/api"
231
+ or normalized.startswith("/api/")
232
+ or normalized == "/lab/api"
233
+ or normalized.startswith("/lab/api/")
234
+ )
237
235
 
238
236
 
239
237
  def _trusted_proxies() -> frozenset:
@@ -1263,9 +1261,13 @@ async def dashboard_control_boundary(request: Request, call_next):
1263
1261
  # /health and /metrics are intentionally NOT in the sensitive list, so a
1264
1262
  # container health probe and a Prometheus scrape keep working unconfigured.
1265
1263
  gated = request.method in ("POST", "PUT", "PATCH", "DELETE")
1264
+ if gated and not browser_mutation_origin_allowed(request, _cors_origins):
1265
+ return JSONResponse(
1266
+ status_code=403,
1267
+ content={"detail": "cross-origin mutation refused"},
1268
+ )
1266
1269
  if not gated:
1267
- path = request.url.path
1268
- gated = any(path.startswith(pfx) for pfx in _SENSITIVE_READ_PREFIXES)
1270
+ gated = _is_state_bearing_get(request.url.path)
1269
1271
  if gated:
1270
1272
  try:
1271
1273
  require_local_or_authenticated(request)
@@ -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:** v9.22.12
5
+ **Version:** v9.24.0
6
6
 
7
7
  ---
8
8
 
@@ -343,7 +343,7 @@ The `HUMAN_INPUT.md` file has security controls:
343
343
 
344
344
  ## Multi-Provider Support
345
345
 
346
- Loki Mode supports four active providers across three tiers, plus historical/upcoming entries. Pick by capability + cost.
346
+ Loki Mode supports five active providers across three capability modes, plus historical/upcoming entries. Pick by capability + cost.
347
347
 
348
348
  ### Supported Providers
349
349
 
@@ -220,7 +220,15 @@ Every commit proven on BOTH routes (`bin/loki` + `LOKI_LEGACY_BASH=1 bin/loki`).
220
220
  - **Post-release smoke from SHIPPED artifacts on BOTH routes**: `npm pack loki-mode@8.0.0` + `bun run bin/loki
221
221
  version` and `LOKI_LEGACY_BASH=1 ... version`; `docker run --rm asklokesh/loki-mode:8.0.0 doctor --json`;
222
222
  WebFetch brew formula version+sha.
223
- - **Cleanup after every local-ci/validation**: `lsof -ti:57374 | xargs kill -9; rm -rf /tmp/loki-* /tmp/test-* /tmp/package /tmp/*.tgz`.
223
+ - **Cleanup after every local-ci/validation**: create one exact run-owned directory with `mktemp -d`
224
+ under the canonical `${TMPDIR:-/tmp}` root and keep all validation artifacts and recorded child
225
+ PIDs inside it. Use the executable operator-side sequence in
226
+ [`enterprise/performance.md`](enterprise/performance.md#cleanup); it does not depend on a shipped
227
+ cleanup helper. Stop only the exact verified child PIDs. Before removing the exact saved path,
228
+ require its `.loki-run-owned` marker to contain that path, require the target and marker to belong
229
+ to the current UID, require the target to be a canonical direct child of the temp root, refuse
230
+ symlinks, and refuse any target with `.git` worktree metadata. A failed guard leaves the path in
231
+ place for inspection.
224
232
 
225
233
  ---
226
234
 
@@ -267,19 +267,61 @@ When enterprise features are active, background processes consume resources:
267
267
 
268
268
  ### Cleanup
269
269
 
270
- Enterprise processes are cleaned up on session termination:
270
+ Keep cleanup scoped to the processes and paths created by the current invocation. Record the exact
271
+ child process IDs when starting background services and stop only those verified processes. Do not
272
+ kill by a shared port or process-name pattern.
271
273
 
272
- ```bash
273
- # Kill dashboard server
274
- lsof -ti:57374 | xargs kill -9 2>/dev/null || true
275
-
276
- # Kill background enterprise processes
277
- pkill -f "loki-run-" 2>/dev/null || true
274
+ Create one run-owned temporary directory before writing any temporary package, log, PID, or test
275
+ artifact. Run the creation and cleanup blocks in the same shell so `run_tmp` retains the exact path:
278
276
 
279
- # Clean temp files
280
- rm -rf /tmp/loki-* /tmp/test-* 2>/dev/null || true
277
+ ```bash
278
+ # Create the only temporary directory this invocation may remove.
279
+ temp_root="$(cd "${TMPDIR:-/tmp}" 2>/dev/null && pwd -P)" || exit 1
280
+ run_tmp="$(mktemp -d "${temp_root}/loki-run.XXXXXXXX")" || exit 1
281
+ chmod 700 "$run_tmp" || exit 1
282
+ run_marker="${run_tmp}/.loki-run-owned"
283
+ printf '%s\n' "$run_tmp" >"$run_marker" || exit 1
284
+ chmod 600 "$run_marker" || exit 1
285
+
286
+ # Put this invocation's temporary artifacts under "$run_tmp" and retain exact child PIDs there.
287
+
288
+ # Immediately before removal, fail closed unless every ownership boundary still holds.
289
+ cleanup_target="$run_tmp"
290
+ cleanup_root="$(cd "${TMPDIR:-/tmp}" 2>/dev/null && pwd -P)" || exit 64
291
+ case "$cleanup_target" in
292
+ "${cleanup_root}"/loki-run.*) ;;
293
+ *) printf '%s\n' "Refusing cleanup outside the run-owned namespace" >&2; exit 64 ;;
294
+ esac
295
+ [ "$(dirname "$cleanup_target")" = "$cleanup_root" ] || exit 64
296
+ [ -d "$cleanup_target" ] && [ ! -L "$cleanup_target" ] || exit 64
297
+
298
+ cleanup_marker="${cleanup_target}/.loki-run-owned"
299
+ [ -f "$cleanup_marker" ] && [ ! -L "$cleanup_marker" ] || exit 64
300
+ IFS= read -r marker_value <"$cleanup_marker" || exit 64
301
+ [ "$marker_value" = "$cleanup_target" ] || exit 64
302
+
303
+ target_uid="$(stat -f '%u' "$cleanup_target" 2>/dev/null || stat -c '%u' "$cleanup_target")" || exit 64
304
+ marker_uid="$(stat -f '%u' "$cleanup_marker" 2>/dev/null || stat -c '%u' "$cleanup_marker")" || exit 64
305
+ [ "$target_uid" = "$(id -u)" ] && [ "$marker_uid" = "$(id -u)" ] || exit 64
306
+
307
+ target_real="$(cd "$cleanup_target" 2>/dev/null && pwd -P)" || exit 64
308
+ [ "$target_real" = "$cleanup_target" ] || exit 64
309
+ [ "$(dirname "$target_real")" = "$cleanup_root" ] || exit 64
310
+
311
+ # Refuse primary worktrees, linked worktrees, and a broken .git symlink.
312
+ if [ -e "${cleanup_target}/.git" ] || [ -L "${cleanup_target}/.git" ]; then
313
+ printf '%s\n' "Refusing to remove a Git worktree" >&2
314
+ exit 64
315
+ fi
316
+
317
+ rm -rf -- "$cleanup_target"
318
+ [ ! -e "$cleanup_target" ] || exit 1
281
319
  ```
282
320
 
321
+ These are operator-side shell commands, not a Loki cleanup helper. Never replace the exact target
322
+ with a wildcard under `/tmp` or `$TMPDIR`, and never remove a path whose marker, UID ownership,
323
+ canonical location, symlink status, or worktree status cannot be verified.
324
+
283
325
  ## Performance Monitoring Checklist
284
326
 
285
327
  1. **Verify no-op behavior** -- When enterprise env vars are unset, confirm zero overhead by checking that no background threads are created and no disk I/O occurs.
@@ -84,6 +84,10 @@ Machine-readable output is `--format json` here, not `--json`.
84
84
  | 0 | Every required check passed. Optional warnings do not fail the command. |
85
85
  | nonzero | At least one required check failed; the output names which |
86
86
 
87
+ The contract is identical for human-readable output and `--json`. JSON is
88
+ still emitted in full before the command exits nonzero, so automation can save
89
+ or parse the report while also using the process status as a readiness gate.
90
+
87
91
  Safe as a preflight gate in an init container or pipeline step:
88
92
 
89
93
  ```sh
@@ -16,4 +16,4 @@ var EJ=Object.defineProperty;var TJ=(J)=>J;function WJ(J,Q){this[J]=TJ.bind(null
16
16
  `),3}let G=process.stdout.columns||Number(process.env.COLUMNS)||0,q=G>4?G-2:void 0,K=await CJ($,{protocol:Q,forceText:X,cols:q});if(Z)try{let{writeFileSync:P}=await import("fs");P(Z,K.svg)}catch{}if(K.kind==="image"&&K.data)return process.stdout.write(K.data),0;return process.stderr.write(`FALLBACK ${K.reason||"image unavailable"}
17
17
  `),3}if(import.meta.main)rJ().then((J)=>process.exit(J));export{rJ as main};
18
18
 
19
- //# debugId=7F0CBB32F48339CA64756E2164756E21
19
+ //# debugId=FA320A456FDE2DB7437C9AA473A12AC3