Commit Graph

211 Commits

Author SHA1 Message Date
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