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:
parent
f258470a6a
commit
39aaa04a00
2 changed files with 26 additions and 15 deletions
BIN
DesignReferenceShonar.png
Normal file
BIN
DesignReferenceShonar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
|
|
@ -100,9 +100,14 @@ class ContractProgress:
|
||||||
self._key_score += grant
|
self._key_score += grant
|
||||||
self._earned += grant
|
self._earned += grant
|
||||||
|
|
||||||
def feed(self, prefix: str) -> int | None:
|
def feed(self, prefix: str) -> list[int]:
|
||||||
new = self._emit(_CONTRACT_FIRST_TOKEN) if self._reported == 0 else None
|
"""Scan the new tail of the accumulated prefix; return every
|
||||||
result: int | None = new
|
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
|
text = prefix
|
||||||
while self._pos < len(text):
|
while self._pos < len(text):
|
||||||
c = text[self._pos]
|
c = text[self._pos]
|
||||||
|
|
@ -129,8 +134,9 @@ class ContractProgress:
|
||||||
_KEY_POINTS_BUDGET[self._cur_key])
|
_KEY_POINTS_BUDGET[self._cur_key])
|
||||||
self._done_keys.add(self._cur_key)
|
self._done_keys.add(self._cur_key)
|
||||||
self._cur_key = None
|
self._cur_key = None
|
||||||
result = self._emit(
|
got = self._emit(_CONTRACT_FIRST_TOKEN + self._earned)
|
||||||
_CONTRACT_FIRST_TOKEN + self._earned) or result
|
if got is not None:
|
||||||
|
emits.append(got)
|
||||||
self._at_key_slot = True
|
self._at_key_slot = True
|
||||||
elif self._depth == 2:
|
elif self._depth == 2:
|
||||||
# a closed array item (arrays hold strings)
|
# a closed array item (arrays hold strings)
|
||||||
|
|
@ -140,8 +146,9 @@ class ContractProgress:
|
||||||
- _ARR_ITEM * (self._items_open - 1)
|
- _ARR_ITEM * (self._items_open - 1)
|
||||||
self._award(min(_ARR_ITEM, max(0, remaining)),
|
self._award(min(_ARR_ITEM, max(0, remaining)),
|
||||||
_KEY_POINTS_BUDGET[self._cur_key])
|
_KEY_POINTS_BUDGET[self._cur_key])
|
||||||
result = self._emit(
|
got = self._emit(_CONTRACT_FIRST_TOKEN + self._earned)
|
||||||
_CONTRACT_FIRST_TOKEN + self._earned) or result
|
if got is not None:
|
||||||
|
emits.append(got)
|
||||||
else:
|
else:
|
||||||
self._str_buf.append(c)
|
self._str_buf.append(c)
|
||||||
continue
|
continue
|
||||||
|
|
@ -153,8 +160,9 @@ class ContractProgress:
|
||||||
if c == "[" and self._cur_key:
|
if c == "[" and self._cur_key:
|
||||||
self._items_open = 0
|
self._items_open = 0
|
||||||
self._award(_ARR_OPEN, _KEY_POINTS_BUDGET[self._cur_key])
|
self._award(_ARR_OPEN, _KEY_POINTS_BUDGET[self._cur_key])
|
||||||
result = self._emit(
|
got = self._emit(_CONTRACT_FIRST_TOKEN + self._earned)
|
||||||
_CONTRACT_FIRST_TOKEN + self._earned) or result
|
if got is not None:
|
||||||
|
emits.append(got)
|
||||||
elif c in "}]":
|
elif c in "}]":
|
||||||
self._depth -= 1
|
self._depth -= 1
|
||||||
if self._depth == 1:
|
if self._depth == 1:
|
||||||
|
|
@ -164,15 +172,18 @@ class ContractProgress:
|
||||||
_KEY_POINTS_BUDGET[self._cur_key])
|
_KEY_POINTS_BUDGET[self._cur_key])
|
||||||
self._done_keys.add(self._cur_key)
|
self._done_keys.add(self._cur_key)
|
||||||
self._cur_key = None
|
self._cur_key = None
|
||||||
result = self._emit(
|
got = self._emit(_CONTRACT_FIRST_TOKEN + self._earned)
|
||||||
_CONTRACT_FIRST_TOKEN + self._earned) or result
|
if got is not None:
|
||||||
|
emits.append(got)
|
||||||
self._at_key_slot = True
|
self._at_key_slot = True
|
||||||
elif self._depth == 0:
|
elif self._depth == 0:
|
||||||
self._root_closed = True
|
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:
|
elif c == "," and self._depth == 1:
|
||||||
self._at_key_slot = True
|
self._at_key_slot = True
|
||||||
return result
|
return emits
|
||||||
|
|
||||||
|
|
||||||
def make_progress_ticker(on_progress):
|
def make_progress_ticker(on_progress):
|
||||||
|
|
@ -188,10 +199,10 @@ def make_progress_ticker(on_progress):
|
||||||
|
|
||||||
def feed(prefix: str) -> None:
|
def feed(prefix: str) -> None:
|
||||||
try: # noqa: SIM105 — swallow deliberately: progress is display state
|
try: # noqa: SIM105 — swallow deliberately: progress is display state
|
||||||
pct = tracker.feed(prefix or "")
|
pcts = tracker.feed(prefix or "")
|
||||||
except Exception: # display state only
|
except Exception: # display state only
|
||||||
return
|
return
|
||||||
if pct is not None:
|
for pct in pcts:
|
||||||
on_progress(pct)
|
on_progress(pct)
|
||||||
|
|
||||||
return feed
|
return feed
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue