tickmarkr 2.1.6 → 2.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -20,15 +20,15 @@
20
20
  # than absent. Four rules the shipped version broke, each of which made the tier lie:
21
21
  # 1. BEAT ON THE SUPERVISION CADENCE, NOT ON THE POLL. Beats gap by TICK (below), never by POLL, so a
22
22
  # poll interval above the supervision beat interval cannot leave the tier stale half of every cycle.
23
- # 2. BEAT ONLY AFTER A SUCCESSFUL READ. A watcher that cannot see its seat's percentage is not
24
- # watching it; beating anyway reports coverage it is not providing, and the tier must age out.
23
+ # 2. BEAT AFTER EVERY SUCCESSFUL SCREEN READ. A missing percentage is an explicit UNREADABLE
24
+ # observation: it must keep the live watcher armed but can never reach warn or act. Only a failed
25
+ # screen read withholds the beat, because then the watcher cannot see its seat at all.
25
26
  # 3. WARN DOES NOT EXIT. Warn precedes act, so exiting at warn meant the act was never reached and the
26
27
  # last beat aged into a permanent stale — gradual growth never reached the auto-clear path at all.
27
28
  # 4. EVERY TERMINAL EXIT STANDS THE TIER DOWN, so a watcher that finished reads DISARMED, not dead.
28
29
  # Only a killed watcher reads STALE, which is exactly what STALE means.
29
- # 5. A BLIND READ ALARMS (OBS-739). Ageing the tier is visible only to someone already reading the beat
30
- # table; the seat that armed this watcher must be TOLD its instrument went blind. Silence and health
31
- # must never look alike.
30
+ # 5. AN UNREADABLE FIELD REPORTS ITSELF (OBS-739/780). Silence, health and a trustworthy percentage
31
+ # are three different states. UNREADABLE stays supervised but never authorises a destructive act.
32
32
  #
33
33
  # usage: watch-context.sh <role-slug> <agent|pane> <warn-pct> <act-pct> [handoff-file] [poll-s] [cap-s]
34
34
  # <role-slug> is ANY seat role — orchestrator, overseer, surgeon, consult — and names the tier
@@ -63,8 +63,8 @@ TIER="${ROLE}-context"
63
63
 
64
64
 
65
65
  # The supervision beat interval (SUPERVISION_BEAT_MS = 10s). The loop ticks at the beat cadence or the
66
- # caller's poll, whichever is SHORTER: a beat may only follow a successful read (rule 2), so the read
67
- # cadence is the floor on the beat cadence, and the tier's freshness is never the caller's to widen.
66
+ # caller's poll, whichever is SHORTER. Every successful screen read beats; its result is either a
67
+ # percentage safe to compare or the explicit UNREADABLE state.
68
68
  BEAT_EVERY=5
69
69
  TICK=$(( POLL < BEAT_EVERY ? POLL : BEAT_EVERY ))
70
70
  [ "$TICK" -ge 1 ] 2>/dev/null || TICK=1 # a zero or junk poll would spin, not watch
@@ -100,16 +100,18 @@ stand_down() { tickmarkr beat "$TIER" --stand-down --seat "$SEAT" >/dev/null 2>&
100
100
  # record the hand-off. A killed watcher never runs it, which is the one case that must read STALE.
101
101
  trap stand_down EXIT
102
102
 
103
- # The seat's own rendered truth. Prefer a line carrying a context marker so a percentage elsewhere on
104
- # screen cannot be mistaken for the gauge — but NEVER require one: the ✳ marker the shipped version
105
- # anchored on is rendered by a single vendor, so every other seat read empty, beat anyway and slept to
106
- # its cap while its tier claimed coverage. The last percentage in the statusline window is the fallback.
103
+ # The seat's rendered truth lives on the model banner. Select that line first, then read a percentage
104
+ # only from it: a bare numeric search across the visible window can borrow an old N% from scrollback
105
+ # when a long live-run segment pushes the real field past the pane's visible width (OBS-780).
107
106
  context_pct() {
108
- local screen marked
107
+ local screen banner pct
109
108
  screen=$(herdr agent read "$TARGET" --source visible --lines 8 2>/dev/null) || return 1
110
- marked=$(printf '%s\n' "$screen" | grep -iE '✳|context' | grep -oE '[0-9]+%' | tail -1 | tr -d '%')
111
- [ -n "$marked" ] && { printf '%s\n' "$marked"; return 0; }
112
- printf '%s\n' "$screen" | grep -oE '[0-9]+%' | tail -1 | tr -d '%'
109
+ banner=$(printf '%s\n' "$screen" |
110
+ grep -Ei '(^|[^[:alnum:]])(claude|opus|sonnet|haiku|gpt|gemini|glm|kimi|grok|composer|openai|zai)[[:alnum:]_./-]*([[:space:]]|$)' |
111
+ tail -1)
112
+ [ -n "$banner" ] || { printf 'UNREADABLE\n'; return 0; }
113
+ pct=$(printf '%s\n' "$banner" | grep -oE '[0-9]+%' | tail -1 | tr -d '%')
114
+ [ -n "$pct" ] && printf '%s\n' "$pct" || printf 'UNREADABLE\n'
113
115
  }
114
116
 
115
117
  handoff_fresh() {
@@ -144,35 +146,47 @@ act_on() {
144
146
 
145
147
  warned=0
146
148
  elapsed=0
147
- blind=0 # seconds in the current unreadable spell (OBS-739)
149
+ blind=0 # seconds in the current failed-read spell (OBS-739)
148
150
  blind_alarmed=0
151
+ unreadable_reported=0
149
152
  BLIND_ALARM_S="${TKR_BLIND_ALARM_S:-120}"
150
153
  while [ "$elapsed" -lt "$CAP" ]; do
151
- P=$(context_pct)
154
+ if ! P=$(context_pct); then P=""; fi
152
155
  if [ -z "$P" ]; then
153
- # Rule 2: no reading, no beat. The tier ages to STALE and a supervisor comes looking, which is the
154
- # truth about a watcher that cannot see the seat it was armed on.
155
- #
156
- # Rule 5 (OBS-739): ALARM ON THE BLIND READ. Ageing a tier is not enough — it is only visible to
157
- # someone already reading the beat table, and the measured failure was a watcher ALIVE AND BLIND for
158
- # hours because the run's own status text pushed the percentage off the statusline. An instrument
159
- # that cannot report its own absence is worse than none: the seat believes it has coverage and stops
160
- # looking. So say so on stdout, where the supervising seat is actually woken. Once per blind spell,
161
- # not every tick — a repeating alarm trains the reader to ignore it.
156
+ # The screen read itself failed: no observation, no beat. The tier ages to STALE and the watcher
157
+ # alarms once, because an instrument that cannot see its seat must never look healthy.
158
+ unreadable_reported=0
162
159
  blind=$((blind + TICK))
163
160
  if [ "$blind" -ge "$BLIND_ALARM_S" ] && [ "$blind_alarmed" -eq 0 ]; then
164
161
  blind_alarmed=1
165
- echo "CONTEXT_BLIND $TARGET — no percentage readable for ${blind}s; tier ${TIER} is ALIVE AND BLIND"
166
- echo " this watcher is NOT providing coverage: read the seat by hand and re-arm on a clear statusline"
167
- echo " do NOT substitute the token total — '∑ Nk tok' is cumulative SPEND, not context fill"
162
+ echo "CONTEXT_BLIND $TARGET — screen unreadable for ${blind}s; tier ${TIER} is ALIVE AND BLIND"
163
+ echo " this watcher is NOT providing coverage: read the seat by hand and repair the screen read"
168
164
  fi
169
165
  sleep "$TICK"; elapsed=$((elapsed + TICK)); continue
170
166
  fi
171
- # a successful read closes the blind spell and re-arms the alarm for the next one
167
+
168
+ if [ "$P" = "UNREADABLE" ]; then
169
+ # The screen read succeeded, so keep the watcher armed. The absent field is still not a number:
170
+ # report it once per spell and never let it flow into warn or act.
171
+ if [ "$blind_alarmed" -eq 1 ]; then
172
+ echo "CONTEXT_BLIND_CLEARED $TARGET — screen readable again after ${blind}s blind"
173
+ fi
174
+ blind=0; blind_alarmed=0
175
+ beat
176
+ if [ "$unreadable_reported" -eq 0 ]; then
177
+ unreadable_reported=1
178
+ echo "CONTEXT_UNREADABLE $TARGET — model banner has no visible percentage; no warn or act authorised"
179
+ fi
180
+ sleep "$TICK"; elapsed=$((elapsed + TICK)); continue
181
+ fi
182
+
183
+ if [ "$unreadable_reported" -eq 1 ]; then
184
+ echo "CONTEXT_UNREADABLE_CLEARED $TARGET — percentage readable again at ${P}%"
185
+ fi
172
186
  if [ "$blind_alarmed" -eq 1 ]; then
173
187
  echo "CONTEXT_BLIND_CLEARED $TARGET — percentage readable again at ${P}% after ${blind}s blind"
174
188
  fi
175
- blind=0; blind_alarmed=0
189
+ blind=0; blind_alarmed=0; unreadable_reported=0
176
190
  beat
177
191
 
178
192
  if [ "$P" -ge "$ACT" ] 2>/dev/null; then
@@ -190,4 +204,4 @@ while [ "$elapsed" -lt "$CAP" ]; do
190
204
  sleep "$TICK"; elapsed=$((elapsed + TICK))
191
205
  done
192
206
 
193
- echo "WATCH_CAP_REACHED $TARGET context=$(context_pct)% — no threshold crossed in ${CAP}s"
207
+ echo "WATCH_CAP_REACHED $TARGET — no threshold crossed in ${CAP}s"