Commit Graph

93 Commits

Author SHA1 Message Date
matevip
0476447ab6 fix(agent): persist mid-turn narrative, queue follow-ups without dispose, flush on shutdown
A bundle of stability fixes that all surfaced together while running
the same long-form generation task across multiple turns. Each one
addresses a distinct way the previous behavior silently dropped
content the user had already seen on screen.

1. Mid-turn narrative persistence (StateGraphReActAgent +
   SummarizingNode). Intermediate ReasoningNode rounds and
   SummarizingNode broadcast their content_delta directly to the
   SSE channel for live display, but the StreamAccumulator only
   received the final answer. After refresh the assistant message
   showed only tool_call cards with no body text.
   StateGraphReActAgent now also forwards STREAMED_CONTENT (already
   set per round) as a persistOnly StreamDelta whenever it changes,
   so every narrative chunk lands in the accumulator's content
   buffer and gets written to mate_message. SummarizingNode now
   writes its summary into the same key so summarize narratives
   persist too.

2. Follow-up message queue, not dispose (ChatController#interruptStream).
   Sending a new message while a turn was running called
   requestInterrupt, which dispose()d the active Reactor chain mid
   LLM call. That cancelled the in-flight generation, lost partial
   tokens, and left the user staring at a half-finished bubble.
   The endpoint now uses enqueueMessage in all paths, matching
   the "wait for current turn, then run" behavior. The old
   requestInterrupt API is kept for any future force-replace UI
   but no caller routes to it.

3. Queued user message ordering (ChatStreamTracker.QueuedInput +
   ChatController.startQueuedMessage). interruptStream used to save
   the queued user message immediately, before the in-flight
   assistant message finalized in doOnError. listMessages orders
   by create_time ASC, so the queued user message ended up above
   the assistant reply it was supposed to follow. QueuedInput now
   carries contentParts; persistence is delayed to startQueuedMessage,
   which runs only after Asst-N is on disk.

4. JVM shutdown flush (ChatStreamTracker @PreDestroy +
   emergencySaveAccumulator). A mvn spring-boot:run restart used to
   wipe in-flight turns: SSE emitter timed out, ShutdownHook fired,
   HikariPool closed before doOnError could save. ChatStreamTracker
   now exposes an emergency-save callback per RunState; ChatController
   registers one per stream that snapshots the accumulator and
   writes status="interrupted_shutdown". @PreDestroy walks active
   runs, invokes the callback, then disposes. Spring's reverse-order
   bean teardown keeps ConversationService and Hikari alive long
   enough for the save to complete.

5. Observation thresholds for summarize (GraphObservationProperties +
   application.yml). The previous total-chars threshold of 12 KB
   triggered summarize after one or two RFC reads, costing a 40 to
   80 second compaction LLM call per loop. Tuned to: total 200 KB,
   single 16 KB, large-result 32 KB, rounds safety net 25. Java
   field defaults reverted to the conservative original values so
   application.yml stays the source of truth.

6. Frontend thinking segmentation (useChat.ts thinking_delta +
   phase). Multi-round ReAct turns merged every reasoning + summarize
   round's thinking into one segment, accumulating to 9 KB+ in a
   single bubble. thinking_delta now uses findLast(running) so a
   tool_call_started or phase transition closes the previous segment
   and the next delta opens a fresh one. phase event also closes
   running thinking/content segments.

7. Other small things bundled: removed a debug metadata-keys log
   that flooded the log file with one line per stream chunk; fixed
   three stale tests that didn't compile after earlier constructor
   changes (WikiLogServiceTest, WikiOverviewSpliceTest,
   WikiProcessingServiceLazyTest); added rfc-066 documenting the
   unified message queue + priority refactor as the next logical
   step on top of these stabilizations.

Verified end-to-end with multiple full sessions: a four-minute
generation that produced the expected docx and a follow-up enqueue
that ran cleanly after the previous turn naturally completed,
without the old "Disposable unavailable" interrupt path.
2026-04-27 07:51:49 +08:00
matevip
941653d185 fix(agent): also drop the queue guard in doOnError path
Same bug as the prior queue-drop fix in doOnComplete, but in the
sister branch that fires when the agent's reactive stream errors
out (CancellationException from a user stop). The guard

  cr.queuedInput() != null && !(isUserStop && !isInterruptFollowup)

mis-classified "user stopped, no interrupt-with-followup, but a
message is in the queue" as an explicit abort and silently dropped
the freshly-typed follow-up.

The frontend's enqueue path never sets interruptType — it just
calls requestStop + offers to messageQueue. Whoever puts a message
in the queue means it; just run it. Aligns with doOnComplete and
the four other queue-launch sites in this controller.
2026-04-27 07:51:18 +08:00
matevip
fcdb3fc15e fix(agent): break self-replicating 400, narration, args truncation, queue drop
A series of cross-cutting stability fixes that surfaced together
during a long debugging session.

reasoning_content / Claude prefill self-replicating 400:

- ChatController persists typed errors (content starts with '[错误] ')
  with status='error', so the failure text stops being re-sent as
  multi-turn context — DeepSeek thinking 400 ('reasoning_content
  must be passed back') and Claude 400 ('does not support assistant
  message prefill') used to recursively re-create themselves every
  retry by polluting history.
- BaseAgent.sanitizeForLlm filters status='error' / '[错误] ' prefix
  assistant messages from history before LLM dispatch.
- BaseAgent.fetchHistoryMessages defensively drops trailing
  AssistantMessages — Claude rejects assistant-tail prompts.
- NodeStreamingChatHelper.dropTrailingAssistant runs the same
  defense at every doStreamCall pre-egress, so the in-turn
  summarizing→reasoning transition (which leaves an assistant
  scaffold at the tail) doesn't trip Claude either.
- AgentGraphBuilder.FallbackPolicy.DEEPSEEK switched (null,true,true)
  → (' ',false,true), aligning with KIMI/OPENAI's tolerant ' '
  fallback. The previous 'force explicit 400' design was the
  self-replicating loop's prime mover.

narration + tool args truncation:

- ReasoningNode.DEFAULT_MAX_OUTPUT_TOKENS 4096 → 16384. The 4k cap
  was decapitating renderDocx tool_call args mid-stream when the
  model emitted a long content field on top of thinking content;
  the resulting 'invalid JSON' aborted execution silently.
- ReasoningNode appends a hermes-style TOOL_USE_ENFORCEMENT clause
  to every system prompt: 'when you say you will perform an action,
  call the tool now in the same response — narration is a protocol
  violation'. Treats 'now I will generate the docx' (and never
  actually calling renderDocx) as a forbidden pattern.
- ToolExecutionExecutor.normalizeToolExecutionError reframes the
  JSON-truncated error as actionable instructions: 're-call the
  same tool now with shorter content or split into multiple
  sequential calls; do NOT describe the result as text'.

side fixes from the same evening:

- ChatController doOnComplete skips completionPublisher.publish
  when isError=true, keeping memory extraction off the garbage path.
- ChatController doOnComplete queued-message guard simplified to
  'cr.queuedInput() != null', matching the other 4 sites in the
  controller. The previous 'isInterruptFollowup || !wasStopped'
  guard silently dropped queued messages when the user did
  Stop-then-Enqueue (wasStopped=true && interruptType=null), losing
  the freshly-typed follow-up message.
- prompts/graph/summarize-system.txt now distinguishes 'single
  task' (default; output one cohesive summary) from 'multiple
  independent sub-tasks' (use the子任务 N format). Stops the
  summarizer from inventing '子任务 1: PRO-027' decomposition for
  unitary requests like 'write me a project proposal'.
2026-04-27 07:51:01 +08:00
matevip
187197e804 fix(sse): preserve done event for late reconnect window 2026-04-27 07:50:35 +08:00
matevip
b7c911f01d feat(stt): DashScope realtime voice + language-aware routing + TalkMode polish
- DashScope paraformer-realtime-v2 WebSocket streaming
- Language-aware provider routing: Whisper for English, Paraformer for Chinese
- PCM WAV recording replaces WebM (provider filename bug + diagnostics)
- TalkMode push-to-talk fixes (audio drop, WS connecting race)
- Vite dev proxy WebSocket upgrade fix
- WebSocket binary buffer 8KB → 8MB (Tomcat default truncated voice clips)
- Audio chunk pacing at 100ms (DashScope returned 0 chars otherwise)
- Resolved language hint propagation + raw frame logging
- V46 seed idempotency fix on UI-toggled STT row
- Diagnostic cleanup after debugging session
2026-04-26 16:37:55 +08:00
matevip
3f10553186 fix(sse): distinguish stream_not_local vs completed on reconnect 2026-04-26 08:32:44 +08:00
matevip
0b55d5a227 feat(agent): Utf8SseEmitter + returnDirect end-to-end chain test 2026-04-26 08:32:44 +08:00
matevip
4a95e7dfe4 feat(tool): tool returnDirect and sensitive-data quarantine 2026-04-25 19:02:35 +08:00
matevip
9632edb008 fix(webchat): persist assistant reply and publish memory event on stream end 2026-04-23 08:09:48 +08:00
matevip
869e0c47e6 refactor(memory): unify ConversationCompletedEvent publish 2026-04-23 08:09:48 +08:00
matevip
762218cd32 refactor(db): drop @TableLogic on all entities, purge soft-deleted rows 2026-04-18 23:31:48 +08:00
matevip
a3e6724cf4 fix(chat): channel conversation sync + running indicator
- ChannelMessageRouter: include assistantMessageId in message_complete
  and done broadcasts so ChatConsole observers can reconcile the
  streaming placeholder to the persisted DB row by id instead of
  falling back to the FIFO 'claim' heuristic (which occasionally
  dropped the assistant bubble on external channel conversations).
  Capture the id from ConversationService.saveMessage in both the
  sync agentService.chat path and the streaming processWithStreaming
  path; switch to HashMap since Map.of rejects null values when save
  is skipped (e.g. under approval).

- ChatConsole: add a 'running' indicator on the sidebar so users can
  tell which conversations have an in-flight agent run. Pulsing amber
  dot on the channel icon (both expanded and collapsed modes) plus a
  '生成中…' / 'Generating…' pill in expanded mode.

- ChatConsole: don't cancel the previous conversation's streaming run
  when switching conversations — let it keep running in the background
  and reconcile when the user comes back.
2026-04-17 10:03:56 +08:00
matevip
b35c29767c feat(chat): realtime sync for external channel conversations 2026-04-17 08:25:32 +08:00
matevip
8a25d723f0 fix(chat): reset stale stopRequested flag on new stream register 2026-04-16 18:13:34 +08:00
matevip
7d8d16e458 feat: 5 defensive hardenings
- ConversationWindowManager: cap reserve token at 50% of effective max
  to prevent negative historyBudget on small-context models (8K/16K)
- common.security.SecretEquals: new constant-time comparison utility
  (MessageDigest.isEqual wrapper) for secrets/tokens/signatures
- WeixinChannelAdapter: migrate context_token comparison to SecretEquals
- FeishuChannelAdapter: fail-fast on empty encrypt_key when connection_mode=webhook
- TelegramChannelAdapter: sanitize attachment captions — strip control bytes
  (\p{Cc} except \t\r\n) + format chars (\p{Cf}) + 4096 char cap
- AgentGraphBuilder: fallback Anthropic max_tokens to 4096 on null/0/negative

Tests: SecretEqualsTest (5) + TelegramCaptionSanitizeTest (5) — all green.
2026-04-15 22:14:55 +08:00
matevip
4a4b39249e chore(channel): remove stale external-reference comments; fix(wiki): canonical slug lookup on merge path 2026-04-15 16:44:02 +08:00
matevip
0adb3dddf1 feat(plugin): Plugin SDK + UI layout improvements 2026-04-13 18:38:03 +08:00
matevip
97e101ab52 feat(channel): auto-start on create, multi-account UX, wecom mixed message 2026-04-13 17:10:34 +08:00
matevip
881badd15f feat(channel): weixin voice ASR fallback, wecom mixed message handling 2026-04-13 16:38:09 +08:00
matevip
d5e3502829 fix(wiki): backend hardening + batch delete UI 2026-04-13 15:37:12 +08:00
matevip
80b7b8e126 feat(agent): multi-agent delegation with parallel execution (RFC-004) 2026-04-12 23:40:27 +08:00
matevip
2a8b90365b feat(agent): add deep thinking toggle (RFC-001) 2026-04-12 11:29:32 +08:00
matevip
379422101c refactor(ui): minimalist login redesign, MCP fixes, Flyway upgrade compatibility 2026-04-11 23:01:08 +08:00
matevip
408ad980dd feat(i18n): complete remaining i18n — DefaultToolGuard, LLM/Datasource services, Channels UI 2026-04-11 18:02:03 +08:00
matevip
af9addaaec feat(i18n): structured i18n keys for exceptions and auto-translate 100+ error messages 2026-04-11 17:52:25 +08:00
matevip
f28ad8d9d4 fix(chat): preserve refresh history and thinking order 2026-04-11 00:16:33 +08:00
matevip
367dd4bed0 fix(agent): preserve per-subtask results in multi-task answer synthesis, add listAvailableSkills tool 2026-04-10 17:10:02 +08:00
matevip
ba34f1eed3 fix(chat): preserve intermediate reasoning in segments via phase-based splitting 2026-04-10 07:58:19 +08:00
matevip
dafdcb4182 feat(chat): segmented message display, progressive loading, and real-time segment persistence 2026-04-10 07:36:24 +08:00
matevip
a219f92410 feat: productize webchat channel config 2026-04-09 21:28:47 +08:00
matevip
5124842526 feat(platform): workspace isolation, info architecture, and UI redesign 2026-04-09 16:29:02 +08:00
matevip
d44ba0cdd8 fix(security): close workspace isolation gaps and audit context loss 2026-04-09 14:19:51 +08:00
matevip
3506c55bc8 feat(platform): Phase 3 Sprint 1-3 — permission, agent binding, dashboard 2026-04-09 11:58:20 +08:00
matevip
3d58a48eae feat(platform): add workspace foundation and channel execution upgrades 2026-04-09 10:29:16 +08:00
matevip
d3f2a310e8 feat(agent): context pruning, thinking recovery, channel health monitor 2026-04-07 07:39:14 +08:00
matevip
5fc60ec513 feat(agent): smart truncation, stale stream cleanup, configurable tool timeouts, and new indexes 2026-04-07 06:39:46 +08:00
matevip
a8613fb05c fix(agent): improve execution stability to prevent premature task exits 2026-04-07 01:34:40 +08:00
matevip
2ac7cc4af4 feat(stream): add fine-grained phase status hints for frontend UX 2026-04-07 00:40:38 +08:00
matevip
af40fbd7de feat(channel): add file upload support for WeChat and WeCom channels 2026-04-06 10:51:07 +08:00
matevip
eb78032752 feat(video): support video upload, preview, and multimodal analysis 2026-04-06 09:42:54 +08:00
matevip
cc0569440e fix(chat): harden multimodal attachment handling across all paths 2026-04-06 07:03:11 +08:00
matevip
84a205509b feat(chat): multimodal image injection and upload UX improvements 2026-04-05 23:58:42 +08:00
matevip
579d60125b Initial commit: MateClaw — Java + Vue 3 AI Assistant System
Full-stack AI assistant built on Spring AI Alibaba.
Features: ReAct Agent, Plan-and-Execute, MCP Protocol, Multi-Model, Multi-Channel.

Apache-2.0 License
2026-04-04 19:03:49 +08:00