feat(companion): route pet chat and voice through lime host

This commit is contained in:
coso
2026-04-03 21:40:32 +08:00
parent 8ea5050aba
commit cf242a6fe5
6 changed files with 1427 additions and 316 deletions
@@ -5,18 +5,21 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { useCompanionProviderBridge } from "./useCompanionProviderBridge";
import {
COMPANION_OPEN_PROVIDER_SETTINGS_EVENT,
COMPANION_REQUEST_PROVIDER_SYNC_EVENT,
type CompanionPetStatus,
getCompanionPetStatus,
listenCompanionPetStatus,
sendCompanionPetCommand,
} from "@/lib/api/companion";
import { safeListen } from "@/lib/dev-bridge";
import { apiKeyProviderApi } from "@/lib/api/apiKeyProvider";
import { providerPoolApi } from "@/lib/api/providerPool";
import { subscribeProviderDataChanged } from "@/lib/providerDataEvents";
import { SettingsTabs } from "@/types/settings";
vi.mock("@/lib/api/companion", () => ({
COMPANION_OPEN_PROVIDER_SETTINGS_EVENT: "companion-open-provider-settings",
COMPANION_REQUEST_PROVIDER_SYNC_EVENT: "companion-request-provider-sync",
COMPANION_PROVIDER_OVERVIEW_CAPABILITY: "provider-overview",
getCompanionPetStatus: vi.fn(),
listenCompanionPetStatus: vi.fn(),
@@ -27,6 +30,12 @@ vi.mock("@/lib/dev-bridge", () => ({
safeListen: vi.fn(),
}));
vi.mock("@/lib/api/apiKeyProvider", () => ({
apiKeyProviderApi: {
getProviders: vi.fn(),
},
}));
vi.mock("@/lib/api/providerPool", () => ({
providerPoolApi: {
getOverview: vi.fn(),
@@ -93,6 +102,7 @@ function renderHook(props?: Partial<HookProps>) {
describe("useCompanionProviderBridge", () => {
beforeEach(() => {
vi.clearAllMocks();
(
globalThis as typeof globalThis & {
IS_REACT_ACT_ENVIRONMENT?: boolean;
@@ -119,6 +129,7 @@ describe("useCompanionProviderBridge", () => {
credentials: [],
},
]);
vi.mocked(apiKeyProviderApi.getProviders).mockResolvedValue([]);
vi.mocked(subscribeProviderDataChanged).mockReturnValue(vi.fn());
vi.mocked(safeListen).mockResolvedValue(vi.fn());
});
@@ -187,6 +198,115 @@ describe("useCompanionProviderBridge", () => {
});
});
it("收到桌宠摘要同步请求事件时,应强制重发脱敏摘要", async () => {
let requestProviderSyncHandler: (() => void) | null = null;
vi.mocked(safeListen).mockImplementation(async (event, handler) => {
if (event === COMPANION_REQUEST_PROVIDER_SYNC_EVENT) {
requestProviderSyncHandler = handler as () => void;
}
return vi.fn();
});
const { render } = renderHook();
await render();
vi.mocked(sendCompanionPetCommand).mockClear();
vi.mocked(providerPoolApi.getOverview).mockClear();
vi.mocked(apiKeyProviderApi.getProviders).mockClear();
await act(async () => {
requestProviderSyncHandler?.();
await Promise.resolve();
await Promise.resolve();
});
expect(providerPoolApi.getOverview).toHaveBeenCalledWith({
forceRefresh: true,
});
expect(apiKeyProviderApi.getProviders).toHaveBeenCalledWith({
forceRefresh: true,
});
expect(sendCompanionPetCommand).toHaveBeenCalledWith({
event: "pet.provider_overview",
payload: {
providers: [
{
provider_type: "openai",
display_name: "OpenAI",
total_count: 1,
healthy_count: 1,
available: true,
needs_attention: false,
},
],
total_provider_count: 1,
available_provider_count: 1,
needs_attention_provider_count: 0,
},
});
});
it("应把 API Key Provider 一并整理进桌宠摘要", async () => {
vi.mocked(apiKeyProviderApi.getProviders).mockResolvedValue([
{
id: "deepseek",
name: "DeepSeek",
type: "openai",
api_host: "https://api.deepseek.com/v1",
is_system: false,
group: "cloud",
enabled: true,
sort_order: 10,
custom_models: [],
api_key_count: 1,
api_keys: [
{
id: "deepseek-key-1",
provider_id: "deepseek",
api_key_masked: "sk-***1234",
enabled: true,
usage_count: 0,
error_count: 0,
created_at: "2026-04-01T00:00:00Z",
},
],
created_at: "2026-04-01T00:00:00Z",
updated_at: "2026-04-01T00:00:00Z",
},
]);
const { render } = renderHook();
await render();
expect(sendCompanionPetCommand).toHaveBeenCalledWith({
event: "pet.provider_overview",
payload: {
providers: [
{
provider_type: "deepseek",
display_name: "DeepSeek",
total_count: 1,
healthy_count: 1,
available: true,
needs_attention: false,
},
{
provider_type: "openai",
display_name: "OpenAI",
total_count: 1,
healthy_count: 1,
available: true,
needs_attention: false,
},
],
total_provider_count: 2,
available_provider_count: 2,
needs_attention_provider_count: 0,
},
});
});
it("桌宠未声明 provider 能力时,不应下发摘要", async () => {
vi.mocked(getCompanionPetStatus).mockResolvedValue(
createConnectedStatus({
+31 -10
View File
@@ -2,6 +2,7 @@ import { useEffect, useRef } from "react";
import type { UnlistenFn } from "@tauri-apps/api/event";
import {
COMPANION_OPEN_PROVIDER_SETTINGS_EVENT,
COMPANION_REQUEST_PROVIDER_SYNC_EVENT,
COMPANION_PROVIDER_OVERVIEW_CAPABILITY,
getCompanionPetStatus,
listenCompanionPetStatus,
@@ -9,9 +10,8 @@ import {
type CompanionPetStatus,
} from "@/lib/api/companion";
import { safeListen } from "@/lib/dev-bridge";
import { providerPoolApi } from "@/lib/api/providerPool";
import { subscribeProviderDataChanged } from "@/lib/providerDataEvents";
import { buildCompanionProviderOverview } from "@/lib/provider/companionProviderOverview";
import { loadCompanionProviderOverview } from "@/lib/provider/companionProviderOverview";
import type { Page, PageParams } from "@/types/page";
import { SettingsTabs } from "@/types/settings";
@@ -24,7 +24,7 @@ function supportsProviderOverview(
): boolean {
return Boolean(
status?.connected &&
status.capabilities.includes(COMPANION_PROVIDER_OVERVIEW_CAPABILITY),
status.capabilities.includes(COMPANION_PROVIDER_OVERVIEW_CAPABILITY),
);
}
@@ -39,8 +39,12 @@ export function useCompanionProviderBridge({
let cancelled = false;
let statusUnlisten: UnlistenFn | null = null;
let openSettingsUnlisten: UnlistenFn | null = null;
let requestProviderSyncUnlisten: UnlistenFn | null = null;
const syncProviderOverview = async (forceRefresh = false) => {
const syncProviderOverview = async (
forceRefresh = false,
forceDeliver = false,
) => {
if (!supportsProviderOverview(statusRef.current)) {
return;
}
@@ -48,16 +52,15 @@ export function useCompanionProviderBridge({
const requestId = ++syncRequestIdRef.current;
try {
const overview = await providerPoolApi.getOverview(
forceRefresh ? { forceRefresh: true } : undefined,
);
const payload = await loadCompanionProviderOverview({
forceRefresh,
});
if (cancelled || requestId !== syncRequestIdRef.current) {
return;
}
const payload = buildCompanionProviderOverview(overview);
const fingerprint = JSON.stringify(payload);
if (fingerprint === lastFingerprintRef.current) {
if (!forceDeliver && fingerprint === lastFingerprintRef.current) {
return;
}
@@ -91,7 +94,8 @@ export function useCompanionProviderBridge({
const becameProviderAware =
supportsProviderOverview(status) &&
(!previousStatus?.connected || !supportsProviderOverview(previousStatus));
(!previousStatus?.connected ||
!supportsProviderOverview(previousStatus));
if (becameProviderAware) {
void syncProviderOverview(true);
@@ -148,6 +152,20 @@ export function useCompanionProviderBridge({
console.warn("[Companion] 监听桌宠设置跳转失败:", error);
});
void safeListen(COMPANION_REQUEST_PROVIDER_SYNC_EVENT, () => {
void syncProviderOverview(true, true);
})
.then((unlisten) => {
if (cancelled) {
void unlisten();
return;
}
requestProviderSyncUnlisten = unlisten;
})
.catch((error) => {
console.warn("[Companion] 监听桌宠摘要同步请求失败:", error);
});
return () => {
cancelled = true;
unsubscribeProviderData();
@@ -158,6 +176,9 @@ export function useCompanionProviderBridge({
if (openSettingsUnlisten) {
openSettingsUnlisten();
}
if (requestProviderSyncUnlisten) {
requestProviderSyncUnlisten();
}
};
}, [onNavigate]);
}
+43 -1
View File
@@ -24,7 +24,10 @@ import type {
AgentThreadItem,
AgentThreadTurn,
} from "./agentProtocol";
import { normalizeLegacyThreadItem } from "./agentTextNormalization";
import {
normalizeLegacyThreadItem,
normalizeLegacyToolSurfaceName,
} from "./agentTextNormalization";
import type {
AsterApprovalPolicy,
AsterExecutionStrategy,
@@ -998,8 +1001,13 @@ export interface AgentRuntimeUpdateSessionRequest {
export interface AgentRuntimeSpawnSubagentRequest {
parent_session_id: string;
message: string;
name?: string;
team_name?: string;
agent_type?: string;
model?: string;
run_in_background?: boolean;
mode?: string;
isolation?: 'worktree' | 'remote' | string;
reasoning_effort?: string;
fork_context?: boolean;
blueprint_role_id?: string;
@@ -1013,6 +1021,7 @@ export interface AgentRuntimeSpawnSubagentRequest {
theme?: string;
system_overlay?: string;
output_contract?: string;
cwd?: string;
}
export interface AgentRuntimeSpawnSubagentResponse {
@@ -1256,6 +1265,31 @@ export interface AgentRuntimeToolInventory {
mcp_tools: AgentRuntimeToolInventoryMcpEntry[];
}
function normalizeSubagentSessionInfo(
session: AsterSubagentSessionInfo,
): AsterSubagentSessionInfo {
return {
...session,
origin_tool: normalizeLegacyToolSurfaceName(session.origin_tool),
};
}
function normalizeSubagentParentContext(
context?: AsterSubagentParentContext | null,
): AsterSubagentParentContext | undefined {
if (!context) {
return undefined;
}
return {
...context,
origin_tool: normalizeLegacyToolSurfaceName(context.origin_tool),
sibling_subagent_sessions: Array.isArray(context.sibling_subagent_sessions)
? context.sibling_subagent_sessions.map(normalizeSubagentSessionInfo)
: context.sibling_subagent_sessions,
};
}
export async function submitAgentRuntimeTurn(
request: AgentRuntimeSubmitTurnRequest,
): Promise<void> {
@@ -1383,6 +1417,14 @@ export async function getAgentRuntimeSession(
normalizeLegacyThreadItem(item as AgentThreadItem),
)
: normalizedDetail?.items,
child_subagent_sessions: Array.isArray(
normalizedDetail?.child_subagent_sessions,
)
? normalizedDetail.child_subagent_sessions.map(normalizeSubagentSessionInfo)
: normalizedDetail?.child_subagent_sessions,
subagent_parent_context: normalizeSubagentParentContext(
normalizedDetail?.subagent_parent_context,
),
queued_turns: normalizeQueuedTurnSnapshots(normalizedDetail?.queued_turns),
thread_read: normalizeThreadReadModel(normalizedDetail?.thread_read),
};
+10 -2
View File
@@ -198,6 +198,12 @@ export interface TranscribeResult {
provider: string;
}
export type VoiceWindowTarget = "companion-pet";
export interface OpenVoiceWindowOptions {
target?: VoiceWindowTarget;
}
/** 润色结果 */
export interface PolishResult {
text: string;
@@ -229,8 +235,10 @@ export async function polishVoiceText(
}
/** 打开语音输入窗口 */
export async function openVoiceWindow(): Promise<void> {
return safeInvoke<void>("open_voice_window");
export async function openVoiceWindow(
options: OpenVoiceWindowOptions = {},
): Promise<void> {
return safeInvoke<void>("open_voice_window", { ...options });
}
/** 关闭语音输入窗口 */
+350 -296
View File
@@ -117,6 +117,16 @@ type MockBrowserConnectorInstallStatus = {
message: string | null;
};
type MockToolSpec = {
name: string;
description: string;
capabilities: string[];
source: string;
tags: string[];
input_examples_count: number;
execution_restriction_profile?: string;
};
const DEFAULT_MOCK_BROWSER_ACTION_CAPABILITIES = [
{
key: "tabs_context_mcp",
@@ -1073,17 +1083,6 @@ function syncMockAutomationBrowserSessionState(
return session;
}
type MockClawSolutionSummary = {
id: string;
title: string;
summary: string;
outputHint: string;
recommendedCapabilities: string[];
readiness: "ready" | "needs_setup" | "needs_capability";
readinessMessage: string;
reasonCode?: string;
};
type MockReviewDecisionRequest = {
session_id?: string;
sessionId?: string;
@@ -1108,101 +1107,6 @@ type MockReviewDecisionRequest = {
notes?: string;
};
const mockClawSolutionCatalog: MockClawSolutionSummary[] = [
{
id: "web-research-brief",
title: "网页研究简报",
summary: "快速整理研究目标、关键来源与结论框架。",
outputHint: "研究提纲 + 结论简报",
recommendedCapabilities: ["模型", "研究"],
readiness: "ready",
readinessMessage: "可直接开始",
},
{
id: "social-post-starter",
title: "社媒主稿生成",
summary: "进入社媒专项工作台并生成一版首稿。",
outputHint: "社媒首稿 + 平台结构",
recommendedCapabilities: ["模型", "社媒主题"],
readiness: "ready",
readinessMessage: "可直接开始",
},
{
id: "frontend-concept",
title: "前端概念方案",
summary: "输出信息架构、核心模块与页面关系。",
outputHint: "IA + 模块方案",
recommendedCapabilities: ["模型", "结构化输出"],
readiness: "ready",
readinessMessage: "可直接开始",
},
{
id: "slide-outline",
title: "演示提纲草案",
summary: "生成一版可讲述的演示结构。",
outputHint: "PPT 大纲 + 讲述线",
recommendedCapabilities: ["模型", "结构化输出"],
readiness: "ready",
readinessMessage: "可直接开始",
},
{
id: "browser-assist-task",
title: "浏览器协助办事",
summary: "进入工作区后直接打开浏览器协助。",
outputHint: "浏览器任务执行",
recommendedCapabilities: ["模型", "浏览器协助"],
readiness: "ready",
readinessMessage: "可直接开始",
},
{
id: "team-breakdown",
title: "多代理拆任务",
summary: "默认启用多代理偏好,按 team runtime 方式展开任务。",
outputHint: "任务拆解 + 分工执行",
recommendedCapabilities: ["模型", "多代理"],
readiness: "ready",
readinessMessage: "可直接开始,进入后会启用多代理偏好",
reasonCode: "team_recommended",
},
] as const;
function getMockClawSolution(solutionId: string) {
const solution = mockClawSolutionCatalog.find(
(item) => item.id === solutionId,
);
if (!solution) {
throw new Error(`Unknown mock claw solution: ${solutionId}`);
}
return solution;
}
function buildMockClawSolutionPrompt(
solutionId: string,
context?: { userInput?: string },
) {
const prompts: Record<string, string> = {
"web-research-brief":
"请围绕这个主题先给我做一版网页研究简报:明确研究目标、关键信息来源、核心发现、风险点,以及接下来最值得继续追踪的问题。",
"social-post-starter":
"请先帮我起草一版社媒内容首稿:明确目标受众、平台语境、标题方向、正文结构和可继续扩写的角度。",
"frontend-concept":
"请帮我先整理一版前端概念方案:输出信息架构、核心页面、关键模块、交互流程和第一轮组件拆分建议。",
"slide-outline":
"请基于这个目标先生成一版演示提纲:包含封面定位、目录、核心论点、案例支撑、结论和下一步行动。",
"browser-assist-task":
"请协助我完成一个浏览器任务:先明确目标网页、目标动作、约束条件和预期结果,再进入执行。",
"team-breakdown":
"请把这个任务按多代理方式拆解:先定义目标和约束,再拆成并行子任务,明确每个子代理的职责、产出和回收方式。",
};
const basePrompt = prompts[solutionId] ?? "";
const userInput = context?.userInput?.trim();
if (!userInput) {
return basePrompt;
}
return `${basePrompt}\n\n补充上下文:${userInput}`;
}
function buildMockAgentRuntimeToolInventory(request?: {
caller?: string;
workbench?: boolean;
@@ -1214,170 +1118,374 @@ function buildMockAgentRuntimeToolInventory(request?: {
browser_assist: request?.browserAssist === true,
};
const catalogTools = [
{
name: "ToolSearch",
profiles: ["core"],
capabilities: ["web_search"],
lifecycle: "current",
source: "lime_injected",
permission_plane: "session_allowlist",
workspace_default_allow: true,
execution_warning_policy: "none",
execution_warning_policy_source: "default",
execution_restriction_profile: "none",
execution_restriction_profile_source: "default",
execution_sandbox_profile: "none",
execution_sandbox_profile_source: "default",
},
{
name: "WebSearch",
profiles: ["core"],
capabilities: ["web_search"],
lifecycle: "current",
source: "aster_builtin",
permission_plane: "session_allowlist",
workspace_default_allow: true,
execution_warning_policy: "none",
execution_warning_policy_source: "default",
execution_restriction_profile: "safe_https_url_required",
execution_restriction_profile_source: "default",
execution_sandbox_profile: "none",
execution_sandbox_profile_source: "default",
},
{
name: "ask",
profiles: ["core"],
capabilities: ["planning"],
lifecycle: "current",
source: "aster_builtin",
permission_plane: "session_allowlist",
workspace_default_allow: true,
execution_warning_policy: "none",
execution_warning_policy_source: "default",
execution_restriction_profile: "none",
execution_restriction_profile_source: "default",
execution_sandbox_profile: "none",
execution_sandbox_profile_source: "default",
},
{
name: "spawn_agent",
profiles: ["core"],
capabilities: ["delegation"],
lifecycle: "current",
source: "lime_injected",
permission_plane: "session_allowlist",
workspace_default_allow: true,
execution_warning_policy: "none",
execution_warning_policy_source: "default",
execution_restriction_profile: "none",
execution_restriction_profile_source: "default",
execution_sandbox_profile: "none",
execution_sandbox_profile_source: "default",
},
];
const registryTools = [
const toolSpecs: MockToolSpec[] = [
{
name: "ToolSearch",
description: "搜索当前会话可用工具与能力清单。",
catalog_entry_name: "ToolSearch",
catalog_source: "lime_injected",
catalog_lifecycle: "current",
catalog_permission_plane: "session_allowlist",
catalog_workspace_default_allow: true,
catalog_execution_warning_policy: "none",
catalog_execution_warning_policy_source: "default",
catalog_execution_restriction_profile: "none",
catalog_execution_restriction_profile_source: "default",
catalog_execution_sandbox_profile: "none",
catalog_execution_sandbox_profile_source: "default",
deferred_loading: false,
always_visible: true,
allowed_callers: [caller],
capabilities: ["web_search"],
source: "lime_injected",
tags: ["search"],
input_examples_count: 1,
caller_allowed: true,
visible_in_context: true,
},
{
name: "ListMcpResourcesTool",
description: "列出当前已连接 MCP 服务暴露的资源。",
capabilities: ["web_search"],
source: "lime_injected",
tags: ["mcp", "resource", "list"],
input_examples_count: 1,
},
{
name: "ReadMcpResourceTool",
description: "按 server 与 uri 读取指定 MCP 资源内容。",
capabilities: ["web_search"],
source: "lime_injected",
tags: ["mcp", "resource", "read"],
input_examples_count: 1,
},
{
name: "Bash",
description: "执行工作区命令并返回结果。",
capabilities: ["execution"],
source: "aster_builtin",
tags: ["command", "workspace"],
input_examples_count: 1,
},
{
name: "Read",
description: "读取文件内容。",
capabilities: ["workspace_io"],
source: "aster_builtin",
tags: ["read", "file"],
input_examples_count: 1,
},
{
name: "Write",
description: "写入文件内容。",
capabilities: ["workspace_io"],
source: "aster_builtin",
tags: ["write", "file"],
input_examples_count: 1,
},
{
name: "Edit",
description: "按补丁方式编辑文件。",
capabilities: ["workspace_io"],
source: "aster_builtin",
tags: ["edit", "file"],
input_examples_count: 1,
},
{
name: "Glob",
description: "按模式列出匹配文件。",
capabilities: ["workspace_io"],
source: "aster_builtin",
tags: ["search", "file"],
input_examples_count: 1,
},
{
name: "Grep",
description: "在工作区中搜索文本。",
capabilities: ["workspace_io"],
source: "aster_builtin",
tags: ["search", "text"],
input_examples_count: 1,
},
{
name: "WebFetch",
description: "抓取指定网页内容。",
capabilities: ["web_search"],
source: "aster_builtin",
tags: ["web", "fetch"],
input_examples_count: 1,
execution_restriction_profile: "safe_https_url_required",
},
{
name: "WebSearch",
description: "联网检索公开网页信息。",
catalog_entry_name: "WebSearch",
catalog_source: "aster_builtin",
catalog_lifecycle: "current",
catalog_permission_plane: "session_allowlist",
catalog_workspace_default_allow: true,
catalog_execution_warning_policy: "none",
catalog_execution_warning_policy_source: "default",
catalog_execution_restriction_profile: "safe_https_url_required",
catalog_execution_restriction_profile_source: "default",
catalog_execution_sandbox_profile: "none",
catalog_execution_sandbox_profile_source: "default",
deferred_loading: false,
always_visible: true,
allowed_callers: [caller],
capabilities: ["web_search"],
source: "aster_builtin",
tags: ["research"],
input_examples_count: 2,
caller_allowed: true,
visible_in_context: true,
execution_restriction_profile: "safe_https_url_required",
},
{
name: "ask",
name: "AskUserQuestion",
description: "向用户发起单轮最小必要澄清。",
catalog_entry_name: "ask",
catalog_source: "aster_builtin",
catalog_lifecycle: "current",
catalog_permission_plane: "session_allowlist",
catalog_workspace_default_allow: true,
catalog_execution_warning_policy: "none",
catalog_execution_warning_policy_source: "default",
catalog_execution_restriction_profile: "none",
catalog_execution_restriction_profile_source: "default",
catalog_execution_sandbox_profile: "none",
catalog_execution_sandbox_profile_source: "default",
deferred_loading: false,
always_visible: true,
allowed_callers: [caller],
capabilities: ["planning"],
source: "aster_builtin",
tags: ["clarify"],
input_examples_count: 1,
caller_allowed: true,
visible_in_context: true,
},
{
name: "spawn_agent",
name: "SendUserMessage",
description: "向用户发送一条主可见消息,可用于回复、进度同步或主动提醒。",
capabilities: ["session_control"],
source: "aster_builtin",
tags: ["message", "user"],
input_examples_count: 1,
},
{
name: "Agent",
description: "在需要并行处理时派生子代理。",
catalog_entry_name: "spawn_agent",
catalog_source: "lime_injected",
catalog_lifecycle: "current",
catalog_permission_plane: "session_allowlist",
catalog_workspace_default_allow: true,
catalog_execution_warning_policy: "none",
catalog_execution_warning_policy_source: "default",
catalog_execution_restriction_profile: "none",
catalog_execution_restriction_profile_source: "default",
catalog_execution_sandbox_profile: "none",
catalog_execution_sandbox_profile_source: "default",
deferred_loading: false,
always_visible: true,
allowed_callers: [caller],
capabilities: ["delegation"],
source: "lime_injected",
tags: ["delegation"],
input_examples_count: 1,
caller_allowed: true,
visible_in_context: true,
},
];
{
name: "SendMessage",
description: "向已存在的协作成员追加说明或指令。",
capabilities: ["delegation"],
source: "aster_builtin",
tags: ["delegation"],
input_examples_count: 1,
},
{
name: "TeamCreate",
description: "创建共享 task board 与 team 协作上下文。",
capabilities: ["delegation"],
source: "aster_builtin",
tags: ["delegation", "team"],
input_examples_count: 1,
},
{
name: "TeamDelete",
description: "删除当前 team 协作上下文。",
capabilities: ["delegation"],
source: "aster_builtin",
tags: ["delegation", "team"],
input_examples_count: 1,
},
{
name: "ListPeers",
description: "列出当前 team 中可直接通信的协作成员。",
capabilities: ["delegation"],
source: "aster_builtin",
tags: ["delegation", "team"],
input_examples_count: 1,
},
{
name: "Skill",
description: "加载并执行当前可用技能。",
capabilities: ["skill_execution"],
source: "aster_builtin",
tags: ["skill"],
input_examples_count: 1,
},
{
name: "Workflow",
description: "执行工作流脚本。",
capabilities: ["execution"],
source: "aster_builtin",
tags: ["workflow"],
input_examples_count: 1,
},
{
name: "TaskCreate",
description: "创建结构化任务。",
capabilities: ["planning"],
source: "aster_builtin",
tags: ["task"],
input_examples_count: 1,
},
{
name: "TaskList",
description: "查看结构化任务列表。",
capabilities: ["planning"],
source: "aster_builtin",
tags: ["task"],
input_examples_count: 1,
},
{
name: "TaskGet",
description: "读取单个结构化任务。",
capabilities: ["planning"],
source: "aster_builtin",
tags: ["task"],
input_examples_count: 1,
},
{
name: "TaskUpdate",
description: "更新结构化任务状态。",
capabilities: ["planning"],
source: "aster_builtin",
tags: ["task"],
input_examples_count: 1,
},
{
name: "TaskOutput",
description: "读取任务输出结果。",
capabilities: ["planning"],
source: "aster_builtin",
tags: ["task", "output"],
input_examples_count: 1,
},
{
name: "TaskStop",
description: "停止正在执行的任务。",
capabilities: ["planning"],
source: "aster_builtin",
tags: ["task"],
input_examples_count: 1,
},
{
name: "NotebookEdit",
description: "编辑 notebook 单元内容。",
capabilities: ["workspace_io"],
source: "aster_builtin",
tags: ["notebook"],
input_examples_count: 1,
},
{
name: "EnterPlanMode",
description: "进入计划模式以拆解方案。",
capabilities: ["planning"],
source: "aster_builtin",
tags: ["planning"],
input_examples_count: 1,
},
{
name: "ExitPlanMode",
description: "退出计划模式并继续执行。",
capabilities: ["planning"],
source: "aster_builtin",
tags: ["planning"],
input_examples_count: 1,
},
{
name: "EnterWorktree",
description: "进入独立工作树执行隔离修改。",
capabilities: ["workspace_io"],
source: "aster_builtin",
tags: ["worktree"],
input_examples_count: 1,
},
{
name: "ExitWorktree",
description: "退出独立工作树并回到主工作区。",
capabilities: ["workspace_io"],
source: "aster_builtin",
tags: ["worktree"],
input_examples_count: 1,
},
{
name: "Config",
description: "查看或调整当前运行配置。",
capabilities: ["session_control"],
source: "aster_builtin",
tags: ["config"],
input_examples_count: 1,
},
{
name: "Sleep",
description: "等待一段时间后继续执行。",
capabilities: ["execution"],
source: "aster_builtin",
tags: ["timing"],
input_examples_count: 1,
},
{
name: "PowerShell",
description: "在 PowerShell 环境中执行命令。",
capabilities: ["execution"],
source: "aster_builtin",
tags: ["command", "windows"],
input_examples_count: 1,
},
{
name: "LSP",
description: "查询语言服务返回的语义信息。",
capabilities: ["workspace_io"],
source: "aster_builtin",
tags: ["code", "lsp"],
input_examples_count: 1,
},
{
name: "RemoteTrigger",
description: "管理或触发远程 trigger 执行。",
capabilities: ["execution"],
source: "aster_builtin",
tags: ["trigger", "remote"],
input_examples_count: 1,
},
{
name: "CronCreate",
description: "创建新的定时触发器。",
capabilities: ["planning"],
source: "aster_builtin",
tags: ["trigger", "schedule"],
input_examples_count: 1,
},
{
name: "CronList",
description: "查看当前可用的定时触发器。",
capabilities: ["planning"],
source: "aster_builtin",
tags: ["trigger", "schedule"],
input_examples_count: 1,
},
{
name: "CronDelete",
description: "删除指定的定时触发器。",
capabilities: ["planning"],
source: "aster_builtin",
tags: ["trigger", "schedule"],
input_examples_count: 1,
},
] as const;
const catalogTools = toolSpecs.map((tool) => ({
name: tool.name,
profiles: ["core"],
capabilities: [...tool.capabilities],
lifecycle: "current",
source: tool.source,
permission_plane: "session_allowlist",
workspace_default_allow: true,
execution_warning_policy: "none",
execution_warning_policy_source: "default",
execution_restriction_profile:
tool.execution_restriction_profile || "none",
execution_restriction_profile_source: "default",
execution_sandbox_profile: "none",
execution_sandbox_profile_source: "default",
}));
const registryTools = toolSpecs.map((tool) => ({
name: tool.name,
description: tool.description,
catalog_entry_name: tool.name,
catalog_source: tool.source,
catalog_lifecycle: "current",
catalog_permission_plane: "session_allowlist",
catalog_workspace_default_allow: true,
catalog_execution_warning_policy: "none",
catalog_execution_warning_policy_source: "default",
catalog_execution_restriction_profile:
tool.execution_restriction_profile || "none",
catalog_execution_restriction_profile_source: "default",
catalog_execution_sandbox_profile: "none",
catalog_execution_sandbox_profile_source: "default",
deferred_loading: false,
always_visible: true,
allowed_callers: [caller],
tags: [...tool.tags],
input_examples_count: tool.input_examples_count,
caller_allowed: true,
visible_in_context: true,
}));
const extensionSurfaces = surface.browser_assist
? [
{
extension_name: "lime-browser",
extension_name: "mcp__lime-browser",
description: "浏览器协助桥接工具集。",
source_kind: "mcp_bridge",
deferred_loading: false,
allowed_caller: caller,
available_tools: ["mcp__lime-browser__navigate"],
always_expose_tools: ["mcp__lime-browser__navigate"],
available_tools: ["navigate"],
always_expose_tools: ["navigate"],
loaded_tools: ["mcp__lime-browser__navigate"],
searchable_tools: ["mcp__lime-browser__navigate"],
},
@@ -1388,7 +1496,7 @@ function buildMockAgentRuntimeToolInventory(request?: {
{
name: "mcp__lime-browser__navigate",
description: "导航到目标网页。",
extension_name: "lime-browser",
extension_name: "mcp__lime-browser",
source_kind: "mcp_bridge",
deferred_loading: false,
allowed_caller: caller,
@@ -1675,6 +1783,7 @@ const defaultMocks: Record<string, any> = {
workspace_preferences: {
schema_version: 1,
media_defaults: {},
companion_defaults: {},
},
navigation: {
schema_version: 1,
@@ -3279,61 +3388,6 @@ const defaultMocks: Record<string, any> = {
// API Key Provider 相关
get_api_key_providers: () => [],
get_api_key_provider: () => null,
claw_solution_list: () => mockClawSolutionCatalog,
claw_solution_detail: (args: any) => {
const solution = getMockClawSolution(args?.solutionId ?? "");
return {
...solution,
starterPrompt: buildMockClawSolutionPrompt(solution.id),
themeTarget:
solution.id === "social-post-starter" ? "social-media" : undefined,
followupMode:
solution.id === "browser-assist-task"
? "browser_assist"
: solution.id === "team-breakdown"
? "team_runtime"
: "iterative",
capabilityTags:
solution.id === "browser-assist-task"
? ["browser", "automation"]
: solution.id === "team-breakdown"
? ["team", "decomposition"]
: solution.id === "social-post-starter"
? ["social-media", "draft"]
: ["general"],
};
},
claw_solution_check_readiness: (args: any) => {
const solution = getMockClawSolution(args?.solutionId ?? "");
return {
solutionId: solution.id,
readiness: solution.readiness,
readinessMessage: solution.readinessMessage,
reasonCode: solution.reasonCode,
};
},
claw_solution_prepare: (args: any) => {
const solution = getMockClawSolution(args?.solutionId ?? "");
return {
solutionId: solution.id,
actionType:
solution.id === "social-post-starter"
? "navigate_theme"
: solution.id === "browser-assist-task"
? "launch_browser_assist"
: solution.id === "team-breakdown"
? "enable_team_mode"
: "fill_input",
prompt: buildMockClawSolutionPrompt(solution.id, args?.context),
themeTarget:
solution.id === "social-post-starter" ? "social-media" : undefined,
shouldLaunchBrowserAssist: solution.id === "browser-assist-task",
shouldEnableTeamMode: solution.id === "team-breakdown",
readiness: solution.readiness,
readinessMessage: solution.readinessMessage,
reasonCode: solution.reasonCode,
};
},
add_custom_api_key_provider: () => ({ success: true }),
update_api_key_provider: () => ({ success: true }),
delete_custom_api_key_provider: () => ({ success: true }),