Commit Graph

263 Commits

Author SHA1 Message Date
matevip
8762a79ec9 fix(delegate): reliability patches for multi-agent delegation 2026-04-22 05:08:08 +08:00
matevip
2b867af959 chore: remove .codex + openspec, add to .gitignore 2026-04-21 17:36:39 +08:00
matevip
57b711e305 config(memory): enable production Dream v2 flags 2026-04-21 17:34:40 +08:00
matevip
2eebdf2a47 fix(memory): HiL edit uses exact key match, not substring contains 2026-04-21 17:34:34 +08:00
matevip
b02f2ebfee fix(memory): HiL edit binds key to report's candidate entries 2026-04-21 17:34:28 +08:00
matevip
be9dfdf727 fix(memory): HiL edit validates key exists in MEMORY.md sections 2026-04-21 17:34:22 +08:00
matevip
e69aa2be04 fix(memory): P2 review fixes — API boundaries + identity + experimental flag 2026-04-21 17:34:15 +08:00
matevip
84c8f8f9a0 fix(memory): P1 review fixes — close 4 semantic gaps in data truth layer 2026-04-21 17:34:10 +08:00
matevip
13a3394ffd chore(sync): exclude .codex and openspec from opensource rsync 2026-04-21 17:34:03 +08:00
matevip
824f20a69d chore: remove .codex and openspec — private directories, not for public repo 2026-04-21 15:32:26 +08:00
matevip
96b89414f7 feat(memory): enable Memory Browser tab + audit fixes 2026-04-21 15:28:31 +08:00
matevip
0351cc369e feat(memory): memory audit fixes — 4 missing items 2026-04-21 15:28:26 +08:00
matevip
6ae221b38d feat(memory): batch 3 — Grafana dashboard + 3 Low fixes + issues cleanup 2026-04-21 15:28:21 +08:00
matevip
e42444f50a feat(ui): batch 2 — Facts Tab + Contradiction Inbox + Trust Bar 2026-04-21 15:28:16 +08:00
matevip
0a776f96fd feat(memory): batch 1 — 4 core fact projection fixes 2026-04-21 15:28:09 +08:00
matevip
c252cf50a4 docs: update README title and tagline 2026-04-21 09:27:42 +08:00
matevip
d9d677e762 refactor(ui): complete Memory view redesign — kill EP, match design system 2026-04-21 04:59:01 +08:00
matevip
eece5e96f5 feat(memory): dream-v2 E3-E5 — Forget + Contradictions + Feedback API 2026-04-21 04:58:57 +08:00
matevip
d983a1e02e feat(memory): dream-v2 E2 — Fact query tools + FactMemoryProvider 2026-04-21 04:58:52 +08:00
matevip
acc6f448db feat(memory): dream-v2 E1 — Fact Projection foundation 2026-04-21 04:58:47 +08:00
matevip
bf4cebb7b3 feat(memory): dream-v2 D3 — Diff viewer + SSE + Focused Dream dialog 2026-04-21 04:58:43 +08:00
matevip
30b9912ded refactor(ui): redesign Memory view — follow mc-page-shell design system 2026-04-21 04:58:39 +08:00
matevip
8ecc6d10ca feat(memory): dream-v2 D2 — Morning Card + HiL (Confirm/Edit) 2026-04-21 04:58:33 +08:00
matevip
90067d1c9f feat(memory): dream-v2 D1 — Memory Timeline view (frontend + backend) 2026-04-21 04:58:26 +08:00
matevip
155bab1739 fix(llm): skip unconfigured provider when resolving default model 2026-04-20 21:50:01 +08:00
matevip
fdde74959a chore: drop .github/ — this repo is a read-only mirror
Contributions land in the upstream private tracker, not here.
Removing .github avoids confusing contributors into opening PRs
against a mirror that never merges directly.
2026-04-20 20:29:27 +08:00
matevip
70c90d814d feat(memory): Dream v2 Phase 1 engine — consolidate refactor, focused endpoint, monthly archive
Five-commit bundle brings the Dream v2 P1 engine layer online, sitting
on top of the lifecycle mediator foundation already merged.

B.1-B.4 · Schema + records
- Flyway V26 (dream_report) + V27 (memory_recall review fields),
  both h2 and mysql
- DreamReportEntity + DreamMode + DreamStatus enum + record types
- DreamReportMapper repository layer

B.5-B.8 · Consolidate refactor + focused dream
- MemoryEmergenceService refactored for plug-in dream modes
- MemoryRecallService extended with promoted/rejected review fields
- Focused dream endpoint + prompt template
- MemoryController exposes the review/trigger surface

B.9-B.10 · Monthly archive service
- MemoryArchiveService rolls cold promoted entries into archival rows
  and reclaims daily_count storage
- DreamingScheduler runs archive job on its own schedule

B.12-B.14 · Tests
- MemoryArchiveServiceTest
- DreamFlagGuardTest
- DreamV2AcceptanceIT (end-to-end acceptance under feature flag)

Plus a verification script + HTTP e2e kit in the private test/ dir,
used for local staged rollout — not part of the open-source
distribution.

All features stay gated behind the mate.memory.dream.* flags from
Phase 1. Enable per-phase after staging validation.
2026-04-20 20:21:03 +08:00
matevip
907c6eff8c fix(memory): add success-path debug logs to MemoryLifecycleMediator
beforeLlmCall / afterLlmCall / onSessionEnd only logged on failure,
making flag on/off indistinguishable in logs. Add debug lines on the
success path so lifecycle activation is observable.
2026-04-20 17:34:40 +08:00
matevip
74928d615d feat(memory): Dream v2 Phase 1 — lifecycle mediator foundation
Wire memory-facing events (turn-started, turn-completed, session-ended,
memory-written) through a single MemoryLifecycleMediator so
MemoryProvider implementations can hook into the agent conversational
flow without spreading side-effects across the runtime.

Ten atomic steps shipped under feat/dream-v2-p1-lifecycle:

- A.1 + A.2: MemoryLifecycleMediator class + TurnContext value object
- A.3: TurnStartedEvent / TurnCompletedEvent domain events
- A.4: MemoryLifecycleEventListener bean for Spring event plumbing
- A.5: MemoryProvider.onMemoryWrite default method (backward compatible)
- A.7: wire the mediator into AgentService at the right hook points
- A.8: LifecycleFlagGuardTest — feature flag must gate every hook
- A.9: MemoryLifecycleMediatorTest — unit coverage per hook
- A.10: LifecycleRecallCountIT — F4 regression across the stack

Feature flags (all default OFF; enable per phase after staging):
- mate.memory.lifecycle-mediator-enabled
- mate.memory.dream.focused-enabled
- mate.memory.dream.archive-enabled

This is Phase 1 foundation only — focused-dream and archive-dream
providers arrive in later phases.
2026-04-20 17:31:21 +08:00
matevip
e99165f346 docs(readme): tighten positioning — Plan B tagline, honest peer comparison
Five surgical edits (en + zh parallel):

1. Tagline: 'Your AI needs a Plan B.' — category-defining one-liner
   replacing the prior descriptive 'fourteen brains' version.

2. New 'AI is becoming infrastructure' section anchors MateClaw to
   the 2026 industry inflection point (the March Claude outages,
   57% of enterprises running agents in production).

3. Honest peer comparison — OpenClaw and Hermes Agent added to the
   table with accurate facts (both are multi-provider, both are
   personal-first tooling). Windsurf removed. License fixed:
   OpenClaw is MIT.

4. Reframed competitive positioning under the table — dropped the
   self-flattering caption and replaced with an honest split:
   OpenClaw and Hermes are for single-user laptops; MateClaw is
   the team-grade version with RBAC, approval, audit, admin
   dashboard, and a Spring Boot core.

5. Project structure corrected to list only modules that actually
   ship to the open-source repo (server, ui, webchat, plugin-api,
   plugin-sample). Desktop noted separately as a binary release.

Also: UI-path hint in the failover section, a new centered $0 cost
statement after the surfaces table, Java badge bumped to 21+.
2026-04-19 21:57:53 +08:00
matevip
19e4adbb2e docs(readme): rewrite both READMEs around 1.1.x product features
Hero + headline
- Lead with the insight most AI tools fail on: vendors go down, memory
  resets, one channel = one product. Position MateClaw as the whole
  widget — one deployment covering reasoning, knowledge, memory, tools
  and channels.

Three product anchors replace the old feature wall
1. Multi-model failover — primary vendor key fails, runtime routes to
   the next healthy provider (DashScope / OpenAI / Anthropic / Gemini
   / DeepSeek / Kimi / Ollama / LM Studio / MLX, 14+ total) with a
   provider health tracker cooling down bad vendors.
2. Knowledge that links itself — LLM Wiki digests raw material into
   linked pages with citation-level traceability.
3. Five surfaces, one brain — Web Console, Desktop (bundled JRE 21),
   Webchat Widget, 7 IM channels, Plugin SDK.

Comparison table tightened
- 13-column × 7-product matrix collapsed to 5 rows × 4 competitors,
  focused on dimensions where MateClaw carves real space.

Project structure corrected
- Previous version only listed 3 modules. Now lists all seven:
  mateclaw-server / -ui / -desktop / -webchat / -plugin-api /
  -plugin-sample / matevip-sites.

Tech stack updated
- Java badge bumped to 21+ (was 17+); Flyway surfaced; Webchat row
  added.

Size: each README 230 → 203 lines.
2026-04-19 21:14:57 +08:00
matevip
55a7eb96dc fix(wiki): JobStageBar terminal state — badge sync, pulse stop, line coloring
Three bugs fixed:

1. Badge stays "processing" after job completes: pollJobs() never called
   fetchRawMaterials() when a job reached terminal status, so
   raw.processingStatus stayed processing in the store. Fix: detect
   terminal job status in pollJobs, trigger fetchRawMaterials to sync.

2. "Completed" dot pulses instead of solid: dotClass() treated completed
   the same as in-progress (target === cur → active). Fix: add
   isTerminal computed (includes completed), return done for all
   dots at or before the terminal position — no pulse animation.

3. Stage label stays orange at terminal: same cause — active class
   applied regardless of terminal state. Fix: use done class for
   terminal labels (green instead of orange).

Also: v-if on JobStageBar now shows for terminal status jobs (not just
stage !== queued), so completed/failed stage bars remain visible.
2026-04-19 21:00:08 +08:00
matevip
0301d5628e fix(wiki): JobStageBar stuck at queued — add job stage transitions
Root cause: processRawMaterial() created a job record at queued stage
but never called jobService.transition() during processing. The job row
stayed at queued forever, so the stage bar never advanced.

Backend (WikiProcessingService):
- Transition job to ROUTING immediately after creation
- Transition to PHASE_A_RUNNING before chunk processing begins
- Transition to COMPLETED/PARTIAL/FAILED at the end based on finalStatus
- Transition to FAILED in the catch block on unhandled exceptions

Backend (WikiProcessingJobService.transition):
- Handle FAILED, PARTIAL terminal stages (set finishedAt + status)
- Handle non-terminal intermediate stages (set status to running)

Frontend (JobStageBar.vue):
- Add stageMapping for backend stages not shown as dots: phase_a_done →
  phase_b_running, failed/partial/cancelled → completed position
- Guard stageIndex() against -1 (unknown stages default to all-pending)
- Terminal failure states show red failed dot instead of pulsing active
2026-04-19 20:53:35 +08:00
matevip
af8f712986 fix(failover): source fallback chain from the pool, not is_default flags
Two related changes that align buildFallbackChain with how users actually
think about failover.

1) Source = configured providers (was: only providers with fallback_priority > 0)
   Earlier the chain was strictly "providers the user explicitly opted in via
   fallback_priority > 0". A healthy in-pool provider with priority=0 was
   silently excluded — surprising since the pool was supposed to be the source
   of truth for "what is usable". After this change:
     - Candidates  = every configured provider
     - Pool gating = same as before (in-pool members only at build time;
                     runtime walker re-checks)
     - Order       = agent prefs (PR-3) → fallback_priority asc (>0) →
                     priority==0 alphabetical
   So fallback_priority is now purely an ordering hint, never an exclusion.

2) Per-provider model picker = default OR first-enabled (was: default only)
   Previously a provider was skipped if no chat model on it had is_default=true.
   That is admin friction with no benefit — every provider had to be visited in
   Settings just to mark a default before it could appear in failover. New
   pickFallbackModel():
     - first try getDefaultModelByProvider — user explicit pick wins
     - otherwise take the first enabled chat model on the provider
     - skip only if neither exists

User-visible effect on the deployment that surfaced this:
  - kimi-code primary fails (401 — real auth issue, separate from this bug)
  - Pool short-circuits primary → walker fires
  - Walker now sees dashscope (in-pool) AND ollama (in-pool) as candidates,
    even though neither has fallback_priority set
  - dashscope first enabled qwen model is picked → request succeeds via
    dashscope without anyone touching Settings

45 failover-related tests still green (unit-level chain-build behavior is
backward-compatible; only the candidate set and model-selection lookups
changed, both broadening the chain rather than narrowing it).
2026-04-19 20:34:58 +08:00
matevip
a171f2ac0e fix(wiki): recover raw materials stuck in processing on server restart
Root cause: recoverOnStartup() only reset mate_wiki_processing_job rows,
not mate_wiki_raw_material. claimForProcessing() only accepts pending,
so restart-orphaned processing rows were permanently stuck — frontend
showed "preparing..." forever.

Fix:
- Add WikiRawMaterialService.recoverStuckRawMaterialsOnStartup():
  resets processing→pending, clears progress fields, fires
  WikiProcessingEvent when autoProcessOnUpload is enabled
- WikiAutoConfiguration: call raw recovery after job recovery
- Execution order: job table first (queued), then raw table (pending)

Test: WikiRawMaterialRecoveryTest — 4 cases: reset + events, reset
without events (autoProcess=false), noop on empty.
2026-04-19 19:37:50 +08:00
matevip
cbcb7229b6 fix(wiki): citation drawer shows real data instead of empty fallbacks
Root cause: PageCitationWithRaw record only had id/pageId/chunkId/rawId/
paragraphIdx/anchorText/confidence — missing rawTitle, chunkOrdinal,
startOffset, endOffset, snippet. Frontend displayed Source + Chunk ?
for every card.

Backend:
- Extend PageCitationWithRaw with rawTitle, chunkOrdinal, startOffset,
  endOffset, snippet fields
- Expand listWithRawByPageId SQL to LEFT JOIN mate_wiki_raw_material for
  title, JOIN mate_wiki_chunk for ordinal/offsets/content snippet (first
  200 chars via SUBSTRING)

Frontend:
- CitationDrawer: replace hardcoded Source / Chunk ? / offset with
  i18n keys
- Add 4 new i18n keys: citationUnknownSource, citationChunkN,
  citationChunkUnknown, citationOffset (zh-CN + en-US)
2026-04-19 19:36:57 +08:00
matevip
527a67374d fix(failover): probe URL construction + permissive 4xx/5xx handling
Two real bugs the user restart surfaced — both turned healthy providers
into HARD-removed false positives.

Bug #1 — URL duplication
  OpenAiCompatibleListModelsProbe always concatenated /v1/models, so
  providers whose Base URL already includes the version segment got the
  wrong URL:
    LMStudio  http://localhost:1234/v1     → /v1/v1/models  → 404
    ZhipuAI   .../api/paas/v4              → /v4/v1/models  → 404
  Fix: detect a trailing /vN suffix and append /models instead. Six unit
  tests in OpenAiCompatibleListModelsProbeTest lock the rule down.

Bug #2 — 404 false positives
  Kimi for Coding API does not expose /v1/models even though chat works
  fine, so the probe correctly received a 404 and incorrectly HARD-removed
  the provider from the pool. Other vendors will hit the same — listing
  is not a universal contract.
  Fix: classify HTTP responses semantically.
    401 / 403  → HARD remove (real auth failure)
    404 / 405 / 410 → fail-open (endpoint missing, server may be alive)
    other 4xx / 5xx → fail-open (probe inconclusive — let chat decide)
    network errors → fail (unreachable)
  This is the same philosophy as ChatGPTOAuthStatusProbe: when we cannot
  cheaply confirm health, we do not proactively penalize the provider.
  Same logic applied to Anthropic + DashScope probes for consistency.

Net effect on the user deployment after restart:
  - kimi-code stays in pool (404 → fail-open) → primary path works again
  - lmstudio + zhipu-cn also stay in pool (URL bug fixed)
  - dashscope + ollama unchanged (real 200 OK)

Tests: 6 new for resolveModelsPath. The 2 unrelated WikiRawMaterialDedupTest
failures pre-date this commit and live in ba86bea.
2026-04-19 19:36:38 +08:00
matevip
4700d0312d fix(wiki): deduplicate raw material uploads across all processing statuses
Root cause: addFile()/addText() hash dedup only matched rows with
status=completed, so the same file uploaded while in partial/pending/
processing/failed status would create a duplicate row.

Fix:
- Remove .eq(processingStatus, "completed") from dedup queries — match
  any non-deleted row with the same content hash in the KB
- On dedup hit: completed/pending/processing → return as-is;
  partial/failed → trigger reprocess (partial enters resume branch)
- Clean up the newly uploaded temp file when dedup discards it
- Frontend: uploadRawFile/addRawText check for existing id in the list
  before unshift to prevent visual duplicates

Test: WikiRawMaterialDedupTest — 10 cases covering all 5 statuses,
reprocess triggers for partial/failed, no-op for others, insert only
when no match.
2026-04-19 19:36:13 +08:00
matevip
d9dfd602f9 fix(wiki): fix progress display and reprocess feedback issues
Three root causes for broken progress:

1. JobStageBar was shown whenever a job record existed (even at queued
   stage), hiding the working SSE-driven progress bar. Fix: only show
   JobStageBar when job.stage !== queued.

2. SSE connection only opened when hasProcessing was true (status ===
   processing), but reprocess sets status to pending first. Fix:
   include pending in the hasProcessing check.

3. After reprocess, if processing finished before SSE connected, the
   status badge stayed on pending forever. Fix: immediately set local
   status to processing after reprocess API call, clear stale job
   entries, and add delayed re-fetches (5s/15s) as safety net.

Also: clear rawJobs entries on raw.completed/raw.failed SSE events to
prevent stale JobStageBar from lingering after processing ends.
2026-04-19 19:35:30 +08:00
matevip
6c15622b59 refactor(llm): RFC-009 PR-0b — migrate DashScope + Anthropic helpers out of AgentGraphBuilder
PR-0 only installed the strategy seam; the actual ~600 LOC of provider-
specific construction stayed in AgentGraphBuilder as transitional public
helpers. PR-0b moves the DashScope + Anthropic halves into their builders
proper. (OpenAI larger refactor — 5 sub-helpers including Kimi/o-series
special cases — is left for a follow-up PR-0c.)

AgentDashScopeChatModelBuilder now owns:
  - buildDashScopeApi (with provider/env/reflection key+url fallback chain)
  - buildDashScopeOptions (model/temp/max-tokens/topP + built-in search)
  - normalizeDashScopeBaseUrl (strip /compatible-mode/, return null for SDK default)
  - readApiKeyFromDefaultChatModel + readBaseUrlFromDefaultChatModel +
    readDashScopeApiFromDefaultChatModel (reflection-based final fallback)
  - isBuiltinSearchEnabled (renamed from isDashScopeSearchEnabled, called
    by AgentGraphBuilder.build via the now-injected dashScopeBuilder ref)

AgentAnthropicChatModelBuilder now owns:
  - buildAnthropicApi (key validation, applyHttpTimeouts duplicated locally)
  - buildAnthropicOptions (extended-thinking budget mapping low/medium/high/max
    → 4k/8k/16k/32k, temperature=1 enforcement, RFC-014 prompt cache options)

AgentGraphBuilder dropped:
  - DashScope: ~120 LOC (api + options + 4 helpers + isDashScopeSearchEnabled)
  - Anthropic: ~75 LOC (api + options)
  - DashScopeChatModel + DashScopeConnectionProperties fields (unused after move)
  - Deprecated single-fallback buildFallbackModel (no callers, superseded
    by buildFallbackChain since RFC-009 PR-1)
  - 5 imports for moved DashScope/Anthropic types

Net: -154 LOC in AgentGraphBuilder (1721 → 1567), +372 across the two new
builders. Strategy seam is now real for 3 of 4 protocols (ChatGPT was
already standalone, OpenAI is PR-0c). 220/220 tests still green — no
behavior change.
2026-04-19 19:22:57 +08:00
matevip
3d213eb281 chore: sync multiple commits from private dev
Covers 15 upstream commits (private mirror → public):

Multi-provider failover (RFC-009):
- PR-0: extract ChatModelBuilder strategy seam
- PR-1a: AvailableProviderPool data structure
- PR-1b: startup provider liveness probe + 4 protocol strategies
- PR-1c: wire AvailableProviderPool into runtime chat-model selection
- PR-1d: provider pool REST endpoint + UI badges
- PR-1e: manual reprobe trigger + auto-reprobe on provider config change
- PR-3: per-agent provider preferences (agents can override the
  org-wide fallback chain)

Wiki subsystem (RFC-029~033):
- Relation model, resilient background jobs, light-weight processing
  path, retrieval enhancement, frontend redesign (single landing commit)
- Follow-up fixes: null guards + stats query + i18n polish, move
  WikiProcessingJobMapper to repository/ for @MapperScan, align
  implementation with RFC-029~031 spec
- Copy pass: replace "富化 / enrich" wording with clearer "链接 / link"
- Style: switch enrich/repair buttons to @element-plus/icons-vue
2026-04-19 18:37:44 +08:00
matevip
3b11a3def6 fix(failover): AUTH_ERROR triggers fallback chain + UI splits provider 401 from session expiry
Two related issues from the Kimi-401 user report:

1. Backend (NodeStreamingChatHelper): a primary AUTH_ERROR (e.g. Kimi 401
   with an invalid API key) returned immediately without trying the
   fallback chain — a fallback provider with a different, valid key
   never got a chance. Even with DashScope correctly configured as the
   fallback, the user chat dead-ended on a 401.

   The original assumption ("auth never self-heals so do not retry")
   holds for the primary same-model retry loop but is wrong for the
   fallback chain — different providers have different keys. Apply the
   same break-into-fallback policy that BILLING and MODEL_NOT_FOUND
   already use. recordPrimary(false) is preserved so the cooldown
   counter still accumulates.

2. Frontend (chatError.ts + i18n): the error-text matching for
   /认证|auth|unauthorized|401/i was so broad it matched the substring
   "auth" inside URLs like https://api.kimi.com/.../auth, classifying
   any model 401 as user "session expired" and rendering the misleading
   "页面将自动跳转到登录页" copy. (The redirect itself only fires from
   /api/v1/auth/* axios paths and SSE-connection 401s, not from this
   payload-text path — but the copy alone is the worst kind of false
   alarm.)

   Add a new ChatErrorCategory provider_auth_error and split the
   pattern matching: narrow auth_expired (HTTP 401 / 登录已过期 /
   session expired / 凭证失效) is matched FIRST, then the broad
   401-ish pattern routes to provider_auth_error. BACKEND_ERROR_TYPE_MAP
   for AUTH_ERROR is also remapped, since structured backend payloads
   currently always come from LLM providers — never from our own
   /api/v1/auth path.

Tests
- NodeStreamingChatHelperFailoverTest (5 cases): primary 401 →
  fallback succeeds; chain skips auth-failing fallback to next healthy
  one; whole-chain failure surfaces last AUTH_ERROR (no silent drop);
  BILLING regression unchanged; primary-success path does not touch
  chain
- Browser preview verified: new i18n keys resolve in en-US, classifier
  correctly routes "[错误] 401 from kimi.com" → provider_auth_error
  while "[错误] HTTP 401 from /api/v1/auth/ping" stays auth_expired
- 186 tests pass (was 181 + 5 new); vue-tsc clean

Do-not-touch list: handleAuthFailure() in useStream/api/index.ts (real
session-expiry path) is unmodified — only the misclassification
upstream is fixed. auth_expired i18n copy is unchanged.
2026-04-19 17:45:15 +08:00
matevip
7ba8fe602b feat(llm): track primary health + split BILLING / MODEL_NOT_FOUND from generic client errors
Track the primary model health, not just fallback entries
- NodeStreamingChatHelper accepts primaryProviderId via a new 5-arg
  constructor; AgentGraphBuilder passes ModelConfigEntity.getProvider()
- Before the 5-retry primary loop, check
  healthTracker.isInCooldown(primaryProviderId): if true, log + broadcast
  "主模型暂时不可用(冷却中),直接尝试备选模型..." and short-circuit
  straight to the fallback chain. Prevents a degraded primary from
  burning 30+ seconds of backoff on every conversation turn.
- recordPrimary(success/failure) now fires on every primary verdict —
  AUTH, BILLING, MODEL_NOT_FOUND, EMPTY_RESPONSE, generic UNKNOWN, and
  the explicit success path. Three consecutive failures push the
  primary provider into cooldown automatically.
- Legacy 1/2/3-arg constructors leave primaryProviderId null; tracking
  silently disables for them so existing tests/wiring keep working.

Split BILLING and MODEL_NOT_FOUND out of CLIENT_ERROR / AUTH_ERROR
- BILLING (HTTP 402, "insufficient_quota", "credit balance is too low",
  "billing_hard_limit_reached", "quota exceeded"): payment failure on
  primary does not kill the call — a different provider may have credits.
  Skips same-model retries and heads to fallback chain.
- MODEL_NOT_FOUND (HTTP 404, "Model not exist", "model_not_found",
  DashScope "[InvalidParameter] url error"): unknown model id will not
  start working on retry. Was previously misclassified as CLIENT_ERROR
  and terminated the whole call; now routes to fallback so a different
  provider can attempt with its default model.
- classifyError ordering matters: BILLING / MODEL_NOT_FOUND are matched
  BEFORE the generic 400 / Bad Request branch, otherwise they would be
  swallowed by CLIENT_ERROR.

Tests
- ErrorClassificationTest: 11 tests, covers multi-vendor error phrasing
  for both new types + regression checks that 401 / 429 / 400 still
  classify as before
- NodeStreamingChatHelperFallbackChainTest: +2 tests verifying
  primaryProviderId persistence on the new constructor and null on
  legacy ones
- 181 tests pass (was 168 + 13 new)
2026-04-19 17:10:27 +08:00
matevip
7b12c5f0c9 feat(llm): provider health tracker + UI editor for failover priority
UI — Failover priority editor
- ProviderConfigRequest + ProviderInfoDTO carry fallbackPriority
- ModelProviderService.updateProviderConfig persists it (null = unchanged);
  toProviderInfo exposes the current value to the UI (defaults to 0)
- ProviderConfigModal advanced panel exposes a number input with hint
- ProviderCard shows a "Fallback #N" badge for chain members so the
  priority order is visible at a glance without opening the modal
- 5 new i18n keys (zh + en) — verified to resolve at runtime via i18n.global.t

Backend — Per-provider health tracker
- ProviderHealthTracker: ConcurrentHashMap-backed counters; N consecutive
  failures (default 3) push the provider into a cooldown window (default
  5 min) during which the chain walker skips it. Success resets both
  counter and cooldown atomically. Lazy expiry on lookup so dead entries
  do not accumulate.
- ProviderHealthProperties exposed under mateclaw.llm.failover.health.*
  with sane production defaults
- New FallbackEntry record (providerId + ChatModel) replaces raw
  List<ChatModel> in the chain so the walker can correlate cooldown
  state to entries; AgentGraphBuilder.buildFallbackChain returns the
  new type
- NodeStreamingChatHelper takes the tracker through a new 4-arg
  constructor and consults it before each fallback call; records
  success/failure on each chain attempt. Legacy 2/3-arg constructors
  preserved as @Deprecated wrappers (synthetic providerId means no
  health tracking on the legacy path — that path is opt-out anyway)

Tests
- ProviderHealthTrackerTest (9 tests): below/at threshold, success
  reset, cooldown expiry (via reflection on the min-clamp setter),
  disabled-tracker no-op, null-providerId safety, per-provider
  isolation, snapshot output
- NodeStreamingChatHelperFallbackChainTest updated to FallbackEntry
  field type — verifies providerId + ChatModel survive the chain
- 168 tests pass (was 159 + 9 new)

Verification
- mvn test green; vue-tsc clean; live UI confirms i18n resolution
2026-04-19 16:57:03 +08:00
matevip
ed37e81e7e feat(llm): multi-model failover chain driven by per-provider priority
Replaces the hardcoded single-DashScope fallback with a DB-driven
ordered chain. Same-provider primary deployments (e.g., DashScope
qwen-max) finally get a real fallback; if any provider in the chain
returns an empty body or transient failure, the next is tried.

Schema — DB-driven chain
- mate_model_provider gains `fallback_priority INT DEFAULT 0`. Positive
  values define try-order; 0 = not in chain. Migration V21 (h2 + mysql)
  seeds DashScope as priority 1 to preserve existing behavior.
- ModelProviderService.listFallbackChain() returns providers ordered by
  priority ascending.
- ModelProviderEntity gains the new field.

Runtime — chain walk + empty-response trigger
- AgentGraphBuilder.buildFallbackChain(primaryConfig) returns a
  List<ChatModel>, identity-filtering the primary by (providerId,
  modelName) — fixes the bug where same-provider-primary deployments got
  null fallback. Providers whose API key is missing are silently
  skipped with WARN. Old buildFallbackModel(ChatModel) kept as
  @Deprecated wrapper.
- NodeStreamingChatHelper accepts List<ChatModel>; the post-retry
  fallback block now walks the chain in priority order, single-shot
  per entry. Old single-fallback constructors retained as @Deprecated
  one-element-list wrappers so legacy callers keep working.
- New ErrorType.EMPTY_RESPONSE: when the LLM returns no content, no
  thinking, AND no tool calls, mark the result as a soft failure and
  break the same-model retry loop, handing off directly to the
  fallback chain.
- Broadcast updated to "切换到备选模型 (N/M)..." so SSE consumers see
  chain progress.

Tests
- NodeStreamingChatHelperFallbackChainTest covers constructor variants,
  chain immutability, deprecated-overload back-compat, and the
  EMPTY_RESPONSE enum exists as a compile-time contract.
- 159 tests pass (was 153 + 6 new).
2026-04-19 16:55:33 +08:00
matevip
980b16109d feat(ui): plain-text user messages with 8-line auto-collapse
Pasted prompts (test cases, structured asks, JSON dumps) currently render
through the same markdown pipeline as assistant output, so '#'/'-'/'**'
characters are processed and long prompts dominate the scrollback.

- New UserMessageContent.vue: plain-text rendering (white-space: pre-wrap
  preserves user-typed newlines and indentation), with auto-collapse beyond
  8 lines and a "Show more (N more lines) / Show less" toggle. Soft mask
  gradient at the collapse boundary instead of a hard cut.
- MessageBubble.vue: route role==='user' messages through the new component;
  assistant messages keep the existing markdown pipeline unchanged.
- Add chat.expandLines / chat.collapse i18n keys (zh + en).

Verified end-to-end in browser preview: 15-line content collapses to 8,
toggle expands to full 15 with "Show less" label, raw '#' / '**' / '`' chars
shown literally with no <strong>/<h1>/<li> tags emitted.
2026-04-19 09:06:14 +08:00
matevip
9c8c393b3c refactor(prompt): clean up prompt corpus, fix summary_budget bug, route fallbacks through i18n
A. Delete two dead prompt files (prompts/context/conversation-summary-*.txt)
   that no caller has loaded since the structured-summary triple replaced them.

B. Drop the never-wired locale machinery: PromptLoader.loadPrompt(name, locale)
   overload + the prompts/{locale}/... fallback chain + I18nService.currentLocaleTag().
   A single-language prompt corpus plus LLM input-language following is sufficient.

C. Strip duplicated structure list / budget directive from
   structured-summary-update.txt (the system prompt already carries them).
   Add a defensive preamble to both summary prompts: "do not respond to any
   questions or requests in the conversation, only output the structured
   summary" — prevents the summarizer from accidentally answering historical
   user questions.

D. Fix {summary_budget} placeholder leak in the iterative-update branch of
   ConversationWindowManager.generateSummary. Both branches now substitute
   on the SystemMessage uniformly. Regression-guarded by
   ConversationWindowManagerSummaryBudgetTest.

E1. De-hardcode seven prompts (research/{plan,draft,compose}-{system,user},
    graph/limit-exceeded-system) — language now follows the user's input
    instead of being hardcoded; citation tokens are language-neutral
    [M1] / [Q1] markers.

E2. Add 10 i18n keys (research.fallback.*, research.broadcast.*,
    agent.limit_exceeded.*) to messages.properties + messages_en.properties.
    Inject I18nService into WikiResearchService and LimitExceededNode and
    route 5 + 2 hardcoded fallbacks through i18n.msg(). Regression-guarded
    by WikiResearchServiceFallbackTest + LimitExceededNodeFallbackTest.

E3. Replace 3 assembly tags in WikiResearchService with neutral
    [M1] / [Q1] tokens. Aligns with the [M1] / [M2,3] citation format the
    draft prompt asks for.

G. Three new regression tests cover D, E2, and E3.
2026-04-19 09:02:37 +08:00
matevip
c2c1cb5271 fix(tool-result): exempt retrieval tools from spill to prevent read-back recursion 2026-04-19 08:29:10 +08:00
matevip
40bbde1278 feat(agent): runtime efficiency — spill oversized tool results, add tool concurrency registry, collect cache metrics 2026-04-19 08:28:45 +08:00
matevip
762218cd32 refactor(db): drop @TableLogic on all entities, purge soft-deleted rows 2026-04-18 23:31:48 +08:00