feat: free-form sessions, rename/end from the panel, live Claude Context (0.2.0)
- Sessions no longer need a project folder: any name works and starts in DEVDESK_DEFAULT_DIR unless it matches a project; `devdesk new NAME [PROJECT]`. - Rename (✎) and end/forget (×) sessions from the Sessions panel, plus `devdesk rename`. The browser runs them as `devdesk attach --new|--rename|--end` through ttyd. Renames keep the running Claude and its recorded conversation: the hook, the run-wrapper and the statusline ask tmux for the current name. - Claude Context panel is live: devdesk-statusline snapshots model, context-window use, cost and edits per session, which --list reports; Claude's footer gets a one-line summary. The panel now sits under Sessions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+107
-3
@@ -46,11 +46,14 @@ claude_args() {
|
||||
|
||||
@test "rejects invalid session names" {
|
||||
start_server
|
||||
for bad in ../etc "a b" -x .hidden "$(printf 'x%.0s' {1..70})"; do
|
||||
for bad in ../etc "a b" .hidden "$(printf 'x%.0s' {1..70})"; do
|
||||
run devdesk attach "$bad"
|
||||
[ "$status" -ne 0 ]
|
||||
[[ $output == *"invalid session name"* ]]
|
||||
done
|
||||
run devdesk attach -x
|
||||
[ "$status" -ne 0 ]
|
||||
[[ $output == *"unknown option"* ]]
|
||||
run devdesk attach one two
|
||||
[ "$status" -ne 0 ]
|
||||
}
|
||||
@@ -98,12 +101,113 @@ claude_args() {
|
||||
[ "$output" = "$DEVDESK_PROJECTS_ROOT/test code" ]
|
||||
}
|
||||
|
||||
@test "sessions without a project start in the default folder" {
|
||||
start_server
|
||||
run devdesk new pbs-upgrade
|
||||
[ "$status" -eq 0 ]
|
||||
run tmux -S "$DEVDESK_SOCKET" display -p -t =pbs-upgrade:claude '#{pane_current_path}'
|
||||
[ "$output" = "$DEVDESK_PROJECTS_ROOT" ]
|
||||
}
|
||||
|
||||
@test "symlinks may not escape the projects root" {
|
||||
start_server
|
||||
run devdesk new escape
|
||||
[ "$status" -eq 0 ]
|
||||
run tmux -S "$DEVDESK_SOCKET" display -p -t =escape:claude '#{pane_current_path}'
|
||||
[ "$output" = "$DEVDESK_PROJECTS_ROOT" ]
|
||||
run devdesk new other escape
|
||||
[ "$status" -ne 0 ]
|
||||
[[ $output == *"no project directory"* ]]
|
||||
run devdesk new nonexistent
|
||||
[[ $output == *"no project 'escape'"* ]]
|
||||
}
|
||||
|
||||
@test "new NAME PROJECT starts the session in that project" {
|
||||
start_server
|
||||
run devdesk new ops-alpha alpha
|
||||
[ "$status" -eq 0 ]
|
||||
run tmux -S "$DEVDESK_SOCKET" display -p -t =ops-alpha:claude '#{pane_current_path}'
|
||||
[ "$output" = "$DEVDESK_PROJECTS_ROOT/alpha" ]
|
||||
run devdesk new ops-alpha alpha
|
||||
[ "$status" -ne 0 ]
|
||||
[[ $output == *"already exists"* ]]
|
||||
}
|
||||
|
||||
@test "rename moves a running session, its registry entry and its context" {
|
||||
start_server
|
||||
devdesk new alpha
|
||||
echo '{"session_id":"abc-123","source":"startup"}' | DEVDESK_SESSION=alpha "$REPO/libexec/devdesk-session-hook"
|
||||
mkdir -p "$DEVDESK_STATE_DIR/context" && echo '{"used_pct":12}' >"$DEVDESK_STATE_DIR/context/alpha.json"
|
||||
run devdesk rename alpha ops
|
||||
[ "$status" -eq 0 ]
|
||||
run tmux -S "$DEVDESK_SOCKET" has-session -t =ops
|
||||
[ "$status" -eq 0 ]
|
||||
run tmux -S "$DEVDESK_SOCKET" show-environment -t =ops DEVDESK_SESSION
|
||||
[ "$output" = "DEVDESK_SESSION=ops" ]
|
||||
[ "$(jq -r '.sessions.ops.claude_session_id' "$DEVDESK_STATE_DIR/registry.json")" = abc-123 ]
|
||||
[ "$(jq -r '.sessions | has("alpha")' "$DEVDESK_STATE_DIR/registry.json")" = false ]
|
||||
[ -f "$DEVDESK_STATE_DIR/context/ops.json" ]
|
||||
[ ! -e "$DEVDESK_STATE_DIR/context/alpha.json" ]
|
||||
}
|
||||
|
||||
@test "rename refuses clashes and unknown sessions" {
|
||||
start_server
|
||||
devdesk new alpha
|
||||
devdesk new main
|
||||
run devdesk rename alpha main
|
||||
[ "$status" -ne 0 ]
|
||||
[[ $output == *"already exists"* ]]
|
||||
run devdesk rename nosuch other
|
||||
[ "$status" -ne 0 ]
|
||||
[[ $output == *"no session named"* ]]
|
||||
run devdesk rename alpha "bad name"
|
||||
[ "$status" -ne 0 ]
|
||||
}
|
||||
|
||||
@test "rename works for dormant sessions" {
|
||||
echo '{"session_id":"old-1","source":"resume"}' | DEVDESK_SESSION=gone "$REPO/libexec/devdesk-session-hook"
|
||||
jq '.sessions.gone.dir = "/tmp"' "$DEVDESK_STATE_DIR/registry.json" >"$TMP/r" && mv "$TMP/r" "$DEVDESK_STATE_DIR/registry.json"
|
||||
run devdesk rename gone archived
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$(jq -r '.sessions.archived.claude_session_id' "$DEVDESK_STATE_DIR/registry.json")" = old-1 ]
|
||||
}
|
||||
|
||||
@test "hooks follow the tmux session name after a rename" {
|
||||
start_server
|
||||
devdesk new alpha
|
||||
devdesk rename alpha renamed
|
||||
pane=$(tmux -S "$DEVDESK_SOCKET" display -p -t =renamed:shell '#{pane_id}')
|
||||
echo '{"session_id":"after-rename","source":"clear"}' |
|
||||
TMUX="$DEVDESK_SOCKET,1,0" TMUX_PANE="$pane" DEVDESK_SESSION=alpha "$REPO/libexec/devdesk-session-hook"
|
||||
[ "$(jq -r '.sessions.renamed.claude_session_id' "$DEVDESK_STATE_DIR/registry.json")" = after-rename ]
|
||||
[ "$(jq -r '.sessions | has("alpha")' "$DEVDESK_STATE_DIR/registry.json")" = false ]
|
||||
}
|
||||
|
||||
@test "statusline snapshots context for the session and --list reports it" {
|
||||
start_server
|
||||
devdesk new alpha
|
||||
pane=$(tmux -S "$DEVDESK_SOCKET" display -p -t =alpha:shell '#{pane_id}')
|
||||
status_json='{"session_id":"s1","model":{"id":"m","display_name":"Opus"},"effort":{"level":"high"},
|
||||
"context_window":{"context_window_size":1000000,"used_percentage":34.6,
|
||||
"current_usage":{"input_tokens":1000,"cache_creation_input_tokens":2000,"cache_read_input_tokens":343000}},
|
||||
"cost":{"total_cost_usd":1.5,"total_lines_added":10,"total_lines_removed":2}}'
|
||||
run bash -c "echo '$status_json' | TMUX='$DEVDESK_SOCKET,1,0' TMUX_PANE='$pane' '$REPO/libexec/devdesk-statusline'"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ $output == *"alpha"* && $output == *"ctx 34%"* ]]
|
||||
run devdesk attach --list
|
||||
[ "$(jq -r '.sessions[] | select(.name=="alpha") | .context.used_tokens' <<<"$output")" = 346000 ]
|
||||
[ "$(jq -r '.sessions[] | select(.name=="alpha") | .context.model' <<<"$output")" = Opus ]
|
||||
}
|
||||
|
||||
@test "web actions run through attach and answer with JSON" {
|
||||
start_server
|
||||
run devdesk attach --new web1 alpha
|
||||
[ "$output" = '{"ok":true}' ]
|
||||
run devdesk attach --rename web1 web2
|
||||
[ "$output" = '{"ok":true}' ]
|
||||
run devdesk attach --end web2
|
||||
[ "$output" = '{"ok":true}' ]
|
||||
run tmux -S "$DEVDESK_SOCKET" has-session -t =web2
|
||||
[ "$status" -ne 0 ]
|
||||
run devdesk attach --bogus
|
||||
[ "$status" -ne 0 ]
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user