feat(issue-24): startup dialogs come from the agent adapter and are handled while waiting for the TUI
handle_startup_dialogs was a claude-only string ladder called only when AGENT=claude, and wait_for_tui_ready does not treat a dialog screen as ready. So agy, grok and codex sat on their directory-trust prompt for the full readiness timeout, the session was recorded as tui-ready-timeout, and onboarding injection was disabled (create_session.sh:313-314) - a human had to accept each dialog by hand. The signatures and accept keys move to the adapters as `startup_dialogs` (pattern, keys, kind), published through the existing `facts` eval contract as MAM_STARTUP_DIALOGS. claude's ladder moves over verbatim; agy takes Enter, grok y, codex Enter, and codex's update notice takes 2 (Skip), which blocks a start just as hard while changing nothing. kind is `trust` or `notice`: --no-auto-trust leaves trust dialogs to a human and still answers notices, since trusting a directory makes codex load its project-local config, hooks and exec policy. Re-sending is gated by time (MAM_DIALOG_RESEND_S, default 5s), not by frames. #24's comment reports that `--source visible` keeps showing an accepted dialog for a while, and _pane_capture hardcodes that source, so "still on screen" is not evidence the keys were missed; acceptance is judged by the ready tokens alone. Two existing tests asserted the old shape and are updated, not weakened: b19's "Escape branch stays" now looks for it in claude's adapter, and c1b's "Enter once per iteration" becomes "never twice for one frame", which the cooldown satisfies strictly more tightly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3bcaa39ab0
commit
5ba04a27ca
11 files changed
+344
-29
No files matched your search
+106
-22
@@ -2204,7 +2204,7 @@ wait_for_tui_ready() {
|
||||
# handle it and do NOT declare ready on this iteration.
|
||||
tail_lines=$(printf '%s\n' "$content" | grep -v '^[[:space:]]*$' | tail -n 20)
|
||||
if printf '%s\n' "$tail_lines" | grep -E -q "$modal_pat" 2>/dev/null; then
|
||||
handle_startup_dialogs "$sess" 1 || true
|
||||
handle_startup_dialogs "$sess" 1 "$agent" || true
|
||||
sleep 1
|
||||
continue
|
||||
fi
|
||||
@@ -2254,7 +2254,7 @@ wait_for_tui_ready() {
|
||||
fi
|
||||
|
||||
# 4. Startup dialog & hint resolution if not ready
|
||||
handle_startup_dialogs "$sess" 1 || true
|
||||
handle_startup_dialogs "$sess" 1 "$agent" || true
|
||||
sleep 1
|
||||
done
|
||||
echo "⚠️ TUI readiness check timed out for '$sess'." >&2
|
||||
@@ -2702,29 +2702,113 @@ send_keys_safe() {
|
||||
return 4
|
||||
}
|
||||
|
||||
# handle_startup_dialogs <sess> [timeout_sec=20]
|
||||
# Post-start/resume dialog policy for claude: accept the trust / bypass
|
||||
# dialogs ONLY when their signature is positively on screen; return as soon
|
||||
# as the TUI banner is ready. Replaces the blind Enter/Down/Enter sequence.
|
||||
# _mam_load_dialog_facts <agent>
|
||||
# Populate MAM_STARTUP_DIALOGS for <agent>, once per agent per shell. Only that
|
||||
# one fact is taken: `facts` is an eval contract (F8), and evaluating all of it
|
||||
# here would clobber ready/placeholder tokens a caller deliberately set.
|
||||
# An agent the adapters do not know keeps an empty list.
|
||||
_mam_load_dialog_facts() {
|
||||
local agent="$1" _f=""
|
||||
if [ "${_MAM_DIALOGS_AGENT:-}" = "$agent" ]; then
|
||||
return 0
|
||||
fi
|
||||
_f="$("$(_delegate_py_bin)" -m lib_py.agents facts "$agent" 2>/dev/null | sed -n 's/^MAM_STARTUP_DIALOGS=//p')" || _f=""
|
||||
if [ -n "$_f" ]; then
|
||||
eval "MAM_STARTUP_DIALOGS=$_f"
|
||||
else
|
||||
MAM_STARTUP_DIALOGS='[]'
|
||||
fi
|
||||
_MAM_DIALOGS_AGENT="$agent"
|
||||
return 0
|
||||
}
|
||||
|
||||
# _mam_dialog_pick — first actionable dialog on this frame.
|
||||
# stdout: "send|<idx>|<key> <key>..." or "skip|<idx>|", nothing when there is
|
||||
# none to act on. rc 1 means "nothing to do" (matched-but-cooling counts).
|
||||
_mam_dialog_pick() {
|
||||
MAM_DLG_PANE="$1" MAM_DLG_JSON="$2" MAM_DLG_TRUST="$3" \
|
||||
MAM_DLG_STATE="$4" MAM_DLG_NOW="$5" MAM_DLG_COOLDOWN="$6" \
|
||||
python3 -c '
|
||||
import os, re, sys, json
|
||||
pane = os.environ.get("MAM_DLG_PANE", "")
|
||||
try:
|
||||
dialogs = json.loads(os.environ.get("MAM_DLG_JSON") or "[]")
|
||||
except Exception:
|
||||
dialogs = []
|
||||
auto_trust = os.environ.get("MAM_DLG_TRUST", "1") != "0"
|
||||
try:
|
||||
now = int(os.environ.get("MAM_DLG_NOW") or 0)
|
||||
cooldown = int(os.environ.get("MAM_DLG_COOLDOWN") or 5)
|
||||
except ValueError:
|
||||
sys.exit(1)
|
||||
state = {}
|
||||
for tok in (os.environ.get("MAM_DLG_STATE") or "").split():
|
||||
k, _, v = tok.partition(":")
|
||||
try:
|
||||
state[int(k)] = int(v)
|
||||
except ValueError:
|
||||
pass
|
||||
for idx, entry in enumerate(dialogs):
|
||||
try:
|
||||
pattern, keys, kind = entry[0], entry[1], entry[2]
|
||||
if not re.search(pattern, pane):
|
||||
continue
|
||||
except Exception:
|
||||
continue
|
||||
last = state.get(idx)
|
||||
if last is not None and 0 <= now - last < cooldown:
|
||||
sys.exit(1)
|
||||
if kind == "trust" and not auto_trust:
|
||||
print("skip|%d|" % idx)
|
||||
sys.exit(0)
|
||||
print("send|%d|%s" % (idx, " ".join(keys)))
|
||||
sys.exit(0)
|
||||
sys.exit(1)
|
||||
'
|
||||
}
|
||||
|
||||
# handle_startup_dialogs <sess> [timeout_sec=20] [agent=claude]
|
||||
# Answer the blocking modals an agent shows before its TUI is usable. The
|
||||
# signatures and accept keys come from the agent adapter (MAM_STARTUP_DIALOGS),
|
||||
# so this is agent-agnostic - issue #24: it used to be a claude-only ladder
|
||||
# called only for claude, and agy/grok/codex sat on their trust prompt until
|
||||
# the readiness wait timed out and onboarding was skipped.
|
||||
#
|
||||
# `--source visible` keeps showing an accepted dialog for a while (#24
|
||||
# comment), so a signature still on screen is NOT evidence that the keys were
|
||||
# missed: re-sending for the same signature is gated by MAM_DIALOG_RESEND_S
|
||||
# (default 5s) and acceptance is judged only by the ready tokens.
|
||||
#
|
||||
# MAM_AUTO_TRUST=0 leaves `trust` dialogs unanswered (warned once per
|
||||
# cooldown); `notice` dialogs are always answered.
|
||||
handle_startup_dialogs() {
|
||||
local sess="$1" timeout="${2:-20}" waited=0 pane
|
||||
local sess="$1" timeout="${2:-20}" agent="${3:-claude}"
|
||||
local waited=0 pane pick action rest idx keys key now
|
||||
local cooldown="${MAM_DIALOG_RESEND_S:-5}"
|
||||
local auto_trust="${MAM_AUTO_TRUST:-1}"
|
||||
_mam_load_dialog_facts "$agent"
|
||||
local dialogs="${MAM_STARTUP_DIALOGS:-[]}"
|
||||
local ready_re="${MAM_STRONG_READY_TOKENS:-${MAM_READY_TOKENS:-}}"
|
||||
while [ "$waited" -lt "$timeout" ]; do
|
||||
pane=$(_pane_tail "$sess" 20)
|
||||
if printf '%s\n' "$pane" | grep -Eq 'Do you trust the files|Yes, I trust this folder|Quick safety check'; then
|
||||
_sks_herdr send-keys -t "$sess" Enter
|
||||
elif printf '%s\n' "$pane" | grep -q 'Yes, try it'; then
|
||||
# Fullscreen-renderer upsell modal (not the idle /tui tip). Enter would
|
||||
# accept and restart the session without permission flags — reject.
|
||||
_sks_herdr send-keys -t "$sess" Escape
|
||||
elif printf '%s\n' "$pane" | grep -q 'Yes, proceed'; then
|
||||
_sks_herdr send-keys -t "$sess" Down
|
||||
sleep 0.3
|
||||
_sks_herdr send-keys -t "$sess" Enter
|
||||
elif printf '%s\n' "$pane" | grep -q 'Resuming the full session'; then
|
||||
_sks_herdr send-keys -t "$sess" Enter
|
||||
elif printf '%s\n' "$pane" | grep -q 'Press Enter to continue'; then
|
||||
_sks_herdr send-keys -t "$sess" Enter
|
||||
elif printf '%s\n' "$pane" | grep -Eq "${_MAM_READY_TOKENS_CLAUDE:-Anthropic|Assistant|Chat|Welcome}"; then
|
||||
now=$(date +%s)
|
||||
pick=""
|
||||
pick=$(_mam_dialog_pick "$pane" "$dialogs" "$auto_trust" "${_MAM_DIALOG_SENT:-}" "$now" "$cooldown" 2>/dev/null || true)
|
||||
if [ -n "$pick" ]; then
|
||||
action="${pick%%|*}"
|
||||
rest="${pick#*|}"
|
||||
idx="${rest%%|*}"
|
||||
keys="${rest#*|}"
|
||||
if [ "$action" = "send" ]; then
|
||||
for key in $keys; do
|
||||
_sks_herdr send-keys -t "$sess" "$key" || true
|
||||
sleep 0.3
|
||||
done
|
||||
else
|
||||
echo "handle_startup_dialogs: a trust dialog is open on '$sess' and --no-auto-trust was given; accept it manually" >&2
|
||||
fi
|
||||
_MAM_DIALOG_SENT="${_MAM_DIALOG_SENT:-} $idx:$now"
|
||||
elif [ -n "$ready_re" ] && printf '%s\n' "$pane" | grep -Eq "$ready_re" 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
|
||||
Reference in new issue
Block a user