Skip to content

fix object issue - #32

Merged
Kitenite merged 2 commits into
mainfrom
object-issue-m78z
Nov 7, 2025
Merged

Kitenite merged 2 commits into
mainfrom
object-issue-m78z

Conversation

@Kitenite

@Kitenite Kitenite commented Nov 7, 2025

Copy link
Copy Markdown
Member

Description

Related Issues

Type of Change

  • Bug fix
  • New feature
  • Documentation
  • Refactor
  • Other (please describe):

Testing

Screenshots (if applicable)

Additional Notes

@Kitenite
Kitenite merged commit 2e1efbf into main Nov 7, 2025
1 of 4 checks passed
@Kitenite
Kitenite deleted the object-issue-m78z branch November 7, 2025 23:39
Kitenite added a commit that referenced this pull request Feb 11, 2026
Every error response now includes a machine-readable `code` field
(SESSION_NOT_FOUND, WRITE_FAILED, FINISH_FAILED, INVALID_BODY, etc.)
for deterministic client error handling.
Kitenite added a commit that referenced this pull request Feb 11, 2026
…rhaul (#1391)

* Reduce stream latency

* Smooth stream

* Smooth stream

* fix(streams): fail /generations/finish when producer had background errors

Track producer background errors per session. finishGeneration now
flushes, clears per-message seq state, and throws if any producer
errors occurred during the run. The finish route returns structured
error response (code: FINISH_FAILED) instead of silent success.

* refactor(streams): extract producer error helpers for clarity

Extract recordProducerError and drainProducerErrors as private
helpers. Simplifies the onError callback and finishGeneration
method, making the error lifecycle (record → drain → throw) explicit.

* fix(desktop): check res.ok for /generations/finish and send messageId

Non-2xx responses from finish are now logged with the response body.
The finish request now sends the messageId so the server can clear
per-message seq state.

* fix(streams): await producer flush and detach in deleteSession

deleteSession is now async and awaits producer.flush() then
producer.detach() before cleaning up session state. Prevents
returning 204 while queued chunks are still in flight.

* fix(streams): flush producer before reset control event

Ensures all queued chunks are durably written before the reset
event is appended, preventing reset from racing ahead of buffered
data. Also clears producer errors on reset.

* fix(streams): route all writes through producer for global ordering

Extract appendToStream helper that prefers the producer when
available, falling back to direct stream.append. writeChunk,
writeUserMessage, and writePresence all use this single write
path now. User messages and presence flush immediately for
durability while streaming chunks remain buffered.

* fix(desktop): add abort signal to chunk POSTs for fast interrupt

Pass the agent abort signal to streaming chunk fetch calls so that
interrupting an agent cancels in-flight chunk sends immediately.
AbortError is silently swallowed since it's the expected outcome.

* fix(desktop): emit error event when generation finish fails

If /generations/finish returns non-2xx or the network request fails,
emit an explicit error event so the UI shows a visible failure
instead of silently appearing done.

* fix(streams): guard session delete/reset with per-session mutex

Add a promise-chain based per-session lock so concurrent delete,
reset, and close operations serialize rather than race. Prevents
interleaved lifecycle transitions from corrupting session state.

* fix(streams): write user messages directly to stream for txid immediacy

Flush the producer first to preserve global ordering, then append
the user message directly to the stream. This avoids producer
queue latency that was causing txid timeout errors on the client
side (5s default timeout in stream-db awaitTxId).

* perf(desktop): remove /generations/start round trip

Generate messageId client-side with crypto.randomUUID() instead of
blocking on POST /generations/start. Eliminates a full HTTP round
trip before the first token can stream.

* perf: batch chunk sends to reduce per-chunk HTTP overhead

Add writeChunks method to protocol and POST /chunks/batch endpoint
that accepts an array of chunks in a single HTTP request.

On the desktop side, replace the sequential per-chunk POST chain
with a ChunkBatcher that coalesces chunks within a 5ms window
(or 50-chunk max) before sending as a batch. This reduces HTTP
round trips from N to ~N/batch_size during active streaming.

* Update docs

* perf(streams): tune producer lingerMs and add flush timeout

Reduce producer lingerMs from 5ms to 1ms since the desktop
ChunkBatcher already coalesces at 5ms — avoids double-buffering
latency. Add 10s timeout to flushSession so flush/finish cannot
hang indefinitely on a stuck producer.

* perf: skip Zod on batch endpoint, add bounded queue to ChunkBatcher

The /chunks/batch endpoint now does a lightweight array check
instead of full Zod schema validation on every chunk — this is
an authenticated internal path from the desktop client.

ChunkBatcher now has a maxBufferSize (default 2000) that drops
oldest chunks when the buffer exceeds the cap, preventing OOM
when the network or proxy is slower than the agent.

* Add retry with exponential backoff for batch sends (#21)

ChunkBatcher now retries failed sendBatch calls up to 3 times with
exponential backoff (50ms base). sendBatch callback throws on non-ok
responses so the retry logic can catch transient failures. AbortError
is rethrown immediately to respect cancellation.

* Add producer health tracking with sync fallback (#23, #24)

Track per-session producer health. When a producer fires onError,
mark it unhealthy and route subsequent writes through direct
stream.append instead. A successful flush restores healthy status.
Prevents cascading failures when the producer is in a bad state.

* Track active generation per session for single-writer enforcement (#25, #33)

Add startGeneration/getActiveGeneration/finishGeneration lifecycle to
protocol. Chunk routes auto-register the generation from the first
chunk if none is active. finishGeneration clears the active generation.
Reset and delete also clean up generation state.

* Add sessionId and messageId to all route responses (#34)

Include sessionId (and messageId where applicable) in both success
and error responses from chunk and session routes for tracing.

* Add structured error codes to all route responses (#32)

Every error response now includes a machine-readable `code` field
(SESSION_NOT_FOUND, WRITE_FAILED, FINISH_FAILED, INVALID_BODY, etc.)
for deterministic client error handling.

* Add structured error codes and sessionId to auth routes (#32, #34)

* Format: lint fixes across streams and desktop

* Update recommendations doc: mark completed items

30 of 51 items now done. Remaining items are larger architectural
changes (14, 29, 31), operational (16-18), and observability/test/
rollout work (35-50).

* Add perf

* Chunks

* Remove /generations/start endpoint (#29)

Generation is now auto-registered from the first chunk written.
Desktop generates messageId client-side, so this endpoint was
dead code. Replaced with chunksBatch in the discovery listing.

* Document terminal semantics convention (#31)

message-end chunk = UI signal (isLoading → false)
/generations/finish = server lifecycle cleanup (flush, seq clear, error drain)
Both are required and always sent in that order.

* Fix restore

* Refactor

* More splitting

* More split

* Fixed feedback

* Chunk

* Fixed comments

* Update CI env
saddlepaddle added a commit that referenced this pull request Oct 30, 2026
Drafts the composer saves were markdown, so a reload re-parsed them and lost
anything markdown cannot say exactly: italic that starts mid-word and ends on
a space came back as literal asterisks. The composer now saves one `editor`
segment, its serialized Lexical state and the markdown the agent would get,
and loads it back as is.

Drafts built outside the composer stay `text` (markdown) and `chip`
segments. linkedContextSeed now gives chips only, and a chip loads inline at
the end of the last paragraph with a space after it, so no draft mixes chips
into markdown and the chip placeholders are gone. Text and editor segments
load as blocks a blank line apart, so joinDrafts no longer adds a separator.

The markdown sent to the agent puts the edge whitespace of formatted text
outside its markers (`*x* y`, not `*x *y`), so Lexical never writes
entities there. Typed `&#N;` are shielded from Lexical's markdown import,
which decodes every one and throws past U+10FFFF.

draftChips reads chips from every segment kind, for linked context.
saddlepaddle added a commit that referenced this pull request Oct 30, 2026
* feat(chat-ui): rich text in the agent prompt composer

The prompt composer moves from PlainTextPlugin to RichTextPlugin with lists,
quotes and code blocks.

- Typing `- `, `* `, `1. `, `> `, `| `, ``` and `code` converts as you type.
  Bold and italic come only from Cmd+B / Cmd+I, so prompt text like `*args`
  and `__init__` stays literal.
- Shift+Enter continues a list, Tab / Shift+Tab nest. Enter adds a line
  inside a code block; Cmd+Enter still sends.
- Cmd+B / Cmd+I stop propagation so the sidebar and terminal-input hotkeys do
  not also fire. Underline is blocked, so Cmd+U still attaches a file.
- @ mentions and / commands do not open inside code.
- Paste is plain text, except a copy from this composer, which keeps its list
  structure (Lexical's text/plain copy of a list has no markers).
- Escape keeps focus as before; RichTextPlugin would blur the editor.

Draft text segments now hold markdown: submit, saved drafts, failed-send
restores and history all read and write it through draftSegments. Chips are
swapped for private-use placeholders around the markdown conversion. Plain
text exports verbatim, because Lexical backslash-escapes `*_~` and the agent
would receive `\_\_init\_\_`. Empty bullets at the ends are not sent.

The large-paste card loses "Show in text field": it would now have to insert
text past the markdown parser, and the card already sends the paste as a file.

CodeFencePlugin, $isInCode and the theme move from MessageComposer to
PromptInput, since both composers use them now.

* fix(chat-ui): keep typed entities and walk past re-read history

draftText decoded every numeric entity, so a typed `<` changed on send
and `�` threw, which blocked the send. Only whitespace entities are
decoded now; Lexical writes those for the edge spaces of formatted text.

History navigation compared the editor with the raw history entry. An entry
whose markdown reads back differently, such as `  - a`, stopped ↑/↓ at that
entry. It now compares with the entry as the editor reads it back.

* refactor(chat-ui): save composer drafts as Lexical state

Drafts the composer saves were markdown, so a reload re-parsed them and lost
anything markdown cannot say exactly: italic that starts mid-word and ends on
a space came back as literal asterisks. The composer now saves one `editor`
segment, its serialized Lexical state and the markdown the agent would get,
and loads it back as is.

Drafts built outside the composer stay `text` (markdown) and `chip`
segments. linkedContextSeed now gives chips only, and a chip loads inline at
the end of the last paragraph with a space after it, so no draft mixes chips
into markdown and the chip placeholders are gone. Text and editor segments
load as blocks a blank line apart, so joinDrafts no longer adds a separator.

The markdown sent to the agent puts the edge whitespace of formatted text
outside its markers (`*x* y`, not `*x *y`), so Lexical never writes
entities there. Typed `&#N;` are shielded from Lexical's markdown import,
which decodes every one and throws past U+10FFFF.

draftChips reads chips from every segment kind, for linked context.

* fix(chat-ui): survive unreadable saved drafts; ⌘⇧V pastes text from the composer too

A saved editor state with a node type this build does not register, or a
malformed entry, threw while seeding the composer, and again on every open of
that draft. It now loads from the segment's markdown, and draftChips skips a
segment with no state.

⌘⇧V skipped the plain-text path when the clipboard came from this composer.

* refactor(chat-ui): drop an unreadable saved draft instead of rebuilding it

A saved state that fails to parse is skipped, so the chat opens with an empty composer. Rebuilding it from its markdown was more code than the case is worth.

* feat(chat-ui): paste formatted text, headings, links and tables into the composer

Pasted HTML now keeps its structure: headings, links, lists, quotes and code
blocks become composer blocks and go to the agent as markdown. A pasted table
becomes markdown table rows. ⌘⇧V still pastes plain text, and a paste of
5,000+ characters still becomes an attachment.

Copying from the composer puts markdown in the plain-text clipboard, so copied
bullets keep their markers in other apps. A copy inside one code block stays
plain code. Lexical's own paste already restores a copy from this composer,
so the namespace check is gone.

tableClipboard moves to src/utils, now that the composer uses it too.

* feat(desktop): your own sent messages render as markdown

A sent list, bold text, code block, heading, link or table now shows in your
bubble the way the agent's replies do, not as raw `- ` and `**` markers. Chips
and link chips still draw inside the formatting, a typed line break stays a
line break, and copying a selection still gives back the typed URLs.

Typed text that only looks like markdown stays as typed: the composer writes
formatting only as `*`, `**` and `~~`, so `_`, `__` and single `~` are shown
literally, and a sent message is complete, so an unclosed `*` is not closed
for it the way a streaming reply's is (new `complete` option on ChatMarkdown).

The bubble's own URL splitter is gone; markdown finds the links.
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.

1 participant