mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-13 19:23:42 +08:00
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.
This commit is contained in:
parent
696646f614
commit
cbdd70379b
@ -358,13 +358,16 @@ public class AgentGraphBuilder {
|
||||
agent.topP = runtimeModel.getTopP();
|
||||
agent.toolCallingEnabled = toolCallingEnabled;
|
||||
|
||||
// 查找工作区活动目录
|
||||
if (entity.getWorkspaceId() != null) {
|
||||
// Agent 级别覆盖优先,否则继承工作区
|
||||
if (entity.getWorkspaceBasePath() != null && !entity.getWorkspaceBasePath().isBlank()) {
|
||||
agent.workspaceBasePath = entity.getWorkspaceBasePath();
|
||||
log.info("Agent {} using agent-level basePath: {}", entity.getName(), agent.workspaceBasePath);
|
||||
} else if (entity.getWorkspaceId() != null) {
|
||||
try {
|
||||
var workspace = workspaceService.getById(entity.getWorkspaceId());
|
||||
if (workspace != null && workspace.getBasePath() != null && !workspace.getBasePath().isBlank()) {
|
||||
agent.workspaceBasePath = workspace.getBasePath();
|
||||
log.info("Agent {} bound to workspace basePath: {}", entity.getName(), agent.workspaceBasePath);
|
||||
log.info("Agent {} inherited workspace basePath: {}", entity.getName(), agent.workspaceBasePath);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("Failed to lookup workspace basePath for agent {}: {}", entity.getName(), e.getMessage());
|
||||
|
||||
@ -78,6 +78,10 @@ public class AgentEntity {
|
||||
/** 默认思考深度:off / low / medium / high / max,null 表示跟随模型默认 */
|
||||
private String defaultThinkingLevel;
|
||||
|
||||
/** Agent 级别的工作目录覆盖,为 null 时继承工作区 basePath */
|
||||
@TableField(value = "workspace_base_path", updateStrategy = FieldStrategy.ALWAYS)
|
||||
private String workspaceBasePath;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
-- V100: Add workspace_base_path column to mate_agent for Agent-level directory override.
|
||||
-- When set, this overrides the workspace-level basePath for this Agent only.
|
||||
ALTER TABLE mate_agent ADD COLUMN IF NOT EXISTS workspace_base_path VARCHAR(512) DEFAULT NULL;
|
||||
@ -0,0 +1,12 @@
|
||||
-- V125: Add workspace_base_path column to mate_agent for Agent-level directory override.
|
||||
-- Idempotent: checks INFORMATION_SCHEMA before ADD COLUMN.
|
||||
SET @col_exists := (
|
||||
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'mate_agent'
|
||||
AND COLUMN_NAME = 'workspace_base_path'
|
||||
);
|
||||
SET @stmt := IF(@col_exists = 0,
|
||||
'ALTER TABLE mate_agent ADD COLUMN workspace_base_path VARCHAR(512) DEFAULT NULL',
|
||||
'SELECT 1');
|
||||
PREPARE s FROM @stmt; EXECUTE s; DEALLOCATE PREPARE s;
|
||||
@ -1163,6 +1163,8 @@ export default {
|
||||
extraInstructionsHint: 'Optional. Use for output format, process checklists, or boundary rules.',
|
||||
maxIterations: 'Max Iterations',
|
||||
defaultThinkingLevel: 'Default Thinking Level',
|
||||
workspaceBasePath: 'Working Directory',
|
||||
workspaceBasePathHint: 'Optional. Set a dedicated working directory for this employee (relative to workspace root). Leave blank to inherit the workspace default.',
|
||||
modelName: 'Model',
|
||||
modelGlobalDefault: 'Use global default',
|
||||
modelHint: 'Override the global default model for this employee. Leave blank to follow Settings → Models.',
|
||||
@ -1198,6 +1200,7 @@ export default {
|
||||
backstory: 'e.g. Spent 10 years in data — believes in asking the right question before writing SQL...',
|
||||
extraInstructions: 'Optional: output format, process checklist, or boundary rules...',
|
||||
tags: 'tag1,tag2',
|
||||
workspaceBasePath: 'e.g. projects/code-review',
|
||||
},
|
||||
messages: {
|
||||
noDescription: 'No description',
|
||||
|
||||
@ -1055,6 +1055,8 @@ export default {
|
||||
extraInstructionsHint: '可选。用于细化输出格式、流程清单或边界规则。',
|
||||
maxIterations: '最大迭代次数',
|
||||
defaultThinkingLevel: '默认思考深度',
|
||||
workspaceBasePath: '工作目录',
|
||||
workspaceBasePathHint: '可选。为该员工指定独立的工作目录(相对于工作区根目录)。留空则继承工作区默认目录。',
|
||||
modelName: '模型',
|
||||
modelGlobalDefault: '使用全局默认模型',
|
||||
modelHint: '为该员工单独指定模型,留空则跟随「设置 → 模型」中的全局默认。',
|
||||
@ -1090,6 +1092,7 @@ export default {
|
||||
backstory: '例:在数据里待了十年,相信先问对问题再写 SQL...',
|
||||
extraInstructions: '可选:补充输出格式、流程清单或边界规则...',
|
||||
tags: 'tag1,tag2',
|
||||
workspaceBasePath: '例如:projects/code-review',
|
||||
},
|
||||
messages: {
|
||||
noDescription: '暂无描述',
|
||||
|
||||
@ -43,6 +43,7 @@ export interface Agent {
|
||||
enabled: boolean
|
||||
icon?: string
|
||||
tags?: string
|
||||
workspaceBasePath?: string
|
||||
createTime?: string
|
||||
updateTime?: string
|
||||
}
|
||||
|
||||
@ -271,6 +271,11 @@
|
||||
<option value="max">{{ t('agents.thinkingLevels.max') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group full-width">
|
||||
<label class="form-label">{{ t('agents.fields.workspaceBasePath') }}</label>
|
||||
<input v-model="form.workspaceBasePath" class="form-input" :placeholder="t('agents.placeholders.workspaceBasePath')" />
|
||||
<p class="form-hint">{{ t('agents.fields.workspaceBasePathHint') }}</p>
|
||||
</div>
|
||||
<!--
|
||||
Identity triad: role + goal + backstory map to H2 sections in
|
||||
the stored systemPrompt. The card tagline is derived from
|
||||
@ -703,6 +708,7 @@ const defaultForm = (): Partial<Agent> & { name: string; defaultThinkingLevel: s
|
||||
tags: '',
|
||||
enabled: true,
|
||||
defaultThinkingLevel: null,
|
||||
workspaceBasePath: null,
|
||||
})
|
||||
|
||||
const form = ref(defaultForm())
|
||||
@ -890,6 +896,7 @@ async function openEditModal(agent: Agent) {
|
||||
tags: agent.tags || '',
|
||||
enabled: agent.enabled,
|
||||
defaultThinkingLevel: (agent as any).defaultThinkingLevel || null,
|
||||
workspaceBasePath: agent.workspaceBasePath || null,
|
||||
}
|
||||
profileForm.value = parsePrompt(agent.systemPrompt)
|
||||
modalTab.value = 'basic'
|
||||
|
||||
Loading…
Reference in New Issue
Block a user