Files
multica/server/pkg
Multica Eve de3e0ae556 fix(agent): make cursor stream protocol drift loud instead of silent (MUL-5434) (#6089)
* fix(agent): make cursor stream protocol drift loud instead of silent (MUL-5434)

#6071 reports Cursor tasks that demonstrably read files, ran commands and
called the Multica CLI, yet showed a single blob of agent text with no
reasoning and no tool rows. The run still reported success and tools=0.

`switch evt.Type` in cursor.go had no `default` branch, so any top-level
event type we do not handle was dropped with no counter and no warning.
Renaming only the top-level types of a healthy stream
(`thinking`->`reasoning`, `tool_call`->`tool_calls`), leaving every nested
field untouched, reproduces the report exactly: status=completed, output =
the result text alone, tool_use=0, thinking=0, zero diagnostics. The
existing unknown-subtype warning cannot catch this — it only increments
once the type has already matched — so "no unknown-subtype warning" does
not rule out protocol drift.

Two diagnostic gaps are closed:

- Add the `default` branch with a bounded, content-free tally of unhandled
  top-level types, reported once per run as a warning and alongside
  tool_use_count in the protocol summary. Type names are normalized through
  observedCursorEventType and distinct names are capped at 16 plus an
  overflow bucket, so a hostile or noisy stream cannot grow the map or leak
  payload into logs. `user` (the CLI echoing our prompt, present in every
  recorded run) is explicitly benign so the warning does not fire always.
- Count assistant text separately from the terminal result text. The result
  event writes into the same builder, so last_assistant_bytes equalled
  result_bytes even when the assistant streamed nothing — erasing the
  signal that says "only the final answer arrived". This also aligns cursor
  with claude, which already reports assistant bytes only.

Unrecognized events are still never coerced into tool or reasoning
messages; guessing at upstream additions is the failure mode MUL-5231
already fixed once. This is diagnosis only and does not itself restore the
missing tool rows — identifying which upstream shape changed requires a
captured 2026.07.23 stream, and this warning is what makes that
identifiable from a single production log line.

Co-authored-by: multica-agent <github@multica.ai>

* fix(agent): report cursor unhandled events as evidence, not as a verdict

Addresses the review's must-fix on MUL-5434. The diagnostic was described
as deciding WHY a transcript is empty, which it cannot do:

- "tools=0 with unhandled types" does not establish that a rename ate the
  tool rows. Cursor 2026.07.23 also emits transport/control frames, so an
  unhandled type only proves the stream carried events we do not parse.
- "tools=0 with no unhandled types" does not establish the agent used no
  tools. The CLI may execute tools without handing the updates to its
  stream serializer at all — the main branch #6071 has NOT ruled out — a
  new shape may be nested inside an event type we already recognize, or
  events may be lost to invalid framing or a scanner boundary.

Changes, no behaviour change to messages, status or output:

- Reword the tally doc, the switch default, the warning and the shared
  observation struct to state what a non-zero and a zero count each do and
  do not establish, and point at the branches that stay open.
- Rename unknown* to unhandled* (fields, log keys, warning text, the
  pre-existing subtype counter) so the diagnostic never implies the type is
  unrecognized upstream — only that this parser does not handle it.
- Classify `connection` and `retry` explicitly. They join `user` in
  cursorNonTranscriptEventTypes, with per-entry provenance recorded: `user`
  is confirmed in the recorded 2026.07.20 stream, the control frames are
  reported on newer builds and listed defensively. A build that does not
  emit them makes the entry inert; one that does must not have a known
  control frame reported as an unhandled protocol event.

Tests assert signal presence and that nothing is fabricated, not causality:
the healthy-stream test now carries `connection` / `retry` and additionally
asserts the real thinking/tool rows still arrive, so suppressing the warning
cannot silently suppress the transcript. TestCursorNonTranscriptEventType
also pins that no type the parser handles can enter the suppression list.

Co-authored-by: multica-agent <github@multica.ai>

---------

Co-authored-by: Eve <eve@multica-ai.local>
Co-authored-by: multica-agent <github@multica.ai>
2026-07-29 15:15:41 +08:00
..