Commit Graph

334 Commits

Author SHA1 Message Date
matevip
e6f8da9606 fix(agent): recover tool-call follow-up on interleaved-thinking models (#256) 2026-06-12 11:07:55 +08:00
matevip
aaf06b262c feat(agent): retain image context across turns for follow-ups (#303) 2026-06-11 13:53:56 +08:00
matevip
83615593cd fix(tool/guard): harden workspace filesystem sandbox (#313)
- Fail closed to a global fallback sandbox root when a conversation has no
  per-workspace base path, instead of leaving file/shell tools unconstrained
- Refuse shell commands that delete the workspace root directory itself
- Block workspace-boundary escapes at the policy layer before the approval
  prompt, not only at execution time
- Approval bar now shows the actual command / target path being approved
2026-06-10 17:17:07 +08:00
matevip
c1fd39a1e2 fix(agent/plan): 分流器证据闸门防止复杂任务不执行就停止(v2 剥离 memory-context) 2026-06-09 17:55:26 +08:00
matevip
64b8e167d5 fix(mcp): refresh agent cache on MCP connection change + non-blocking connect (#289)
Closes #289 — after an MCP server (re)connects, chat queries kept replying
"from memory" instead of calling MCP tools.

Root cause: agents snapshot their tool set at build time and are cached in
AgentService.agentInstances, but MCP server lifecycle changes never
invalidated that cache (unlike model-config / tool-guard changes which do).
A stale, tool-less agent graph survived until process restart.

Changes:
- Add McpServerChangedEvent; McpServerService publishes it on connect /
  disconnect / reconnect / delete / (re)connect-failure / batch refresh /
  startup init. AgentService listens and calls refreshAllAgents(), so the
  next turn rebuilds against the live MCP tool set. Also closes the boot
  race where the web server accepts requests before the @Order(200) MCP
  init runner finishes.
- Make create/update/toggle connect asynchronously on a dedicated pool
  ("mcp-connect") so a slow/unreachable server can no longer freeze the
  admin request; status returns immediately as "connecting".
- UI: render the new "connecting" status (pulsing amber dot), show a
  friendly "connecting in background" toast, and poll until the status
  settles (window widened to ~40s to outlast the default connect timeout).
- UI: MCP config modal no longer closes on outside/backdrop click — only
  the × and Cancel buttons close it, so an accidental click can't discard
  unsaved config.

Verified E2E: ckjia-shopping (参考价) MCP server connected at runtime with
no backend restart; the cached 通用助手 agent immediately enabled and called
ckjia_shopping_recommend, returning real product cards.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 14:14:08 +08:00
matevip
94cf812207 feat(tool): add execute_code for running agent-authored code (#257)
Add an execute_code built-in tool that runs python/bash/node code the agent
writes on the fly, so a documentation-only skill (a SKILL.md with no bundled
scripts) can be acted on. Scoped runs inject the skill's secrets and run in
the skill directory; otherwise a private scratch directory is used. Host
secret env vars are scrubbed from the subprocess. execute_code is an
agent-wide capability, registered in the tool catalog (V143), and screened
by the tool guard with a dedicated set of destructive-pattern rules.

Tests cover python/bash/node execution, scratch-dir fallback, env scrubbing,
argument decoding, and guard gating.
2026-06-09 08:05:50 +08:00
倪程伟
cd3ae0c001 feat(agent): append static About You identity block to system prompt 2026-06-08 20:53:39 +08:00
倪程伟
2e1ef6b4b1 feat(agent): thread runtime model/provider into per-turn context injection 2026-06-08 20:53:39 +08:00
倪程伟
bd1ceace74 feat(agent): render runtime model identity line in RuntimeContextInjector
Add 5-arg buildContextMessage overload that emits [system-context] Model:
for every origin (web/cron/IM). Legacy 3/4-arg overloads delegate to the
new one with null model args, keeping their output byte-identical.

Also fix pre-existing FeishuMentionTest compile error caused by removed
mentionMatchesAnyAlias/collectMentionIdentifiers methods.
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
5ebdccb1f6 feat(agent): scope agent knowledge base access to a bound subset (#261) 2026-06-07 22:41:14 +08:00
matevip
46f3d425e0 feat(agent): add kill-switch for final-answer Markdown normalization
Gate MarkdownNormalizer behind mate.agent.markdown-normalize-enabled (default
true) so operators can disable the rewrite verbatim if a normalization edge
case ever mangles a legitimate answer.
2026-06-07 19:15:14 +08:00
倪程伟
398d7a2d80
feat(agent): deterministic Markdown normalization for final answers (#275)
LLMs routinely emit malformed Markdown (missing heading spaces, glued `---`, unaligned table pipes) that prompt rules cannot reliably prevent. Add a zero-token, regex-only MarkdownNormalizer applied on the FinalAnswerNode convergence path before persistence / channel delivery. It is code-fence aware, idempotent, and conservative (em-dash `---`, `#5`-style refs, stray prose pipes are left untouched). RETURN_DIRECT verbatim output and approval-wait paths return earlier and are unaffected.

Closes #274
2026-06-07 19:10:53 +08:00
matevip
7894a50067 fix(tool): emit absolute download URLs for streaming-generated files (#164)
Streaming chat ran render tools on an async thread with no bound request,
so download links lost their host and arrived without a domain. Resolve the
host on the request thread and carry it through ChatOrigin/ToolContext;
falls back to a configurable public-base-url, then a relative path.
2026-06-07 15:03:48 +08:00
matevip
76c6504527 fix(llm): preferred provider now drives primary model selection — per-provider model resolution + unconfigured-provider skip 2026-06-04 07:36:31 +08:00
倪程伟
5746bcf8cc
feat(agent): 偏好提供商作为主模型选择依据 (#223)
偏好提供商从「仅 capability 触发」改为两轮筛选,使 Agent 偏好提供商能决定主模型选择;并在 Agent 显式配置 modelName 时优先 honour,不被偏好提供商覆盖。

Closes #222
2026-06-03 23:33:14 +08:00
matevip
c5ccce8ebc feat(goal): deterministic completion + remaining-criteria followup + auto-followup gates 2026-06-03 21:19:26 +08:00
matevip
0fc8579a3e feat(goal): persist checklist end-to-end + GoalResponse wire DTO 2026-06-03 21:19:26 +08:00
matevip
9f02a0a221 fix(agent): unbreak DashScope tool calls, sharpen error class, rebalance plan triage (refs #246) 2026-06-03 08:38:48 +08:00
matevip
48611a6f4d feat(memory): per-owner memory isolation with owner_key + visibility scope (#235) 2026-06-02 17:04:06 +08:00
matevip
7e9f2ee54c fix(memory): prefer recalled personal memory over knowledge base for user/project questions 2026-05-29 18:03:13 +08:00
matevip
d4ea75a806 fix(approval): deny approval-required tools in non-interactive runs 2026-05-29 18:03:13 +08:00
matevip
ceb4da642b fix(memory): rebuild agent on workspace file change so memory edits apply 2026-05-29 16:05:21 +08:00
matevip
237f649e7e fix(tool-guard): trust shared skill root outside the workspace boundary 2026-05-29 09:59:19 +08:00
lichuan
bd02734d61 feat(agent): add knowledge base binding tab to agent editor (#237)
Agents now have a per-agent primary wiki KB stored on
mate_agent.primary_kb_id. KBs remain workspace-shared — selecting one in
the agent editor only chooses the default wiki target for that agent, it
does not change the KB's ownership or visibility.

Backend
- AgentEntity: add primary_kb_id field (FieldStrategy.ALWAYS so the UI
  can clear it back to "no primary")
- AgentController#update: switch body to Map<String, Object> so we can
  tell "field missing" apart from "explicit null" via containsKey, then
  convertValue back to AgentEntity
- WikiKnowledgeBaseService:
  - new resolvePrimaryKb(agentId): prefers agent.primary_kb_id when it
    points to a workspace-visible KB; falls back to legacy
    kb.agent_id marker, then to most-recently-updated workspace KB
  - listByAgentId now returns the full workspace set (KBs are
    workspace-shared under the new model)
  - update(id, name, description) no longer touches agent_id
- WikiController: new GET /knowledge-bases/bindable for the UI picker;
  PUT /knowledge-bases/{id} no longer reads agentId
- WikiKnowledgeBaseEntity: add FieldStrategy.ALWAYS on embeddingModelId
  and configContent so explicit nulls actually unbind/clear instead of
  being silently skipped by MyBatis-Plus's NOT_NULL default
- Migrations V129 (H2 + MySQL): add primary_kb_id column + index, backfill
  from legacy kb.agent_id, MySQL uses INFORMATION_SCHEMA guard +
  PREPARE/EXECUTE for idempotency
- WikiKnowledgeBaseServiceTest: 13 cases, all passing

Frontend
- Agents.vue: new "Knowledge Base" tab, radio-select bindable KBs
- API: listBindableKBs() + Agent.primaryKbId typed string | number | null
- IDs handled as strings throughout (Snowflake-safe)
- i18n keys for the new tab in zh-CN and en-US
2026-05-29 06:00:52 +08:00
matevip
b7e923fac4 feat(approval): grant-based auto-approve with safety floor and resolution log 2026-05-27 14:07:39 +08:00
matevip
c8b25e1bfb fix(agent): deny skill-discovery tools when skillsDisabled (#184 follow-up) 2026-05-26 22:16:51 +08:00
matevip
0ac325a337 feat(agent): explicit "no skills / no tools" opt-out flags (#184) 2026-05-26 22:07:12 +08:00
matevip
3ae4498f38 fix(channel,agent,chat): unify channel binding / conversation agent / model pin state sources 2026-05-26 09:40:49 +08:00
matevip
7f5652b2f0 fix(agent): replace per-loop head/tail trim with anchored token-budget budgeter 2026-05-26 07:34:25 +08:00
matevip
a01f0354eb feat(conversation): introduce ChatResult to carry token usage through sync chat paths 2026-05-25 21:15:53 +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
a37074a9a6 fix(tool): close three sandbox follow-up gaps surfaced by review
1. Relative parent traversal in shell commands (HIGH)

   validateShellCommand only scanned absolute path tokens, so commands
   like `cat ../mateclaw/CLAUDE.md`, `cd .. && cat foo`, or
   `ln -sf ../bar breakout` had no absolute path to trip the check.
   From a workspace cwd that's a real escape — `..` segments resolve
   against the JVM cwd at file-tool time and reach anywhere the user
   can read.

   Add a second pass: any token containing `..` as a path segment is
   resolved against the workspace root via root.resolve(token).
   normalize(); reject when the result falls outside. In-workspace
   traversal like `subdir/../sibling` normalizes back inside and
   passes. Identifiers without slashes (e.g. version strings with
   `1.2..3`) are not treated as paths.

2. Shell validation and process working directory used different
   context sources (MEDIUM)

   execute_shell_command validated with the explicit ToolContext, but
   buildShellProcess called WorkspacePathGuard.getWorkingDirectory()
   (no-arg), which only sees the ThreadLocal fallback. Today the
   ToolExecutionExecutor sets both so the discrepancy is latent, but
   a future direct Spring AI invocation passing only ToolContext would
   validate against one basePath and exec against another. Thread ctx
   through buildShellProcess and call getWorkingDirectory(ctx) so
   validation and execution agree on a single source of truth.

3. Absolute agent override could disable workspace scoping (MEDIUM)

   resolveAgentBasePath accepted an absolute override verbatim, even
   when it pointed outside the workspace root. An admin (or any
   account with agent-edit permission) could set workspaceBasePath="/"
   or another team's repo and bypass workspace boundaries entirely.

   When a workspace has its own basePath, require absolute overrides
   to sit underneath it. The caller in build() catches the rejection,
   logs WARN, and falls back to the workspace basePath so chat stays
   available rather than crashing agent construction. When the
   workspace has no basePath there's no boundary to enforce, so legacy
   behavior is preserved.

Test coverage: WorkspacePathGuardShellTest grows from 17 to 23 (six
new cases for `cd ..`, relative parent traversal, relative symlink
escape, deeper traversal, in-workspace normalization, and the
identifier false-positive guard). AgentGraphBuilderBasePathResolutionTest
grows from 7 to 10 (three new cases for in-workspace absolute,
outside-workspace absolute rejection, and no-workspace legacy
behavior). All 45 sandbox-area tests pass with no regressions.
2026-05-25 17:55:56 +08:00
matevip
9e9a96f674 fix(agent): resolve relative workspaceBasePath under workspace root 2026-05-25 15:58:05 +08:00
倪程伟
cbdd70379b
feat(agent): optional agent-level workspace basePath override (#212)
* feat(agent): optional agent-level workspace basePath override

Add workspaceBasePath field to AgentEntity that optionally overrides
the workspace-level basePath. When set, the agent uses its own directory;
when null, it inherits the workspace's basePath (existing behavior).

- AgentEntity: new workspaceBasePath field with ALWAYS update strategy
- AgentGraphBuilder: agent-level override takes priority over workspace
- Flyway migration V121 for H2 and MySQL
- UI: form input in basic tab with i18n (zh-CN, en-US)

* fix(agent): rename migration V121→V125 to avoid Flyway conflict with upstream

Upstream already has V121__tool_disclosure_tier.sql. Rename our
migration to V125 (next available after V124).

* fix(agent): make MySQL V125 migration idempotent

Use INFORMATION_SCHEMA check before ADD COLUMN to avoid
"Duplicate column name" error on re-deploy.
2026-05-25 15:42:01 +08:00
matevip
7f45b95432 feat(agent): include progress-ledger snapshot in limit-exceeded wrap-up 2026-05-24 23:00:49 +08:00
matevip
59d090ebb5 chore(agent): raise default max_iterations 100 -> 150 2026-05-24 23:00:42 +08:00
matevip
c36abf38b8 feat(agent): inject stale-ledger reminder when the model stops updating 2026-05-24 23:00:35 +08:00
matevip
8798668524 feat(agent): require ledger discipline in system prompt for multi-step tasks 2026-05-24 23:00:30 +08:00
matevip
7736f6b0ab fix(agent): serialise progress-ledger upsert per conversation 2026-05-24 23:00:17 +08:00
matevip
e953f8be5a feat(agent): age-based compaction of older tool-response bodies 2026-05-24 23:00:11 +08:00
matevip
05289e6bdb feat(agent): per-conversation progress ledger to survive context trims 2026-05-24 23:00: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
倪程伟
f16021690f
feat(tool): add send_file tool for sending existing server files as IM attachments (#199)
* feat(tool): add send_file tool for sending existing server files as IM attachments

Adds a new built-in tool that reads a file from the server and stashes it
in GeneratedFileCache so the channel adapter (Feishu, DingTalk, etc.)
automatically sends it as a native attachment. This fills the gap where
agents had no way to send existing server files to users — ReadFileTool
only reads text, and render tools only generate new files.

- New SendFileTool with path validation, MIME detection, 20MB limit
- Added "send_file" to tool allowlist in AgentBindingService
- Added i18n error messages (zh-CN + en-US)

* fix(tool): send_file returns URL in scrubber-detectable format

The previous JSON return format caused the LLM to reply with just
"status: sent" without echoing the /api/v1/files/generated/{id} URL.
GeneratedFileScrubber only scans the LLM's final text output, so the
file was never delivered as a native attachment.

Changed to match GeneratedFileLink's format: returns a markdown link
with explicit instructions for the LLM to echo the URL verbatim.
2026-05-22 16:27:53 +08:00
matevip
5f571e86a2 feat(agent,ui): multi-level subagent delegation tree 2026-05-22 13:44:01 +08:00
matevip
7d7ea99747 fix(goal): emit goal_evaluated on every GoalEvaluationNode skip path 2026-05-21 22:27:17 +08:00
matevip
9e93c52d9a fix(goal): real evaluator, retry refactor, hardened node + extra edges 2026-05-21 22:27:00 +08:00
matevip
c34e8290ac feat(goal,ui): inline set-goal prompt, terminal system-line, sidebar dot 2026-05-21 22:26:40 +08:00
倪程伟
7861f603eb
fix(llm): MiMo thinking 模式 reasoning_content 多轮对话兼容修复 (#189)
MiMo V2 系列在 thinking 模式下,assistant 消息携带 tool_calls 时必须同时包含 reasoning_content,否则提供方返回 400。

- ModelFamily 新增 MIMO_THINKING 族,detect() 添加 mimo* 匹配
- FallbackPolicy 新增 XIAOMI_MIMO(patchCrossTurn=true, patchNonToolCall=true)
- 新增 ReasoningContentCache,按 tool_call_ids 回放真实推理内容
- 缓存作用范围:所有 patchCrossTurn=true 的 thinking provider(MiMo + DeepSeek)
- NodeStreamingChatHelper 流式响应完成后写入缓存

Closes #188
2026-05-21 17:15:35 +08:00
matevip
a910004b3b feat(agent): digital-employee builder skill to auto-create agents and chain them into a workflow (#165) 2026-05-21 16:26:04 +08:00
matevip
2b6a4c64c9 fix(agent): add data-fidelity rules to summarizer prompts + fix fallback cut (#187) 2026-05-21 15:13:15 +08:00
matevip
03e68d3c74 fix(agent): structure-aware truncation to stop mid-JSON cuts inducing hallucination (#187) 2026-05-21 15:13:07 +08:00
matevip
43bbe26ff9 fix(goal,agent): expose goal management tools to every agent by default 2026-05-21 14:44:06 +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
6a4318c268 fix(channel): IM conversations respect per-conversation model selection (#183) 2026-05-20 17:49:03 +08:00
倪程伟
d7378273b2
fix(llm): classify "network connection error" as retryable SERVER_ERROR (#179)
Some providers (notably SiliconFlow) return "network connection error" in the response body when their backend is overloaded or the upstream model connection is disrupted. classifyError() had no pattern for this string, so it fell through to UNKNOWN (non-retryable), surfacing the raw error to the user on the first failure instead of running the exponential-backoff recovery. Adds the pattern to the SERVER_ERROR classifier and a friendly message mapping in extractUserFriendlyError(); bumps MAX_RETRIES from 5 to 10 so sustained wiki batch load can ride out provider flaps without surfacing an error to the channel user.

Closes #178
2026-05-20 16:49:54 +08:00
matevip
3554da8dbc sync: inject sender context into agent prompt + Feishu DONE ack hook 2026-05-20 11:51:58 +08:00
matevip
db16ff02a5 chore: drop external project name references from code comments 2026-05-20 08:09:43 +08:00
matevip
651d53050e fix(workspace): return 403 instead of 500 for cross-workspace access 2026-05-19 20:06:50 +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
95ce1bae24 docs(agent): describe binding-service helpers functionally 2026-05-19 16:53:24 +08:00
matevip
b0d9bde664 fix(agent): clamp DashScope max_tokens to the provider's 8192 ceiling 2026-05-19 16:53:06 +08:00
matevip
4cd21056a0 feat(skill): automatic lifecycle archival for idle skills 2026-05-19 09:55:59 +08:00
matevip
d53d66abe3 feat(chat): per-conversation model selection (#150) 2026-05-18 16:27:27 +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
75107ac815 feat(llm): native Gemini chat builder, Nano Banana image gen, xAI/Grok provider 2026-05-18 10:00:55 +08:00
matevip
a88edbdd07 refactor(llm): decouple model construction from the agent graph layer (#147) 2026-05-18 07:47:49 +08:00
matevip
18df97a8e6 fix(agent): scope MCP tools to explicit per-agent selection (#117) 2026-05-17 09:24:56 +08:00
matevip
e647f1d6cf fix(cron): isolate scheduled-job runs from shared conversation (#142) 2026-05-17 07:58:10 +08:00
matevip
b4add8b139 fix(agent): keep wiki tools available to skill-bound agents (#143) 2026-05-16 23:00:45 +08:00
matevip
69fb03968b fix(agent): expose memory-search and async-delegation tools to skill-bound agents 2026-05-16 14:51:47 +08:00
matevip
419ee57cc8 feat(agent): structured compaction on prompt-too-long preserves prefix 2026-05-16 14:51:14 +08:00
matevip
82594878a0 refactor(ui): fold the live runtime view into the Employees page 2026-05-16 14:50:58 +08:00
matevip
3b9b4d79d5 feat(skill): scope skill catalog and runtime by workspace (#135) 2026-05-15 20:01:25 +08:00
matevip
3ba22b99f3 feat(workspace,system): add @RequireGlobalAdmin and gate admin-only controllers 2026-05-15 10:18:10 +08:00
matevip
65dd4c1f49 fix(skill): cascade-delete agent-skill bindings on skill removal (#127) 2026-05-15 10:17:41 +08:00
matevip
f0f97232a8 fix(agent): pre-tool no-claim system rule + correct write/edit approval docs 2026-05-14 14:59:32 +08:00
matevip
bf3f4af913 fix(agent): structured tool-call replay + final-answer-only content (#120) 2026-05-14 07:46:30 +08:00
matevip
aed559d1cf docs(agent): update head-guard call-site comment to match the order-sensitive forward scan 2026-05-13 09:05:17 +08:00
matevip
1ea90c25cb fix(agent): make head-orphan repair order-sensitive — a later same-id assistant does not redeem an earlier orphan 2026-05-13 09:03:16 +08:00
matevip
bff57924d6 fix(agent): repair head-side orphan tool responses on the pagination cut 2026-05-13 09:03:10 +08:00
matevip
af56763156 fix(context): keep the latest summary even when its row sits outside the recent window; include summaryId in compact_status 2026-05-13 08:50:42 +08:00
matevip
faf7f98358 feat(context): consume compact_status on the frontend; load history from latest boundary; backfill summaryId 2026-05-13 08:50:35 +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
7015d1513c feat(context): anchor the first user message after compaction so long tasks remember the original goal 2026-05-13 08:50:15 +08:00
matevip
37894a6978 fix(context): never compact across a split tool_call ↔ tool_response pair 2026-05-13 08:50:08 +08:00
matevip
ae3d27922d fix(context): preserve raw older tool results instead of rewriting them to a lossy summary 2026-05-13 08:50:02 +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
cc5d7d5ad0 fix(agent): auto-include enabled MCP tools in effective allowlist (#108) 2026-05-12 21:24:36 +08:00
matevip
aded30bbb1 feat(agent): auto-redirect skill-as-tool calls to readSkillFile 2026-05-12 17:20:19 +08:00
matevip
691d2b867b fix(agent): hide disabled agents from the chat picker and reject chat calls against them (#105) 2026-05-12 14:53:14 +08:00
matevip
8d01efbbb1 fix(agent): sanitize streamed tool-call arguments to valid JSON 2026-05-12 11:25:58 +08:00
matevip
071ccff9bf feat(tool): add render_html_image to deliver HTML artifacts as native IM images 2026-05-12 07:11:35 +08:00
matevip
b74176ef66 refactor(agent): tighten template pre-binding contract and prune product-assistant skills 2026-05-12 07:11:29 +08:00
matevip
201e510b17 feat(agent): pre-bind skills and tools when applying an agent template 2026-05-12 07:11:19 +08:00
matevip
08fcab12d1 fix(deploy): drop DASHSCOPE_API_KEY requirement so Docker boots with no LLM key configured (#99) 2026-05-11 20:44:31 +08:00
matevip
6d5fb595d3 fix(agent): harden digital-employee isolation across creation, skill binding and prompt catalog 2026-05-11 16:10:28 +08:00
matevip
9fd5843dda fix(llm): pin OpenAI-compatible HTTP client to HTTP/1.1 (#89) 2026-05-11 11:22:23 +08:00
matevip
f71f49bda1 fix(agent): dedup repetition output + mark INCOMPLETE instead of NORMAL 2026-05-10 19:16:16 +08:00
matevip
fb0ae2b6cc fix(agent): break out of self-arguing reasoning loops via content-repetition cap 2026-05-10 19:16:10 +08:00
matevip
f18626304e feat(agent,ui): recovery affordance card for non-transient LLM errors 2026-05-10 19:15:57 +08:00
matevip
00098fe5f1 feat(agent,channel): scrub fake generated-file URLs + paste-body hint for public-account articles 2026-05-10 19:15:44 +08:00
matevip
19a8c9e16c fix(agent): retry transient TLS / socket errors instead of failing the LLM call 2026-05-10 19:15:38 +08:00
matevip
c2aecf18ef feat(agent,llm): multimodal sidecar routing for unsupported attachments (#87) 2026-05-09 16:41:26 +08:00
matevip
231724d6a9 feat(agent,trigger): wire agent_lifecycle as a real event source 2026-05-08 15:07:18 +08:00
matevip
5cb82ed8f8 feat(agent,mcp): validate tool bindings on save and keep returnDirect raw-name config working 2026-05-07 08:17:53 +08:00
matevip
80cb3c84eb fix(agent): decouple framework recursion limit from per-agent max_iterations 2026-05-05 13:06:40 +08:00
matevip
2ab0bd000b fix(chat): suppress duplicate assistant row on force-recycle 2026-05-05 10:39:54 +08:00
matevip
2e0ff8f90f fix(agent): unblock multi-role parallel delegation + propagate force-stop 2026-05-04 20:54:38 +08:00
matevip
be1fc86836 feat(agents): pixelart icons, per-role colors, runtime identity merge, locale templates 2026-05-04 15:26:02 +08:00
matevip
2a8f6774e7 feat(agents): role/goal/backstory profile, employee-card tagline, 5 career templates 2026-05-04 15:25:53 +08:00
matevip
42d406ffc8 fix(agent): drop brittle output policing, add evidence-grounded long-task safeguards 2026-05-04 11:55:44 +08:00
matevip
3d50b9c132 feat(skill): catalog sort + usage stats 2026-05-04 11:55:35 +08:00
matevip
a2c96946e3 fix(repetition-detector): catch markdown-list duplication separated by transition paragraph 2026-05-03 17:15:33 +08:00
matevip
74edc09b2b feat(backstage): admin runtime console for live agent visibility + force-recycle 2026-05-03 17:15:17 +08:00
matevip
66f09a968a feat(chat-stream): streaming UX overhaul + multi-agent stability layer 2026-05-03 17:15:02 +08:00
matevip
062b5169fa fix(llm): drop bogus qwen3-plus seed and gate enable_search to supporting models (issue #53) 2026-05-02 19:40:03 +08:00
matevip
bc417d00ef chore: neutralize internal references in code comments and migrations 2026-05-02 15:42:10 +08:00
matevip
2246df9d09 fix(agent): allow PUT modelName=null to revert to global default 2026-05-02 15:42:04 +08:00
matevip
55f4ba1195 feat(llm): per-Model HTTP read-timeout override 2026-05-02 15:40:59 +08:00
matevip
311cad7ea6 feat(agent): cap tool_calls per single LLM response 2026-05-02 15:40:45 +08:00
matevip
92bd8e9b6e feat(agent): re-enable per-Agent model override 2026-05-02 15:40:24 +08:00
matevip
eaacb3a78f fix(agent): include tools schema in context-window budget 2026-05-02 15:40:03 +08:00
matevip
c2ebe51f63 fix(plan): per-step wall-clock timeout (default 10 min) 2026-05-02 15:39:57 +08:00
matevip
c4f2117a23 fix(agent): tolerate LLM-mangled tool names 2026-05-02 15:39:49 +08:00
matevip
3d0797787b fix(agent): align skill tool whitelist with real @Tool method names + restore globally-promised tools 2026-05-01 20:14:56 +08:00
matevip
888200e1cd feat(acp): visual env editor with per-endpoint key suggestions 2026-05-01 20:14:49 +08:00
matevip
5e6764ecf1 fix(skill): tool-gate whitelist + markdown link host normalization
When an agent had any skill bound, the runtime tool gate was silently
hiding @Tool beans that aren't declared in any skill manifest, even
though the global system prompts (SOUL.md / "Web Search Capability" /
"File Reading Guidelines") explicitly tell the LLM these tools are
available. Result: the model would call search / renderDocx / read_file
/ etc., hit "Tool not found", then either give up or fall back to
unhelpful behaviour (e.g. dumping markdown text instead of producing a
.docx download).

This commit:

- Adds universally-promised, agent-wide tools to SYSTEM_LEVEL_TOOLS so
  they bypass the manifest restriction: document/media generation
  (renderDocx*, image_generate, music_generate, video_generate),
  global capability tools the system prompt mentions (search,
  browser_use, read_file / write_file / edit_file /
  execute_shell_command, detect_file_type, extract_*_text,
  readMateClawDoc), skill discovery siblings (listSkillFiles,
  listAvailableSkills), and the delegate triplet (delegateToAgent,
  delegateParallel, listAvailableAgents).

- Fixes 5 entries in the prior whitelist whose names did not match
  any real @Tool bean and were therefore silently dead:
    read_workspace_file   -> read_workspace_memory_file
    write_workspace_file  -> write_workspace_memory_file
    list_workspace_files  -> list_workspace_memory_files
    delegate_agent        -> delegateToAgent
    datetime              -> getCurrentDate / getCurrentDateTime / getCurrentTime
  Also adds the missing edit_workspace_memory_file.

- In the chat markdown renderer, strips any hallucinated
  https?://<host> prefix from /api/v1/files/generated/<id> download
  links before building the <a href>. Multiple LLMs have been
  observed prepending bogus hosts when echoing tool-returned download
  URLs back to the user, breaking the click. One-line defensive
  normalization independent of which model is in use.

Verified end-to-end on a previously-broken agent: search / browser_use
/ execute_shell_command / renderDocx all dispatch correctly now and
the final markdown link is a clean same-origin path. 36 whitelist
entries cross-checked against real @Tool method names.
AgentBindingServiceTest green.
2026-05-01 12:24:25 +08:00
matevip
b759eb8719 feat(skill): MCP-Skill bridge + system tool dedup + Lessons i18n 2026-05-01 09:50:45 +08:00
matevip
7ca568c69b fix(skill): knowledge wrappers + provider routing + feature gates 2026-05-01 09:49:37 +08:00
matevip
d927521d51 feat(skill): install/uninstall split + Requirements API + provider router 2026-05-01 09:48:59 +08:00
matevip
688b37b652 feat(skill): features matrix + effective-tool expansion 2026-05-01 09:48:39 +08:00
matevip
a0c6ed5a86 fix(agent): vision gate + history media drop + friendlier skip notice 2026-04-30 17:26:13 +08:00
matevip
47bdb97a3a fix(agent): per-model multimodal capability resolution (issue #44) 2026-04-30 17:25:51 +08:00
matevip
101aa3209e fix(skill): stop the LLM from calling skill names as tools (issue #46)
When a user-installed skill (e.g. RedisOps) was bound to an agent, the
model frequently called the skill name directly as a tool, hit
"Tool not found: RedisOps", and either gave up or fell back to shell
guessing. Two compounding causes:

1. The system prompt block injected by SkillRuntimeService listed each
   skill as `- **RedisOps** — desc`, which is the same format used for
   tool catalogs and primed the model to call the names directly. The
   "how to use" instructions referenced `read_skill_file` /
   `run_skill_script` — names that don't exist in the tool registry,
   so even a compliant LLM couldn't follow them.

2. ToolExecutionExecutor's `callback == null` branches returned a bare
   "Tool not found: <name>" string. The model had no recovery signal
   and no hint that the name it called was actually a skill.

Fix is two-layered:

- Prompt rewrite (SkillRuntimeService.buildSkillPromptEnhancement): lead
  with an explicit warning that skills are NOT directly callable, use the
  correct camelCase tool names (readSkillFile / runSkillScript), include
  a concrete worked example anchored to the first enabled skill, and
  render the listing as a markdown table so it stops looking like a
  callable tool list. listAvailableSkills tool description and output
  follow the same pattern.

- Runtime safety net (ToolExecutionExecutor): when toolCallbackMap.get
  misses, check if the requested name (case-insensitive) matches an
  active skill. If so, return a precise hint telling the LLM the right
  invocation pattern instead of the bare error. Wired through both the
  main execute path and the pre-approved replay path. SkillRuntimeService
  is attached via a setter from AgentGraphBuilder so the executor's many
  legacy constructors stay untouched, and it's nullable so isolated
  tests still work.

Adds 5 unit tests covering: skill match -> hint, case-insensitive match,
no-match -> bare error, no SkillRuntimeService wired -> bare error,
pre-approved replay path -> hint.

Reported and reproduced by @pipima9950-glitch in issue #46.
2026-04-30 16:36:30 +08:00
matevip
977e181949 feat(cron): unify output, add reminder task type, in-flight progress UI
Three layers landed together because they share the same routing /
lifecycle plumbing:

1. Cron output unification
   - New CronConversationResolver routes web-origin jobs to the per-workspace
     tasks_<wsId> conversation; IM-bound jobs go to the channel session
     conversation when one exists (matched by senderId then targetId);
     legacy cron_<id> remains as the fallback.
   - CronJobLifecycleService inserts a system-role header divider when a
     run starts so users browsing the unified tasks_<wsId> view can tell
     which job started a run. BaseAgent.sanitizeForLlm filters these
     headers so they never reach the model.
   - WorkspaceService seeds tasks_<wsId> on workspace creation; V65
     migration backfills existing workspaces.
   - DeliveryConfig gains a userId field so IM session lookup can match
     by senderId (replyToken-based targetId is not stable across runs).
   - ConversationVO recognizes tasks_/cron_ underscore prefix as cron
     source. MessageList renders the system header as a labeled divider.
   - ChatConsole pins tasks_* conversations and tracks per-conversation
     read state so new cron output gets a visible unread dot.

2. Reminder task type
   - New task_type='reminder' in CronJobEntity + service validation.
   - CronJobRunner short-circuits 'reminder' jobs: hands trigger_message
     to finishRunAndPublish verbatim, no LLM call. Fixes a regression
     where reminders were rephrased into echoed wrappers.
   - New create_reminder tool alongside create_cron_job, with descriptions
     tightened so the model picks the right one (verbatim push vs LLM
     query that needs computation).
   - CronJobs.vue gets a third radio option + dedicated reminder field.

3. In-flight progress placeholder
   - Cron uses non-streaming chat()/execute(); tool-heavy ReAct loops
     can run 1-5 minutes between start and finish with no visible
     state, looking hung.
   - New GET /api/v1/cron-jobs/active-runs returns runs in status=running
     for a conversation. ChatConsole polls it on the existing 4s tick
     (and on conversation switch) and shows a spinner bar with elapsed
     time. When run count drops to zero, it refetches messages so the
     assistant bubble appears within ~1s of finish.
2026-04-30 15:01:24 +08:00
matevip
6390abdecc fix(llm): apply read timeout to streaming chat WebClient (openai-compat + anthropic) 2026-04-30 08:54:46 +08:00
matevip
1864801c90 fix(tool): browser_use Windows compat + stop LLM treating it as web search 2026-04-30 08:54:15 +08:00
matevip
0aee2a1ca5 fix(agent): raise per-step tool-call ceiling to 100, soften limit-exceeded prompt 2026-04-30 07:00:42 +08:00
matevip
ca5387ed65 fix(agent): always write FINAL_SUMMARY in DirectAnswerNode 2026-04-30 07:00:24 +08:00
matevip
172784bc5f docs(readme): lead with team / IT-deployable positioning
Add a callout above the existing intro to make the wedge explicit:
multi-user workspaces, approval-gated sensitive actions, full audit trail,
production-grade health monitoring, per-channel error isolation.
One JAR on your own machine, zero data egress.
2026-04-29 14:10:12 +08:00
matevip
e759ad4a1b sync: settings UI polish, channel reliability fixes, DeepSeek cross-turn fix
- Settings → Models: inline API key, frosted drawer, dark-mode polish, provider icons, i18n sweep
- WeChat Work channel: rebuild HttpClient on reconnect, dedup failure signals, route auth_succeed errcode!=0 through failure handler
- Channel framework: per-adapter error isolation, QR auth SPI, health indicators
- Agent: patch cross-turn assistants for DeepSeek thinking-mode
- GitHub: bilingual issue templates with required fields
2026-04-29 11:22:47 +08:00
matevip
b4697f2806 fix(cron): post-deploy bug bundle — flakiness, scheduler, channel UI
User-reported field issues + a deeper code audit revealed multiple
overlapping bugs in the prior cron-channel delivery change. This fixes
all six.

#1 — Concurrency race on ToolExecutionExecutor (root cause of 'sometimes
   succeeds, sometimes fails' tool calls). The volatile instance fields
   currentRequesterId / currentWorkspaceBasePath / currentChatOrigin
   were shared by every conversation routed through the same per-agent
   executor; one user mid-build-loop while another's execute()
   overwrote the field would cross-contaminate the captured values into
   PreparedToolCall. Fix: kill the instance fields, thread
   origin/requester/workspace as method params straight into
   PreparedToolCall snapshot. Comment pins the rule so it cannot regress.

#2 — CHAT_ORIGIN missing from KeyStrategyFactory (latent timebomb,
   masked by spring-ai-alibaba-graph-core's non-filtering builder path).
   Without an addStrategy registration, multi-node state merges in long
   ReAct / Plan-Execute loops drop the key, ActionNode reads
   ChatOrigin.EMPTY, and the cron persists with channel_id=NULL. Also
   caught 4 more keys that were latently unregistered:
   WORKSPACE_BASE_PATH, STOP_REQUESTED, RETURN_DIRECT_TRIGGERED,
   DIRECT_TOOL_OUTPUTS. All five now registered in both ReAct and
   Plan-Execute factories.

#3 — CronJobs UI didn't surface channel binding. CronJobDTO carried
   channelId / deliveryConfig but the list page never rendered them.
   Added: (a) 'channel' column on list page, (b) channel + targetId
   rows in the detail modal, (c) backend batch-loads channel names via
   ChannelMapper.selectBatchIds so the column shows the human-readable
   name, (d) i18n keys (zh + en), (e) channelName field on TS CronJob
   type.

#4a — DingTalk targetId expiry. ChannelChatOriginFactory.resolveTargetId
   used to prefer ChannelMessage.replyToken which for DingTalk encodes
   a sessionWebhook URL that expires ~90 minutes after the inbound
   message. Cron persisted with that webhook then dies with 401/403 and
   marks NOT_DELIVERED forever. Fix: prefer the stable chatId, fall
   back to senderId — both work indefinitely via DingTalk's Robot API.

#4b — Scheduler pool exhaustion under long LLM. CronJobService's
   ThreadPoolTaskScheduler ran with poolSize=4 AND the LLM call lived
   on the scheduler thread. Four concurrent crons saturated the pool
   and the 5th silently missed its tick. Fix: keep scheduler tiny (it
   just fires triggers) and offload runAgent to a dedicated
   virtual-thread executor (cron-execute-* threads). LLM workload is
   I/O-bound — virtual threads scale to thousands at trivial cost.

#5 — Minor latent bugs:
   - AbstractCronResultDelivery.claimRun used .in(... 'NONE','PENDING',null),
     but SQL IN never matches NULL. Rewrote as IS NULL OR IN
     (NONE,PENDING) so legacy pre-V57 rows can still claim.
   - CronDeliveryListener.onCompletedRaw was an empty @EventListener
     with a wrong-headed comment about test fallbackExecution. Removed.
   - CronJobTool.resolveAgentId silently returned 1L when origin
     lacked an agentId — would silently bind to whatever agent #1
     happens to be. Replaced with explicit error so wiring bugs surface
     immediately instead of producing scheduled-but-never-runs crons.

State-key registration guard. New StateKeyRegistrationCoverageTest
scans MateClawStateKeys via reflection and parses
AgentGraphBuilder.java to extract every
.addStrategy(MateClawStateKeys.X, ...). Asserts every non-_NODE
constant appears in at least one factory. Caught the 4 unregistered
keys above on first run; will catch any future 'forgot to register'
regression.

Tests: 33 unit/arch tests + 27 regression in touched areas — all green.
Vue typecheck clean.

Refs: #25, #16
2026-04-28 21:45:07 +08:00
matevip
4011050ceb feat(cron): channel delivery via ChatOrigin + Spring AI ToolContext
Replaces the prior ThreadLocal context plumbing with explicit Spring AI
ToolContext threading carried by an immutable ChatOrigin value object,
so a cron created from inside WeChat (or any IM channel) delivers its
results back to the originating channel.

Architecture
- ChatOrigin / ChannelTarget value objects + per-entry-point factories
  (ChannelChatOriginFactory in vip.mate.channel, CronChatOriginFactory
  in vip.mate.cron — symmetric, no cyclic deps).
- LocaleAwareToolCallback now forwards call(String, ToolContext) and
  getToolMetadata so the decorator chain cannot silently drop the origin.
- AgentService 6-method overhaul + ChatOriginHolder bridge into
  StateGraph buildInitialState which writes CHAT_ORIGIN; ActionNode +
  StepExecutionNode forward it to ToolExecutionExecutor.
- ToolExecutionExecutor builds ToolContext per call; 8/8 tools migrated
  (CronJobTool, WorkspacePathGuard, Video/Image/Browser/ReadFile/Music,
  DelegateAgentTool with parent-origin inheritance).
- CronJobRunner + CronJobLifecycleService 3-segment REQUIRES_NEW model
  (T1 startRun / no-tx runAgent / T2 finishRunAndPublish); ArchUnit
  pins CronJobRunner as @Transactional-free.
- CronResultDelivery Strategy + AbstractCronResultDelivery Template
  with SQL CAS idempotency on mate_cron_job_run.delivery_status —
  replaces the prior process-local Caffeine TTL, cluster-safe.
- CronJobCompletedEvent + @Async @TransactionalEventListener(AFTER_COMMIT);
  cronDeliveryExecutor (core=2, max=4, queue=1000, AbortPolicy + audit).
- CronRunStaleCleanup @Scheduled(5min) sweeps PENDING-15min and
  status='running'-30min in one query each.
- CronJobRunner.wrapWithDeliveryGuard prepends a system note for
  channel-bound crons to suppress hallucinated 'install CLI to send
  WeChat' suggestions.
- ApprovalWorkflowService Memento: persist ChatOrigin snapshot on
  create, restore on replay so cross-restart approvals keep channel
  binding; ChannelMessageRouter + ChatController web-replay both prefer
  the Memento and fall back to fresh-build.
- ChannelManager.sendToChannel 4-arg DeliveryOptions overload;
  ChannelAdapter#proactiveSend default 4-arg pass-through; Slack
  overrides for thread_ts and Telegram overrides for message_thread_id.
- CronJobs UI: read-only 'last delivery' badge driven by
  CronJobMapper.selectListWithDeliveryStatus subquery.

Schema migrations V57/V58/V59 (V56 was already taken by an unrelated
provider migration — Flyway processes versions in order regardless of
gaps):
- V57: mate_cron_job_run delivery_status / target / error + composite
       index (delivery_status, started_at) covering the cleanup sweep.
- V58: mate_cron_job channel_id (indexed) + delivery_config TEXT (JSON
       via MyBatis Plus JacksonTypeHandler).
- V59: mate_tool_approval chat_origin TEXT (Memento).
All idempotent in both H2 (IF NOT EXISTS) and MySQL (INFORMATION_SCHEMA
guard + PREPARE).

ArchUnit guards (test scope, archunit-junit5 1.3.0):
- every concrete vip.mate.* ToolCallback must override
  call(String, ToolContext) — pins the decorator-forward fix.
- CronJobRunner must NOT carry @Transactional on the class or any
  method — pins the 3-segment lifecycle rule.

Tests: 32 new unit tests + 21 regression tests in touched areas, all
53 green:
- ChatOriginTest (6) — value-object invariants + JSON round-trip.
- LocaleAwareToolCallbackToolContextTest (2) — decorator forward.
- DeliveryConfigTest (4) — Jackson round-trip + forward-compat.
- ToolCallbackToolContextForwardArchTest (2) — both ArchUnit guards.
- CronJobRunnerDeliveryGuardTest (3) — channel-cron prefix injection.
- AbstractCronResultDeliveryTest (4) — claim CAS + concurrent CAS.
- ChannelCronResultDeliveryTest (6) — supports / doDeliver / errors.
- ApprovalReplayContinuityTest (5) — Memento round-trip + corrupt
  payload fallback + unknown-field tolerance.

Refs: #25, #16
2026-04-28 21:43:58 +08:00
matevip
69f065e212 fix(llm): support Volcano Ark base URLs and surface friendly errors
- Generalize the OpenAI-compatible chat/models path resolver so any
  baseUrl ending in /v{N} (Ark /v3, Zhipu /v4, ...) drops the duplicate
  /v1 prefix. Volcano Engine test-connection and chat were posting to
  /api/v3/v1/chat/completions and getting 404.
- Replace the six pre-seeded Doubao alias rows (doubao-1.5-*) with five
  valid Ark direct-call ids (doubao-seed-1-8-251228 etc.) and flip
  support_model_discovery=TRUE so users can refresh their account's
  actual catalog. Aliases were marketing names, not API names, so every
  call hit InvalidEndpointOrModel.NotFound.
- Translate Ark business errors into actionable Chinese hints: include
  the response body in the error chain, match ModelNotOpen and
  InvalidEndpointOrModel codes, extract the offending model id, and
  classify them as MODEL_NOT_FOUND so failover skips retries.
2026-04-28 19:26:58 +08:00
matevip
c0c642380a feat(llm): provider liveness model + honor requireApiKey on chat path
Phase 1 of the model-module refactor: combine pool / cooldown / probe-
completion signals into a single Liveness state surfaced through the
provider DTO, so the dropdown stops listing providers that are provably
unreachable. Zero schema change; one PR backend + frontend.

Backend
- Liveness enum with five mutually-exclusive states: LIVE, COOLDOWN,
  REMOVED, UNPROBED, UNCONFIGURED. Computed in ModelProviderService
  from AvailableProviderPool / ProviderHealthTracker / ProviderInitProbe
  snapshots batched once per listProviders() call.
- ProviderInitProbe.hasBeenProbed exposes a monotonic Set so the UI
  can distinguish 'still booting' from 'probed and removed' — without
  it the startup window flashes false REMOVED states.
- ProviderInfoDTO gains liveness + unavailableReason +
  cooldownRemainingMs + lastProbedAtMs. The legacy 'available' boolean
  stays but is now derived from liveness == LIVE so the chat fallback
  walker and the dropdown agree about what's usable.
- ProviderInitProbe injected into ModelProviderService via
  ObjectProvider to break the startup cycle (probe already depends on
  the service).

Frontend
- ProviderInfo type extended with liveness + the three detail fields.
- ModelSelector filters UNCONFIGURED + REMOVED out of the dropdown,
  shows COOLDOWN / UNPROBED with a status dot and dimmed rows that the
  user can still click to override.
- ProviderCard renders a five-state badge driven by liveness instead
  of the old configured + pool-entry combo. Reprobe button now keys
  off liveness in {REMOVED, COOLDOWN}.
- useProviders drops loadProviderPool / providerPool — pool data ships
  inline on each ProviderInfo, saves a round trip per page load and
  keeps a single source of truth.
- i18n: 8 new keys across zh-CN and en-US for liveness labels and the
  cooldown countdown tooltips.

Bonus fix (discovered during verification): AgentGraphBuilder.buildOpenAiApi
hard-required a usable API key on every OpenAI-compat provider, ignoring
the per-provider requireApiKey flag. That bug stranded keyless local
runtimes (LM Studio / MLX / llama.cpp) the moment a user actually
launched them; Ollama only worked by accident because its seed row
carries a placeholder string in api_key. keyRequired now honors
requireApiKey, and Spring AI's NoopApiKey is used when no key is needed
so the Authorization header is omitted entirely.

Test
- ModelProviderServiceLivenessTest covers all five Liveness states +
  the probe-bean-absent fallback branch.
- vip.mate.llm.** suite (118 tests) green; vue-tsc clean.
- End-to-end browser sanity: 27 raw providers reduce to 6 LIVE groups
  in the chat dropdown; LM Studio / MLX / llama.cpp render REMOVED red
  badges with reprobe buttons; cloud providers without keys show
  UNCONFIGURED.
2026-04-28 14:59:11 +08:00