aboutsummaryrefslogtreecommitdiff
path: root/tests/e2e.sh
diff options
context:
space:
mode:
Diffstat (limited to 'tests/e2e.sh')
-rwxr-xr-xtests/e2e.sh49
1 files changed, 47 insertions, 2 deletions
diff --git a/tests/e2e.sh b/tests/e2e.sh
index e73dc73..e5b5865 100755
--- a/tests/e2e.sh
+++ b/tests/e2e.sh
@@ -32,6 +32,8 @@ PASSES=0
export DINGGER_TMUX_SOCKET="$SOCK" DINGGER_AGENT_SOCKET="$HOOK" DINGGER_STATE_DIR="$DIR/state"
# The daemon updates an installed pi extension when it starts: not the user's.
export PI_CODING_AGENT_DIR="$DIR/pi-agent"
+# What pi agents cost: the daemons read session files here every 0.3 s.
+export DINGGER_COST_SCAN_SECS=0.3
t() { tmux -u -L "$SOCK" "$@"; }
@@ -260,14 +262,14 @@ if [ -x "$MENUBAR" ]; then
expect "A1 done" "opt $A1 @agent_state" done
expect "menu title" "mb --print | head -1" "title: ×1 ✓1"
expect "menu rows: herdr priority; idle most recent first (C1 since its jump in step 4, B1 since the start)" \
- "mb --print | tail -n +2 | grep -v '^⟳' | cut -f1,2 | tr '\t\n' '|;'" "× blocked|pi;✓ done|Deploy the widget;○ idle|pi;○ idle|Review PR 42;"
+ "mb --print | tail -n +2 | grep -v '^⟳\|^\$ ' | cut -f1,2 | tr '\t\n' '|;'" "× blocked|pi;✓ done|Deploy the widget;○ idle|pi;○ idle|Review PR 42;"
# Wait for working: B1 was idle before, so idle alone would not show the turn.
hook "$B1" working
expect "B1 working" "opt $B1 @agent_state" working
hook "$B1" idle
expect "B1 finishes a turn while watched: idle" "opt $B1 @agent_state" idle
expect "and is now the most recent idle agent: listed first" \
- "mb --print | tail -n +2 | grep -v '^⟳' | cut -f1,2 | tr '\t\n' '|;'" "× blocked|pi;✓ done|Deploy the widget;○ idle|Review PR 42;○ idle|pi;"
+ "mb --print | tail -n +2 | grep -v '^⟳\|^\$ ' | cut -f1,2 | tr '\t\n' '|;'" "× blocked|pi;✓ done|Deploy the widget;○ idle|Review PR 42;○ idle|pi;"
expect "subtitle: location, harness, waiting time" "mb --print | grep 'Deploy the widget' | cut -f3" "a · wa1 · pi · done now"
expect "idle subtitle has no waiting time" "mb --print | grep 'Review PR 42' | cut -f3" "b · wb1 · pi"
mb --click "$A1"
@@ -516,6 +518,7 @@ in_snapshot() { python3 -c 'import sys; print(sys.argv[2] in open(sys.argv[1]).r
expect "snapshot: the agent's session" "in_snapshot '\"session\": \"$W/sessions/a b.jsonl\"'" True 80
expect "snapshot: a running command's exact arguments" "in_snapshot \"'sleep 100000; :' 'two words'\"" True 80
expect "snapshot: the zsh hook's text" "in_snapshot '\"command\": \"slp 200000 | cat\"'" True 80
+expect "snapshot: the other agent's session" "in_snapshot gone.jsonl" True 80
# An agent that exits just before tmux dies (a shutdown stops programs first).
kill "$(ps -o tpgid= -p "$(r display -p -t "$GONE" '#{pane_pid}')" | tr -d ' ')"
expect "the exited agent is noticed" "r show -pv -t $GONE @agent 2>/dev/null" ""
@@ -1099,6 +1102,48 @@ if command -v node >/dev/null; then
fi
t kill-session -t dr
+# --- 25. what agents cost -----------------------------------------------------
+# The daemon reads pi's session files: each reply once, for the session it
+# first appeared in (a fork copies its parent's), closed agents included.
+echo "25. what agents cost"
+SESS="$PI_CODING_AGENT_DIR/sessions/--work--"
+mkdir -p "$SESS"
+reply() { # id cost [days ago]
+ python3 -c 'import json,sys,time; print(json.dumps({"type":"message","id":sys.argv[1],"message":{"role":"assistant","timestamp":int((time.time()-86400*float(sys.argv[3]))*1000),"usage":{"cost":{"total":float(sys.argv[2])}}}}))' "$1" "$2" "${3:-0}"
+}
+header() { echo "{\"type\":\"session\",\"timestamp\":\"$1\"}"; }
+{ header 2026-01-01T00:00:00Z; reply p0 100 40; reply p1 1.25; reply p2 0.25; } >"$SESS/parent.jsonl"
+# The fork: its parent's entries, then its own.
+{ header 2026-01-02T00:00:00Z; tail -n +2 "$SESS/parent.jsonl"; reply f1 0.5; } >"$SESS/fork.jsonl"
+expect "today, each reply once (not 40 days ago)" "gopt @dingger_cost_today" 2.00 50
+expect "this week" "gopt @dingger_cost_week" 2.00
+expect "this month" "gopt @dingger_cost_month" 2.00
+session_report() { # pane session-file
+ python3 - "$HOOK" "$1" "$2" <<'PY'
+import json, socket, sys
+params = {"pane_id": sys.argv[2], "agent": "pi", "state": "idle", "agent_session_path": sys.argv[3]}
+s = socket.socket(socket.AF_UNIX)
+s.connect(sys.argv[1])
+s.sendall((json.dumps({"method": "pane.report_agent", "params": params}) + "\n").encode())
+s.close()
+PY
+}
+t new-session -d -s cost -n c1 -c "$DIR" "$DIR/bin/pi"
+t new-window -d -t cost: -n c2 -c "$DIR" "$DIR/bin/pi"
+C1=$(t display -p -t cost:c1 '#{pane_id}'); C2=$(t display -p -t cost:c2 '#{pane_id}')
+session_report "$C1" "$SESS/parent.jsonl"; session_report "$C2" "$SESS/fork.jsonl"
+expect "an agent: what its session spent today" "opt $C1 @dingger_cost" 1.50
+expect "a fork: only its own replies" "opt $C2 @dingger_cost" 0.50
+expect "the menu's top line" "mb --print | grep '^\\$ '" '$ $2.00 today · $2.00 this week · $2.00 this month'
+expect "an agent's line" "mb --print | grep -F 'cost · c1' | cut -f3" "cost · c1 · pi · \$1.50"
+expect "dingger status: per agent" "$DINGGER status | grep -F 'cost · c2' | grep -c ' \$0.50 '" 1
+expect "and in total" "$DINGGER status | tail -1" 'spent: $2.00 today · $2.00 this week · $2.00 this month (UTC)'
+reply f2 0.75 >>"$SESS/fork.jsonl"
+expect "a new reply: counted" "opt $C2 @dingger_cost" 1.25 50
+expect "in the total too" "gopt @dingger_cost_today" 2.75
+t kill-session -t cost
+expect "closed agents still count" "gopt @dingger_cost_today" 2.75 10
+
# Every daemon this run started must stop with it.
kill "$DAEMON_PID" "$DAEMON2_PID" 2>/dev/null
expect "the test daemons stop" "ps -o pid= -p $DAEMON_PID,$DAEMON2_PID | wc -l | tr -d ' '" 0