Skip to content

fix(#55): count teammate relays as transcript turns - #58

Merged
axisrow merged 2 commits into
mainfrom
fix/55-team-turn-count
Oct 7, 2026
Merged

axisrow merged 2 commits into
mainfrom
fix/55-team-turn-count

Conversation

@axisrow

@axisrow axisrow commented Oct 7, 2026

Copy link
Copy Markdown
Owner

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 (релей биллится в ход лидера) — пиннится хук-тестами.

Фикс

  • turn-accounting core (scripts/turn-accounting.mjs): новые каноничные
    isTeammateRelayLine (симметрична исключениям релеев внутри
    isUserChunkLine) и isTranscriptTurnLine; декларации в .d.mts,
    реэкспорты через src/shared/turnAccounting.ts и враппер
    isParsedTeammateRelayMessage в messages.ts; allowlist
    no-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.
  • Доки: SessionItem title, Session.turnCount, CLAUDE.md (мёртвое имя
    isParsedTeammateMessage → фактические предикаты).

Верификация

  • Красный→зелёный: 10 новых тестов (матрица предикат с пином хук-канона,
    teammates-only кейс сканера 3 != 0, расширенный парити-инвариант
    turnCount == userChunks + relayCount, 6 кейсов нумерации чипов) падали
    на замороженных счётчиках, зелёные после фикса.
  • Полный сьют 949/949 (73 файла), typecheck чист, lint: 0 ошибок.
  • Мутации убиты: сканер игнорирует релеи → T2/T3 красные; релей считается
    после нумерации → гигантский/гибридный кейсы красные; incremental без
    пересчёта релеев → T4-5 красный; релей пущен в isUserChunkLine →
    хук-тесты + пин предикаты красные (изоляция хука доказана).
  • Хук-тесты и биллинг-парити (316200/10798/102320, rounds 5/2/2) зелёные
    без правок — биллинг не задет.

🤖 Generated with Claude Code

axisrow and others added 2 commits October 7, 2026 23:32
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

turnCount is 0 for agent-team sessions whose only inputs are teammate relays

1 participant