# Tool controller 把 body.sessionId merge 进 input → 用户传的同名 input field 被覆盖

**日期**：2026-05-17
**触发场景**：实现 SendMessage tool，让父 agent 给指定 sub-session 发消息；E2E 测试时 child session 收不到，错误"不能给当前 session 自己发"

## 现象

新增 `SendMessage` tool，schema：
```ts
inputSchema: {
  sessionId: { type: 'string', required: true, ... },  // ← 目标 session id
  prompt:    { type: 'string', required: true, ... },
}
```

调用：
```bash
POST /agent/tools/SendMessage {
  "input":     { "sessionId": "<child>", "prompt": "..." },
  "sessionId": "<parent>"
}
```

期望：`inv.input.sessionId === <child>`，`inv.sessionId === <parent>`，工具能在 child 上发 message。

实际：工具拒绝 "target === current"。打印 `inv.input.sessionId` 输出竟然是 **`<parent>`** 不是 `<child>`。

## 怎么定位

看 `controllers/tools.controller.ts` invoke 处：

```ts
return this.toolRegistry.invoke(name, {
  organizationId: orgId,
  userId,
  sessionId: body.sessionId,
  turnId: body.turnId,
  input: { ...body.input, ...(body.sessionId ? { sessionId: body.sessionId } : {}) },
  //                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  //                       这一段把 body.sessionId merge 进 input，**覆盖用户传的 input.sessionId**
});
```

这个 merge 行为本来是给 scratchpad_write 等内部工具用的（"工具不需要 LLM 显式传 sessionId，自动注入"），但**任何 input.sessionId 字段都会被覆盖**，包括 SendMessage 这种"需要传别的 session id"的工具。

## 预防 / 修法

**短期**：tool input 不要用 `sessionId` 作字段名。改 `targetSessionId` 或 `destSessionId`。
SendMessage 改后立刻通过 E2E。

**长期建议**（写到 follow-up）：
- 改 controller 的 merge 顺序：`{ ...{sessionId:body.sessionId}, ...body.input }`，让用户传的优先；
  - 副作用：scratchpad_write 这种"想被自动注入"的工具如果 LLM 自己传了同名 field 会出错——需要在那些工具自己写默认值兜底
- 或者用 `_session` / `_meta` 前缀，明确"controller 注入字段不污染 user input namespace"

## 项目侧 root cause

`tools.controller.ts:91` 的 merge spread 顺序是隐式契约，没文档说"input 里不能有 sessionId 字段"。任何后续新写 tool 的人会踩同坑。

## 元规则

> **任何 controller 自动注入 input 字段的位置，必须文档化"工具 inputSchema 禁止使用哪些
> 字段名"**。或者改 spread 顺序让"用户传的赢"，再让需要 default 的工具自己写兜底。
> 现在 sessionId 是已知冲突；turnId 也大概率冲突；organizationId / userId 不在 input 里
> 所以暂时安全。

跟 [[markdown_wrapper_no_pre_wrap]]（隐式行为踩坑） / [[html_regex_must_verify]] 同类。

## 影响范围

历史所有 tool 的 inputSchema 都没有 sessionId 字段（grep 确认），所以以前没人踩。
SendMessage 是第一个想用 input.sessionId 的工具。

立即 fix 完成（commit `1a05c352`）。
