# QQ 指令通道（qq-bridge 补丁）

给 qq-bridge 加一条**只对主人开放**的私有指令通道：主人在 QQ 私聊里发「指令: xxx」，
由独立会话执行并把结果发回；群友、陌生人、主人的普通闲聊一律不受影响。

## 为什么这么做

- 群里有 131 人，不能把执行权开放给任何能发消息的人
- 指令会话和人设会话隔离，不会污染角色上下文
- 复用桥接已有的 learnerSessions 事件收集，不重造轮子

## 接入步骤

1. 在 `src/bridge.js` 的 `learnerWaiters` 声明之后加入下面这段
2. 把 `bot.onPrivateMessage` 换成带指令拦截的版本
3. `config.json` 增加 `agentCommandPreset: "standard"`（可选 `agentCommandTimeoutMs`）
4. 重启桥接

## 核心代码

```js
const COMMAND_PREFIX_RE = /^\s*指令\s*[:：]\s*([\s\S]+)$/;
const COMMAND_SESSION_FILE = path.join(STATE_DIR, 'command-session.json');
const COMMAND_PRESET = String(cfg.agentCommandPreset || 'standard');

function matchOwnerCommand(event) {
  const owner = cfg.ownerQQ;
  if (owner == null || String(owner) === '') return null;
  if (String(event?.user_id ?? '') !== String(owner)) return null;
  if (String(event?.message_type ?? 'private') !== 'private') return null;
  const m = String(event?.raw_message ?? '').match(COMMAND_PREFIX_RE);
  return m ? m[1].trim() || null : null;
}
```

```js
bot.onPrivateMessage(async (event) => {
  if (String(event?.user_id ?? '') === String(event?.self_id ?? '')) return;
  const cmd = matchOwnerCommand(event);
  if (cmd) { void dispatchCommand(event.user_id, cmd); return; }  // 不进正常聊天
  await handleIncoming('private', event.user_id, event, cfg);
});
```

执行侧：`ensureCommandSession()` 用 `api.sessions.create({ agentPreset: 'standard' })` 建独立会话，
`runCommand()` 走 `api.sessions.prompt` + `waitLearnerTurn()` 拿回复。

## 安全边界

| 条件 | 是否执行 |
|---|---|
| 主人在私聊 | ✅ |
| 主人在群聊 | ❌（handler 只挂私聊） |
| 群友在群里 | ❌ |
| 陌生人在私聊 | ❌ |
| 主人不带「指令:」前缀 | ❌（走正常聊天） |

## 自测

```bash
curl -X POST "http://127.0.0.1:3100/api/command/run?token=$(cat state/console-token)" \
  -H 'content-type: application/json' \
  -d '{"text":"现在几点了"}'
```
