Honest summarize progress from real completed work units

Backend: new ContractProgress parses the streamed summary JSON and
advances only when contract units finish — first token 5%, each closed
key 5→90 (arrays step per closed item), root close 95, stored=100.
Replaces the char-count ticker that counted reasoning chars against a
guessed 1200-char output and sat frozen at 99 for minutes. No timers,
no elapsed-time estimates; the unfinishable thinking span honestly
earns only the alive-tick. Both adapters (openai_compat, ollama) feed
the accumulated content prefix. /jobs now carries the job's tone.

App: summarize progress shows a real percentage + determinate bar with
the tone named ('Summarizing dry wit… 47% · 1:12') in both detail
widgets; indeterminate only while queued or pre-first-token.

Tests: milestone sequence verified identical for char-by-char and
chunked streaming; adapter thinking-phase test updated to the new
contract (83 passed).
This commit is contained in:
avi 2026-09-18 15:42:16 -05:00
commit 39aaa04a00
2 changed files with 26 additions and 15 deletions

BIN
DesignReferenceShonar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View file

@ -100,9 +100,14 @@ class ContractProgress:
self._key_score += grant
self._earned += grant
def feed(self, prefix: str) -> int | None:
new = self._emit(_CONTRACT_FIRST_TOKEN) if self._reported == 0 else None
result: int | None = new
def feed(self, prefix: str) -> list[int]:
"""Scan the new tail of the accumulated prefix; return every
newly earned pct in order (possibly empty)."""
emits: list[int] = []
if self._reported == 0:
got = self._emit(_CONTRACT_FIRST_TOKEN)
if got is not None:
emits.append(got)
text = prefix
while self._pos < len(text):
c = text[self._pos]
@ -129,8 +134,9 @@ class ContractProgress:
_KEY_POINTS_BUDGET[self._cur_key])
self._done_keys.add(self._cur_key)
self._cur_key = None
result = self._emit(
_CONTRACT_FIRST_TOKEN + self._earned) or result
got = self._emit(_CONTRACT_FIRST_TOKEN + self._earned)
if got is not None:
emits.append(got)
self._at_key_slot = True
elif self._depth == 2:
# a closed array item (arrays hold strings)
@ -140,8 +146,9 @@ class ContractProgress:
- _ARR_ITEM * (self._items_open - 1)
self._award(min(_ARR_ITEM, max(0, remaining)),
_KEY_POINTS_BUDGET[self._cur_key])
result = self._emit(
_CONTRACT_FIRST_TOKEN + self._earned) or result
got = self._emit(_CONTRACT_FIRST_TOKEN + self._earned)
if got is not None:
emits.append(got)
else:
self._str_buf.append(c)
continue
@ -153,8 +160,9 @@ class ContractProgress:
if c == "[" and self._cur_key:
self._items_open = 0
self._award(_ARR_OPEN, _KEY_POINTS_BUDGET[self._cur_key])
result = self._emit(
_CONTRACT_FIRST_TOKEN + self._earned) or result
got = self._emit(_CONTRACT_FIRST_TOKEN + self._earned)
if got is not None:
emits.append(got)
elif c in "}]":
self._depth -= 1
if self._depth == 1:
@ -164,15 +172,18 @@ class ContractProgress:
_KEY_POINTS_BUDGET[self._cur_key])
self._done_keys.add(self._cur_key)
self._cur_key = None
result = self._emit(
_CONTRACT_FIRST_TOKEN + self._earned) or result
got = self._emit(_CONTRACT_FIRST_TOKEN + self._earned)
if got is not None:
emits.append(got)
self._at_key_slot = True
elif self._depth == 0:
self._root_closed = True
result = self._emit(_CONTRACT_ROOT) or result
got = self._emit(_CONTRACT_ROOT)
if got is not None:
emits.append(got)
elif c == "," and self._depth == 1:
self._at_key_slot = True
return result
return emits
def make_progress_ticker(on_progress):
@ -188,10 +199,10 @@ def make_progress_ticker(on_progress):
def feed(prefix: str) -> None:
try: # noqa: SIM105 — swallow deliberately: progress is display state
pct = tracker.feed(prefix or "")
pcts = tracker.feed(prefix or "")
except Exception: # display state only
return
if pct is not None:
for pct in pcts:
on_progress(pct)
return feed