-
Notifications
You must be signed in to change notification settings - Fork 4
feat(ps): goal column joined from launch receipts #523
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feat/goal-receipts
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1246,6 +1246,28 @@ pick_container() { | |
| return 1 | ||
| } | ||
|
|
||
| # Goal for a container: the last launch receipt wins (attach with a fresh | ||
| # --goal restamps via a new receipt; create-time env cannot change), the | ||
| # create-time DEVA_GOAL env is the fallback when receipts are missing | ||
| # (receipt writes are warning-only and the launches dir is prunable). | ||
| container_goal() { | ||
| local name="$1" goal="" | ||
| local dir="${XDG_DATA_HOME:-$HOME/.local/share}/ccx/launches" | ||
| if [ -d "$dir" ]; then | ||
| goal=$( | ||
| for f in "$dir"/*.jsonl; do | ||
| if [ -f "$f" ]; then cat "$f"; fi | ||
| done | jq -rs --arg c "$name" \ | ||
| '[.[] | select(.container == $c) | .goal] | last // empty' 2>/dev/null | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
After Useful? React with 👍 / 👎. |
||
| ) || goal="" | ||
| fi | ||
| if [ -z "$goal" ]; then | ||
| goal=$(docker inspect --format '{{range .Config.Env}}{{println .}}{{end}}' "$name" 2>/dev/null | | ||
| sed -n 's/^DEVA_GOAL=//p' | head -n 1) || goal="" | ||
| fi | ||
| printf '%s' "${goal:---}" | ||
| } | ||
|
|
||
| list_containers_pretty() { | ||
| local rows | ||
| rows=$(project_container_rows) | ||
|
|
@@ -1257,9 +1279,9 @@ list_containers_pretty() { | |
| local output | ||
| output=$( | ||
| { | ||
| printf 'NAME\tAGENT\tSTATUS\tCREATED AT\n' | ||
| printf 'NAME\tAGENT\tGOAL\tSTATUS\tCREATED AT\n' | ||
| printf '%s\n' "$rows" | while IFS=$'\t' read -r name status created; do | ||
| printf '%s\t%s\t%s\t%s\n' "$name" "$(extract_agent_from_name "$name")" "$status" "$created" | ||
| printf '%s\t%s\t%s\t%s\t%s\n' "$name" "$(extract_agent_from_name "$name")" "$(container_goal "$name")" "$status" "$created" | ||
| done | ||
| } | ||
| ) | ||
|
|
@@ -1687,6 +1709,10 @@ cmd_status() { | |
| [ "$state" = "running" ] && printf ' up: %s' "$uptime_str" | ||
| echo "" | ||
|
|
||
| local goal | ||
| goal=$(container_goal "$name") | ||
| [ "$goal" != "--" ] && printf ' goal: %s\n' "$goal" | ||
|
|
||
| if [ "$show_all" = true ]; then | ||
| local ws_label | ||
| ws_label=$(printf '%s' "$inspect_json" | jq -r '.[0].Config.Labels["deva.workspace"] // "--"') | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| * [2026-07-28] Shipping the receipts stack + session-manager roadmap :FEAT:ARCH:PROCESS: | ||
|
|
||
| ** Tension | ||
| The "launcher -> session manager" direction existed only as a chat | ||
| brainstorm. On disk: =feat/goal-receipts= (#499) committed but never | ||
| pushed, with two UNRELATED dirty files riding the worktree (pin bumps | ||
| in =versions.env=, a stale container-context block in =AGENTS.md=). | ||
| Receipts themselves were write-only — nothing read them back, so | ||
| =--goal= bought attribution for ccx archaeology but zero operator | ||
| visibility. And the roadmap (config/state split, options registry, | ||
| ps/doctor/serve) had no issues, so nothing was actionable. | ||
|
|
||
| ** Observation | ||
| - =deva ps= ALREADY EXISTS (=list_containers_pretty=, dispatch token | ||
| =ps | --ps=), and =deva status= already prints per-container | ||
| agent/auth/uptime/mounts plus a Health section that is half of the | ||
| imagined =deva doctor=. The brainstorm was designing features the | ||
| script already had. Filed issue #520 before checking; the PR | ||
| carries the scope correction. | ||
| - The #518 double-mount is live, not theoretical: =status= on a real | ||
| container shows =~/.config/deva/claude/.claude -> /home/deva/.claude= | ||
| AND =~/.config/deva -> /home/deva/.config/deva= simultaneously. | ||
| - =origin/main= had moved TWO releases (0.18.0, 0.18.1) under the | ||
| unpushed receipts branch. Both prepend-at-top files (CHANGELOG, | ||
| DEV-LOGS) conflict by construction on every rebase; everything else | ||
| merged clean. | ||
| - The =claude= security-audit action fails in ~2-4s on EVERY PR | ||
| (#507, #508 included — both merged anyway). Empty | ||
| =ANTHROPIC_API_KEY=, oauth token passed, dies right after settings | ||
| setup: smells like an expired secret (#524). A permanently red | ||
| check is training everyone to ignore it. | ||
| - Two test scripts (=test-ccx-args.sh=, =test-status-helpers.sh=) are | ||
| =100644= in git — the suite silently can't run via =./=. | ||
|
|
||
| ** Decision | ||
| - Three PRs, not one: #516 receipts as-committed, #517 chore | ||
| (pins + AGENTS block + exec bits), #523 goal column STACKED on #516 | ||
| (=--base feat/goal-receipts=) so its diff is only the join. | ||
| Rejected: folding the dirty files into #516 (unrelated concerns), | ||
| and waiting for #516 to merge before building on it (serializes an | ||
| autonomous session on a human review). | ||
| - Goal source of truth: last matching receipt wins, =docker inspect= | ||
| =DEVA_GOAL= env as fallback. Rejected: env-only (attach restamps | ||
| never reach create-time env, so it goes stale) and receipts-only | ||
| (writes are warning-only and the dir is prunable — the fallback is | ||
| the honest path for exactly those cases). | ||
| - =container_goal()= assignments are =|| goal=""= guarded — under | ||
| =set -euo pipefail= a failed pipeline substitution aborts AT the | ||
| assignment (same trap as the #484 silent exit); the receipt glob | ||
| loop uses =if/fi= not =[ ] &&= so the subshell never ends on a | ||
| failed test (the #414 flock lesson). | ||
| - CHANGELOG entries written in main's CURRENT Keep-a-Changelog format. | ||
| The claude-code-style restyle sits on local =chore/changelog-style= | ||
| with no PR; matching an unmerged style would conflict every branch. | ||
|
|
||
| ** Tradeoff | ||
| - Stacked PRs put a merge-order burden on the reviewer (#516 first; | ||
| GitHub retargets #523 to main when the branch deletes). | ||
| - =container_goal()= scans every =launches/*.jsonl= per container per | ||
| =ps= invocation. Fine at day-file scale; if launches accumulate for | ||
| years this wants a "recent files only" cut. Not built now — no | ||
| evidence it's needed. | ||
| - Issue #520's body describes a feature that didn't need building. | ||
| Left as-is with the correction in the PR; the lesson (check the | ||
| dispatch table before filing) is cheaper than rewriting history. | ||
|
|
||
| ** Next | ||
| The live wire is #518: the config/state split must be decided BEFORE | ||
| the bun port freezes the current layout, and it interlocks with the | ||
| accounts/<tag> scoping from #497. Everything else on the roadmap | ||
| (#519 registry, #521 doctor, #522 serve) composes cleanly after it. | ||
|
|
||
| ** Artifacts | ||
| - PRs: #516 (receipts, rebased onto v0.18.1), #517 (pins chore, | ||
| 2 commits), #523 (goal column, stacked) | ||
| - Issues: #518 config/state split (+live evidence comment), | ||
| #519 options registry, #520 ps/goal, #521 doctor, #522 serve | ||
| umbrella, #524 broken PR bot | ||
| - =scripts/test-launch-receipts.sh=: 13 hermetic cases (scratch XDG, | ||
| stubbed docker), wired into ci.yml — first coverage for | ||
| =write_launch_receipt= at all | ||
| - Verified: shellcheck severity=error clean; local suite green | ||
| (mount-shape, image-precedence, kimi-auth, version-targets, | ||
| container-slug, status-helpers, launch-receipts); live =ps -g= / | ||
| =status -g= against the real daemon renders the GOAL column; all | ||
| five bumped pins resolve on npm. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| #!/usr/bin/env bash | ||
| # Round-trip tests for --goal launch receipts (#499) and the ps/status | ||
| # goal column (#520): write_launch_receipt -> container_goal. | ||
| # Hermetic: scratch XDG_DATA_HOME, docker stubbed. | ||
| set -euo pipefail | ||
|
|
||
| SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | ||
| REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" | ||
|
|
||
| PASS=0 | ||
| FAIL=0 | ||
| ERRORS="" | ||
|
|
||
| pass() { PASS=$((PASS + 1)); echo " ok: $1"; } | ||
| fail() { FAIL=$((FAIL + 1)); ERRORS="${ERRORS}\n FAIL: $1"; echo " FAIL: $1" >&2; } | ||
|
|
||
| assert_eq() { | ||
| local label="$1" expected="$2" actual="$3" | ||
| if [ "$expected" = "$actual" ]; then | ||
| pass "$label" | ||
| else | ||
| fail "$label: expected='$expected' actual='$actual'" | ||
| fi | ||
| } | ||
|
|
||
| source_helpers() { | ||
| eval "$(sed -n '/^write_launch_receipt()/,/^}/p' "$REPO_ROOT/deva.sh")" | ||
| eval "$(sed -n '/^container_goal()/,/^}/p' "$REPO_ROOT/deva.sh")" | ||
| } | ||
|
|
||
| source_helpers | ||
|
|
||
| SCRATCH="$(mktemp -d)" | ||
| trap 'rm -rf "$SCRATCH"' EXIT | ||
| export XDG_DATA_HOME="$SCRATCH/data" | ||
| LAUNCH_DIR="$XDG_DATA_HOME/ccx/launches" | ||
|
|
||
| # docker stub: emits env lines only for the one container the fallback | ||
| # test expects; fails for everything else like a missing container would. | ||
| docker() { | ||
| if [ "${DOCKER_STUB_NAME:-}" = "${!#}" ]; then | ||
| printf 'PATH=/usr/bin\nDEVA_GOAL=%s\nHOME=/home/deva\n' "$DOCKER_STUB_GOAL" | ||
| return 0 | ||
| fi | ||
| return 1 | ||
| } | ||
|
|
||
| echo "=== write_launch_receipt ===" | ||
|
|
||
| GOAL="" | ||
| ACTIVE_AGENT="claude" | ||
| CONTAINER_NAME="deva--claude--auth-default--proj..abcd" | ||
| write_launch_receipt | ||
| if [ -d "$LAUNCH_DIR" ] && [ -n "$(ls -A "$LAUNCH_DIR" 2>/dev/null)" ]; then | ||
| fail "no goal writes no receipt" | ||
| else | ||
| pass "no goal writes no receipt" | ||
| fi | ||
|
|
||
| GOAL="fix-auth-race" | ||
| write_launch_receipt | ||
| receipt_file="$LAUNCH_DIR/$(date -u +%Y-%m-%d).jsonl" | ||
| if [ -f "$receipt_file" ]; then | ||
| pass "receipt file created" | ||
| else | ||
| fail "receipt file created" | ||
| fi | ||
| assert_eq "receipt line count" "1" "$(wc -l <"$receipt_file" | tr -d ' ')" | ||
| assert_eq "receipt goal field" "fix-auth-race" "$(jq -r '.goal' "$receipt_file")" | ||
| assert_eq "receipt agent field" "claude" "$(jq -r '.agent' "$receipt_file")" | ||
| assert_eq "receipt container field" "$CONTAINER_NAME" "$(jq -r '.container' "$receipt_file")" | ||
| assert_eq "receipt source field" "deva" "$(jq -r '.source' "$receipt_file")" | ||
|
|
||
| echo "=== container_goal ===" | ||
|
|
||
| assert_eq "goal from receipt" "fix-auth-race" "$(container_goal "$CONTAINER_NAME")" | ||
|
|
||
| GOAL="ship-ps-column" | ||
| write_launch_receipt | ||
| assert_eq "last receipt wins" "ship-ps-column" "$(container_goal "$CONTAINER_NAME")" | ||
|
|
||
| GOAL="other-goal" | ||
| CONTAINER_NAME="deva--codex--auth-default--other..ef01" | ||
| write_launch_receipt | ||
| assert_eq "receipts keyed per container" "ship-ps-column" "$(container_goal "deva--claude--auth-default--proj..abcd")" | ||
|
|
||
| DOCKER_STUB_NAME="deva--grok--auth-default--env..2345" | ||
| DOCKER_STUB_GOAL="from-create-env" | ||
| assert_eq "env fallback when no receipt" "from-create-env" "$(container_goal "$DOCKER_STUB_NAME")" | ||
|
|
||
| assert_eq "no receipt no env is --" "--" "$(container_goal "deva--kimi--auth-default--gone..6789")" | ||
|
|
||
| rm -rf "$LAUNCH_DIR" | ||
| assert_eq "missing launches dir falls through" "--" "$(container_goal "deva--claude--auth-default--proj..abcd")" | ||
|
|
||
| echo "" | ||
| if [ "$FAIL" -gt 0 ]; then | ||
| printf 'FAILED: %d/%d%b\n' "$FAIL" "$((PASS + FAIL))" "$ERRORS" >&2 | ||
| exit 1 | ||
| fi | ||
| echo "All $PASS tests passed" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When any archived receipt is malformed—something
write_launch_receiptcan produce when a valid workspace path contains a quote or certain backslashes becausecwdis interpolated without JSON escaping—this whole-archivejq -sinvocation exits before filtering.container_goalthen discards every valid receipt and falls back to the create-time environment or--for all containers, so one malformed JSONL line globally disables newer attach-time goal restamps.Useful? React with 👍 / 👎.