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
5 changes: 3 additions & 2 deletions examples/smart-cockpit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,9 @@ A preflight validates the Realtime configuration and all four ports before any c
backend Agent loads these workflows and composes existing MCP tools; they are
not dynamic MCP plugins, A2A Agent Card entries, or globally installed Agent Skills.
- The foreground Agent owns realtime conversation and directly calls weather,
vehicle-state, window, and headlight tools through standard MCP. Explicit
window and headlight commands execute inline without a redundant second confirmation.
vehicle-state, window, sunroof, headlight, and climate tools through standard
MCP. Explicit single vehicle-control commands execute inline without a redundant
second confirmation.
- Other cockpit work goes through the fixed `spawn_thinking` bridge. The example
backend attaches over A2A, and Qwen3.8-Flash discovers the complete backend MCP
surface for vehicle control, navigation, music, flash-buy, and custom workflows,
Expand Down
4 changes: 2 additions & 2 deletions examples/smart-cockpit/README_ZH.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ npm run example:smart-cockpit
- UI 通过场景自己的 HTTP/SSE 通道展示车辆、路线、音乐、天气和订单状态,以及细粒度场景进度;Gateway 不解析这些对象。
- 用户可以通过语音创建和运行持久化的座舱自定义技能;技能是按座舱隔离的用户工作流,
由后台 Agent 加载后编排现有 MCP 工具。它不是动态 MCP 插件、A2A Agent Card 或全局 Agent Skill。
- 前台 Agent 负责实时聊天,通过标准 MCP 直接调用天气、车况、车窗和大灯工具;
用户明确说出的车窗和大灯指令直接执行,不再增加重复确认。
- 前台 Agent 负责实时聊天,通过标准 MCP 直接调用天气、车况以及车窗、天窗、
大灯和空调工具;用户明确说出的单次车辆控制指令直接执行,不再增加重复确认。
- 其他座舱任务通过固定的 `spawn_thinking` 桥梁提交给后台。示例后台通过 A2A
接入 Gateway,Qwen3.8-Flash 会发现并调用独立的后台 MCP 工具面,完成车控、
导航、音乐、闪购和自定义技能任务,包括有序的多途经点导航。
Expand Down
4 changes: 2 additions & 2 deletions examples/smart-cockpit/agent/executor.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ const CUSTOM_SKILL_LIST_TOOL = 'custom_skill_list'
export const COCKPIT_AGENT_PROMPT = `你是智能座舱的后台 Agent,负责理解并执行座舱任务。

规则:
- 单次车况查询、车窗和大灯操作通常由前台低延迟处理;当它们属于后台收到的组合任务或自定义技能时,仍须使用提供的工具真实执行。
- 天窗、空调、导航、音乐、闪购和自定义技能操作必须使用提供的工具,不得假装已经执行。
- 单次车况查询及车窗、天窗、大灯、空调操作通常由前台低延迟处理;当车辆操作属于后台收到的组合任务或自定义技能时,仍须使用提供的工具真实执行。
- 导航、音乐、闪购、自定义技能及后台收到的车辆操作必须使用提供的工具,不得假装已经执行。
- 复杂请求可以连续调用多个工具;严格按照用户表达的先后顺序执行。
- 导航请求可以包含多个有序途经点。将中间地点放入 waypoints,最后一个地点作为 destination。
- 用户明确说“导航到”“带我去”“去某地”或“开始导航”时直接调用 navigation_start,成功后不要再次询问是否开始。
Expand Down
4 changes: 2 additions & 2 deletions examples/smart-cockpit/docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ cockpit-client ── GCP 6.0 ──► cockpit-gateway ── A2A ──► coc

- `cockpit-service` 是车辆、导航、音乐、天气和闪购状态的唯一来源。
- UI 通过 HTTP 获取快照、执行面板操作,通过 SSE 接收状态变化。
- Gateway 的前台 Agent 通过 `/mcp/frontend` 直接使用天气、车况、车窗和大灯工具;
明确的车窗和大灯口头指令直接执行,不增加重复确认。
- Gateway 的前台 Agent 通过 `/mcp/frontend` 直接使用天气、车况以及车窗、天窗、
大灯和空调工具;明确的单次车辆控制口头指令直接执行,不增加重复确认。
- 后台 Agent 通过 `/mcp/backend` 使用完整工具面,支持组合任务以及自定义技能的
发现、创建、加载和执行。
- 两个工具面由 `service/tools/registry.mjs` 显式组合,但共用同一份执行器和座舱状态;
Expand Down
8 changes: 8 additions & 0 deletions examples/smart-cockpit/gateway/frontend-mcp.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,17 @@
"enabled": true,
"description": "立即打开或关闭指定车窗;只在用户明确要求控制时调用,该指令即为本次操作授权。不要再通过 spawn_thinking 重复执行同一操作。"
},
"vehicle_sunroof_control": {
"enabled": true,
"description": "立即打开或关闭车辆天窗;只在用户明确要求控制时调用,该指令即为本次操作授权。不要再通过 spawn_thinking 重复执行同一操作。"
},
"vehicle_headlights_control": {
"enabled": true,
"description": "立即打开或关闭车辆大灯;只在用户明确要求控制时调用,该指令即为本次操作授权。不要再通过 spawn_thinking 重复执行同一操作。"
},
"vehicle_climate_control": {
"enabled": true,
"description": "立即打开或关闭空调,或设置用户明确指定的温度、制冷制热模式和风量;不要把状态查询当成控制指令,也不要再通过 spawn_thinking 重复执行同一操作。"
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion examples/smart-cockpit/gateway/spawn-thinking-tool.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export const COCKPIT_SPAWN_THINKING_DESCRIPTION = [
'将需要座舱 Agent 执行的任务异步提交到后台。',
'用于车辆天窗和空调控制,单点或多途经点导航与当前路线查询,音乐播放,以及示例闪购流程。车况查询、车窗和大灯控制由前台工具直接执行,不要提交到后台。',
'用于单点或多途经点导航与当前路线查询、音乐播放,以及示例闪购流程。天气、车况和单次车辆控制由前台工具直接执行,不要提交到后台。',
'复杂、多步骤或包含多个地点的座舱指令也应提交给后台,不要由前台自行判断不支持。',
'用户要求创建、更新或运行自定义座舱技能时也应提交给后台;objective 必须保留技能名称、触发语义和完整步骤。',
'闪购的搜索、加购预览和确认下单是不同任务;必须在 objective 中忠实保留用户选定的商品和当前动作,不要把加购改写为搜索。',
Expand Down
13 changes: 13 additions & 0 deletions examples/smart-cockpit/gateway/test/frontend-tools.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ test('calls selected cockpit tools inline through the frontend MCP client', asyn
weather: { enabled: true },
vehicle_state_query: { enabled: true },
vehicle_window_control: { enabled: true },
vehicle_sunroof_control: { enabled: true },
vehicle_headlights_control: { enabled: true },
vehicle_climate_control: { enabled: true },
},
},
},
Expand All @@ -42,7 +44,9 @@ test('calls selected cockpit tools inline through the frontend MCP client', asyn
'mcp__cockpit__weather',
'mcp__cockpit__vehicle_state_query',
'mcp__cockpit__vehicle_window_control',
'mcp__cockpit__vehicle_sunroof_control',
'mcp__cockpit__vehicle_headlights_control',
'mcp__cockpit__vehicle_climate_control',
])
const output = await client.execute('mcp__cockpit__weather', { city: '杭州' })
assert.match(output.text, /杭州,晴,27°/u)
Expand All @@ -53,12 +57,21 @@ test('calls selected cockpit tools inline through the frontend MCP client', asyn
await client.execute('mcp__cockpit__vehicle_headlights_control', {
action: 'open',
})
await client.execute('mcp__cockpit__vehicle_sunroof_control', {
action: 'open',
})
await client.execute('mcp__cockpit__vehicle_climate_control', {
action: 'set_temp',
temperature: 22,
})
const state = await client.execute('mcp__cockpit__vehicle_state_query', {
part: 'all',
})
assert.match(state.text, /主驾车窗: 开启/u)
assert.match(state.text, /大灯: 开启/u)
assert.equal(service.snapshot().weather.daytemp, '27')
assert.equal(service.snapshot().vehicle.windowFL, 1)
assert.equal(service.snapshot().vehicle.sunroof, 1)
assert.equal(service.snapshot().vehicle.headlights, 1)
assert.equal(service.snapshot().vehicle.acTemp, 22)
})
2 changes: 1 addition & 1 deletion examples/smart-cockpit/service/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ npm run example:smart-cockpit:service
Endpoints:

- `POST /mcp/frontend` — foreground MCP surface; weather, vehicle-state queries,
window control, and headlight control.
and window, sunroof, headlight, and climate control.
- `POST /mcp/backend` — complete backend Agent MCP surface for composed work,
including custom skill discovery, creation, and loading.
- `GET /api/cockpit/state?cockpitId=default` — current snapshot.
Expand Down
22 changes: 20 additions & 2 deletions examples/smart-cockpit/service/test/server.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,9 @@ test('scopes frontend tools while retaining a complete backend MCP surface', asy
'weather',
'vehicle_state_query',
'vehicle_window_control',
'vehicle_sunroof_control',
'vehicle_headlights_control',
'vehicle_climate_control',
])

const output = await backend.callTool({
Expand All @@ -210,16 +212,32 @@ test('scopes frontend tools while retaining a complete backend MCP surface', asy
assert.equal(window.isError, undefined)
assert.equal(window.structuredContent.vehicle.windowFL, 1)

const unavailable = await frontend.callTool({
const sunroof = await frontend.callTool({
name: 'vehicle_sunroof_control',
arguments: { action: 'open' },
})
assert.equal(sunroof.isError, undefined)
assert.equal(sunroof.structuredContent.vehicle.sunroof, 1)

const climate = await frontend.callTool({
name: 'vehicle_climate_control',
arguments: { action: 'close' },
arguments: { action: 'set_fan', fan: 4 },
})
assert.equal(climate.isError, undefined)
assert.equal(climate.structuredContent.vehicle.acFan, 4)

const unavailable = await frontend.callTool({
name: 'music_pause',
arguments: {},
})
assert.equal(unavailable.isError, true)
assert.match(unavailable.content[0].text, /not available on this MCP surface/u)

const state = await fetch(`${server.origin}/api/cockpit/state?cockpitId=mcp-car`)
.then(response => response.json())
assert.equal(state.vehicle.acTemp, 22)
assert.equal(state.vehicle.acFan, 4)
assert.equal(state.vehicle.sunroof, 1)
assert.equal(state.vehicle.windowFL, 1)
assert.equal(state.weather.dayweather, '晴')
})
Expand Down
2 changes: 2 additions & 0 deletions examples/smart-cockpit/service/tools/registry.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,9 @@ export const FRONTEND_TOOL_NAMES = Object.freeze([
'weather',
'vehicle_state_query',
'vehicle_window_control',
'vehicle_sunroof_control',
'vehicle_headlights_control',
'vehicle_climate_control',
])

const toolDefinitionsByName = new Map(
Expand Down
6 changes: 6 additions & 0 deletions examples/smart-cockpit/test/tool-surfaces.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ test('adds a foreground fast path while retaining a complete backend orchestrati
'weather',
'vehicle_state_query',
'vehicle_window_control',
'vehicle_sunroof_control',
'vehicle_headlights_control',
'vehicle_climate_control',
])
assert.equal(BACKEND_TOOL_DEFINITIONS.length, 18)
const backendNames = BACKEND_TOOL_DEFINITIONS.map(tool => tool.name)
Expand Down Expand Up @@ -42,10 +44,14 @@ test('binds the cockpit frontend profile to the scoped MCP configuration', () =>
'weather',
'vehicle_state_query',
'vehicle_window_control',
'vehicle_sunroof_control',
'vehicle_headlights_control',
'vehicle_climate_control',
])
assert.equal(config.servers.cockpit.tools.vehicle_window_control.enabled, true)
assert.ok(!('approval' in config.servers.cockpit.tools.vehicle_window_control))
assert.equal(config.servers.cockpit.tools.vehicle_climate_control.enabled, true)
assert.ok(!('approval' in config.servers.cockpit.tools.vehicle_climate_control))
})

test('keeps asynchronous cockpit acknowledgements natural and action-specific', () => {
Expand Down