mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
fix(web): render one Claude response-viewer message per model message
The Claude reader concatenated every assistant row between two human prompts
into one card, fusing up to 74 distinct model messages into a single card, and
it never read the attachment rows that hold a prompt typed while the agent was
working. Measured over 57 real transcripts on 2026-09-01, the viewer shows
1,806 messages instead of 356 and 353 user cards instead of 178, with the
assistant text sequence unchanged row for row and the response without
?context=full byte-identical on all 57 files.
One assistant row IS one whole model message: in that corpus no assistant row
carries more than one content block and no message id carries more than one
text block, so there was nothing to reassemble. Each row becomes its own
message carrying an additive {kind, label, turn}, and the frontend renders a
same-role run inside one turn as badge-less continuation segments — which is
what keeps a p90 of 11 messages per turn from reading as card spam. A numeric
turn gates that rendering, so Codex, the external-CLI pane parser and an older
server keep one badge per card.
A prompt typed while Claude is working is recorded ONLY as an
attachment/queued_command row. Taking it when origin.kind is 'human' and
commandMode is 'prompt' recovers 162 user cards from 163 such rows — one is a
verbatim repeat inside an unanswered user run and is collapsed by the existing
dedup guard — and restores the turn boundary whose absence let the assistant
runs fuse. The CLI's own queue entries are cleanly separable: of 322
queued_command rows, 159 are commandMode 'task-notification' and not one of
them carries an origin key.
This narrows #169 rather than reverting it: sidechain exclusion, the
restored-<uuid8> rebind, replayed-snapshot dedup and synthetic-row filtering
are all unchanged and still asserted.
This commit is contained in:
@@ -283,6 +283,7 @@ than into an existing checkout.
|
||||
| create a session in an arbitrary directory (no case, **no PTY**, id at `.data.session.id`) | `POST /api/v1/sessions`, then `POST /api/v1/sessions/:id/interactive` or `.../shell` to start it, see [Starting a worker](#starting-a-worker) |
|
||||
| send input | `POST /api/v1/sessions/:id/input` |
|
||||
| **read a worker's answer** (claude/codex/deepseek) | `GET /api/v1/sessions/:id/last-response` → `.data.{text,timestamp}`, clean transcript text, no TUI noise. ⚠️ **Poll it**, see [symptom 7](#7-last-response-returns-an-empty-string-right-after-stop) |
|
||||
| read the whole conversation | `GET /api/v1/sessions/:id/last-response?context=full` → `.data.messages[]`. ⚠️ **Only `{role,text}` is present for every mode.** `kind`/`label` come from claude (`prompt`/`response`), deepseek and the pane parser (which also emit `status`/`tool`) but NOT from codex; `timestamp` from claude and codex but not deepseek/pane; `turn` and `queued:true` (a prompt typed while the agent was working) from claude only. `.data.text` is unchanged by `context=full` — it stays the last assistant message, never `messages[-1]` |
|
||||
| read terminal (tail is in **BYTES**, raw ANSI) | `GET /api/v1/sessions/:id/terminal?tail=3000` → `.data.terminalBuffer`, for *diagnosis* (unsubmitted prompt?), not for reading answers |
|
||||
| full tmux scrollback (context bomb; post-mortems only) | `GET /api/v1/sessions/:id/terminal?full=1` |
|
||||
| background agents, one session | `GET /api/v1/sessions/:id/subagents` |
|
||||
|
||||
@@ -391,7 +391,17 @@ done
|
||||
printf '%s\n' "$TXT"
|
||||
```
|
||||
|
||||
`.data` is `{text, timestamp}`. ⚠️ **On a hook-less workspace this reads the PREVIOUS
|
||||
`.data` is `{text, timestamp}`. Add `?context=full` for the whole conversation in
|
||||
`.data.messages[]`. ⚠️ **The four readers do not emit the same fields — only `{role, text}`
|
||||
is guaranteed.** `kind`/`label` come from claude (`prompt`/`response`), deepseek and the pane
|
||||
parser (the last two also emit `status`/`tool`), but **not** from codex; `timestamp` comes
|
||||
from claude and codex but not from deepseek or the pane parser. A claude worker additionally
|
||||
carries `turn` (a run of same-speaker messages inside one `turn` is one utterance split into
|
||||
segments, not separate exchanges) and `queued: true` on a prompt the user typed while the
|
||||
agent was still working. Filter on `role`, not on `kind`, unless you know the mode.
|
||||
`.data.text` does not change under `context=full`: it stays the
|
||||
last **assistant** message, so never read it as `messages[-1]`, which can be a prompt.
|
||||
⚠️ **On a hook-less workspace this reads the PREVIOUS
|
||||
turn.** `last-response` returns whatever the transcript last flushed, so it is only as
|
||||
correct as your end-of-turn signal: pair it with a `stop` signal or a marker, never
|
||||
with a bare `idle` ([§5.1](#51-where-to-spawn)). ⚠️ **Poll it, do not read it once.** `text` is written
|
||||
|
||||
Reference in New Issue
Block a user