77856f0a09
This directory is what gets packaged into localhost.apkovl.tar.gz and
baked into adaptiveos-vN.iso. It had drifted out of sync with the real
project (root/shell-project/ had gone missing entirely at some point
after being written earlier this session -- cause unconfirmed, Desktop
is a plausible sync-related suspect but not verified) and separately
carried a stale, pre-fix copy of fx.sh.
Current contents:
etc/profile.d/adaptive-loop.sh -- disk-persists p AND p.trace
(fx()'s fix-provenance record)
across reboot, two separate 1MB
blocks on the attached state disk
etc/profile.d/adaptive-setup.sh -- unchanged, network/git/fzf bring-up
root/shell-project/ -- synced from space.seodr.ovh/
Daveswo/self-healing-autopoietic-
shell @ 3c4947e (main), plus three
deployment-only files not in that
repo: contrib/fx/builtin-fixes.txt,
contrib/fx/sources.d/15-builtin-
fixes.sh, contrib/heal/heal.sh
Verified byte-identical to what's actually embedded in the currently
shipping adaptiveos-v6.iso before this commit (full recursive diff),
and every .sh file passes `sh -n`.
.gitattributes (text=auto eol=lf) added to prevent the CRLF corruption
that hit the canonical repo earlier this session from recurring here.
107 lines
4.0 KiB
Bash
107 lines
4.0 KiB
Bash
#!/bin/sh
|
|
# Last-resort source: queries a local LLM inference server (Ollama or
|
|
# LM Studio, both common on dev machines) for a fix, only when nothing
|
|
# cheaper matched. Silent no-op if no local server is reachable -- this
|
|
# never touches the network, never calls a paid API, and costs nothing
|
|
# when you don't have a local model running.
|
|
#
|
|
# Numbered 70, after every deterministic/heuristic source (10-50) and
|
|
# before the team-shared source (90): cheap, verified candidates should
|
|
# always win first. fx.sh's FX_AUTO picks the *first* candidate in
|
|
# source order, so this only gets auto-selected when it's the only
|
|
# opinion offered at all -- still review it, this is a guess, not a
|
|
# fact. Every other default source is either a cached human decision or
|
|
# a deterministic rule; this is the one source capable of confidently
|
|
# suggesting something wrong.
|
|
#
|
|
# Needs python3 (uses urllib, no extra dependency beyond stdlib) --
|
|
# silently no-ops without it rather than ship a fragile hand-rolled
|
|
# JSON encoder in shell.
|
|
|
|
cmd="$1"
|
|
[ -n "$cmd" ] || exit 0
|
|
command -v python3 >/dev/null 2>&1 || exit 0
|
|
|
|
OLLAMA_URL="${FX_LLM_OLLAMA_URL:-http://127.0.0.1:11434}"
|
|
LMSTUDIO_URL="${FX_LLM_LMSTUDIO_URL:-http://127.0.0.1:1234}"
|
|
|
|
python3 - "$cmd" "$FX_OUTPUT" "$OLLAMA_URL" "$LMSTUDIO_URL" <<'PYEOF'
|
|
import sys, json, urllib.request, urllib.error
|
|
|
|
cmd, output, ollama_url, lmstudio_url = sys.argv[1:5]
|
|
|
|
PROMPT = f"""You are suggesting a fix for a failed shell command, to be taught to a self-healing shell function called f().
|
|
Rules:
|
|
- f() will eval your fix ONCE as a precondition, then re-run the ORIGINAL command verbatim. Your fix never replaces the command; it only sets up state so the original command then succeeds.
|
|
- If the fix should generalize beyond this one invocation, define a shell function with the same name as the failing program, wrapping the real binary via `command <prog> "$@"`, and only special-case the failure inside it.
|
|
- Respond with EXACTLY ONE LINE of POSIX shell and nothing else: no explanation, no markdown, no code fences, no commentary.
|
|
|
|
Failing command: {cmd}
|
|
|
|
Its output:
|
|
{output}
|
|
"""
|
|
|
|
def http_json(url, payload, timeout):
|
|
req = urllib.request.Request(
|
|
url, data=json.dumps(payload).encode(),
|
|
headers={"Content-Type": "application/json"}, method="POST",
|
|
)
|
|
with urllib.request.urlopen(req, timeout=timeout) as r:
|
|
return json.loads(r.read().decode())
|
|
|
|
def http_get_json(url, timeout):
|
|
with urllib.request.urlopen(url, timeout=timeout) as r:
|
|
return json.loads(r.read().decode())
|
|
|
|
def try_ollama():
|
|
tags = http_get_json(ollama_url.rstrip("/") + "/api/tags", 2)
|
|
models = tags.get("models") or []
|
|
if not models:
|
|
return None
|
|
model = models[0].get("name")
|
|
if not model:
|
|
return None
|
|
resp = http_json(
|
|
ollama_url.rstrip("/") + "/api/generate",
|
|
{"model": model, "prompt": PROMPT, "stream": False},
|
|
25,
|
|
)
|
|
return (resp.get("response") or "").strip()
|
|
|
|
def try_lmstudio():
|
|
models = http_get_json(lmstudio_url.rstrip("/") + "/v1/models", 2)
|
|
data = models.get("data") or []
|
|
if not data:
|
|
return None
|
|
model = data[0].get("id")
|
|
if not model:
|
|
return None
|
|
resp = http_json(
|
|
lmstudio_url.rstrip("/") + "/v1/chat/completions",
|
|
{
|
|
"model": model,
|
|
"messages": [{"role": "user", "content": PROMPT}],
|
|
"temperature": 0,
|
|
},
|
|
25,
|
|
)
|
|
choices = resp.get("choices") or []
|
|
if not choices:
|
|
return None
|
|
return (choices[0].get("message", {}).get("content") or "").strip()
|
|
|
|
for attempt in (try_ollama, try_lmstudio):
|
|
try:
|
|
result = attempt()
|
|
except (urllib.error.URLError, TimeoutError, OSError, ValueError, KeyError):
|
|
result = None
|
|
if result:
|
|
# Enforce the one-line contract even if the model ignores instructions.
|
|
first_line = result.splitlines()[0].strip()
|
|
first_line = first_line.strip("`")
|
|
if first_line:
|
|
print(first_line)
|
|
break
|
|
PYEOF
|