Files
Daveswo 77856f0a09 Initial commit: apkovl deployment source, synced from shell-project main
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.
2026-08-22 02:55:40 -04:00

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