Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion astrbot/core/config/default.py
Original file line number Diff line number Diff line change
Expand Up @@ -4736,7 +4736,7 @@
},
}

CONFIG_METADATA_3["plugin_group"]["metadata"]["btw"] = {
CONFIG_METADATA_3["ai_group"]["metadata"]["btw"] = {
"description": "BTW 双循环",
"type": "object",
"items": {
Expand Down Expand Up @@ -4769,6 +4769,7 @@
"description": "工作循环 Computer Use 运行时",
"type": "string",
"options": ["inherit", "none", "local", "sandbox"],
"labels": ["继承", "无", "本地", "沙箱"],
"hint": "inherit 沿用当前 Computer Use 配置。对话循环始终禁用电脑和文件工具;工作循环仍须满足已有角色、路径和沙箱授权规则。",
"condition": {"btw.enabled": True},
},
Expand Down
1 change: 1 addition & 0 deletions dashboard/src/components/config/AiConfigPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,7 @@ const localTabGroups = computed(() => {
'websearch',
'agent_computer_use',
'proactive_capability',
'btw',
]
.filter((key) => props.metadata?.[key])
.map((key) => ({
Expand Down
22 changes: 22 additions & 0 deletions dashboard/src/components/shared/ConfigItemRenderer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,26 @@
@update:model-value="emitUpdate"
/>
</template>
<template v-else-if="itemMeta?._special === 'select_plugin_loop_routes'">
<PluginLoopSelector
:model-value="modelValue"
@update:model-value="emitUpdate"
/>
</template>
<template v-else-if="itemMeta?._special === 'select_mcp_loop_routes'">
<CapabilityLoopSelector
kind="mcp"
:model-value="modelValue"
@update:model-value="emitUpdate"
/>
</template>
<template v-else-if="itemMeta?._special === 'select_skill_loop_routes'">
<CapabilityLoopSelector
kind="skill"
:model-value="modelValue"
@update:model-value="emitUpdate"
/>
</template>
<template v-else-if="itemMeta?._special === 't2i_template'">
<T2ITemplateEditor />
</template>
Expand Down Expand Up @@ -323,6 +343,8 @@ import ProviderSelector from './ProviderSelector.vue';
import PersonaSelector from './PersonaSelector.vue';
import KnowledgeBaseSelector from './KnowledgeBaseSelector.vue';
import PluginSetSelector from './PluginSetSelector.vue';
import PluginLoopSelector from './PluginLoopSelector.vue';
import CapabilityLoopSelector from './CapabilityLoopSelector.vue';
import T2ITemplateEditor from './T2ITemplateEditor.vue';
import DashboardTotpManager from './DashboardTotpManager.vue';
import { computed, ref } from 'vue';
Expand Down
103 changes: 52 additions & 51 deletions dashboard/src/i18n/locales/en-US/features/config-metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,58 @@
}
}
},
"btw": {
"description": "BTW dual loops",
"btw": {
"enabled": {
"description": "Enable BTW dual loops",
"hint": "Experimental and disabled by default. Ordinary admitted AI requests use the conversation entry over the existing Agent."
},
"conversation_loop": {
"provider_id": {
"description": "Conversation loop model",
"hint": "Leave empty to keep the current session model selection. When set, this model takes priority."
}
},
"work_loop": {
"enabled": {
"description": "Enable work loop",
"hint": "Disabled by default. Allow explicit work execution."
},
"provider_id": {
"description": "Work loop model",
"hint": "Leave empty to keep the current session model selection. When set, this model takes priority."
},
"computer_use_runtime": {
"description": "Work loop Computer Use runtime",
"hint": "inherit uses the existing Computer Use setting. The conversation loop has no computer or file tools. Work still follows existing role, path, and sandbox authorization rules.",
"labels": ["Inherit", "None", "Local", "Sandbox"]
},
"max_concurrent": {
"description": "Concurrent work execution",
"hint": "Active execution limit, default 2. This is not a waiting-queue length limit."
}
},
"work_session": {
"max_age_seconds": {
"description": "Terminal work retention (seconds)",
"hint": "Keep completed, failed or cancelled records for 3600 seconds by default."
}
},
"plugin_routes": {
"description": "Plugin tool loop assignments",
"hint": "Plugin LLM tools default to the work loop; explicitly assign an enabled plugin to the conversation loop or both when needed."
},
"mcp_routes": {
"description": "MCP server loop assignments",
"hint": "MCP tools default to the work loop; explicitly assign an enabled server to the conversation loop or both when needed."
},
"skill_routes": {
"description": "Skill loop assignments",
"hint": "Skills default to both loops; explicitly restrict an enabled Skill to one loop when needed. Workspace Skills remain work-only with the local runtime."
}
}
},
"truncate_and_compress": {
"hint": "How AstrBot manages working memory.",
"description": "Context Management Strategy",
Expand Down Expand Up @@ -1202,57 +1254,6 @@
"description": "Available Plugins",
"hint": "All non-disabled plugins are enabled by default. If a plugin is disabled on the plugins page, selections here will not take effect."
}
},
"btw": {
"description": "BTW dual loops",
"btw": {
"enabled": {
"description": "Enable BTW dual loops",
"hint": "Experimental and disabled by default. Ordinary admitted AI requests use the conversation entry over the existing Agent."
},
"work_loop": {
"enabled": {
"description": "Enable work loop",
"hint": "Disabled by default. Allow explicit work execution."
},
"max_concurrent": {
"description": "Concurrent work execution",
"hint": "Active execution limit, default 2. This is not a waiting-queue length limit."
},
"provider_id": {
"description": "Work loop model",
"hint": "Leave empty to keep the current session model selection. When set, this model takes priority."
},
"computer_use_runtime": {
"description": "Work loop Computer Use runtime",
"hint": "inherit uses the existing Computer Use setting. The conversation loop has no computer or file tools. Work still follows existing role, path, and sandbox authorization rules."
}
},
"work_session": {
"max_age_seconds": {
"description": "Terminal work retention (seconds)",
"hint": "Keep completed, failed or cancelled records for 3600 seconds by default."
}
},
"conversation_loop": {
"provider_id": {
"description": "Conversation loop model",
"hint": "Leave empty to keep the current session model selection. When set, this model takes priority."
}
},
"plugin_routes": {
"description": "Plugin tool loop assignments",
"hint": "Plugin LLM tools default to the work loop; explicitly assign an enabled plugin to the conversation loop or both when needed."
},
"mcp_routes": {
"description": "MCP server loop assignments",
"hint": "MCP tools default to the work loop; explicitly assign an enabled server to the conversation loop or both when needed."
},
"skill_routes": {
"description": "Skill loop assignments",
"hint": "Skills default to both loops; explicitly restrict an enabled Skill to one loop when needed. Workspace Skills remain work-only with the local runtime."
}
}
}
},
"ext_group": {
Expand Down
103 changes: 52 additions & 51 deletions dashboard/src/i18n/locales/zh-CN/features/config-metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,58 @@
}
}
},
"btw": {
"description": "BTW 双循环",
"btw": {
"enabled": {
"description": "启用 BTW 双循环",
"hint": "实验功能,默认关闭。普通且已通过准入的 AI 请求经对话入口使用现有 Agent。"
},
"conversation_loop": {
"provider_id": {
"description": "对话循环模型",
"hint": "留空时沿用当前会话的模型选择。配置后优先使用此模型。"
}
},
"work_loop": {
"enabled": {
"description": "启用工作循环",
"hint": "默认关闭;允许显式工作请求使用工作执行器。"
},
"provider_id": {
"description": "工作循环模型",
"hint": "留空时沿用当前会话的模型选择。配置后优先使用此模型。"
},
"computer_use_runtime": {
"description": "工作循环 Computer Use 运行时",
"hint": "inherit 沿用当前 Computer Use 配置。对话循环始终禁用电脑和文件工具;工作循环仍须满足已有角色、路径和沙箱授权规则。",
"labels": ["继承", "无", "本地", "沙箱"]
},
"max_concurrent": {
"description": "工作任务执行并发",
"hint": "同时执行的工作任务数,默认 2。此值不是等待队列的长度限制。"
}
},
"work_session": {
"max_age_seconds": {
"description": "终态工作会话保留秒数",
"hint": "已完成、失败或取消的工作会话保留时间,默认 3600 秒。"
}
},
"plugin_routes": {
"description": "插件工具循环分配",
"hint": "插件 LLM 工具默认仅在工作循环可用;可为每个已启用插件显式改为对话循环或两者。"
},
"mcp_routes": {
"description": "MCP 服务器循环分配",
"hint": "MCP 工具默认仅在工作循环可用;可为每个已启用服务器显式改为对话循环或两者。"
},
"skill_routes": {
"description": "Skills 循环分配",
"hint": "Skill 默认注入两个循环;可为每个已启用 Skill 显式限制到单一循环。 工作区 Skill 仅在本地工作循环可用。"
}
}
},
"truncate_and_compress": {
"hint": "AstrBot 如何管理工作记忆。",
"description": "上下文管理策略",
Expand Down Expand Up @@ -1196,57 +1248,6 @@
"description": "可用插件",
"hint": "默认启用全部未被禁用的插件。若插件在插件页面被禁用,则此处的选择不会生效。"
}
},
"btw": {
"description": "BTW 双循环",
"btw": {
"enabled": {
"description": "启用 BTW 双循环",
"hint": "实验功能,默认关闭。普通且已通过准入的 AI 请求经对话入口使用现有 Agent。"
},
"work_loop": {
"enabled": {
"description": "启用工作循环",
"hint": "默认关闭;允许显式工作请求使用工作执行器。"
},
"max_concurrent": {
"description": "工作任务执行并发",
"hint": "同时执行的工作任务数,默认 2。此值不是等待队列的长度限制。"
},
"provider_id": {
"description": "工作循环模型",
"hint": "留空时沿用当前会话的模型选择。配置后优先使用此模型。"
},
"computer_use_runtime": {
"description": "工作循环 Computer Use 运行时",
"hint": "inherit 沿用当前 Computer Use 配置。对话循环始终禁用电脑和文件工具;工作循环仍须满足已有角色、路径和沙箱授权规则。"
}
},
"work_session": {
"max_age_seconds": {
"description": "终态工作会话保留秒数",
"hint": "已完成、失败或取消的工作会话保留时间,默认 3600 秒。"
}
},
"conversation_loop": {
"provider_id": {
"description": "对话循环模型",
"hint": "留空时沿用当前会话的模型选择。配置后优先使用此模型。"
}
},
"plugin_routes": {
"description": "插件工具循环分配",
"hint": "插件 LLM 工具默认仅在工作循环可用;可为每个已启用插件显式改为对话循环或两者。"
},
"mcp_routes": {
"description": "MCP 服务器循环分配",
"hint": "MCP 工具默认仅在工作循环可用;可为每个已启用服务器显式改为对话循环或两者。"
},
"skill_routes": {
"description": "Skills 循环分配",
"hint": "Skill 默认注入两个循环;可为每个已启用 Skill 显式限制到单一循环。 工作区 Skill 仅在本地工作循环可用。"
}
}
}
},
"ext_group": {
Expand Down
90 changes: 90 additions & 0 deletions dashboard/tests/aiConfigCapabilities.vitest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { describe, expect, it, vi } from 'vitest';
import AiConfigPanel from '@/components/config/AiConfigPanel.vue';
import { initI18n } from '@/i18n/composables';
import { mountWithVuetify } from './utils/mountWithVuetify';

vi.mock('@/utils/monacoLoader', () => ({}));

vi.mock('@guolao/vue-monaco-editor', () => ({
VueMonacoEditor: {
name: 'VueMonacoEditor',
template: '<div class="monaco-editor-stub"></div>',
},
}));

function buildMetadata(includeBtw: boolean) {
const metadata: Record<string, unknown> = {
agent_runner: { items: {} },
ai: { items: {} },
persona: { items: {} },
knowledgebase: { description: 'Knowledge Base', items: {} },
websearch: { description: 'Web Search', items: {} },
agent_computer_use: { description: 'Computer Use', items: {} },
proactive_capability: { description: 'Proactive Agent', items: {} },
others: { items: {} },
};
if (includeBtw) {
metadata.btw = {
description: 'BTW dual loops',
type: 'object',
items: {
'btw.enabled': { description: 'Enable BTW dual loops', type: 'bool' },
},
};
}
return metadata;
}

function mountAiPanel(metadata: Record<string, unknown>) {
return mountWithVuetify(AiConfigPanel, {
props: {
metadata,
configData: {
provider_settings: { enable: true },
agent_runner: { runner_type: 'local' },
btw: { enabled: false },
},
},
global: {
stubs: {
AstrBotConfigV4: {
props: ['metadataKey'],
template: '<div class="v4-group">{{ metadataKey }}</div>',
},
},
},
});
}

describe('AI capabilities panel', () => {
it('lists the BTW dual-loop group after the other capabilities', async () => {
await initI18n('en-US');
const wrapper = mountAiPanel(buildMetadata(true));

const capabilitiesTab = wrapper.findAll('.ai-config-tabs__item')[2];
expect(capabilitiesTab?.text()).toBe('Capabilities');
await capabilitiesTab?.trigger('click');

expect(wrapper.findAll('.v4-group').map((group) => group.text())).toEqual([
'knowledgebase',
'websearch',
'agent_computer_use',
'proactive_capability',
'btw',
]);
});

it('omits the BTW group when the panel metadata does not carry it', async () => {
await initI18n('en-US');
const wrapper = mountAiPanel(buildMetadata(false));

await wrapper.findAll('.ai-config-tabs__item')[2]?.trigger('click');

expect(wrapper.findAll('.v4-group').map((group) => group.text())).toEqual([
'knowledgebase',
'websearch',
'agent_computer_use',
'proactive_capability',
]);
});
});
Loading
Loading