fix(issue-23): onboarding delivery is judged by the agent's own started event, and a rollback verifies the kill
Plan commits 3 and 4 landed together: both rewrite the same region of create_session.sh and share one test file, and splitting them afterwards would have meant hunk-level surgery on already-verified code. Delivery verdict (item 3). create_session --onboard called a healthy agent a failure three seconds after registering the job: inject_instructions -> send_keys_safe returns rc 4 when `agent prompt --wait --until working` fails and the pane is still not working two seconds later, which is exactly where a TUI that is still starting up lands. It never got the ISSUE-18 confirmation the delegate-job wrapper has. rc 4 is a warning now and the agent's own `started` decides, within the job's own ack timeout; rc 1/2/3 stay hard failures, since they say the pane never quiesced, a dialog was blocking, or the text never appeared, and no later `started` repairs those. The self-published `started` had to go first. create_session published it on the agent's behalf right after injection, so the registry showed a started job whether or not the agent ever saw the prompt - with that line in place, no started-based verdict can mean anything. delegate_wait_started is lib.sh's counterpart to the wrapper's _wait_started (plan item D10-b). The wrapper's own `submit` was the first choice, but it blocks until the job reaches a terminal state, and create_session must return as soon as delivery is confirmed; the on-disk evidence it reads - registry status past `pending`, or a started line carrying this dispatch token - is written by publish_event before its wire publish, so no broker is involved. Rollback integrity (item 4). The trap ran `_herdr kill-session -t "$SESSION_NAME" 2>/dev/null || true`, so a kill that closed nothing still printed "rolling back" while the session kept running, and the row stayed `running` pointing at the job the same rollback had just failed (#23 symptom 5). With item 1 in place this check is finally trustworthy: the kill used to fail because the caller's workspace id hid the pane. The trap now reads the kill's rc, confirms with has-session, closes the onboarding job as `cancelled` (a terminal transition the registry knows, and the truthful one - the job was never dispatched), and marks the row. A session still alive exits 43 with the manual cleanup command, distinct from 42 (TUI never ready). The row keeps a schema-valid status rather than the plan's `error`, which atomic_yaml.py rejects: `terminated` once the session is really gone, and `running` when the kill failed, because it is still running. What makes that case legible is the note, the cleared delegate_job_id and exit 43 - not a status claiming the opposite of the truth. The row is never deleted; a deleted row takes the evidence with it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5ba04a27ca
commit
123dca47bb
3 files changed
+346
-7
No files matched your search
@@ -2131,6 +2131,55 @@ delegate_publish_event() {
|
||||
_delegation_py publish --job "$job_id" --event "$event" --detail "$detail" ${extra[@]+"${extra[@]}"} || true
|
||||
}
|
||||
|
||||
# delegate_wait_started <job_id> [ack_timeout_s] [dispatch_token]
|
||||
#
|
||||
# ISSUE-23 / ISSUE-18: delivery is confirmed by the protocol, not by the
|
||||
# screen. `agent prompt --wait --until working` failing two seconds after the
|
||||
# keys were handed over says nothing about a TUI that is still starting up -
|
||||
# that is exactly what made create_session --onboard call a healthy agent a
|
||||
# failure. The evidence is the agent's own `started`: the registry record
|
||||
# leaves `pending` (publish_event syncs it on this host) or a matching started
|
||||
# line lands in the job's events.log, which publish_event writes before its
|
||||
# wire publish so a slow broker cannot delay the verdict.
|
||||
#
|
||||
# Returns 0 on that evidence, 1 if neither appeared within T. Never re-injects:
|
||||
# the single-injection contract is fail-closed.
|
||||
delegate_wait_started() {
|
||||
local job_id="$1" ack_s="${2:-180}" tok="${3:-}"
|
||||
[ -n "$job_id" ] || return 1
|
||||
local reg_dir="${MAM_JOBS_DIR:-${WORKSPACE_ROOT:-$PWD}/.mam/jobs}"
|
||||
local rec="$reg_dir/$job_id.json"
|
||||
local ev_log="$reg_dir/$job_id.events.log"
|
||||
local started_re='"event": *"started"'
|
||||
if [ -n "$tok" ]; then
|
||||
started_re='"event": *"started".*"dispatch_token": *"'"$tok"'"'
|
||||
fi
|
||||
local deadline st
|
||||
deadline=$(( $(date +%s) + ack_s ))
|
||||
while :; do
|
||||
st=""
|
||||
if [ -f "$rec" ]; then
|
||||
st=$(REC="$rec" python3 -c "
|
||||
import json, os
|
||||
try:
|
||||
print(json.load(open(os.environ['REC'])).get('status', '') or '')
|
||||
except Exception:
|
||||
print('')
|
||||
" 2>/dev/null || true)
|
||||
fi
|
||||
if [ -n "$st" ] && [ "$st" != "pending" ]; then
|
||||
return 0
|
||||
fi
|
||||
if [ -f "$ev_log" ] && grep -Eq "$started_re" "$ev_log" 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
if [ "$(date +%s)" -ge "$deadline" ]; then
|
||||
return 1
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
}
|
||||
|
||||
# ensure_subscriber_daemon <job_id> [workdir]
|
||||
# Start the monitor subscriber for this workspace unless one is already running.
|
||||
# Prints the PID(s) on stdout, exactly as start_watchdog always did.
|
||||
|
||||
Reference in new issue
Block a user