298 lines
6.3 KiB
Bash
Executable File
298 lines
6.3 KiB
Bash
Executable File
#!/usr/bin/env rc
|
|
# sh: interactive chat interface for ollie AI agents.
|
|
|
|
MOUNT=/home/lkn/mnt/ollie
|
|
if(! ~ $#OLLIE 0) MOUNT=$OLLIE
|
|
LAST=$home/.config/ollie/last-session
|
|
|
|
fn load_session {
|
|
awk -F'\t' -v d=$1 'NF==2 && $1==d {print $2; exit}' $LAST >[2]/dev/null
|
|
}
|
|
|
|
fn save_session {
|
|
dir=$1
|
|
sessid=$2
|
|
mkdir -p `{dirname $LAST}
|
|
tmp=`{mktemp}
|
|
if(test -f $LAST)
|
|
awk -F'\t' -v d=$dir 'NF==2 && $1!=d' $LAST > $tmp
|
|
printf '%s\t%s\n' $dir $sessid >> $tmp
|
|
mv $tmp $LAST
|
|
}
|
|
|
|
fn die {
|
|
echo $* >[1=2]
|
|
exit 1
|
|
}
|
|
fn cfg_get {
|
|
awk -F= -v k=$1 '$1==k{gsub(/^[ \t]+|[ \t]+$/,"",$2); print $2; exit}' $S/cfg >[2]/dev/null
|
|
}
|
|
|
|
fn is_idle {
|
|
~ `{cfg_get state} idle
|
|
}
|
|
|
|
fn show_new {
|
|
n=`{wc -c < $S/chat >[2]/dev/null}
|
|
if(~ $#n 0) n=0
|
|
if(test $n -gt $chat_off) {
|
|
tail -c +`{expr $chat_off + 1} $S/chat
|
|
chat_off=$n
|
|
printf '\n'
|
|
}
|
|
}
|
|
|
|
fn usage {
|
|
cat <<'EOF'
|
|
usage: sh [flags]
|
|
|
|
Flags:
|
|
-new always create a new session
|
|
-resume resume last session for cwd; error if none
|
|
-session ID attach to a specific session by ID
|
|
-name NAME name for new session
|
|
-cwd DIR working directory (default: pwd)
|
|
-backend BACKEND backend override (e.g. ollama, anthropic)
|
|
-model MODEL model override
|
|
-agent AGENT agent config name
|
|
-help show this help
|
|
|
|
Input at the prompt:
|
|
<text> submit prompt, stream response
|
|
/stop interrupt the current agent turn
|
|
/kill kill the session and exit
|
|
/compact compact conversation history
|
|
/clear clear conversation history
|
|
/rn <name> rename the session
|
|
/cwd [path] get or set working directory
|
|
/agent [name] get or set agent
|
|
/model [name] get or set model
|
|
/backend [name] get or set backend
|
|
/models list available models
|
|
/backends list available backends
|
|
/mcp list MCP servers
|
|
/usage show token usage
|
|
/ctxsz show context size
|
|
/q <text> enqueue prompt for later
|
|
! <cmd> run shell command in session cwd
|
|
line ending with \ continue input (next line)
|
|
Enter submit input
|
|
Ctrl-C interrupt running turn (or exit if idle)
|
|
EOF
|
|
exit 0
|
|
}
|
|
|
|
new=0
|
|
resume=0
|
|
|
|
while(! ~ $#* 0) {
|
|
switch($1) {
|
|
case -new
|
|
new=1
|
|
case -resume
|
|
resume=1
|
|
case -session
|
|
id_flag=$2
|
|
shift
|
|
case -name
|
|
name=$2
|
|
shift
|
|
case -backend
|
|
backend=$2
|
|
shift
|
|
case -model
|
|
model=$2
|
|
shift
|
|
case -agent
|
|
agent=$2
|
|
shift
|
|
case -cwd
|
|
cwd=$2
|
|
shift
|
|
case -help
|
|
usage
|
|
case *
|
|
die 'unknown flag: '^$1
|
|
}
|
|
shift
|
|
}
|
|
|
|
_dir=`{pwd}
|
|
if(! ~ $#cwd 0) _dir=$cwd
|
|
|
|
fn write_new {
|
|
{
|
|
if(! ~ $#name 0) printf 'name=%s\n' $name
|
|
printf 'cwd=%s\n' $_dir
|
|
if(! ~ $#backend 0) printf 'backend=%s\n' $backend
|
|
if(! ~ $#model 0) printf 'model=%s\n' $model
|
|
if(! ~ $#agent 0) printf 'agent=%s\n' $agent
|
|
} > $MOUNT/s/new
|
|
}
|
|
|
|
fn create_session {
|
|
if(! ~ $#name 0) {
|
|
write_new
|
|
id=$name
|
|
return
|
|
}
|
|
before=`{ls $MOUNT/s >[2]/dev/null}
|
|
write_new
|
|
deadline=`{expr `{date +%s} + 5}
|
|
while(test `{date +%s} -lt $deadline) {
|
|
for(d in `{ls $MOUNT/s >[2]/dev/null}) {
|
|
if(! ~ $d $before) {
|
|
id=$d
|
|
return
|
|
}
|
|
}
|
|
sleep 0.05
|
|
}
|
|
die 'timeout waiting for new session'
|
|
}
|
|
|
|
_picked=0
|
|
|
|
if(! ~ $#id_flag 0) {
|
|
if(! test -d $MOUNT/s/$id_flag) die 'session not found: '^$id_flag
|
|
id=$id_flag
|
|
printf 'session: %s (resumed)\n' $id >[1=2]
|
|
_picked=1
|
|
}
|
|
|
|
if(~ $_picked 0) if(~ $resume 1) {
|
|
id=`{load_session $_dir}
|
|
if(~ $#id 0) die 'no resumable session for '^$_dir
|
|
if(! test -d $MOUNT/s/$id) die 'no resumable session for '^$_dir
|
|
printf 'session: %s (resumed)\n' $id >[1=2]
|
|
_picked=1
|
|
}
|
|
|
|
if(~ $_picked 0) if(~ $new 1) {
|
|
create_session
|
|
save_session $_dir $id
|
|
printf 'session: %s\n' $id >[1=2]
|
|
_picked=1
|
|
}
|
|
|
|
if(~ $_picked 0) {
|
|
id=`{load_session $_dir}
|
|
if(! ~ $#id 0) if(test -d $MOUNT/s/$id) {
|
|
printf 'session: %s (resumed)\n' $id >[1=2]
|
|
_picked=1
|
|
}
|
|
if(~ $_picked 0) {
|
|
create_session
|
|
save_session $_dir $id
|
|
printf 'session: %s\n' $id >[1=2]
|
|
}
|
|
}
|
|
|
|
S=$MOUNT/s/$id
|
|
|
|
fn sigintr {
|
|
printf '\n' >[1=2]
|
|
if(is_idle) exit 0
|
|
echo stop > $S/ctl
|
|
}
|
|
|
|
chat_off=0
|
|
if(test -f $S/chat) chat_off=`{wc -c < $S/chat}
|
|
|
|
if(! test -t 0) {
|
|
cat > $S/prompt || die 'submit failed'
|
|
exit 0
|
|
}
|
|
|
|
tail -n 40 $S/chat >[2]/dev/null
|
|
|
|
while(true) {
|
|
input=`` () {bash -c 'buf=; p="> "; c=". "
|
|
while IFS= read -r -e -p "$p" line </dev/tty >/dev/tty; do
|
|
case $line in
|
|
*\\) buf="${buf}${line%\\}
|
|
"; p="$c";;
|
|
*) printf "%s" "$buf$line"; exit 0;;
|
|
esac
|
|
done; exit 1'}
|
|
if(~ $#input 0) break
|
|
switch($"input) {
|
|
case /stop
|
|
echo stop > $S/ctl
|
|
case /kill
|
|
echo kill > $S/ctl
|
|
break
|
|
case /compact
|
|
echo compact > $S/ctl
|
|
case /clear
|
|
echo clear > $S/ctl
|
|
case /cwd
|
|
cfg_get cwd
|
|
echo
|
|
case /cwd?*
|
|
newcwd=`{printf '%s\n' $"input | sed 's,^/cwd ,,'}
|
|
printf 'cwd=%s\n' $newcwd > $S/cfg
|
|
case /agent
|
|
cfg_get agent
|
|
echo
|
|
case /agent?*
|
|
printf 'agent=%s\n' `{printf '%s\n' $"input | sed 's,^/agent ,,'} > $S/cfg
|
|
show_new
|
|
case /model
|
|
cfg_get model
|
|
echo
|
|
case /model?*
|
|
printf 'model=%s\n' `{printf '%s\n' $"input | sed 's,^/model ,,'} > $S/cfg
|
|
show_new
|
|
case /backend
|
|
cfg_get backend
|
|
echo
|
|
case /backend?*
|
|
printf 'backend=%s\n' `{printf '%s\n' $"input | sed 's,^/backend ,,'} > $S/cfg
|
|
show_new
|
|
case /models
|
|
cat $S/models >[2]/dev/null
|
|
case /backends
|
|
cat $MOUNT/backends >[2]/dev/null
|
|
case /usage
|
|
cat $S/usage >[2]/dev/null
|
|
case /ctxsz
|
|
cat $S/ctxsz >[2]/dev/null
|
|
case /mcp
|
|
cat $S/mcp >[2]/dev/null
|
|
case /rn?*
|
|
newid=`{printf '%s\n' $"input | sed 's,^/rn ,,'}
|
|
printf 'rn %s\n' $newid > $S/ctl
|
|
id=$newid
|
|
S=$MOUNT/s/$id
|
|
save_session $_dir $id
|
|
case /q?*
|
|
printf '%s\n' `{printf '%s\n' $"input | sed 's,^/q ,,'} > $S/fifo.in
|
|
printf 'queued\n' >[1=2]
|
|
case /*
|
|
printf 'unknown command: %s\n' `{printf '%s\n' $"input | awk '{print $1}'} >[1=2]
|
|
case *
|
|
printf '%s\n' $"input > $S/prompt || { printf 'submit failed\n' >[1=2] }
|
|
|
|
cat $S/statewait >/dev/null >[2]/dev/null
|
|
|
|
while(! is_idle) {
|
|
n=`{wc -c < $S/chat >[2]/dev/null}
|
|
if(~ $#n 0) n=0
|
|
if(test $n -gt $chat_off) {
|
|
tail -c +`{expr $chat_off + 1} $S/chat
|
|
chat_off=$n
|
|
}
|
|
sleep 0.05
|
|
}
|
|
|
|
n=`{wc -c < $S/chat >[2]/dev/null}
|
|
if(~ $#n 0) n=0
|
|
if(test $n -gt $chat_off) {
|
|
tail -c +`{expr $chat_off + 1} $S/chat
|
|
chat_off=$n
|
|
}
|
|
printf '\n'
|
|
}
|
|
}
|