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.
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/sandbox.sh +220 -2
- package/dashboard/__init__.py +1 -1
- package/docs/CLI-REFERENCE.md +1 -1
- package/docs/INSTALLATION.md +1 -1
- package/loki-ts/dist/loki.js +1101 -1101
- package/mcp/__init__.py +1 -1
- package/package.json +2 -2
- package/packages/control-plane/dist/server.js +9 -7
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/web-app/dist/assets/{AdminPage-A5UQEQzo.js → AdminPage-BOo6zbtK.js} +1 -1
- package/web-app/dist/assets/{Avatar-xQ1gSPVB.js → Avatar-x0_oZtYF.js} +1 -1
- package/web-app/dist/assets/{Badge-dQyhI3UH.js → Badge-jDB28PQG.js} +1 -1
- package/web-app/dist/assets/{Button-BehU-7tC.js → Button-27f5aDrW.js} +1 -1
- package/web-app/dist/assets/{CockpitPage-BPLFrfno.js → CockpitPage-DiAKMj0I.js} +1 -1
- package/web-app/dist/assets/{ComparePage-Dc7ecTbH.js → ComparePage-RKUVbeCG.js} +1 -1
- package/web-app/dist/assets/{ErrorBoundary-P4CkwBs3.js → ErrorBoundary-CQ7c_YJ9.js} +1 -1
- package/web-app/dist/assets/{EvidenceReceiptPanel-CCC_Wc2o.js → EvidenceReceiptPanel-u65ACFhT.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-Bi3c1_CN.js → GitHubIssuesPanel-8oqCXpcg.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-A7D7tuaQ.js → GitHubPRsPanel-DkagnZtO.js} +1 -1
- package/web-app/dist/assets/{HomePage-Cj7XatdG.js → HomePage-C5IyKJcO.js} +2 -2
- package/web-app/dist/assets/{LoginPage-lFQFWzTA.js → LoginPage-C6-NK6Dd.js} +1 -1
- package/web-app/dist/assets/{MagicPage-sHngqPOB.js → MagicPage-FrUAlTFq.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-vmk3uj53.js → MetricsPage-DbHKrYaS.js} +1 -1
- package/web-app/dist/assets/{NotFoundPage-B37nihqx.js → NotFoundPage-qg7ISn6m.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-TrK9SBhV.js → ProjectPage-BZzSg0vO.js} +3 -3
- package/web-app/dist/assets/{ProjectsPage-BoKOqxu4.js → ProjectsPage-CSrkVM0Z.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-Dfyhp6nl.js → SettingsPage-DoIoG_1E.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-DOCeqHxj.js → ShowcasePage-Cn1JmtRU.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-idLDGVpd.js → SystemSettingsPage-FD9fvAf5.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-CG0cXstx.js → TeamsPage-BkGU3fTI.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-cThU88IX.js → TemplatesPage-Oon4z663.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-CZcfr298.js → TerminalOutput-CLysHg_-.js} +1 -1
- package/web-app/dist/assets/{activity-JwnQAJzU.js → activity-BcCTbGiS.js} +1 -1
- package/web-app/dist/assets/{bell-EMBOci2m.js → bell-BcGnBkdg.js} +1 -1
- package/web-app/dist/assets/{bot-Dwg7eFlL.js → bot-i31uzgM6.js} +1 -1
- package/web-app/dist/assets/{check-DNNlHviH.js → check-CExa_B4X.js} +1 -1
- package/web-app/dist/assets/{chevron-left-BJ2VldHk.js → chevron-left-BP1rmia5.js} +1 -1
- package/web-app/dist/assets/{circle-alert-0P4HhIzB.js → circle-alert-BOLPoLQg.js} +1 -1
- package/web-app/dist/assets/{clock-DxxclmlE.js → clock-DatwTgIM.js} +1 -1
- package/web-app/dist/assets/{cloud-COohLeUE.js → cloud-C3bRnFkT.js} +1 -1
- package/web-app/dist/assets/{code-xml-BJ25iVx3.js → code-xml-C66CBkn_.js} +1 -1
- package/web-app/dist/assets/{database-Dvy7Bbcf.js → database-D1H0smtm.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-qUbMFHU4.js → dollar-sign-6mehyt0I.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-BHhWk-yB.js → file-code-corner-Dcl58rE8.js} +1 -1
- package/web-app/dist/assets/{file-plus-CXDzSmIV.js → file-plus-DusJH-jr.js} +1 -1
- package/web-app/dist/assets/{globe--nSuGAqH.js → globe-8gbaSEAF.js} +1 -1
- package/web-app/dist/assets/{hammer-CyPoRSsX.js → hammer-BeUMO3eS.js} +1 -1
- package/web-app/dist/assets/{index-sCyA5kT1.js → index-BxlLDUq_.js} +3 -3
- package/web-app/dist/assets/{layers-D8YjFUHc.js → layers-CsO5XwHB.js} +1 -1
- package/web-app/dist/assets/{loader-circle-ZB7xFqz2.js → loader-circle-8JWxq7au.js} +1 -1
- package/web-app/dist/assets/{lock-D1UH6v-O.js → lock-ByAv_0Uq.js} +1 -1
- package/web-app/dist/assets/{package-sDqydd_4.js → package-D-xVbDAP.js} +1 -1
- package/web-app/dist/assets/{plus-ClTe2QX5.js → plus-R7T7gJbI.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-D54nCHRH.js → refresh-cw-D65J_QH-.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-CFJoICwQ.js → rotate-ccw-Cs4Tvq2C.js} +1 -1
- package/web-app/dist/assets/{scroll-text-D_5t4SMl.js → scroll-text-N1tgG-co.js} +1 -1
- package/web-app/dist/assets/{server-cUdSs3Jw.js → server-DD1GqOyy.js} +1 -1
- package/web-app/dist/assets/{shield-alert-o3-54oNJ.js → shield-alert-0V3SJyDi.js} +1 -1
- package/web-app/dist/assets/{thumbs-up-Bj5vm_ge.js → thumbs-up-BT7l40R8.js} +1 -1
- package/web-app/dist/assets/{trash-2-D4yWbHl5.js → trash-2-z1gK-C8I.js} +1 -1
- package/web-app/dist/assets/{trending-down-DStUnNZ3.js → trending-down-Cp3fR71x.js} +1 -1
- package/web-app/dist/assets/{trending-up-CL4ZFYFb.js → trending-up-DXL-NgP-.js} +1 -1
- package/web-app/dist/assets/{upload-Dem0HZRa.js → upload-BX_yQ46Z.js} +1 -1
- package/web-app/dist/assets/{usePolling-14ezEt6p.js → usePolling-Bo7CC4Ha.js} +1 -1
- package/web-app/dist/assets/{user-DuS3X15u.js → user-C7nAwa5x.js} +1 -1
- 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.
|
|
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.
|
|
499
|
+
**v11.33.0 | [Autonomi](https://www.autonomi.dev/) flagship product | core skill**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
11.
|
|
1
|
+
11.33.0
|
package/autonomy/sandbox.sh
CHANGED
|
@@ -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
|
-
"
|
|
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)"
|
package/dashboard/__init__.py
CHANGED
package/docs/CLI-REFERENCE.md
CHANGED
|
@@ -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.
|
|
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 |
|
package/docs/INSTALLATION.md
CHANGED
|
@@ -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.
|
|
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
|
|