mirror of
https://gitee.com/mateos/mateclaw.git
synced 2026-09-15 20:08:18 +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.topP = runtimeModel.getTopP();
|
||||||
agent.toolCallingEnabled = toolCallingEnabled;
|
agent.toolCallingEnabled = toolCallingEnabled;
|
||||||
|
|
||||||
// 查找工作区活动目录
|
// Agent 级别覆盖优先,否则继承工作区
|
||||||
if (entity.getWorkspaceId() != null) {
|
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 {
|
try {
|
||||||
var workspace = workspaceService.getById(entity.getWorkspaceId());
|
var workspace = workspaceService.getById(entity.getWorkspaceId());
|
||||||
if (workspace != null && workspace.getBasePath() != null && !workspace.getBasePath().isBlank()) {
|
if (workspace != null && workspace.getBasePath() != null && !workspace.getBasePath().isBlank()) {
|
||||||
agent.workspaceBasePath = workspace.getBasePath();
|
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) {
|
} catch (Exception e) {
|
||||||
log.warn("Failed to lookup workspace basePath for agent {}: {}", entity.getName(), e.getMessage());
|
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 表示跟随模型默认 */
|
/** 默认思考深度:off / low / medium / high / max,null 表示跟随模型默认 */
|
||||||
private String defaultThinkingLevel;
|
private String defaultThinkingLevel;
|
||||||
|
|
||||||
|
/** Agent 级别的工作目录覆盖,为 null 时继承工作区 basePath */
|
||||||
|
@TableField(value = "workspace_base_path", updateStrategy = FieldStrategy.ALWAYS)
|
||||||
|
private String workspaceBasePath;
|
||||||
|
|
||||||
@TableField(fill = FieldFill.INSERT)
|
@TableField(fill = FieldFill.INSERT)
|
||||||
private LocalDateTime createTime;
|
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.',
|
extraInstructionsHint: 'Optional. Use for output format, process checklists, or boundary rules.',
|
||||||
maxIterations: 'Max Iterations',
|
maxIterations: 'Max Iterations',
|
||||||
defaultThinkingLevel: 'Default Thinking Level',
|
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',
|
modelName: 'Model',
|
||||||
modelGlobalDefault: 'Use global default',
|
modelGlobalDefault: 'Use global default',
|
||||||
modelHint: 'Override the global default model for this employee. Leave blank to follow Settings → Models.',
|
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...',
|
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...',
|
extraInstructions: 'Optional: output format, process checklist, or boundary rules...',
|
||||||
tags: 'tag1,tag2',
|
tags: 'tag1,tag2',
|
||||||
|
workspaceBasePath: 'e.g. projects/code-review',
|
||||||
},
|
},
|
||||||
messages: {
|
messages: {
|
||||||
noDescription: 'No description',
|
noDescription: 'No description',
|
||||||
|
|||||||
@ -1055,6 +1055,8 @@ export default {
|
|||||||
extraInstructionsHint: '可选。用于细化输出格式、流程清单或边界规则。',
|
extraInstructionsHint: '可选。用于细化输出格式、流程清单或边界规则。',
|
||||||
maxIterations: '最大迭代次数',
|
maxIterations: '最大迭代次数',
|
||||||
defaultThinkingLevel: '默认思考深度',
|
defaultThinkingLevel: '默认思考深度',
|
||||||
|
workspaceBasePath: '工作目录',
|
||||||
|
workspaceBasePathHint: '可选。为该员工指定独立的工作目录(相对于工作区根目录)。留空则继承工作区默认目录。',
|
||||||
modelName: '模型',
|
modelName: '模型',
|
||||||
modelGlobalDefault: '使用全局默认模型',
|
modelGlobalDefault: '使用全局默认模型',
|
||||||
modelHint: '为该员工单独指定模型,留空则跟随「设置 → 模型」中的全局默认。',
|
modelHint: '为该员工单独指定模型,留空则跟随「设置 → 模型」中的全局默认。',
|
||||||
@ -1090,6 +1092,7 @@ export default {
|
|||||||
backstory: '例:在数据里待了十年,相信先问对问题再写 SQL...',
|
backstory: '例:在数据里待了十年,相信先问对问题再写 SQL...',
|
||||||
extraInstructions: '可选:补充输出格式、流程清单或边界规则...',
|
extraInstructions: '可选:补充输出格式、流程清单或边界规则...',
|
||||||
tags: 'tag1,tag2',
|
tags: 'tag1,tag2',
|
||||||
|
workspaceBasePath: '例如:projects/code-review',
|
||||||
},
|
},
|
||||||
messages: {
|
messages: {
|
||||||
noDescription: '暂无描述',
|
noDescription: '暂无描述',
|
||||||
|
|||||||
@ -43,6 +43,7 @@ export interface Agent {
|
|||||||
enabled: boolean
|
enabled: boolean
|
||||||
icon?: string
|
icon?: string
|
||||||
tags?: string
|
tags?: string
|
||||||
|
workspaceBasePath?: string
|
||||||
createTime?: string
|
createTime?: string
|
||||||
updateTime?: string
|
updateTime?: string
|
||||||
}
|
}
|
||||||
|
|||||||
@ -271,6 +271,11 @@
|
|||||||
<option value="max">{{ t('agents.thinkingLevels.max') }}</option>
|
<option value="max">{{ t('agents.thinkingLevels.max') }}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</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
|
Identity triad: role + goal + backstory map to H2 sections in
|
||||||
the stored systemPrompt. The card tagline is derived from
|
the stored systemPrompt. The card tagline is derived from
|
||||||
@ -703,6 +708,7 @@ const defaultForm = (): Partial<Agent> & { name: string; defaultThinkingLevel: s
|
|||||||
tags: '',
|
tags: '',
|
||||||
enabled: true,
|
enabled: true,
|
||||||
defaultThinkingLevel: null,
|
defaultThinkingLevel: null,
|
||||||
|
workspaceBasePath: null,
|
||||||
})
|
})
|
||||||
|
|
||||||
const form = ref(defaultForm())
|
const form = ref(defaultForm())
|
||||||
@ -890,6 +896,7 @@ async function openEditModal(agent: Agent) {
|
|||||||
tags: agent.tags || '',
|
tags: agent.tags || '',
|
||||||
enabled: agent.enabled,
|
enabled: agent.enabled,
|
||||||
defaultThinkingLevel: (agent as any).defaultThinkingLevel || null,
|
defaultThinkingLevel: (agent as any).defaultThinkingLevel || null,
|
||||||
|
workspaceBasePath: agent.workspaceBasePath || null,
|
||||||
}
|
}
|
||||||
profileForm.value = parsePrompt(agent.systemPrompt)
|
profileForm.value = parsePrompt(agent.systemPrompt)
|
||||||
modalTab.value = 'basic'
|
modalTab.value = 'basic'
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user