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
3 changes: 3 additions & 0 deletions src/adapters/cli/claude-code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1149,6 +1149,9 @@ export function createClaudeFamilyAdapter(variant: ClaudeFamilyVariant, rawBin:
// `botmux session-ready` 给出启动 selector 边界。worker 收到后清掉旧
// readyPattern 证据,并等待新 prompt 再投首条消息。
injectsReadyHook: true,
// `/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.
Expand Down
10 changes: 10 additions & 0 deletions src/core/passthrough-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
]);

/**
Expand Down
30 changes: 28 additions & 2 deletions test/command-handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
}
});
Expand All @@ -979,6 +979,32 @@ describe('PASSTHROUGH_COMMANDS set', () => {
expect(resolvePassthroughCommands('app-2').has('/goal')).toBe(true);
});

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('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);
Expand Down