loki-mode 11.31.1 → 11.33.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 (68) hide show
  1. package/SKILL.md +2 -2
  2. package/VERSION +1 -1
  3. package/autonomy/sandbox.sh +220 -2
  4. package/dashboard/__init__.py +1 -1
  5. package/docs/CLI-REFERENCE.md +1 -1
  6. package/docs/INSTALLATION.md +1 -1
  7. package/loki-ts/dist/loki.js +1101 -1101
  8. package/mcp/__init__.py +1 -1
  9. package/package.json +2 -2
  10. package/packages/control-plane/dist/server.js +9 -7
  11. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  12. package/web-app/dist/assets/{AdminPage-A5UQEQzo.js → AdminPage-BOo6zbtK.js} +1 -1
  13. package/web-app/dist/assets/{Avatar-xQ1gSPVB.js → Avatar-x0_oZtYF.js} +1 -1
  14. package/web-app/dist/assets/{Badge-dQyhI3UH.js → Badge-jDB28PQG.js} +1 -1
  15. package/web-app/dist/assets/{Button-BehU-7tC.js → Button-27f5aDrW.js} +1 -1
  16. package/web-app/dist/assets/{CockpitPage-BPLFrfno.js → CockpitPage-DiAKMj0I.js} +1 -1
  17. package/web-app/dist/assets/{ComparePage-Dc7ecTbH.js → ComparePage-RKUVbeCG.js} +1 -1
  18. package/web-app/dist/assets/{ErrorBoundary-P4CkwBs3.js → ErrorBoundary-CQ7c_YJ9.js} +1 -1
  19. package/web-app/dist/assets/{EvidenceReceiptPanel-CCC_Wc2o.js → EvidenceReceiptPanel-u65ACFhT.js} +1 -1
  20. package/web-app/dist/assets/{GitHubIssuesPanel-Bi3c1_CN.js → GitHubIssuesPanel-8oqCXpcg.js} +1 -1
  21. package/web-app/dist/assets/{GitHubPRsPanel-A7D7tuaQ.js → GitHubPRsPanel-DkagnZtO.js} +1 -1
  22. package/web-app/dist/assets/{HomePage-Cj7XatdG.js → HomePage-C5IyKJcO.js} +2 -2
  23. package/web-app/dist/assets/{LoginPage-lFQFWzTA.js → LoginPage-C6-NK6Dd.js} +1 -1
  24. package/web-app/dist/assets/{MagicPage-sHngqPOB.js → MagicPage-FrUAlTFq.js} +1 -1
  25. package/web-app/dist/assets/{MetricsPage-vmk3uj53.js → MetricsPage-DbHKrYaS.js} +1 -1
  26. package/web-app/dist/assets/{NotFoundPage-B37nihqx.js → NotFoundPage-qg7ISn6m.js} +1 -1
  27. package/web-app/dist/assets/{ProjectPage-TrK9SBhV.js → ProjectPage-BZzSg0vO.js} +3 -3
  28. package/web-app/dist/assets/{ProjectsPage-BoKOqxu4.js → ProjectsPage-CSrkVM0Z.js} +1 -1
  29. package/web-app/dist/assets/{SettingsPage-Dfyhp6nl.js → SettingsPage-DoIoG_1E.js} +1 -1
  30. package/web-app/dist/assets/{ShowcasePage-DOCeqHxj.js → ShowcasePage-Cn1JmtRU.js} +1 -1
  31. package/web-app/dist/assets/{SystemSettingsPage-idLDGVpd.js → SystemSettingsPage-FD9fvAf5.js} +1 -1
  32. package/web-app/dist/assets/{TeamsPage-CG0cXstx.js → TeamsPage-BkGU3fTI.js} +1 -1
  33. package/web-app/dist/assets/{TemplatesPage-cThU88IX.js → TemplatesPage-Oon4z663.js} +1 -1
  34. package/web-app/dist/assets/{TerminalOutput-CZcfr298.js → TerminalOutput-CLysHg_-.js} +1 -1
  35. package/web-app/dist/assets/{activity-JwnQAJzU.js → activity-BcCTbGiS.js} +1 -1
  36. package/web-app/dist/assets/{bell-EMBOci2m.js → bell-BcGnBkdg.js} +1 -1
  37. package/web-app/dist/assets/{bot-Dwg7eFlL.js → bot-i31uzgM6.js} +1 -1
  38. package/web-app/dist/assets/{check-DNNlHviH.js → check-CExa_B4X.js} +1 -1
  39. package/web-app/dist/assets/{chevron-left-BJ2VldHk.js → chevron-left-BP1rmia5.js} +1 -1
  40. package/web-app/dist/assets/{circle-alert-0P4HhIzB.js → circle-alert-BOLPoLQg.js} +1 -1
  41. package/web-app/dist/assets/{clock-DxxclmlE.js → clock-DatwTgIM.js} +1 -1
  42. package/web-app/dist/assets/{cloud-COohLeUE.js → cloud-C3bRnFkT.js} +1 -1
  43. package/web-app/dist/assets/{code-xml-BJ25iVx3.js → code-xml-C66CBkn_.js} +1 -1
  44. package/web-app/dist/assets/{database-Dvy7Bbcf.js → database-D1H0smtm.js} +1 -1
  45. package/web-app/dist/assets/{dollar-sign-qUbMFHU4.js → dollar-sign-6mehyt0I.js} +1 -1
  46. package/web-app/dist/assets/{file-code-corner-BHhWk-yB.js → file-code-corner-Dcl58rE8.js} +1 -1
  47. package/web-app/dist/assets/{file-plus-CXDzSmIV.js → file-plus-DusJH-jr.js} +1 -1
  48. package/web-app/dist/assets/{globe--nSuGAqH.js → globe-8gbaSEAF.js} +1 -1
  49. package/web-app/dist/assets/{hammer-CyPoRSsX.js → hammer-BeUMO3eS.js} +1 -1
  50. package/web-app/dist/assets/{index-sCyA5kT1.js → index-BxlLDUq_.js} +3 -3
  51. package/web-app/dist/assets/{layers-D8YjFUHc.js → layers-CsO5XwHB.js} +1 -1
  52. package/web-app/dist/assets/{loader-circle-ZB7xFqz2.js → loader-circle-8JWxq7au.js} +1 -1
  53. package/web-app/dist/assets/{lock-D1UH6v-O.js → lock-ByAv_0Uq.js} +1 -1
  54. package/web-app/dist/assets/{package-sDqydd_4.js → package-D-xVbDAP.js} +1 -1
  55. package/web-app/dist/assets/{plus-ClTe2QX5.js → plus-R7T7gJbI.js} +1 -1
  56. package/web-app/dist/assets/{refresh-cw-D54nCHRH.js → refresh-cw-D65J_QH-.js} +1 -1
  57. package/web-app/dist/assets/{rotate-ccw-CFJoICwQ.js → rotate-ccw-Cs4Tvq2C.js} +1 -1
  58. package/web-app/dist/assets/{scroll-text-D_5t4SMl.js → scroll-text-N1tgG-co.js} +1 -1
  59. package/web-app/dist/assets/{server-cUdSs3Jw.js → server-DD1GqOyy.js} +1 -1
  60. package/web-app/dist/assets/{shield-alert-o3-54oNJ.js → shield-alert-0V3SJyDi.js} +1 -1
  61. package/web-app/dist/assets/{thumbs-up-Bj5vm_ge.js → thumbs-up-BT7l40R8.js} +1 -1
  62. package/web-app/dist/assets/{trash-2-D4yWbHl5.js → trash-2-z1gK-C8I.js} +1 -1
  63. package/web-app/dist/assets/{trending-down-DStUnNZ3.js → trending-down-Cp3fR71x.js} +1 -1
  64. package/web-app/dist/assets/{trending-up-CL4ZFYFb.js → trending-up-DXL-NgP-.js} +1 -1
  65. package/web-app/dist/assets/{upload-Dem0HZRa.js → upload-BX_yQ46Z.js} +1 -1
  66. package/web-app/dist/assets/{usePolling-14ezEt6p.js → usePolling-Bo7CC4Ha.js} +1 -1
  67. package/web-app/dist/assets/{user-DuS3X15u.js → user-C7nAwa5x.js} +1 -1
  68. package/web-app/dist/index.html +1 -1
package/SKILL.md CHANGED
@@ -3,7 +3,7 @@ name: loki-mode
3
3
  description: An autonomous software factory that knows what it is supposed to deliver, and proves it did. Use when the user says Loki Mode or asks to build, fix or verify software autonomously.
4
4
  ---
5
5
 
6
- # Loki Mode v11.31.1
6
+ # Loki Mode v11.33.0
7
7
 
8
8
  **Current entry points (Loki 10 engine):** `loki "<task>"`, `loki owner/repo#N` and `loki quick "<task>"` run the Loki 10 engine and end with a signed receipt (`loki verify`). `loki backlog owner/repo --all` runs many issues in parallel. Guide: `docs/v10/GUIDE.md`. The skill instructions below, and `loki start`, describe the previous bash loop. PRD-file starts (`loki start ./prd.md`) and the opencode provider still run on it until FC38-SWEEP ports them; the legacy-engine command and the LOKI_ENGINE switch remain removed. Prefer `loki owner/repo#N` for issues.
9
9
 
@@ -496,4 +496,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
496
496
 
497
497
  ---
498
498
 
499
- **v11.31.1 | [Autonomi](https://www.autonomi.dev/) flagship product | core skill**
499
+ **v11.33.0 | [Autonomi](https://www.autonomi.dev/) flagship product | core skill**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 11.31.1
1
+ 11.33.0
@@ -12,6 +12,8 @@
12
12
  # Environment Variables:
13
13
  # LOKI_SANDBOX_IMAGE - Docker image to use (default: loki-mode:sandbox)
14
14
  # LOKI_SANDBOX_NETWORK - Network mode: bridge, none, host (default: bridge)
15
+ # LOKI_SANDBOX_ALLOW_HOSTS - Opt-in egress allowlist (comma-separated hostnames);
16
+ # blocks link-local and private ranges (default: unset)
15
17
  # LOKI_SANDBOX_CPUS - CPU limit (default: 2)
16
18
  # LOKI_SANDBOX_MEMORY - Memory limit (default: 4g)
17
19
  # LOKI_SANDBOX_READONLY - Mount project as read-only (default: false)
@@ -261,6 +263,7 @@ warn_missing_api_keys() {
261
263
  cleanup_container() {
262
264
  log_warn "Interrupted - cleaning up container..."
263
265
  docker rm -f "$CONTAINER_NAME" 2>/dev/null || true
266
+ _sandbox_egress_down
264
267
  exit 130
265
268
  }
266
269
 
@@ -1083,6 +1086,196 @@ except: pass
1083
1086
  fi
1084
1087
  }
1085
1088
 
1089
+ #-------------------------------------------------------------------------------
1090
+ # Opt-in egress allowlist (LOKI_SANDBOX_ALLOW_HOSTS, comma-separated hostnames).
1091
+ # Unset: nothing below runs and the docker run argv is unchanged. Set: the sandbox
1092
+ # joins an --internal network (no route out) and reaches the world only through a
1093
+ # filtering proxy sidecar that permits the listed hosts and refuses any target
1094
+ # resolving to link-local, loopback or private ranges.
1095
+ #-------------------------------------------------------------------------------
1096
+ SANDBOX_ALLOW_HOSTS="${LOKI_SANDBOX_ALLOW_HOSTS:-}"
1097
+ EGRESS_PROXY_PORT=3128
1098
+ EGRESS_BLOCKED_CIDRS="169.254.0.0/16 fe80::/10 fec0::/10 100.64.0.0/10 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 127.0.0.0/8 ::1/128 fc00::/7"
1099
+
1100
+ # Print the validated allowlist one host per line. Rejects IP literals and
1101
+ # anything that is not a plain hostname, so a literal metadata IP can never be
1102
+ # allowlisted. Returns 1 on an invalid entry or an empty list.
1103
+ _sandbox_egress_hosts() {
1104
+ local raw="${1:-$SANDBOX_ALLOW_HOSTS}" h count=0
1105
+ local -a parts
1106
+ IFS=',' read -ra parts <<< "$raw"
1107
+ for h in "${parts[@]}"; do
1108
+ h="${h//[[:space:]]/}"
1109
+ [[ -z "$h" ]] && continue
1110
+ if [[ ! "$h" =~ ^[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?)+$ ]] \
1111
+ || [[ "$h" =~ ^[0-9.]+$ ]]; then
1112
+ log_error "LOKI_SANDBOX_ALLOW_HOSTS: invalid host (hostnames only, no IPs): $h"
1113
+ return 1
1114
+ fi
1115
+ printf '%s\n' "$h" | tr '[:upper:]' '[:lower:]'
1116
+ count=$((count+1))
1117
+ done
1118
+ [[ $count -gt 0 ]]
1119
+ }
1120
+
1121
+ # Filtering proxy (CONNECT and plain HTTP). Decision function is check_target so
1122
+ # tests can exercise it with an injected resolver; no network needed.
1123
+ _sandbox_egress_proxy_source() {
1124
+ cat <<'PYEOF'
1125
+ import ipaddress, os, socket, sys, threading
1126
+
1127
+ ALLOW = {h.strip().lower() for h in os.environ.get("ALLOW_HOSTS", "").split(",") if h.strip()}
1128
+ BLOCKED = [ipaddress.ip_network(c) for c in os.environ.get("BLOCKED_CIDRS", "").split()]
1129
+
1130
+
1131
+ def ip_blocked(ip):
1132
+ a = ipaddress.ip_address(ip)
1133
+ if getattr(a, "ipv4_mapped", None):
1134
+ a = a.ipv4_mapped
1135
+ return (not a.is_global or getattr(a, "is_site_local", False) or a.is_private or a.is_link_local or a.is_loopback or a.is_unspecified
1136
+ or a.is_multicast or a.is_reserved or any(a in n for n in BLOCKED))
1137
+
1138
+
1139
+ def check_target(host, resolve=None, port=443):
1140
+ """Return (ok, reason). Allowed only if host is listed, port is 80 or 443 and every resolved IP is global."""
1141
+ if port not in (80, 443):
1142
+ return False, "port not allowed"
1143
+ host = host.strip().lower().rstrip(".")
1144
+ if host not in ALLOW:
1145
+ return False, "host not in allowlist"
1146
+ resolve = resolve or (lambda h: [i[4][0] for i in socket.getaddrinfo(h, None)])
1147
+ try:
1148
+ ips = resolve(host)
1149
+ except Exception:
1150
+ return False, "resolution failed"
1151
+ if not ips:
1152
+ return False, "no address"
1153
+ for ip in ips:
1154
+ if ip_blocked(ip):
1155
+ return False, "resolves to blocked range " + ip
1156
+ return True, ips[0]
1157
+
1158
+
1159
+ def pipe(a, b):
1160
+ try:
1161
+ while True:
1162
+ d = a.recv(65536)
1163
+ if not d:
1164
+ break
1165
+ b.sendall(d)
1166
+ except OSError:
1167
+ pass
1168
+ finally:
1169
+ for s in (a, b):
1170
+ try:
1171
+ s.shutdown(socket.SHUT_RDWR)
1172
+ except OSError:
1173
+ pass
1174
+
1175
+
1176
+ def handle(c):
1177
+ try:
1178
+ buf = b""
1179
+ while b"\r\n\r\n" not in buf and len(buf) < 65536:
1180
+ d = c.recv(4096)
1181
+ if not d:
1182
+ return
1183
+ buf += d
1184
+ head, _, rest = buf.partition(b"\r\n\r\n")
1185
+ line = head.split(b"\r\n")[0].decode("latin1").split()
1186
+ if len(line) < 3:
1187
+ return
1188
+ method, target = line[0], line[1]
1189
+ if method.upper() == "CONNECT":
1190
+ host, _, port = target.rpartition(":")
1191
+ port = int(port or 443)
1192
+ rest_out = b""
1193
+ else:
1194
+ u = target.split("://", 1)[-1].split("/", 1)[0]
1195
+ host, _, port = u.partition(":")
1196
+ port = int(port or 80)
1197
+ rest_out = buf
1198
+ ok, info = check_target(host, port=port)
1199
+ if not ok:
1200
+ c.sendall(b"HTTP/1.1 403 Forbidden\r\nContent-Length: 0\r\n\r\n")
1201
+ return
1202
+ up = socket.create_connection((info, port), timeout=20)
1203
+ if method.upper() == "CONNECT":
1204
+ c.sendall(b"HTTP/1.1 200 Connection established\r\n\r\n")
1205
+ else:
1206
+ up.sendall(rest_out)
1207
+ t = threading.Thread(target=pipe, args=(up, c), daemon=True)
1208
+ t.start()
1209
+ pipe(c, up)
1210
+ except Exception:
1211
+ pass
1212
+ finally:
1213
+ c.close()
1214
+
1215
+
1216
+ def main():
1217
+ s = socket.socket()
1218
+ s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
1219
+ s.bind(("0.0.0.0", int(os.environ.get("PROXY_PORT", "3128"))))
1220
+ s.listen(128)
1221
+ while True:
1222
+ conn, _ = s.accept()
1223
+ threading.Thread(target=handle, args=(conn,), daemon=True).start()
1224
+
1225
+
1226
+ if __name__ == "__main__":
1227
+ main()
1228
+ PYEOF
1229
+ }
1230
+
1231
+ # Echo the docker run argv fragments the sandbox container needs when the
1232
+ # allowlist is on: one token per line (network override first, then proxy env).
1233
+ _sandbox_egress_container_args() {
1234
+ local net="$1" proxy="http://${CONTAINER_NAME}-egress:${EGRESS_PROXY_PORT}"
1235
+ printf '%s\n' "--network=$net"
1236
+ local v
1237
+ for v in HTTP_PROXY HTTPS_PROXY http_proxy https_proxy; do
1238
+ printf '%s\n' "--env" "$v=$proxy"
1239
+ done
1240
+ printf '%s\n' "--env" "NO_PROXY=localhost,127.0.0.1"
1241
+ }
1242
+
1243
+ # Echo the docker run argv for the proxy sidecar (test-visible, no side effects).
1244
+ _sandbox_egress_proxy_args() {
1245
+ local script="$1" hosts="$2"
1246
+ printf '%s\n' run --detach --name "${CONTAINER_NAME}-egress" \
1247
+ --network=bridge --cap-drop=ALL --security-opt=no-new-privileges:true \
1248
+ --read-only --pids-limit=128 --memory=256m \
1249
+ --env "ALLOW_HOSTS=$hosts" --env "BLOCKED_CIDRS=$EGRESS_BLOCKED_CIDRS" \
1250
+ --env "PROXY_PORT=$EGRESS_PROXY_PORT" \
1251
+ --volume "$script:/egress-proxy.py:ro" \
1252
+ --entrypoint python3 "$SANDBOX_IMAGE" /egress-proxy.py
1253
+ }
1254
+
1255
+ # Create the internal network and proxy sidecar. Echoes the internal network name.
1256
+ _sandbox_egress_up() {
1257
+ local hosts="$1" net="${CONTAINER_NAME}-internal" script
1258
+ local dir="${LOKI_RUN_TMP:-${TMPDIR:-/tmp}/loki-egress-${CONTAINER_NAME}}"
1259
+ mkdir -p "$dir" && chmod 700 "$dir" || return 1
1260
+ script="$dir/egress-proxy.py"
1261
+ _sandbox_egress_proxy_source > "$script"
1262
+ docker rm -f "${CONTAINER_NAME}-egress" >/dev/null 2>&1 || true
1263
+ docker network rm "$net" >/dev/null 2>&1 || true
1264
+ docker network create --internal "$net" >/dev/null || return 1
1265
+ local -a pargs=()
1266
+ while IFS= read -r line; do pargs+=("$line"); done < <(_sandbox_egress_proxy_args "$script" "$(echo "$hosts" | paste -sd, -)")
1267
+ docker "${pargs[@]}" >/dev/null || return 1
1268
+ docker network connect --alias "${CONTAINER_NAME}-egress" "$net" "${CONTAINER_NAME}-egress" >/dev/null || return 1
1269
+ printf '%s\n' "$net"
1270
+ }
1271
+
1272
+ _sandbox_egress_down() {
1273
+ local fb="${TMPDIR:-/tmp}/loki-egress-${CONTAINER_NAME}"
1274
+ if [[ -z "${LOKI_RUN_TMP:-}" && -d "$fb" && ! -L "$fb" ]]; then rm -rf -- "$fb"; fi
1275
+ docker rm -f "${CONTAINER_NAME}-egress" >/dev/null 2>&1 || true
1276
+ docker network rm "${CONTAINER_NAME}-internal" >/dev/null 2>&1 || true
1277
+ }
1278
+
1086
1279
  start_sandbox() {
1087
1280
  # Parse arguments: extract flags before positional args
1088
1281
  local prd_path=""
@@ -1158,6 +1351,22 @@ start_sandbox() {
1158
1351
  # Clean up any stopped container with same name
1159
1352
  docker rm -f "$CONTAINER_NAME" 2>/dev/null || true
1160
1353
 
1354
+ local network_arg="--network=$SANDBOX_NETWORK" egress_args=() egress_ok=1
1355
+ if [[ -n "$SANDBOX_ALLOW_HOSTS" ]]; then
1356
+ local egress_hosts egress_net
1357
+ egress_hosts=$(_sandbox_egress_hosts) || return 1
1358
+ if [[ "$SANDBOX_NETWORK" != "bridge" ]]; then
1359
+ log_error "LOKI_SANDBOX_ALLOW_HOSTS requires LOKI_SANDBOX_NETWORK=bridge (got $SANDBOX_NETWORK)"
1360
+ return 1
1361
+ fi
1362
+ egress_net=$(_sandbox_egress_up "$egress_hosts") || { _sandbox_egress_down; log_error "Failed to start egress proxy"; return 1; }
1363
+ # Any return after this point (refusal, failed docker run) must tear the proxy and network down.
1364
+ egress_ok=0
1365
+ trap '[[ "${egress_ok:-1}" == 1 ]] || _sandbox_egress_down; trap - RETURN' RETURN
1366
+ while IFS= read -r line; do egress_args+=("$line"); done < <(_sandbox_egress_container_args "$egress_net")
1367
+ network_arg="${egress_args[0]}"
1368
+ fi
1369
+
1161
1370
  log_info "Starting sandbox container..."
1162
1371
  log_info " Image: $SANDBOX_IMAGE"
1163
1372
  log_info " Project: $PROJECT_DIR"
@@ -1188,8 +1397,12 @@ start_sandbox() {
1188
1397
  # and should not be able to change UID/GID, which would undermine isolation
1189
1398
 
1190
1399
  # Network
1191
- "--network=$SANDBOX_NETWORK"
1400
+ "$network_arg"
1192
1401
  )
1402
+ if [[ ${#egress_args[@]} -gt 1 ]]; then
1403
+ docker_args+=("${egress_args[@]:1}")
1404
+ log_info " Egress: allowlist only ($(echo "$egress_hosts" | paste -sd, -)); private and link-local ranges blocked"
1405
+ fi
1193
1406
 
1194
1407
  # Add seccomp profile if available
1195
1408
  local seccomp_profile="$SKILL_DIR/autonomy/seccomp-sandbox.json"
@@ -1393,7 +1606,8 @@ start_sandbox() {
1393
1606
 
1394
1607
  # Run container
1395
1608
  local container_id
1396
- container_id=$(docker "${docker_args[@]}")
1609
+ container_id=$(docker "${docker_args[@]}") || { log_error "docker run failed"; return 1; }
1610
+ egress_ok=1
1397
1611
 
1398
1612
  log_success "Sandbox started: ${container_id:0:12}"
1399
1613
  log_info ""
@@ -1435,6 +1649,7 @@ stop_sandbox() {
1435
1649
  while [ $waited -lt 10 ]; do
1436
1650
  if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
1437
1651
  log_success "Sandbox stopped gracefully"
1652
+ _sandbox_egress_down
1438
1653
  # Remove the temp state dir now that the container has stopped.
1439
1654
  if [[ -n "$_state_dir" ]] && [[ -d "$_state_dir" ]]; then
1440
1655
  rm -rf -- "$_state_dir" 2>/dev/null || true
@@ -1455,8 +1670,10 @@ stop_sandbox() {
1455
1670
  rm -rf -- "$_state_dir" 2>/dev/null || true
1456
1671
  fi
1457
1672
 
1673
+ _sandbox_egress_down
1458
1674
  log_success "Sandbox stopped"
1459
1675
  else
1676
+ _sandbox_egress_down
1460
1677
  log_warn "No running sandbox found"
1461
1678
  fi
1462
1679
  }
@@ -1880,6 +2097,7 @@ show_help() {
1880
2097
  echo "Environment Variables:"
1881
2098
  echo " LOKI_SANDBOX_IMAGE Docker image (default: loki-mode:sandbox)"
1882
2099
  echo " LOKI_SANDBOX_NETWORK Network mode: bridge, none, host (default: bridge)"
2100
+ echo " LOKI_SANDBOX_ALLOW_HOSTS Egress allowlist, comma-separated hostnames (default: unset)"
1883
2101
  echo " LOKI_SANDBOX_CPUS CPU limit (default: 2)"
1884
2102
  echo " LOKI_SANDBOX_MEMORY Memory limit (default: 4g)"
1885
2103
  echo " LOKI_SANDBOX_READONLY Mount project read-only (default: false)"
@@ -7,6 +7,6 @@ Modules:
7
7
  control: Session control helpers (status, atomic writes, origin check)
8
8
  """
9
9
 
10
- __version__ = "11.31.1"
10
+ __version__ = "11.33.0"
11
11
 
12
12
  __all__ = ["__version__"]
@@ -1,6 +1,6 @@
1
1
  # CLI reference
2
2
 
3
- Generated from `loki-ts/src/cli/registry.ts` by `scripts/generate-stale-zero.sh` for v11.31.1. Do not edit by hand.
3
+ Generated from `loki-ts/src/cli/registry.ts` by `scripts/generate-stale-zero.sh` for v11.33.0. Do not edit by hand.
4
4
  Hidden and legacy-only commands are omitted; see `docs/v10/CLI-MODERN.md` for the full inventory.
5
5
 
6
6
  | Command | Description |
@@ -2,7 +2,7 @@
2
2
 
3
3
  Loki Mode, by [Autonomi](https://www.autonomi.dev/), turns a spec or an issue into a verified pull request with a receipt you can check. This page covers installing it, a first run, and the options around it.
4
4
 
5
- **Version:** v11.31.1
5
+ **Version:** v11.33.0
6
6
 
7
7
  **Engine note:** `loki start` is the primary command. `loki start`, `loki "<task>"`, `loki owner/repo#N` and `loki quick` all run the Loki 10 engine, whatever the flag order or input form (PRD file, issue ref, task or one-word brief). The previous loop runs only on `--engine legacy`, `LOKI_ENGINE=legacy`, or for one call when a flag Loki 10 cannot honor is present. See the [user guide](v10/GUIDE.md) and the [5-minute quickstart](QUICKSTART.md).
8
8