Commit Graph

319 Commits

Author SHA1 Message Date
matevip
cc3c9a8618 fix(ux): preserve in-flight turn on tab switch + raise max_iterations cap to 100
Three small but high-impact fixes that all surfaced together while
verifying the long-form generation flow.

1. ChatConsole onBeforeUnmount no longer kills the backend turn.
   Previously, switching tabs / route navigation / any cause that
   unmounted the chat view called stopChatGeneration(), which POSTs
   /chat/{cid}/stop and aborts the in-flight LLM call. The user
   reported a turn dying mid-generation just from switching pages.
   Replaced with resetForNewConversation() — front-end SSE disconnect
   only, no /stop. Backend keeps running; pollActivity / status probe
   reconnects on return. Aligns with the existing comment in
   selectConversation: "let A's backend agent run continue running."

2. Agent max_iterations raised 25 → 100 with a hard ceiling.
   The previous 25-step ceiling caused LimitExceededNode to fire on
   substantive multi-tool tasks (document generation + image conversion
   + retry loops). 100 matches QwenPaw's _MAX_MAX_ITERATIONS upper
   bound. New plumbing:
   - BaseAgent.MAX_ITERATIONS_HARD_CEILING = 100 public constant
   - BaseAgent default field 25 → 100 (Java-side fallback)
   - AgentGraphBuilder clamps any per-agent DB override to the
     ceiling at runtime; if the row holds 200, runtime sees 100 and
     a WARN is logged with the original value.
   - V47 migration (h2 + mysql) idempotently bumps the three default
     seeded agents (1000000001, 1000000002, 1000000003) only if they
     still hold the old defaults (25 / 20). User-customized values
     are not touched.
   - data-en/zh/-mysql-en/-mysql-zh seed files updated to 100 for
     fresh installs.

3. DocxRenderTool tells the LLM not to prepend a host to the URL.
   DeepSeek and Claude have both been observed wrapping the
   /api/v1/files/generated/{id} relative path returned by renderDocx
   into an absolute URL with a hallucinated domain (e.g.
   https://ai-tools-system.com/...), breaking the download link in
   the rendered chat bubble. The tool's return string now appends an
   explicit "must use the relative path verbatim, do not add any
   https:// or http:// prefix" instruction, which Claude and
   DeepSeek both honor.
2026-04-27 08:17:17 +08:00
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
4a15027a98 feat(wiki): download original raw material file 2026-04-26 20:46:52 +08:00
matevip
0d78beb44f fix(wiki): batch-create per-slug retry — recover unparseable JSON, bump to 2 attempts 2026-04-26 20:37:09 +08:00
matevip
76956cf990 fix(tool): read_file falls back to chat-upload attachment by basename 2026-04-26 20:24:00 +08:00
matevip
30e7e67bb6 feat(wiki): LLM-narrated overview section with debounced regen + Recent Updates list + scaffold self-heal 2026-04-26 19:53:12 +08:00
matevip
fa5eaf83b4 fix(ui): code-block light-mode theme + cap header height at 38px 2026-04-26 19:09:10 +08:00
matevip
e2df16893e fix(wiki): smaller batch-create + resume button for partial generation 2026-04-26 18:42:27 +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
4d7c6593c4 feat(minimax): expand video model catalog + add CN endpoint support 2026-04-26 08:34:35 +08:00
matevip
410c6c28cd feat(deepseek): integrate DeepSeek V4 (flash + pro) with thinking-mode support 2026-04-26 08:34:34 +08:00
matevip
dfb9fc2cac fix(model-catalog): claude-sonnet-4-7 doesn't exist — Sonnet stays at 4.6 2026-04-26 08:34:12 +08:00
matevip
b9c4f40028 refactor(anthropic): cleanup — deduplicate diagnostic statics, remove dead cache-options code 2026-04-26 08:34:12 +08:00
matevip
dbdb585eed fix(anthropic): rewrite system field to array to pass OAuth anti-abuse gate 2026-04-26 08:34:12 +08:00
matevip
5c2482c307 fix(anthropic): log outgoing request headers on 429 2026-04-26 08:34:12 +08:00
matevip
ed3ff54f0c fix(anthropic): drop (external, cli) UA suffix — it's the anti-abuse fingerprint 2026-04-26 08:34:11 +08:00
matevip
84cb442446 fix(anthropic): log anthropic-ratelimit-* headers on 429 2026-04-26 08:34:11 +08:00
matevip
aabf2b8c32 fix(anthropic): add anthropic-dangerous-direct-browser-access + accept headers 2026-04-26 08:34:11 +08:00
matevip
ae6467a5dc fix(anthropic): bidirectional mcp_ tool-name prefix on OAuth requests 2026-04-26 08:34:10 +08:00
matevip
44548e3010 fix(anthropic): inject Claude Code identity into system prompt 2026-04-26 08:34:10 +08:00
matevip
1d5bb58e9b fix(anthropic): allow ANTHROPIC_CLAUDE_CODE in StateGraph whitelist 2026-04-26 08:34:09 +08:00
matevip
fb4c013ad8 feat(anthropic): surface Claude Code OAuth in admin UI 2026-04-26 08:34:09 +08:00
matevip
bf4e81c554 i18n: add error messages for Claude Code OAuth failures 2026-04-26 08:34:08 +08:00
matevip
a7938b0e68 feat(anthropic): wire Claude Code OAuth into chat model 2026-04-26 08:34:08 +08:00
matevip
8539fb9407 feat(anthropic): Claude Code OAuth credential plumbing 2026-04-26 08:34:08 +08:00
matevip
9187aed273 fix(oauth): support remote-server deployment via MANUAL_PASTE flow 2026-04-26 08:34:08 +08:00
matevip
23a6d16778 feat(model-catalog): add Claude 4.7 + GPT-5.5 sampling-params handling 2026-04-26 08:32:45 +08:00
matevip
fac5ff2838 feat(image-gen): add gpt-image-2 to OpenAiImageProvider 2026-04-26 08:32:44 +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
c13d9b4c88 feat(wiki): expose method=tika short-circuit on extract_document_text 2026-04-25 19:02:35 +08:00
matevip
c752c1f2ae feat(wiki): Tika as last-resort document extractor 2026-04-25 19:02:34 +08:00
matevip
6474d0e6be feat(wiki): normalized relation boost + reason in search results 2026-04-25 19:02:34 +08:00
matevip
a1e40d6eae feat(wiki): enrich batch — N pages per LLM call 2026-04-25 19:02:34 +08:00
matevip
ca4c447250 feat(wiki): enrich prompt knows what's already linked 2026-04-25 19:02:34 +08:00
matevip
8ef523f953 feat(wiki): archived pages drawer in Wiki UI 2026-04-25 19:02:33 +08:00
matevip
51566a47a4 chore(wiki): externalize compile prompts; doc archive tools + admin endpoints 2026-04-25 19:02:33 +08:00
matevip
58b49f6e20 feat(wiki): structured no-evidence compile + ops admin endpoints 2026-04-25 19:02:33 +08:00
matevip
5206d65be7 fix(wiki): eager 0-pages = partial when chunks indexed; per-KB structured route; archived filter completion 2026-04-25 19:02:33 +08:00
matevip
3f25064ef9 fix(wiki): cancel in-flight LLM work when raw is deleted 2026-04-25 19:02:32 +08:00
matevip
4ed88ebbf9 feat(wiki): PR-8 frontend protection badges + delete-button gating 2026-04-25 19:02:32 +08:00
matevip
1e62dbad47 feat(wiki): PR-7 archived soft-archive 2026-04-25 19:02:32 +08:00
matevip
850d59c04f feat(wiki): PR-6b structured-output route phase (opt-in) 2026-04-25 19:02:32 +08:00
matevip
c8af19cae2 feat(wiki): PR-2b/2c overview rebuilder + activity log 2026-04-25 19:02:31 +08:00
matevip
f4d4e973df feat(wiki): PR-5b enrichment via replacement plan 2026-04-25 19:02:31 +08:00
matevip
726265780e build(docker): install poppler + tesseract for PDF extraction parity with Mac 2026-04-25 11:05:29 +08:00