feat: session status at a glance, file & image drop, live Ops Alerts (0.4.0)
build-deb / Lint, test, package (push) Successful in 1m51s
build-deb / Deploy to CT 126 (push) Successful in 3s
build-deb / Notify on failure (push) Skipped

- devdesk-state-hook (Claude UserPromptSubmit/PostToolUse/Notification/Stop/
  SessionStart/SessionEnd) records whether each session is working, waiting on
  you or idle; --list carries it. The sidebar shows it, the header shows the
  current session's state, and a pulsing "needs you" chip jumps to sessions
  waiting for input. Session refresh no longer depends on the sidebar being open.
- `devdesk attach --upload SESSION FILE BYTES` receives a file as base64
  terminal input (raw mode, exact length, size-checked, sanitised name) into
  /var/lib/devdesk/uploads/<session>/. The UI uploads dropped or pasted files and
  pastes the path into Claude, which attaches images itself.
- `devdesk alerts` / `--alerts`: firing and pending Grafana alert rules with
  severity, instances, summary and a link. The panel's "Ask Claude" types an
  investigation prompt into the current session.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-07 19:26:06 -04:00
co-authored by Claude Opus 5.5
parent 531b640ae9
commit 03e4a49e78
14 changed files with 625 additions and 44 deletions
+21 -4
View File
@@ -30,9 +30,14 @@ dd_load_env
: "${DEVDESK_GITEA_URL:=https://code.lotusguild.org}"
: "${DEVDESK_GITEA_ALIASES:=10.10.10.63}"
: "${DEVDESK_GITEA_TOKEN_FILE:=/etc/devdesk/gitea-token}"
: "${DEVDESK_GRAFANA_URL:=}"
: "${DEVDESK_GRAFANA_PUBLIC_URL:=$DEVDESK_GRAFANA_URL}"
: "${DEVDESK_GRAFANA_TOKEN_FILE:=/etc/devdesk/grafana-token}"
: "${DEVDESK_UPLOAD_MAX:=26214400}"
DEVDESK_REGISTRY="$DEVDESK_STATE_DIR/registry.json"
DEVDESK_CONTEXT_DIR="$DEVDESK_STATE_DIR/context"
DEVDESK_STATUS_DIR="$DEVDESK_STATE_DIR/state"
DEVDESK_NAME_RE='^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$'
dd_die() {
@@ -113,15 +118,18 @@ dd_registry_set_claude() {
dd_registry_remove() {
dd_registry_update 'del(.sessions[$n])' --arg n "$1"
rm -f "$DEVDESK_CONTEXT_DIR/$1.json"
rm -f "$DEVDESK_CONTEXT_DIR/$1.json" "$DEVDESK_STATUS_DIR/$1.json"
}
dd_registry_rename() {
dd_registry_update 'if .sessions[$old] then .sessions[$new] = .sessions[$old] | del(.sessions[$old]) else . end' \
--arg old "$1" --arg new "$2"
if [ -f "$DEVDESK_CONTEXT_DIR/$1.json" ]; then
mv -f "$DEVDESK_CONTEXT_DIR/$1.json" "$DEVDESK_CONTEXT_DIR/$2.json"
fi
local d
for d in "$DEVDESK_CONTEXT_DIR" "$DEVDESK_STATUS_DIR"; do
if [ -f "$d/$1.json" ]; then
mv -f "$d/$1.json" "$d/$2.json"
fi
done
}
# Project name (directory or its slug) -> absolute directory inside the projects root.
@@ -147,6 +155,15 @@ dd_project_dir() {
esac
}
# Upload filename -> safe basename ([A-Za-z0-9._-], no leading dots, max 100 chars).
dd_safe_filename() {
local n=${1##*/}
n=${n//[^A-Za-z0-9._-]/_}
while [[ $n == .* ]]; do n=${n:1}; done
n=${n:0:100}
printf '%s' "${n:-upload}"
}
# Session name -> working directory. Sessions don't need a project: the
# registry wins, then a project of the same name, then DEVDESK_DEFAULT_DIR.
dd_resolve_dir() {