#!/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 → 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 → 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