The remove button's label is "Remove attachment" and doesn't say which
file. Its failure description now reads "<file>: <reason>", the same text
as the tooltip.
Follow-ups to moving the failure reason into a tooltip:
- The tooltip now reads "<file>: <reason>". The name is truncated on the
chip, and two long names can look the same.
- A polite role="status" region, always mounted and screen-reader only,
lists the failed files and their reasons. The red line it replaces was
role="alert", so failures were announced; a hover tooltip is not.
- The reason now describes the remove button (aria-describedby on a
hidden span), the chip's only focusable control. Before, it was sr-only
text on a chip that can't take focus.
The composer showed each failed file's reason as a red line of text under
the chips. A failed file no longer blocks the send, since it is dropped
then, so that line was louder than it needed to be. DESIGN.md also says
status text of a sentence or more must not be a coloured paragraph.
The chip's warning icon already marks the failure. The reason is now a
Tooltip on the failed chip. It falls back to "Failed" when no reason is
known. It is also kept as sr-only text so screen readers still read it.
The native title moves from the chip to the truncating file name, where
DESIGN.md keeps title=, and is left off failed chips so the tooltip
doesn't double up.
Aborts /api/store_attachment, attaches a file, waits for the failed
chip's reason, then sends. Checks that one /stream request goes out with
no attachment ids, that the question is in the thread and the chip is
gone, and that the stored message has no attachments.
Uploads had no timeout, so a stalled XHR could leave a chip on
"uploading" forever, and a queued send waited with it. Network failures
also set no errorMessage, so the failed chip showed no reason.
guardUploadStall aborts an upload once it goes 120 s without progress.
It restarts on every upload progress event and again when the body
finishes sending. A large file on a slow link keeps going; only a silent
stall is cut off. That is why it is not xhr.timeout, which caps the whole
request against a 100 MB file limit. onerror, onabort and ontimeout now
mark the chip failed with "Upload failed. The file couldn't be read or
the connection dropped." (single and multi-file paths). A failed chip is
dropped at send, so a queued question always goes out in the end. The
5-minute processing cap is unchanged.
A queued send flushed by calling submitNow directly, which skipped the
loading check in canSubmit. submitNow clears the composer before the
consumer runs, and the consumer ignores a submit while status is
'loading'. If a retry or an edit was streaming when the attachments
settled, the question was cleared and never sent.
useArmedSend takes a canFlush flag. The composer passes whether it can
take a submit right now (not loading, not recording or transcribing).
The armed send stays queued until that holds, then goes out.
A failed attachment made getSendReadiness return 'blocked', so Send was a
no-op until the user found and removed the chip. On a phone the only hint
was a small red line after the tap, and a new user lost their question
that way. A failed file never recovers, so holding the send only costs
the question.
Readiness now ignores failed attachments: it waits only on uploading or
processing files. On submit the composer drops every failed attachment
and sends the text with the completed ones. The payloads already read
completed attachments only, so no failed id reaches the request or the
history. The hero-card path in Conversation also clears failed chips.
The failed chip still shows before Send and can be removed by hand.
Removes the send-blocked alert and its locale key.
The banner still announced 0.17, two releases back, through the old blog
domain. It now links the GitHub Secure Open Source Fund post on
www.docsgpt.cloud directly, skipping the blog.docsgpt.cloud redirect.
gen_continuation is not wrapped by @log_activity, so the llm_* log lines
from a resumed turn carried no user, agent, conversation or endpoint. Every
OpenAI-compatible /v1 client-tool round landed anonymous in the logs.
Add agent_log_context, which binds the same keys log_activity binds while
keeping any enclosing activity_id, and wrap gen_continuation in it. Both
now derive the keys from one helper.
A turn whose agent raised wrote no user_logs row, so it only surfaced as
the agent's system error row. Every finished turn now writes its chat row,
at level error with the error when it failed, and linked to its trace; the
system row for the same traced activity is no longer listed twice.
DeepSeek and every other OpenAI-compatible API run through OpenAILLM, so
spans and metrics reported them as openai. The provider now comes from the
endpoint: known API hosts map to their gen_ai.provider.name (deepseek,
azure.ai.openai, mistral_ai, ...), and an OpenAI client pointed anywhere
else is openai_compatible. LLM spans also record server.address.
Paused, denied, skipped and client-run tool calls all go through
trace_unexecuted_tool_call. The research synthesis, continuation and
workflow step spans use with-blocks instead of hand-rolled GeneratorExit
handling. Fixes blank lines and adds a missing hint and docstrings.
Only message-linked traces cascaded, so scheduled runs, stateless /v1
rounds and unreserved turns kept their previews after the conversation was
deleted; they are now deleted by conversation_id. The Logs search and graph
branches filter by source, so the listing indexes lead with it.