Commit Graph

39 Commits

Author SHA1 Message Date
matevip
bf0d64e46a feat(tool): 工具 schema 预算阈值门——超出窗口预算时按使用频度自动降级到扩展目录,enable_tool 可找回 2026-07-03 18:52:45 +08:00
matevip
7705778903 fix(agent): normalize replayed tool-call arguments to valid JSON (#410)
Strict OpenAI-compatible providers reject the /chat/completions request
with HTTP 400 when an assistant message in history carries a tool call
whose function.arguments is not parseable JSON. Normalize blank or
non-JSON arguments to "{}" at the send chokepoint so streaming,
history-replay, and older-persisted tool calls all stay well-formed.
2026-06-26 11:19:19 +08:00
matevip
5ff58b00ad fix(plans): scrub injected context from persisted plan goal (#402) 2026-06-22 17:54:27 +08:00
matevip
eca4229751 feat(plans): per-step agent delegation + fix kanban pending column (issue #385) 2026-06-21 21:20:58 +08:00
SuperCoderMan521
cb87569264 feat(wiki): make [n] citation markers clickable, linking to wiki pages (#305)
Backend (SourceEvidenceLedger):
- appendWikiSourceTable now normalizes existing source lines in-place to
  canonical "[N] Title - section - page N" format instead of skipping them
- Added replaceSourceLine helper that matches a full source line by regex
  and replaces it with the canonical form
- When source lines exist without a "来源:" header, automatically insert
  one so the frontend preprocessor can locate the source table

Frontend (useMarkdownRenderer):
- Added data-citation-index / data-citation-title to DOMPurify whitelist
- Added preprocessWikiCitations preprocessor: parses the canonical source
  table to build an index-to-title map, replaces [n] markers in the answer
  body with clickable <a> links, and wraps entire source-table rows so the
  full line is clickable
- Integrated into the render pipeline after wikilink substitution and
  before Marked parsing

Frontend (useGlobalWikilinkClick):
- Extended the click delegation selector to match both .wiki-link and
  .wiki-citation elements
- Title extraction falls back: data-citation-title || data-wiki-title

Tests: added three test cases for source-line normalization, idempotency,
and automatic header insertion
2026-06-21 09:58:41 +08:00
matevip
1affbd7b82 feat(agent): loop-engineering robustness — goal continuation, plan re-plan, stall detection
- goal: continue (not skip) on max-iterations and evidence-insufficient turns.
  A max-iterations turn grants a fresh iteration budget ("hard continuation"),
  bounded per run and sized into the graph recursion ceiling, so a task too big
  for one budget keeps going instead of stalling until the next user message.
- plan-execute: re-plan the remaining work on a step exception, and on a
  signature-based stall (repeated failures / identical results / no usable
  result) instead of advancing dependent steps with junk; bounded by a per-run
  re-plan cap, with a graduated change-strategy nudge before the hard stop.
- plan-execute: auto-derive a goal from a genuine multi-step plan, seeding the
  acceptance criteria from the plan steps, so the goal subsystem engages without
  the model calling setGoal; broadcast goal_created so the UI hydrates.
- react: refund the iteration for setup-only rounds (load_skill / enable_tool)
  so a tight budget is not eaten by the load-then-use two-step.
- ui: re-fetch the active goal when a turn finishes so a goal created or mutated
  mid-conversation surfaces without depending on an SSE event.
- streaming: make retry backoff / total-time budget instance fields with a
  test-only seam; clarify that the wall-clock budget (not max-retries) bounds a
  sustained SERVER_ERROR loop to ~8 attempts, fixing the slow/flaky retry test.
2026-06-17 06:37:08 +08:00
matevip
85ceafa055 fix(agent): scope KB grounding to wiki-equipped agents and wiki tools
The knowledge-base trust verification recorded wiki citations from every
non-readFile tool response by sniffing its JSON for a top-level title /
pages / chunks field. Tools like getGoalStatus return a top-level title,
which falsely populated the citation set and then forced [n] citations on
the final answer (otherwise flagged EVIDENCE_INSUFFICIENT). Gate citation
mining on the wiki_* tool name instead.

Likewise, the grounded answer contract (cite-or-refuse) was appended to
every ReasoningNode call unconditionally, degrading general agents that
have no knowledge base. Append it only when the agent has a wiki_* tool
bound, scoping the strict regime to KB-grounded scenarios.

Adds a regression test asserting a non-wiki tool with a top-level title
creates no wiki citations.
2026-06-16 07:48:04 +08:00
jack
88be1f748a
[#305] [Feature] Add knowledge base trust verification (#334)
Co-authored-by: SuperCoderMan521 <SuperCoderManqq.com>
2026-06-16 07:47:04 +08:00
matevip
710b756281 test(chat): cover gateway-resilience error classification; fix PKIX casing
Add ErrorClassificationTest regression cases for the AI-gateway retry
hardening: 5xx-before-4xx ordering (a proxy 502 whose body says
"bad request" stays retryable), Chinese / numeric provider billing
patterns, and DNS / TLS infrastructure-fatal detection.

Fix the cert-trust pattern while adding its test: Java's ValidatorException
emits "PKIX path building failed" with an uppercase PKIX and the error
chain is not lower-cased, so the previous lowercase pattern never matched
— an untrusted/expired cert chain fell through to the retryable
SERVER_ERROR bucket and was retried in vain instead of failing over.
2026-06-15 08:09:59 +08:00
MIST
42f1d5b685 fix(chat): resilient retry for transient AI gateway errors 2026-06-15 08:02:02 +08:00
matevip
e6f8da9606 fix(agent): recover tool-call follow-up on interleaved-thinking models (#256) 2026-06-12 11:07:55 +08:00
matevip
c1fd39a1e2 fix(agent/plan): 分流器证据闸门防止复杂任务不执行就停止(v2 剥离 memory-context) 2026-06-09 17:55:26 +08:00
倪程伟
2e1ef6b4b1 feat(agent): thread runtime model/provider into per-turn context injection 2026-06-08 20:53:39 +08:00
matevip
3c87efdb15 feat(chat): 购物推荐结果渲染为可点击商品卡片
- 聊天 markdown 渲染器解析 product-cards 围栏为卡片网格(图片/价格/平台/去购买按钮),整卡可点跳购买页
- DOMPurify afterSanitizeAttributes 钩子补回被 ALLOWED_URI_REGEXP 剥掉的 target/referrerpolicy,确保防盗链图片加载与新标签打开
- 后端在购物推荐工具结果尾部追加卡片渲染指令,保证模型稳定输出卡片而非表格
- 技能:购物意图优先调用参考价工具并约定 product-cards 输出格式
2026-06-08 15:11:56 +08:00
matevip
d4ea75a806 fix(approval): deny approval-required tools in non-interactive runs 2026-05-29 18:03:13 +08:00
matevip
07eb625d11 fix(agent): collapse SystemMessages at egress to fix LM Studio 400 (#218)
Some OpenAI-compatible providers (LM Studio's built-in server, certain
strict-mode vLLM / SGLang deployments) reject 400 "System message must
be at the beginning" when SystemMessages appear after user / assistant
/ tool messages. The reasoning loop currently emits four SystemMessage
segments — main prompt at index 0, skill catalog inserted at index 1,
progress-ledger snapshot and stale-reminder appended at the end of
nonHistoryPrefix after the runtime-context UserMessage. The latter two
violate the strict shape, so conversations on LM Studio 400 on the
first turn (reported in #218).

Add MessageNormalizer: collects every SystemMessage in the outbound
prompt regardless of position, joins their text with a blank-line
separator, and emits a single SystemMessage at index 0. Non-system
messages keep their relative order, so AssistantMessage(tool_calls) ↔
ToolResponseMessage adjacency is preserved verbatim (required by strict
pair validators).

Wire it into doStreamCall as the first pre-egress step so every node
(reasoning, step-execution, summarizing, plan-generation, limit-exceeded)
inherits the fix without per-node changes, and any future node that
emits multiple SystemMessages stays compliant.

The transformation is semantically equivalent on permissive providers
(OpenAI, DashScope, Ollama, DeepSeek, Kimi, Doubao, GLM) — the merged
token sequence matches what they would have seen across N SystemMessages
— and safe on non-OpenAI protocols (Anthropic, Vertex / Gemini), whose
adapters already extract SystemMessages into a top-level system field
and receive an identical payload.

Kill switch: -Dmateclaw.llm.message-normalizer.enabled=false reverts to
the prior behavior for emergency rollback.

Tests: 11 unit tests on MessageNormalizer cover empty / no-system /
canonical / mid-list / tail / blanks / tool-pair preservation / Prompt
option-reference preservation / kill switch. 1 wiring test pins the
call site in doStreamCall. Full vip.mate.agent.** suite (504 tests)
stays green.

Closes #218.
2026-05-25 17:57:03 +08:00
matevip
c857d6dd45 fix(agent): retry empty LLM completion before treating it as final answer 2026-05-24 22:57:19 +08:00
matevip
a9c2d45790 Harden goal approval and workspace flows 2026-05-23 22:55:16 +08:00
matevip
cef1730e6e feat(tool,skill,ui): progressive tool/skill disclosure (load_skill + enable_tool + tier UI) 2026-05-23 09:07:45 +08:00
matevip
89f8413db8 test(agent): sync LaneDPerformanceFixesTest with MAX_RETRIES bump 2026-05-21 14:43:52 +08:00
matevip
ce74a0ae48 feat(goal): graph topology + evaluation node wired into ReAct + Plan-Execute 2026-05-21 14:43:13 +08:00
matevip
3554da8dbc sync: inject sender context into agent prompt + Feishu DONE ack hook 2026-05-20 11:51:58 +08:00
matevip
c1b878b7e1 fix(agent): fail over to backup providers when the primary is rate-limited 2026-05-19 20:06:21 +08:00
matevip
0ff8da0caa fix(llm): stop MODEL_NOT_FOUND from evicting the whole provider (#150) 2026-05-18 16:27:20 +08:00
matevip
419ee57cc8 feat(agent): structured compaction on prompt-too-long preserves prefix 2026-05-16 14:51:14 +08:00
matevip
bf3f4af913 fix(agent): structured tool-call replay + final-answer-only content (#120) 2026-05-14 07:46:30 +08:00
matevip
51b5eceb7d feat(context): preserve spill markers across compaction phases, persist enriched boundary, broadcast compact_status SSE (#110) 2026-05-13 08:50:28 +08:00
matevip
86a6829102 feat(executor): retention sweep and per-conversation purge for tool-result spill files 2026-05-13 08:50:22 +08:00
matevip
8f49b0ec92 fix(executor): spill raw tool result before falling back to inline truncate 2026-05-13 08:49:56 +08:00
matevip
0ffc224623 chore(test): backfill mateclaw-server/src/test/ that earlier PRIVATE_ITEMS pattern accidentally excluded
The PRIVATE_ITEMS list contained the bare 'test' entry, which rsync
interprets as 'any directory named test at any depth' — so it caught
the root-level /test/ scratch directory (intended) AND every src/test/
under each module (not intended).

Pattern is already anchored to /test (root-only). This commit rsyncs
the accumulated src/test/ tree forward so opensource has the unit tests
that have been written / updated against existing src/main/ code since
the pattern regression. Going forward each per-commit sync will carry
src/test/ files along with the main change.
2026-05-12 17:38:08 +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
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
7f88a6ddd4 release: v1.1.0 2026-04-17 19:41:03 +08:00
matevip
99befe0d25 fix(agent): prevent ReAct degenerate repetition and harden loop termination 2026-04-05 22:34:03 +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