ollie/scripts/osh

133 lines
3.8 KiB
Bash
Executable File

#!/usr/bin/env bash
# osh: interactive chat interface for ollie AI agents.
set -o history
MOUNT=${OLLIE_9MOUNT:-$HOME/mnt/ollie}
LAST=$HOME/.config/ollie/last-session
# load_session <dir> → prints session ID for that dir, or nothing
load_session() {
awk -F'\t' -v d="$1" 'NF==2 && $1==d {print $2; exit}' "$LAST" 2>/dev/null
}
# save_session <dir> <id> → upserts {dir}\t{id} in $LAST
save_session() {
local dir="$1" id="$2" tmp
mkdir -p "$(dirname "$LAST")"
tmp=$(mktemp)
awk -F'\t' -v d="$dir" 'NF==2 && $1!=d' "$LAST" 2>/dev/null > "$tmp" || true
printf '%s\t%s\n' "$dir" "$id" >> "$tmp"
mv "$tmp" "$LAST"
}
die() { printf 'error: %s\n' "$*" >&2; exit 1; }
is_idle() { [ "$(tr -d '[:space:]' < "$S/state" 2>/dev/null)" = "idle" ]; }
drain() {
local n
n=$(wc -c < "$S/chat" 2>/dev/null || echo 0)
[ "$n" -gt "$chat_off" ] && tail -c "+$((chat_off+1))" "$S/chat" | head -c "$((n-chat_off))"
chat_off=$n
}
# Parse flags
new=0; resume=0; id_flag=; name=
backend=; model=; agent=; cwd=
while [ $# -gt 0 ]; do
case $1 in
-new) new=1 ;;
-resume) resume=1 ;;
-session) id_flag=$2; shift ;;
-name) name=$2; shift ;;
-backend) backend=$2; shift ;;
-model) model=$2; shift ;;
-agent) agent=$2; shift ;;
-cwd) cwd=$2; shift ;;
*) die "unknown flag: $1" ;;
esac
shift
done
# Write spec to s/new
write_new() {
{
[ -n "$name" ] && printf 'name=%s\n' "$name"
printf 'cwd=%s\n' "${cwd:-$(pwd)}"
[ -n "$backend" ] && printf 'backend=%s\n' "$backend"
[ -n "$model" ] && printf 'model=%s\n' "$model"
[ -n "$agent" ] && printf 'agent=%s\n' "$agent"
true
} > "$MOUNT/s/new" || die "write s/new"
}
# Create a fresh session, sets $id
create_session() {
local before d deadline
if [ -n "$name" ]; then
write_new
id=$name
else
before=$(ls "$MOUNT/s" 2>/dev/null)
write_new
deadline=$(($(date +%s) + 5))
while [ "$(date +%s)" -lt "$deadline" ]; do
for d in $(ls "$MOUNT/s" 2>/dev/null); do
printf '%s\n' "$before" | grep -qxF "$d" || { id=$d; return; }
done
sleep 0.05
done
die "timeout waiting for new session"
fi
}
# Resolve session
_dir=${cwd:-$(pwd)}
if [ -n "$id_flag" ]; then
[ -d "$MOUNT/s/$id_flag" ] || die "session not found: $id_flag"
id=$id_flag; printf 'session: %s (resumed)\n' "$id" >&2
elif [ "$resume" = 1 ]; then
id=$(load_session "$_dir")
[ -n "$id" ] && [ -d "$MOUNT/s/$id" ] || die "no resumable session for $_dir"
printf 'session: %s (resumed)\n' "$id" >&2
elif [ "$new" = 1 ]; then
create_session
save_session "$_dir" "$id"
printf 'session: %s\n' "$id" >&2
else
id=$(load_session "$_dir")
if [ -n "$id" ] && [ -d "$MOUNT/s/$id" ]; then
printf 'session: %s (resumed)\n' "$id" >&2
else
create_session
save_session "$_dir" "$id"
printf 'session: %s\n' "$id" >&2
fi
fi
S=$MOUNT/s/$id
chat_off=$(wc -c < "$S/chat" 2>/dev/null || echo 0)
trap '
[ "$(tr -d "[:space:]" < "$S/state" 2>/dev/null)" = "idle" ] && exit
printf "\n" >&2; printf "stop\n" > "$S/ctl"
' INT
while true; do
printf '> '
IFS= read -re input || break
[ -z "$input" ] && continue
history -s "$input"
case "$input" in
/kill) printf 'kill\n' > "$S/ctl"; break ;;
"/q "*) printf '%s\n' "${input#/q }" > "$S/enqueue"
printf 'queued\n' >&2; continue ;;
/*) printf '%s\n' "$input" > "$S/ctl" ;;
*) printf '%s' "$input" > "$S/prompt" || { printf 'submit failed\n' >&2; continue; } ;;
esac
sleep 0.15
while true; do
drain
is_idle && { drain; printf '\n'; break; }
sleep 0.05
done
done