Repository navigation
fix(#55): count teammate relays as transcript turns - #58
Merged
Merged
Conversation
Agent-team sessions whose only user-side inputs are teammate relays showed "0 turns" in the sidebar while the chat numbered their chips "Turn 1..N" through the aiCount fallback — the sidebar and the chat visibly disagreed (issue #55 calls the pre-#52 value N the correct one). Define the canonical semantics: a turn is a transcript turn input — a real user message OR a teammate relay (a non-meta user message that provokes an LLM round and renders as a TeammateMessageItem card). - turn-accounting core: new isTeammateRelayLine (symmetric with the relay exclusions inside isUserChunkLine) and isTranscriptTurnLine; the hook canon isUserChunkLine is untouched — a relay still bills to the leader's turn, pinned by the hook tests; - jsonl scanner: turnCount increments on relays too (sidechain/meta/ compact exclusions preserved); messageCount/awaitingAIGroup stay user-only; - groupTransformer: relayCount is fed into aiTurnIndex in both the full and the incremental path (relays of reused AI groups re-count from AIGroup.responses), so the Turn N chips match scan.turnCount 1:1 on teammates-only sessions (giant multi-relay chunks number their last opened turn); - docs: SessionItem title, Session.turnCount doc, CLAUDE.md's stale isParsedTeammateMessage name replaced with the actual predicates. Red→green: 10 new tests (predicate matrix, teammates-only scanner case, extended parity invariant turnCount == userChunks + relayCount, six groupTransformer chip cases) fell on the frozen counters, pass after the fix; full suite 949/949. Mutations killed: scanner ignores relays (T2/T3 fall), relays counted after numbering (T4 giant/hybrid fall), incremental skips reused relays (T4-5 falls), relays admitted into isUserChunkLine (hook tests + predicate pin fall). Co-Authored-By: Claude Code <noreply@anthropic.com>
Follow-up to the review of PR #58 (12 findings; five accepted, addressed here): - turn-accounting core: isUserChunkLine now delegates its relay exclusion to isTeammateRelayLine instead of hand-synced inline copies, and the dead TEAMMATE_MESSAGE_REGEX disjunct (a strict subset of the isTeammateText startsWith) is gone — one relay definition, no drift; - jsonl parity test: the fixture's AI-run count equaled the expected turnCount (4 = 4), so a regression to AI-run semantics passed silently. A trailing unanswered relay glues into the last AI run: turnCount 5 vs 4 AI runs re-arms the discrimination (mutation-verified: the AI-run mutant now falls); - relay helper in the teammates-only test accepts parentUuid: string | null (fresh TS2345 under tsconfig.test.json); - docs: the Turn chip policy is stated accurately — a group's chip shows the last turn it opened, so an unanswered trailing user turn has no chip while an unanswered trailing relay does; isTranscriptTurnLine's doc no longer claims jsonl.ts calls it directly. Rejected findings (pre-existing, out of scope): loose prefix grammar of isTeammateText, strict-renderer-vs-loose-counter mismatch for old-style wrappers, incremental-path relay recount cost, isRealUserLine whitespace trim asymmetry, whitespace-only array text blocks, relay XML in the session preview, fixture factory duplication. Full suite 949/949, tsc --noEmit clean, lint 0 errors. Co-Authored-By: Claude Code <noreply@anthropic.com>
This was referenced Oct 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #55.
Проблема
Agent-team сессии, чьи единственные user-side входы — teammate-релеи, показывали
«0 turns» в сайдбаре (SessionItem скрывает чип при 0), при этом чат нумеровал
чипы «Turn 1..N» через aiCount-фолбэк — сайдбар и чат видимо расходились. Ишью
называет до-#52 значение N правильным, текущее — регрессом.
Семантика
Ход = transcript turn input: реальное user-сообщение или teammate-релей
(user-сообщение isMeta:false, провоцирует LLM-раунд, виден карточкой
TeammateMessageItem). Канон #52 «turn = transcript user message» продолжается
на релеи. Биллинг-хук не тронут: его канон
isUserChunkLineостаётся срелеями=false (релей биллится в ход лидера) — пиннится хук-тестами.
Фикс
scripts/turn-accounting.mjs): новые каноничныеisTeammateRelayLine(симметрична исключениям релеев внутриisUserChunkLine) иisTranscriptTurnLine; декларации в.d.mts,реэкспорты через
src/shared/turnAccounting.tsи врапперisParsedTeammateRelayMessageвmessages.ts; allowlistno-restricted-importsрасширен.jsonl.ts):turnCount++и на релеях (исключенияsidechain/meta/compact сохранены);
messageCount/awaitingAIGroupостаются user-only (пин тестом).
groupTransformer.ts):relayCountизAIGroup.responsesподмешивается в
aiTurnIndex(userCount + relayCount, aiCount)в обоихпутях (full + incremental, релеи переиспользованных групп пересчитываются;
инкремент ДО нумерации — релей открывает ход своей группы). Гигантский
AI-чанк с несколькими релеями нумеруется последним открытым ходом —
сходится со
scan.turnCount.Session.turnCount, CLAUDE.md (мёртвое имяisParsedTeammateMessage→ фактические предикаты).Верификация
teammates-only кейс сканера
3 != 0, расширенный парити-инвариантturnCount == userChunks + relayCount, 6 кейсов нумерации чипов) падалина замороженных счётчиках, зелёные после фикса.
после нумерации → гигантский/гибридный кейсы красные; incremental без
пересчёта релеев → T4-5 красный; релей пущен в
isUserChunkLine→хук-тесты + пин предикаты красные (изоляция хука доказана).
316200/10798/102320, rounds 5/2/2) зелёныебез правок — биллинг не задет.
🤖 Generated with Claude Code