@@ -136,6 +136,54 @@ parse_log() {
136136 last_text=$( printf ' %s' " $last_event " | awk -F' \t' ' {print $2}' )
137137 last_model=$( printf ' %s' " $last_event " | awk -F' \t' ' {print $3}' )
138138
139+ # ── Daemon-log fallback for nested-spawn events ──
140+ # When the orchestrator passed `--log-file <daemon log>` to nested-claude-spawn
141+ # (observed empirically — orchestrator substitutes $GAAI_DELIVERY_LOG_FILE for
142+ # daemon log path), the GLM/secondary subprocess writes its stream-json events
143+ # to the daemon log instead of the per-story log. The per-story panel here
144+ # would display stale orchestrator model unless we also peek at the daemon log.
145+ # Rule per founder 2026-04-30 19:08 BEL : sub-agent / spawned model always wins
146+ # over orchestrator in the display.
147+ local daemon_log=" $PROJECT_DIR /.gaai/project/contexts/backlog/.delivery-daemon.log"
148+ if [[ -f " $daemon_log " ]]; then
149+ local daemon_event
150+ daemon_event=$( tail -400 " $daemon_log " 2> /dev/null \
151+ | grep ' ^{"type":"assistant"' 2> /dev/null \
152+ | jq -r '
153+ def clean: tostring | gsub("\n"; " ") | gsub(" +"; " ");
154+ def arg:
155+ if .name == "Bash" then (.input.command // "" | clean)
156+ elif .name == "Grep" then (.input.pattern // "" | clean)
157+ elif (.name == "Read" or .name == "Edit" or .name == "Write") then ((.input.file_path // "" | clean) | split("/") | .[-1] // "")
158+ elif .name == "Task" then (.input.description // "" | clean)
159+ elif .name == "TodoWrite" then "(todos updated)"
160+ else ((.input.description // .input.file_path // .input.command // "") | clean) end;
161+ . as $m |
162+ (($m.message.model // "") | tostring) as $model |
163+ $m.message.content[]? | select(.type=="tool_use") | "SUB" + "\t" + .name + " " + arg + "\t" + $model
164+ ' 2> /dev/null | tail -1 || true)
165+ local daemon_model
166+ daemon_model=$( printf ' %s' " $daemon_event " | awk -F' \t' ' {print $3}' )
167+ # Override main display when daemon log's most recent event is from a
168+ # different (non-orchestrator) model AND the daemon log was touched more
169+ # recently than the per-story log. Sub-agent / spawned model wins.
170+ if [[ -n " $daemon_model " ]] && [[ " $daemon_model " != " $last_model " ]]; then
171+ local daemon_mtime story_mtime
172+ if [[ " $( uname) " == " Darwin" ]]; then
173+ daemon_mtime=$( stat -f %m " $daemon_log " 2> /dev/null || echo 0)
174+ story_mtime=$( stat -f %m " $log_file " 2> /dev/null || echo 0)
175+ else
176+ daemon_mtime=$( stat -c %Y " $daemon_log " 2> /dev/null || echo 0)
177+ story_mtime=$( stat -c %Y " $log_file " 2> /dev/null || echo 0)
178+ fi
179+ if [[ " $daemon_mtime " -ge " $story_mtime " ]]; then
180+ last_origin=" SUB"
181+ last_text=$( printf ' %s' " $daemon_event " | awk -F' \t' ' {print $2}' )
182+ last_model=" $daemon_model "
183+ fi
184+ fi
185+ fi
186+
139187 # ── Phase detection ──
140188 # Walk recent events, classify each Bash command / Write target into a phase tag,
141189 # keep the LAST non-empty classification — that's the current phase.
0 commit comments