From 3bda1cb5ae6f7a9ad2f363e9c94af27f8b680aed Mon Sep 17 00:00:00 2001 From: Xiaoxue Sun <54162759+xiaoxueSunn@users.noreply.github.com> Date: Wed, 29 Jul 2026 12:05:13 +0800 Subject: [PATCH 1/3] =?UTF-8?q?fix(claude-code):=20=E9=BB=98=E8=AE=A4?= =?UTF-8?q?=E9=80=8F=E4=BC=A0=20/effort=20=E5=91=BD=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/adapters/cli/claude-code.ts | 2 +- test/command-handler.test.ts | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/adapters/cli/claude-code.ts b/src/adapters/cli/claude-code.ts index 188cb6401..dda6d736d 100644 --- a/src/adapters/cli/claude-code.ts +++ b/src/adapters/cli/claude-code.ts @@ -1149,7 +1149,7 @@ export function createClaudeFamilyAdapter(variant: ClaudeFamilyVariant, rawBin: // `botmux session-ready` 给出启动 selector 边界。worker 收到后清掉旧 // readyPattern 证据,并等待新 prompt 再投首条消息。 injectsReadyHook: true, - defaultPassthroughCommands: variant.id === 'claude-code' ? ['/goal'] : undefined, + defaultPassthroughCommands: variant.id === 'claude-code' ? ['/goal', '/effort'] : undefined, // Seed shares most of this adapter but has not been verified to expose the // same native session-rename command. Keep the capability exact to Claude. buildSessionRenameCommand: variant.id === 'claude-code' diff --git a/test/command-handler.test.ts b/test/command-handler.test.ts index c6a862bdf..f18dc1257 100644 --- a/test/command-handler.test.ts +++ b/test/command-handler.test.ts @@ -979,6 +979,12 @@ describe('PASSTHROUGH_COMMANDS set', () => { expect(resolvePassthroughCommands('app-2').has('/goal')).toBe(true); }); + it('enables /effort only for the Claude Code adapter', () => { + expect(PASSTHROUGH_COMMANDS.has('/effort')).toBe(false); + expect(resolvePassthroughCommands('app-1').has('/effort')).toBe(true); + expect(resolvePassthroughCommands('app-2').has('/effort')).toBe(false); + }); + it('does not expose Codex interactive /title through the Lark channel', () => { expect(PASSTHROUGH_COMMANDS.has('/title')).toBe(false); expect(DAEMON_COMMANDS.has('/title')).toBe(false); From 16e38cf6d151098ea568b5c8df076ac569274eb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B3=E6=99=97?= Date: Wed, 29 Jul 2026 01:29:57 -0700 Subject: [PATCH 2/3] =?UTF-8?q?refactor(command-handler):=20/effort=20?= =?UTF-8?q?=E6=94=B9=E7=94=B1=E5=85=A8=E5=B1=80=20PASSTHROUGH=5FCOMMANDS?= =?UTF-8?q?=20=E6=94=BE=E5=BC=80=E7=BB=99=E6=89=80=E6=9C=89=20CLI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原改动把 /effort 加进 claude-code adapter 的 defaultPassthroughCommands, 只对 Claude Code 生效。按维护者要求「全局放开、其它 CLI 早晚也支持」,改为 把 /effort 提升到全局 PASSTHROUGH_COMMANDS 集合,并把它从 adapter 层移除。 为什么放全局而非逐 adapter 加: - 一处改动,所有 CLI(含未来新增)自动继承 —— 前向兼容「早晚都支持」; 全局集合本就是「尽力透传」语义(/plugin /mcp /btw 也非所有 CLI 支持), CLI 认得就生效、认不得顶多回 unknown-command(不崩溃/不损坏/不泄露)。 - 冷启动语义更干净:isInitialSessionPassthrough 只认 adapter 层的 defaultPassthroughCommands,不认全局集合。/effort 是「调档」而非「开一段 工作」的命令,进全局天然不获得空 topic 冷启动能力(空话题单发 /effort 不会凭空拉起会话)。/goal 仍留在 adapter 层,保留其冷启动语义。 影响面: - 仅改 slash 路由的命令归属(adapter 层 → 全局集合);未动公共输入、 PTY/tmux 投递、会话恢复、sandbox 路径。 - Claude Code(2.1.220+)/ Seed / Relay 原生支持 /effort;Codex 亦有 reasoning effort。其它 CLI 无此命令时最坏为 unknown-command UX。 - 已知 pre-existing 非阻塞点:codex 若开 hybrid RPC 模式(默认关), raw_input 会被纯 viewer pane 静默吞掉 —— 但现有 /goal /btw /model 同样 走此路径,本改动只是把既有 gap 多扩一个命令,非新问题、不崩溃。 验证: - pnpm build 通过。 - pnpm vitest run test/command-handler.test.ts:209/209 通过。 - 测试更新:`enables /effort only for the Claude Code adapter` 重写为 `exposes /effort globally to every CLI`(app-1/app-2/无 bot 均含 /effort); `should contain expected slash commands` 加入 /effort。 - 变异测试:删除全局 /effort 后新测试如期失败,证断言有效。 Co-Authored-By: Riff --- src/adapters/cli/claude-code.ts | 5 ++++- src/core/passthrough-commands.ts | 10 ++++++++++ test/command-handler.test.ts | 15 ++++++++++----- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/adapters/cli/claude-code.ts b/src/adapters/cli/claude-code.ts index dda6d736d..a50ab92bd 100644 --- a/src/adapters/cli/claude-code.ts +++ b/src/adapters/cli/claude-code.ts @@ -1149,7 +1149,10 @@ export function createClaudeFamilyAdapter(variant: ClaudeFamilyVariant, rawBin: // `botmux session-ready` 给出启动 selector 边界。worker 收到后清掉旧 // readyPattern 证据,并等待新 prompt 再投首条消息。 injectsReadyHook: true, - defaultPassthroughCommands: variant.id === 'claude-code' ? ['/goal', '/effort'] : undefined, + // `/effort` 不在此处——它是全局 PASSTHROUGH_COMMANDS 的成员(所有 CLI 尽力透传, + // 且刻意不带冷启动语义)。这里只保留 `/goal`:它是「开启一段目标工作」的命令,需要 + // 空 topic 冷启动能力(isInitialSessionPassthrough 只认 adapter 层的这个字段)。 + defaultPassthroughCommands: variant.id === 'claude-code' ? ['/goal'] : undefined, // Seed shares most of this adapter but has not been verified to expose the // same native session-rename command. Keep the capability exact to Claude. buildSessionRenameCommand: variant.id === 'claude-code' diff --git a/src/core/passthrough-commands.ts b/src/core/passthrough-commands.ts index e3c160ea9..df1a935c4 100644 --- a/src/core/passthrough-commands.ts +++ b/src/core/passthrough-commands.ts @@ -27,6 +27,16 @@ export const PASSTHROUGH_COMMANDS = new Set([ '/code-review', '/security-review', '/review', // Codex:/btw 向当前会话追加一条旁注/引导消息 '/btw', + // 推理强度调档。放全局(而非某个 adapter 的 defaultPassthroughCommands)是刻意的: + // ① 这里的命令本就是「尽力透传」——/plugin /mcp /btw 也并非所有 CLI 都支持, + // CLI 认得就生效、认不得顶多回一句 unknown-command(不崩溃 / 不损坏 / 不泄露)。 + // Claude Code(2.1.220+) / Seed / Relay 原生支持 /effort,Codex 亦有 reasoning + // effort;未来别的 CLI 补上后零改动自动生效,无需再逐个 adapter 加。 + // ② 全局集合刻意不带「空 topic 冷启动」能力(那只认 adapter 层的 + // defaultPassthroughCommands,见 isInitialSessionPassthrough)——/effort 是 + // 「调档」而非「开一段工作」的命令,空话题里单发 /effort 不应凭空拉起会话。 + // 对照 /goal(开启目标工作)仍留在 adapter 层,保留其冷启动语义。 + '/effort', ]); /** diff --git a/test/command-handler.test.ts b/test/command-handler.test.ts index f18dc1257..3b4789118 100644 --- a/test/command-handler.test.ts +++ b/test/command-handler.test.ts @@ -955,7 +955,7 @@ describe('/vc preparation command', () => { describe('PASSTHROUGH_COMMANDS set', () => { it('should contain expected slash commands forwarded to CLI', () => { - for (const cmd of ['/compact', '/model', '/clear', '/plugin', '/usage', '/context', '/cost', '/mcp', '/diff', '/btw']) { + for (const cmd of ['/compact', '/model', '/clear', '/plugin', '/usage', '/context', '/cost', '/mcp', '/diff', '/btw', '/effort']) { expect(PASSTHROUGH_COMMANDS.has(cmd), `Expected PASSTHROUGH_COMMANDS to contain ${cmd}`).toBe(true); } }); @@ -979,10 +979,15 @@ describe('PASSTHROUGH_COMMANDS set', () => { expect(resolvePassthroughCommands('app-2').has('/goal')).toBe(true); }); - it('enables /effort only for the Claude Code adapter', () => { - expect(PASSTHROUGH_COMMANDS.has('/effort')).toBe(false); - expect(resolvePassthroughCommands('app-1').has('/effort')).toBe(true); - expect(resolvePassthroughCommands('app-2').has('/effort')).toBe(false); + it('exposes /effort globally to every CLI (best-effort passthrough)', () => { + // /effort 放在全局 PASSTHROUGH_COMMANDS,而非某个 adapter 的 defaultPassthroughCommands + // ——所有 CLI 都尽力透传(Claude 家族 / Codex 原生支持;其它 CLI 认不得顶多回 + // unknown-command,不崩溃)。未来新 CLI 零改动自动继承。 + expect(PASSTHROUGH_COMMANDS.has('/effort')).toBe(true); + expect(resolvePassthroughCommands('app-1').has('/effort')).toBe(true); // claude-code + expect(resolvePassthroughCommands('app-2').has('/effort')).toBe(true); // codex + // 无 bot 上下文时也回落到全局集合,仍含 /effort。 + expect(resolvePassthroughCommands(undefined).has('/effort')).toBe(true); }); it('does not expose Codex interactive /title through the Lark channel', () => { From 77d30c69742ff9ed583ff2db2dc0a9343fa5f3d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B3=E6=99=97?= Date: Wed, 29 Jul 2026 01:39:30 -0700 Subject: [PATCH 3/3] =?UTF-8?q?test(command-handler):=20=E9=94=81=E4=BD=8F?= =?UTF-8?q?=20/effort=20=E4=B8=8D=E8=BF=9B=20adapter=20=E5=B1=82=E7=9A=84?= =?UTF-8?q?=E5=86=B7=E5=90=AF=E5=8A=A8=E8=AF=AD=E4=B9=89=E6=8A=A4=E6=A0=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 采纳 codex 复审建议补强。原有 `/effort` 测试只断言「全局可见」 (resolvePassthroughCommands 各 CLI 都含 /effort),但锁不住本次核心语义: /effort 必须留在全局 PASSTHROUGH_COMMANDS、绝不进 adapter 层的 defaultPassthroughCommands —— 因为冷启动能力(空 topic 里发命令能否拉起 新会话)只认 adapter 层(见 isInitialSessionPassthrough → resolveAdapterDefaultPassthroughCommands),不认全局集合。 盲区:若日后有人误把 /effort 加回某 adapter 的 default,全局也有 /effort, resolvePassthroughCommands 层的可见性断言仍会全绿,回归无人察觉。 新增 `keeps /effort OUT of the adapter default layer` 直接断言 resolveAdapterDefaultPassthroughCommands('app-1'/'app-2') 不含 /effort、 但含 /goal(/goal 是「开启目标工作」命令,刻意保留 adapter 层冷启动语义)。 验证(变异测试确认护栏有效): - 把 /effort 误加回 claude-code adapter default 后: · 旧「全局可见」测试仍全绿(证明它抓不住该回归); · 新护栏测试如期失败(证明它精确锁住语义)。 - pnpm build 通过;pnpm vitest run test/command-handler.test.ts:210/210 通过。 Co-Authored-By: Riff --- test/command-handler.test.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/test/command-handler.test.ts b/test/command-handler.test.ts index 3b4789118..3db33fb8f 100644 --- a/test/command-handler.test.ts +++ b/test/command-handler.test.ts @@ -454,7 +454,7 @@ vi.mock('../src/services/card-mode-store.js', () => ({ // ─── Imports (after mocks) ────────────────────────────────────────────────── -import { DAEMON_COMMANDS, SESSIONLESS_DAEMON_COMMANDS, PASSTHROUGH_COMMANDS, resolvePassthroughCommands, handleCommand, handleCardCommand, handleTermLinkCommand, parseSlashCommandInvocation, parseForceTopicInvocation } from '../src/core/command-handler.js'; +import { DAEMON_COMMANDS, SESSIONLESS_DAEMON_COMMANDS, PASSTHROUGH_COMMANDS, resolvePassthroughCommands, resolveAdapterDefaultPassthroughCommands, handleCommand, handleCardCommand, handleTermLinkCommand, parseSlashCommandInvocation, parseForceTopicInvocation } from '../src/core/command-handler.js'; import { setCardMode } from '../src/services/card-mode-store.js'; import { writeRoleFile, deleteRoleFile, writeTeamRoleFile, deleteTeamRoleFile, resolveRole, resolveRoleFile } from '../src/core/role-resolver.js'; import { setBotCapability, clearBotCapability } from '../src/services/bot-profile-store.js'; @@ -990,6 +990,21 @@ describe('PASSTHROUGH_COMMANDS set', () => { expect(resolvePassthroughCommands(undefined).has('/effort')).toBe(true); }); + it('keeps /effort OUT of the adapter default layer so it never gains cold-start', () => { + // 核心语义护栏:冷启动能力(空 topic 里发命令能否拉起新会话)只认 adapter 层的 + // defaultPassthroughCommands(见 daemon.ts 的 isInitialSessionPassthrough → + // resolveAdapterDefaultPassthroughCommands),不认全局 PASSTHROUGH_COMMANDS。 + // /effort 是「调档」而非「开一段工作」的命令,必须留在全局层、绝不进 adapter 层, + // 否则空话题单发 /effort 会凭空 spawn 一个没活干的会话。这条断言锁住该语义: + // 即使有人日后误把 /effort 加回某个 adapter 的 default,resolvePassthroughCommands + // 层的可见性测试仍会全绿(全局也有),唯有这里能抓住回归。 + expect(resolveAdapterDefaultPassthroughCommands('app-1')).not.toContain('/effort'); // claude-code + expect(resolveAdapterDefaultPassthroughCommands('app-2')).not.toContain('/effort'); // codex + // /goal 相反:它是「开启目标工作」的命令,刻意留在 adapter 层保留冷启动语义。 + expect(resolveAdapterDefaultPassthroughCommands('app-1')).toContain('/goal'); + expect(resolveAdapterDefaultPassthroughCommands('app-2')).toContain('/goal'); + }); + it('does not expose Codex interactive /title through the Lark channel', () => { expect(PASSTHROUGH_COMMANDS.has('/title')).toBe(false); expect(DAEMON_COMMANDS.has('/title')).toBe(false);