关于 integrate-fusion-agent
integrate-fusion-agent 是一项技能,指导开发者为 Cognite Fusion 应用接入 Atlas 侧边栏和 Agent 能力,涵盖主机连接、上下文消息以及应用资源与操作。
主要功能
- 01连接到 Fusion 主机应用
- 02按上下文打开 Atlas 侧边栏
- 03发送包含应用上下文的消息
- 04定义 Agent 资源与操作
- 05修改操作要求事先批准
适用场景
- 01为 Fusion 应用添加 Atlas 启动入口
- 02将所选项目的上下文发送给 Atlas
- 03向 Agent 提供应用状态和操作
MCP 相关
该技能说明如何通过 `@cognite/app-sdk` 注册 Agent 服务,并向主机 Agent 提供资源和操作。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ cognitedata/ builder-skills --skill integrate-fusion-agent安装后在 Claude Code 中输入/integrate-fusion-agent确认已加载。
详细介绍
集成 Atlas / EOS 侧边栏
默认 AI 路径:通过 @cognite/app-sdk 使用平台 Atlas 侧边栏(EOS / Fusion PAIA)。不要嵌入 useAtlasChat、供应商提供的 atlas-agent,也不要调用第三方 LLM API。
仅当用户明确要求应用内聊天,并且 connectToHostApp 无法提供侧边栏时,才使用 integrate-atlas-chat(独立应用;始终会拒绝)。没有对应的 manifest 字段。
只实现所需功能:
- 打开 — Topbar Atlas 按钮;应用内触发器使用
sendAgentLayoutMode - 发送消息 — 使用
sendAgentMessage注入上下文 - 服务端 — 资源(应用状态)和操作(工具)
步骤 0 — 阅读应用
package.json— 包管理器、@cognite/app-sdksrc/App.tsx— 结构、现有 SDK 用法
询问用户需要三项能力中的哪些。除非用户已坚持要求,否则不要提供应用内聊天。
步骤 1 — 安装
pnpm add @cognite/app-sdk(或 npm/yarn)。最低版本为 0.3.1。
步骤 2 — 连接到宿主应用
在 Fusion 之外(独立运行 vite dev)时,connectToHostApp 会拒绝连接。捕获该错误;当 api 为 null 时,隐藏 Agent 触发器。
Comlink 代理可调用 — setApi(proxy) 会让 React 将代理视为更新函数,并存储一个 Promise。始终使用 setApi(() => resolvedApi)。
// src/hooks/useHostApp.ts
import { useState, useEffect } from 'react';
import { connectToHostApp, type HostAppAPI } from '@cognite/app-sdk';
export function useHostApp(): HostAppAPI | null {
const [api, setApi] = useState<HostAppAPI | null>(null);
useEffect(() => {
connectToHostApp({ applicationName: 'my-app' })
.then(({ api: resolvedApi }) => setApi(() => resolvedApi))
.catch(() => { /* outside Fusion — no-op */ });
}, []);
return api;
}
在根组件中调用;将 api 向下传递或通过 context 传递。typeof proxy.method === 'function' 始终为 true — 不要使用 typeof 进行功能检测;应使用 try/catch。
步骤 3 — 打开侧边栏
首选启动器:Aura Topbar Atlas(systemActions.atlas.visible: true,参见 use-topbar)。不要添加第二个“打开助手”控件。
sendAgentLayoutMode 仅用于上下文触发器(sidebar | fullscreen | closed):
await api.sendAgentLayoutMode({ mode: 'sidebar' });
步骤 4 — 发送消息
与 sendAgentLayoutMode 配合使用。对于来自某个条目的新任务,设置 newSession: true;若要继续当前对话,则省略该参数。在消息中提供名称/ID/状态 — 通过侧边栏进行一轮交互,而不是针对查询行生成 N 次补全。
await api.sendAgentLayoutMode({ mode: 'sidebar' });
await api.sendAgentMessage({
message: `Analyse the schedule for "${itemName}" and suggest how to reduce total duration.`,
newSession: true,
});
步骤 5 — Agent 服务端
在挂载时注册,在卸载时注销。工厂函数以服务为参数,因此无需 React 即可进行单元测试:
src/features/agent/
agentActions.ts — (deps) => Action[]
agentResources.ts — (deps) => Resource[]
useAgentServer.ts — register / unregister
资源的 read() 返回 { type: 'json', data }(首选)或 { type: 'text', text }。description 应写成文档字符串。
// src/features/agent/agentResources.ts
import { createAgentResource } from '@cognite/app-sdk';
export function buildAgentResources(storage: StorageService) {
return [
createAgentResource({
uri: 'my-app://current-state',
name: 'Current application state',
description:
'Items currently visible, their statuses, and active filters. Read before answering questions about what the user is looking at.',
async read() {
return [{ type: 'json', data: storage.getAll() }];
},
}),
];
}
操作:使用 snake_case 名称、Zod 参数,并为每个字段调用 .describe()。Agent 不会在调用前确认 — 修改数据的操作必须在 description 中说明这一点,并要求事先获得用户批准。
// src/features/agent/agentActions.ts
import { createAgentAction } from '@cognite/app-sdk';
import { z } from 'zod';
export function buildAgentActions(dataService: DataService) {
return [
createAgentAction({
name: 'get_item_details',
description: 'Full details for an item by ID, including history.',
parameters: z.object({
item_id: z.string().describe('The ID of the item to retrieve'),
}),
async handler({ item_id }) {
const item = await dataService.getItem(item_id);
return { content: [{ type: 'json', data: item }] };
},
}),
];
}
createAgentAction({
name: 'update_item_status',
description:
'Update item status. Call ONLY when the user has explicitly approved the change.',
parameters: z.object({
item_id: z.string().describe('The item to update'),
status: z.enum(['active', 'closed', 'pending']).describe('The new status'),
}),
async handler({ item_id, status }) {
storage.updateStatus(item_id, status);
return { content: [{ type: 'json', data: { success: true } }] };
},
})
// src/features/agent/useAgentServer.ts
import { useEffect } from 'react';
import { createAgentServer, registerAgentServer, type HostAppAPI } from '@cognite/app-sdk';
import { buildAgentActions } from './agentActions';
import { buildAgentResources } from './agentResources';
import { useStorageService } from '../storage/StorageServiceContext';
import { useDataService } from '../data/DataServiceContext';
export function useAgentServer(api: HostAppAPI | null): void {
const storage = useStorageService();
const dataService = useDataService();
useEffect(() => {
if (!api) return;
const server = createAgentServer({
uri: 'my-app', // Fusion namespaces with instance ID
actions: buildAgentActions(dataService),
resources: buildAgentResources(storage),
});
void registerAgentServer(api, server).catch((err: unknown) => {
console.warn('[agent] registerAgentServer failed:', err);
});
return () => {
void api.unregisterAgentServer('my-app').catch((err: unknown) => {
console.warn('[agent] unregisterAgentServer failed:', err);
});
};
}, [api, storage, dataService]);
}
步骤 6 — 连接起来
function App() {
const api = useHostApp();
useAgentServer(api);
return (
<AppLayout>
<MainContent onAnalyseItem={async (item) => {
if (!api) return;
await api.sendAgentLayoutMode({ mode: 'sidebar' });
await api.sendAgentMessage({
message: `Analyse "${item.name}" (id: ${item.id}).`,
newSession: true,
});
}} />
</AppLayout>
);
}
直接测试工厂函数:
const [getItemAction] = buildAgentActions({
getItem: vi.fn().mockResolvedValue({ id: '1', name: 'Test' }),
});
const result = await getItemAction.handler({ item_id: '1' });
expect(result.content[0].data).toEqual({ id: '1', name: 'Test' });
硬性限制 — 对查询结果进行 LLM 调用
不要针对 DMS/SDK 行映射聊天补全(Atlas agents/chat、OpenAI/Anthropic、useAtlasChat().send)。优先使用一次 sendAgentMessage,或使用侧边栏 Agent 可读取的资源。
如果按条目补全是明确的产品需求(默认不需要):
| 规则 | 限制 |
|---|---|
| 默认值 | 每次用户发起的操作最多 5 次补全 |
| 上限 | 50 — 生成的代码绝不能超过此上限 |
| 缓存 | space:externalId:lastUpdatedTime;命中缓存不消耗预算 |
| 批处理 | 一个提示词涵盖 N 个条目,而不是 N 次调用 |
| 触发条件 | 仅由用户发起 — 绝不在渲染、轮询或无界列表上触发 |
| 用户体验 | 告知用户结果集因达到上限而被截断 |
禁止:items.map((row) => complete(row))、对查询页面中的条目使用 Promise.all 执行补全。
检查清单
- Topbar Atlas 启动器(
use-topbar);无应用内聊天小组件 -
@cognite/[email protected]+;setApi(() => resolvedApi);捕获 Fusion 之外的拒绝错误 -
api为 null 时隐藏触发器;使用.catch()注册/注销服务端 - 资源描述说明内容/时机;操作名称使用
snake_case;修改数据的操作需要事先获得批准 - 工厂函数以服务为参数;对行进行 LLM 调用时设上限(5 / 最多 50),并在需要时使用缓存