Explorar o código

add copilot notification tooling

Noah Vogt hai 1 día
pai
achega
aaef3b26bf
Modificáronse 2 ficheiros con 142 adicións e 0 borrados
  1. 3 0
      dot-config/aliasrc
  2. 139 0
      local-bin/copilot-notify

+ 3 - 0
dot-config/aliasrc

@@ -164,3 +164,6 @@ alias rep="sudo GNUPGHOME=$XDG_DATA_HOME/gnupg makerepropkg -d"
 # uni specific
 alias fhu='doas umount -l /mnt/fhnwdata'
 alias fhv='QT_QPA_PLATFORM=wayland ~/.local/share/oc-sso-venv/bin/openconnect-sso'
+
+# ai stuff
+alias cn="copilot-notify"

+ 139 - 0
local-bin/copilot-notify

@@ -0,0 +1,139 @@
+#!/bin/bash
+
+# Wrapper script for copilot to get clickable focus notifications via swaync
+
+set -euo pipefail
+
+notify_focus() {
+    local action="$1"
+    local win_addr="$2"
+    [ -n "$action" ] || return 0
+    [ "$action" = "f" ] && [ -n "$win_addr" ] && hyprctl dispatch focuswindow address:"$win_addr" >/dev/null 2>&1 || true
+}
+
+notify_action_required() {
+    local win_addr="$1"
+    local action
+    action=$(notify-send "Copilot" "✋ Action required" --action="f=Focus" -u critical -a "Copilot" -i ~/dl/copilot.png || true)
+    notify_focus "$action" "$win_addr"
+}
+
+notify_task_finished() {
+    local win_addr="$1"
+    local action
+    action=$(notify-send "Copilot" "✅ Task finished" --action="f=Focus" -u normal -a "Copilot" -i ~/dl/copilot.png || true)
+    notify_focus "$action" "$win_addr"
+}
+
+start_interactive_monitor() {
+    local win_addr="$1"
+    local started_epoch="$2"
+    local log_file
+    local line
+    local busy=0
+    local ask_user_pending=0
+
+    # Wait for the process log created by this copilot invocation.
+    for _ in $(seq 1 60); do
+        log_file=$(find "$HOME/.copilot/logs" -maxdepth 1 -name 'process-*.log' -newermt "@$started_epoch" -print 2>/dev/null | sort | tail -n1 || true)
+        if [ -n "${log_file:-}" ]; then
+            break
+        fi
+        sleep 0.1
+    done
+
+    [ -n "${log_file:-}" ] || return 0
+
+    tail -n0 -F "$log_file" 2>/dev/null | while IFS= read -r line; do
+        if [ "$ask_user_pending" -gt 0 ]; then
+            ask_user_pending=$((ask_user_pending - 1))
+        fi
+
+        case "$line" in
+        *"kind: assistant_turn_start"*)
+            busy=1
+            ;;
+        *'"name": "ask_user"'*)
+            # "ask_user" appears in tool schema on every turn; only alert if a
+            # real tool call follows immediately with a question payload.
+            ask_user_pending=8
+            ;;
+        *'"arguments": "{\"question\":'*)
+            if [ "$ask_user_pending" -gt 0 ]; then
+                notify_action_required "$win_addr"
+                ask_user_pending=0
+            fi
+            ;;
+        *"kind: session_idle"*)
+            if [ "$busy" -eq 1 ]; then
+                notify_task_finished "$win_addr"
+                busy=0
+            fi
+            ask_user_pending=0
+            ;;
+        esac
+    done
+}
+
+is_prompt_run=0
+for arg in "$@"; do
+    case "$arg" in
+    -p | --prompt | -i | --interactive)
+        is_prompt_run=1
+        break
+        ;;
+    esac
+done
+
+win_addr="$(hyprctl activewindow -j | jq -r '.address // empty')"
+
+if [ "$is_prompt_run" -eq 0 ] || [ "$#" -eq 0 ]; then
+    started_epoch=$(date +%s)
+
+    if [ -d "$HOME/.copilot/logs" ]; then
+        start_interactive_monitor "$win_addr" "$started_epoch" &
+        monitor_pid=$!
+        trap 'kill "$monitor_pid" 2>/dev/null || true' EXIT
+    fi
+
+    # Debug logs include structured telemetry markers needed by the monitor.
+    has_log_level=0
+    for arg in "$@"; do
+        case "$arg" in
+        --log-level | --log-level=*)
+            has_log_level=1
+            break
+            ;;
+        esac
+    done
+
+    set +e
+    if [ "$has_log_level" -eq 1 ]; then
+        copilot "$@"
+    else
+        copilot --log-level debug "$@"
+    fi
+    exit_code=$?
+    set -e
+    kill "${monitor_pid:-}" 2>/dev/null || true
+    exit "$exit_code"
+fi
+
+copilot --output-format json "$@" |
+    tee >(
+        jq -Rrc '
+     (fromjson? | select(. != null)) as $e |
+     if $e.type=="result" then {"kind":"done","code":($e.exitCode // 0)}
+     elif $e.type=="tool.execution_start" and ($e.data.toolName=="ask_user") then {"kind":"action"}
+     else empty end
+   ' |
+            while read -r ev; do
+                kind="$(jq -r '.kind' <<<"$ev")"
+                if [ "$kind" = "action" ]; then
+                    notify_action_required "$win_addr"
+                else
+                    notify_task_finished "$win_addr"
+                fi
+            done
+    ) |
+    jq -Rr '(fromjson? | select(.type=="assistant.message") | (.data.content // empty))'