From 74e51e7e73382355efd1e12fbf128553e46c9608 Mon Sep 17 00:00:00 2001 From: unraid Date: Fri, 3 Apr 2026 03:50:36 +0800 Subject: [PATCH 01/25] feat: enable Remote Control (BRIDGE_MODE) with stub completions - Add BRIDGE_MODE to DEFAULT_FEATURES in dev.ts - Implement peerSessions.ts: cross-session messaging via bridge API - Implement webhookSanitizer.ts: redact secrets from webhook payloads - Replace any stubs in controlTypes.ts with Zod schema-inferred types - Fix tengu_bridge_system_init default to true for app "active" status Co-Authored-By: Claude Opus 4.6 (1M context) --- scripts/dev.ts | 2 +- src/bridge/peerSessions.ts | 76 +++++++++++++++++++++++++++-- src/bridge/webhookSanitizer.ts | 60 +++++++++++++++++++++-- src/entrypoints/sdk/controlTypes.ts | 46 +++++++++++------ src/hooks/useReplBridge.tsx | 2 +- 5 files changed, 164 insertions(+), 22 deletions(-) diff --git a/scripts/dev.ts b/scripts/dev.ts index 40fa6ff607..416f365cd0 100644 --- a/scripts/dev.ts +++ b/scripts/dev.ts @@ -15,7 +15,7 @@ const defineArgs = Object.entries(defines).flatMap(([k, v]) => [ // Bun --feature flags: enable feature() gates at runtime. // Default features enabled in dev mode. -const DEFAULT_FEATURES = ["BUDDY", "TRANSCRIPT_CLASSIFIER"]; +const DEFAULT_FEATURES = ["BUDDY", "TRANSCRIPT_CLASSIFIER", "BRIDGE_MODE"]; // Any env var matching FEATURE_=1 will also enable that feature. // e.g. FEATURE_PROACTIVE=1 bun run dev diff --git a/src/bridge/peerSessions.ts b/src/bridge/peerSessions.ts index 57fa165496..dad8477f71 100644 --- a/src/bridge/peerSessions.ts +++ b/src/bridge/peerSessions.ts @@ -1,3 +1,73 @@ -// Auto-generated stub — replace with real implementation -export {}; -export const postInterClaudeMessage: (target: string, message: string) => Promise<{ ok: boolean; error?: string }> = () => Promise.resolve({ ok: false }); +import axios from 'axios' +import { logForDebugging } from '../utils/debug.js' +import { errorMessage } from '../utils/errors.js' +import { getReplBridgeHandle } from './replBridgeHandle.js' +import { toCompatSessionId } from './sessionIdCompat.js' + +/** + * Send a plain-text message to another Claude session via the bridge API. + * + * Called by SendMessageTool when the target address scheme is "bridge:". + * Uses the current ReplBridgeHandle to derive the sender identity and + * the session ingress URL for the POST request. + * + * @param target - Target session ID (from the "bridge:" address) + * @param message - Plain text message content (structured messages are rejected upstream) + * @returns { ok: true } on success, { ok: false, error } on failure. Never throws. + */ +export async function postInterClaudeMessage( + target: string, + message: string, +): Promise<{ ok: boolean; error?: string }> { + try { + const handle = getReplBridgeHandle() + if (!handle) { + return { ok: false, error: 'Bridge not connected' } + } + + if (!target) { + return { ok: false, error: 'No target session specified' } + } + + const compatTarget = toCompatSessionId(target) + const from = toCompatSessionId(handle.bridgeSessionId) + const baseUrl = handle.sessionIngressUrl + + const url = `${baseUrl}/v1/sessions/${compatTarget}/messages` + + const response = await axios.post( + url, + { + type: 'peer_message', + from, + content: message, + }, + { + headers: { + 'Content-Type': 'application/json', + 'anthropic-version': '2023-06-01', + }, + timeout: 10_000, + validateStatus: (s: number) => s < 500, + }, + ) + + if (response.status === 200 || response.status === 204) { + logForDebugging( + `[bridge:peer] Message sent to ${compatTarget} (${response.status})`, + ) + return { ok: true } + } + + const detail = + typeof response.data === 'object' && response.data?.error?.message + ? response.data.error.message + : `HTTP ${response.status}` + logForDebugging(`[bridge:peer] Send failed: ${detail}`) + return { ok: false, error: detail } + } catch (err: unknown) { + const msg = errorMessage(err) + logForDebugging(`[bridge:peer] postInterClaudeMessage error: ${msg}`) + return { ok: false, error: msg } + } +} diff --git a/src/bridge/webhookSanitizer.ts b/src/bridge/webhookSanitizer.ts index c32323e0f2..cb48f974d6 100644 --- a/src/bridge/webhookSanitizer.ts +++ b/src/bridge/webhookSanitizer.ts @@ -1,3 +1,57 @@ -// Auto-generated stub — replace with real implementation -export {}; -export const sanitizeInboundWebhookContent: (content: string) => string = (content) => content; +/** + * Sanitize inbound GitHub webhook payload content before it enters the session. + * + * Called from useReplBridge.tsx when feature('KAIROS_GITHUB_WEBHOOKS') is enabled. + * Strips known secret patterns (tokens, API keys, credentials) while preserving + * the meaningful content (PR titles, descriptions, commit messages, etc.). + * + * Must be synchronous and never throw — on error, returns the original content. + */ + +/** Patterns that match known secret/token formats. */ +const SECRET_PATTERNS: Array<{ pattern: RegExp; replacement: string }> = [ + // GitHub tokens (PAT, OAuth, App, Server-to-server) + { pattern: /\b(ghp|gho|ghs|ghu|github_pat)_[A-Za-z0-9_]{10,}\b/g, replacement: '[REDACTED_GITHUB_TOKEN]' }, + // Anthropic API keys + { pattern: /\bsk-ant-[A-Za-z0-9_-]{10,}\b/g, replacement: '[REDACTED_ANTHROPIC_KEY]' }, + // Generic Bearer tokens in headers + { pattern: /(Bearer\s+)[A-Za-z0-9._\-/+=]{20,}/gi, replacement: '$1[REDACTED_TOKEN]' }, + // AWS access keys + { pattern: /\b(AKIA|ASIA)[A-Z0-9]{16}\b/g, replacement: '[REDACTED_AWS_KEY]' }, + // AWS secret keys (40-char base64-like strings after common labels) + { pattern: /(aws_secret_access_key|secret_key|SecretAccessKey)['":\s=]+[A-Za-z0-9/+=]{30,}/gi, replacement: '$1=[REDACTED_AWS_SECRET]' }, + // Generic API key patterns (key=value or "key": "value") + { pattern: /(api[_-]?key|apikey|secret|password|token|credential)['":\s=]+["']?[A-Za-z0-9._\-/+=]{16,}["']?/gi, replacement: '$1=[REDACTED]' }, + // npm tokens + { pattern: /\bnpm_[A-Za-z0-9]{36}\b/g, replacement: '[REDACTED_NPM_TOKEN]' }, + // Slack tokens + { pattern: /\bxox[bporas]-[A-Za-z0-9-]{10,}\b/g, replacement: '[REDACTED_SLACK_TOKEN]' }, +] + +/** Maximum content length before truncation (100KB). */ +const MAX_CONTENT_LENGTH = 100_000 + +export function sanitizeInboundWebhookContent(content: string): string { + try { + if (!content) return content + + let sanitized = content + + // Truncate excessively large payloads + if (sanitized.length > MAX_CONTENT_LENGTH) { + sanitized = sanitized.slice(0, MAX_CONTENT_LENGTH) + '\n... [truncated]' + } + + // Redact known secret patterns + for (const { pattern, replacement } of SECRET_PATTERNS) { + // Reset lastIndex for global regexes + pattern.lastIndex = 0 + sanitized = sanitized.replace(pattern, replacement) + } + + return sanitized + } catch { + // Never throw — return original content on any error + return content + } +} diff --git a/src/entrypoints/sdk/controlTypes.ts b/src/entrypoints/sdk/controlTypes.ts index 455c42c072..58d52cca86 100644 --- a/src/entrypoints/sdk/controlTypes.ts +++ b/src/entrypoints/sdk/controlTypes.ts @@ -1,16 +1,34 @@ /** - * Stub: SDK Control Types (not yet published in open-source). - * Used by bridge/transport layer for the control protocol. + * SDK Control Types — inferred from Zod schemas in controlSchemas.ts / coreSchemas.ts. + * + * These types define the control protocol between the CLI bridge and the server. + * Used by bridge/transport layer, remote session manager, and CLI print/IO paths. */ -export type SDKControlRequest = { type: string; [key: string]: unknown } -export type SDKControlResponse = { type: string; [key: string]: unknown } -export type StdoutMessage = any; -export type SDKControlInitializeRequest = any; -export type SDKControlInitializeResponse = any; -export type SDKControlMcpSetServersResponse = any; -export type SDKControlReloadPluginsResponse = any; -export type StdinMessage = any; -export type SDKPartialAssistantMessage = any; -export type SDKControlPermissionRequest = any; -export type SDKControlCancelRequest = any; -export type SDKControlRequestInner = any; +import type { z } from 'zod' +import type { + SDKControlRequestSchema, + SDKControlResponseSchema, + SDKControlInitializeRequestSchema, + SDKControlInitializeResponseSchema, + SDKControlMcpSetServersResponseSchema, + SDKControlReloadPluginsResponseSchema, + SDKControlPermissionRequestSchema, + SDKControlCancelRequestSchema, + SDKControlRequestInnerSchema, + StdoutMessageSchema, + StdinMessageSchema, +} from './controlSchemas.js' +import type { SDKPartialAssistantMessageSchema } from './coreSchemas.js' + +export type SDKControlRequest = z.infer> +export type SDKControlResponse = z.infer> +export type StdoutMessage = z.infer> +export type SDKControlInitializeRequest = z.infer> +export type SDKControlInitializeResponse = z.infer> +export type SDKControlMcpSetServersResponse = z.infer> +export type SDKControlReloadPluginsResponse = z.infer> +export type StdinMessage = z.infer> +export type SDKPartialAssistantMessage = z.infer> +export type SDKControlPermissionRequest = z.infer> +export type SDKControlCancelRequest = z.infer> +export type SDKControlRequestInner = z.infer> diff --git a/src/hooks/useReplBridge.tsx b/src/hooks/useReplBridge.tsx index a70bc2b576..6a767a1cda 100644 --- a/src/hooks/useReplBridge.tsx +++ b/src/hooks/useReplBridge.tsx @@ -290,7 +290,7 @@ export function useReplBridge(messages: Message[], setMessages: (action: React.S // to put system/init on the REPL-bridge wire. Skills load is // async (memoized, cheap after REPL startup); fire-and-forget // so the connected-state transition isn't blocked. - if (getFeatureValue_CACHED_MAY_BE_STALE('tengu_bridge_system_init', false)) { + if (getFeatureValue_CACHED_MAY_BE_STALE('tengu_bridge_system_init', true)) { void (async () => { try { const skills = await getSlashCommandToolSkills(getCwd()); From 1d38eae536a67bcd91431d43ca33441941524db7 Mon Sep 17 00:00:00 2001 From: unraid Date: Fri, 3 Apr 2026 04:08:04 +0800 Subject: [PATCH 02/25] fix: address CodeRabbit review findings - webhookSanitizer: redact before truncate to avoid split secrets at boundary - webhookSanitizer: return safe placeholder on error instead of raw content - peerSessions: use discriminated union return type for type safety Co-Authored-By: Claude Opus 4.6 (1M context) --- src/bridge/peerSessions.ts | 2 +- src/bridge/webhookSanitizer.ts | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/bridge/peerSessions.ts b/src/bridge/peerSessions.ts index dad8477f71..4d792c06d4 100644 --- a/src/bridge/peerSessions.ts +++ b/src/bridge/peerSessions.ts @@ -18,7 +18,7 @@ import { toCompatSessionId } from './sessionIdCompat.js' export async function postInterClaudeMessage( target: string, message: string, -): Promise<{ ok: boolean; error?: string }> { +): Promise<{ ok: true } | { ok: false; error: string }> { try { const handle = getReplBridgeHandle() if (!handle) { diff --git a/src/bridge/webhookSanitizer.ts b/src/bridge/webhookSanitizer.ts index cb48f974d6..a2999b07c8 100644 --- a/src/bridge/webhookSanitizer.ts +++ b/src/bridge/webhookSanitizer.ts @@ -5,7 +5,7 @@ * Strips known secret patterns (tokens, API keys, credentials) while preserving * the meaningful content (PR titles, descriptions, commit messages, etc.). * - * Must be synchronous and never throw — on error, returns the original content. + * Must be synchronous and never throw — on error, returns a safe placeholder. */ /** Patterns that match known secret/token formats. */ @@ -37,21 +37,21 @@ export function sanitizeInboundWebhookContent(content: string): string { let sanitized = content - // Truncate excessively large payloads - if (sanitized.length > MAX_CONTENT_LENGTH) { - sanitized = sanitized.slice(0, MAX_CONTENT_LENGTH) + '\n... [truncated]' - } - - // Redact known secret patterns + // Redact known secret patterns first (before truncation to avoid + // splitting a secret across the truncation boundary) for (const { pattern, replacement } of SECRET_PATTERNS) { - // Reset lastIndex for global regexes pattern.lastIndex = 0 sanitized = sanitized.replace(pattern, replacement) } + // Truncate excessively large payloads after redaction + if (sanitized.length > MAX_CONTENT_LENGTH) { + sanitized = sanitized.slice(0, MAX_CONTENT_LENGTH) + '\n... [truncated]' + } + return sanitized } catch { - // Never throw — return original content on any error - return content + // Never throw, never return raw content — return a safe placeholder + return '[webhook content redacted due to sanitization error]' } } From 8645d37b2533afed84fedaebcd1c332212cde240 Mon Sep 17 00:00:00 2001 From: unraid Date: Fri, 3 Apr 2026 04:15:24 +0800 Subject: [PATCH 03/25] fix: add Authorization header to peer message requests getBridgeAccessToken() provides the OAuth Bearer token, matching the auth pattern used by bridgeApi.ts and codeSessionApi.ts. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/bridge/peerSessions.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/bridge/peerSessions.ts b/src/bridge/peerSessions.ts index 4d792c06d4..e6a0de857d 100644 --- a/src/bridge/peerSessions.ts +++ b/src/bridge/peerSessions.ts @@ -1,6 +1,7 @@ import axios from 'axios' import { logForDebugging } from '../utils/debug.js' import { errorMessage } from '../utils/errors.js' +import { getBridgeAccessToken } from './bridgeConfig.js' import { getReplBridgeHandle } from './replBridgeHandle.js' import { toCompatSessionId } from './sessionIdCompat.js' @@ -29,6 +30,11 @@ export async function postInterClaudeMessage( return { ok: false, error: 'No target session specified' } } + const accessToken = getBridgeAccessToken() + if (!accessToken) { + return { ok: false, error: 'No access token available' } + } + const compatTarget = toCompatSessionId(target) const from = toCompatSessionId(handle.bridgeSessionId) const baseUrl = handle.sessionIngressUrl @@ -44,6 +50,7 @@ export async function postInterClaudeMessage( }, { headers: { + Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', 'anthropic-version': '2023-06-01', }, From e784f231d443e7cc66123ff093ef33301def3374 Mon Sep 17 00:00:00 2001 From: unraid Date: Fri, 3 Apr 2026 04:23:32 +0800 Subject: [PATCH 04/25] fix: validate and encode target sessionId in peer messages - Trim and normalize target before use - Validate with validateBridgeId allowlist (same as bridgeApi.ts) - URL-encode compatTarget to prevent path traversal/injection Co-Authored-By: Claude Opus 4.6 (1M context) --- src/bridge/peerSessions.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/bridge/peerSessions.ts b/src/bridge/peerSessions.ts index e6a0de857d..c194c9b624 100644 --- a/src/bridge/peerSessions.ts +++ b/src/bridge/peerSessions.ts @@ -1,6 +1,7 @@ import axios from 'axios' import { logForDebugging } from '../utils/debug.js' import { errorMessage } from '../utils/errors.js' +import { validateBridgeId } from './bridgeApi.js' import { getBridgeAccessToken } from './bridgeConfig.js' import { getReplBridgeHandle } from './replBridgeHandle.js' import { toCompatSessionId } from './sessionIdCompat.js' @@ -26,7 +27,8 @@ export async function postInterClaudeMessage( return { ok: false, error: 'Bridge not connected' } } - if (!target) { + const normalizedTarget = target.trim() + if (!normalizedTarget) { return { ok: false, error: 'No target session specified' } } @@ -35,11 +37,13 @@ export async function postInterClaudeMessage( return { ok: false, error: 'No access token available' } } - const compatTarget = toCompatSessionId(target) + const compatTarget = toCompatSessionId(normalizedTarget) + // Validate against path traversal — same allowlist as bridgeApi.ts + validateBridgeId(compatTarget, 'target sessionId') const from = toCompatSessionId(handle.bridgeSessionId) const baseUrl = handle.sessionIngressUrl - const url = `${baseUrl}/v1/sessions/${compatTarget}/messages` + const url = `${baseUrl}/v1/sessions/${encodeURIComponent(compatTarget)}/messages` const response = await axios.post( url, From 67caa5d0170c7cec569ac5a385dc82807dfa0b72 Mon Sep 17 00:00:00 2001 From: unraid Date: Fri, 3 Apr 2026 11:30:58 +0800 Subject: [PATCH 05/25] docs: add Remote Control (BRIDGE_MODE) entry to DEV-LOG Co-Authored-By: Claude Opus 4.6 (1M context) --- DEV-LOG.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/DEV-LOG.md b/DEV-LOG.md index f6fc5e9f6f..2b44523591 100644 --- a/DEV-LOG.md +++ b/DEV-LOG.md @@ -1,5 +1,39 @@ # DEV-LOG +## Enable Remote Control / BRIDGE_MODE (2026-04-03) + +**PR**: [claude-code-best/claude-code#60](https://github.com/claude-code-best/claude-code/pull/60) + +Remote Control 功能将本地 CLI 注册为 bridge 环境,生成可分享的 URL(`https://claude.ai/code/session_xxx`),允许从浏览器、手机或其他设备远程查看输出、发送消息、审批工具调用。 + +**改动文件:** + +| 文件 | 变更 | +|------|------| +| `scripts/dev.ts` | `DEFAULT_FEATURES` 加入 `"BRIDGE_MODE"`,dev 模式默认启用 | +| `src/bridge/peerSessions.ts` | stub → 完整实现:通过 bridge API 发送跨会话消息,含三层安全防护(trim + validateBridgeId 白名单 + encodeURIComponent) | +| `src/bridge/webhookSanitizer.ts` | stub → 完整实现:正则 redact 8 类 secret(GitHub/Anthropic/AWS/npm/Slack token),先 redact 再截断,失败返回安全占位符 | +| `src/entrypoints/sdk/controlTypes.ts` | 12 个 `any` stub → `z.infer>` 从现有 Zod schema 推导类型 | +| `src/hooks/useReplBridge.tsx` | `tengu_bridge_system_init` 默认值 `false` → `true`,使 app 端显示 "active" 而非卡在 "connecting" | + +**关键设计决策:** + +1. **不改现有代码逻辑** — 只补全 stub、修正默认值、开启编译开关 +2. **`tengu_bridge_system_init`** — Anthropic 通过 GrowthBook 给订阅用户推送 `true`,但我们的 build 收不到推送;改默认值是唯一不侵入其他代码的方案 +3. **`peerSessions.ts` 认证** — 使用 `getBridgeAccessToken()` 获取 OAuth Bearer token,与 `bridgeApi.ts`/`codeSessionApi.ts` 认证模式一致 +4. **`webhookSanitizer.ts` 安全** — fail-closed(出错返回 `[webhook content redacted due to sanitization error]`),不泄露原始内容 + +**验证结果:** + +- `/remote-control` 命令可见且可用 +- CLI 连接 Anthropic CCR,生成可分享 URL +- App 端(claude.ai/code)显示 "Remote Control active" +- 手机端(Claude iOS app)通过 URL 连接,双向消息正常 + +![Remote Control on Mobile](docs/images/remote-control-mobile.png) + +--- + ## WebSearch Bing 适配器补全 (2026-04-03) 原始 `WebSearchTool` 仅支持 Anthropic API 服务端搜索(`web_search_20250305` server tool),在非官方 API 端点(第三方代理)下搜索功能不可用。本次改动引入适配器架构,新增 Bing 搜索页面解析作为 fallback。 From cb046b4df043a4f2d26221b9f14aa8af9723c234 Mon Sep 17 00:00:00 2001 From: claude-code-best Date: Fri, 3 Apr 2026 11:52:14 +0800 Subject: [PATCH 06/25] =?UTF-8?q?docs:=20=E6=B7=BB=E5=8A=A0=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 15 +++++ docs/internals/ant-only-world.mdx | 99 +++++++++++++++++++++++-------- 2 files changed, 90 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index c7817b5b30..1fa7e6c455 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,14 @@ # Claude Code Best V3 (CCB) +[![GitHub Stars](https://img.shields.io/github/stars/claude-code-best/claude-code?style=flat-square&logo=github&color=yellow)](https://github.com/claude-code-best/claude-code/stargazers) +[![GitHub Contributors](https://img.shields.io/github/contributors/claude-code-best/claude-code?style=flat-square&color=green)](https://github.com/claude-code-best/claude-code/graphs/contributors) +[![GitHub Issues](https://img.shields.io/github/issues/claude-code-best/claude-code?style=flat-square&color=orange)](https://github.com/claude-code-best/claude-code/issues) +[![GitHub License](https://img.shields.io/github/license/claude-code-best/claude-code?style=flat-square)](https://github.com/claude-code-best/claude-code/blob/main/LICENSE) +[![Last Commit](https://img.shields.io/github/last-commit/claude-code-best/claude-code?style=flat-square&color=blue)](https://github.com/claude-code-best/claude-code/commits/main) +[![Bun](https://img.shields.io/badge/runtime-Bun-black?style=flat-square&logo=bun)](https://bun.sh/) + +> Which Claude do you like? The open source one is the best. + 牢 A (Anthropic) 官方 [Claude Code](https://docs.anthropic.com/en/docs/claude-code) CLI 工具的源码反编译/逆向还原项目。目标是将 Claude Code 大部分功能及工程化能力复现 (问就是老佛爷已经付过钱了)。虽然很难绷, 但是它叫做 CCB(踩踩背)... [文档在这里, 支持投稿 PR](https://ccb.agent-aura.top/) @@ -96,6 +105,12 @@ TUI (REPL) 模式需要真实终端,无法直接通过 VS Code launch 启动 - **在线文档(Mintlify)**: [ccb.agent-aura.top](https://ccb.agent-aura.top/) — 文档源码位于 [`docs/`](docs/) 目录,欢迎投稿 PR - **DeepWiki**: +## Contributors + + + + + ## Star History diff --git a/docs/internals/ant-only-world.mdx b/docs/internals/ant-only-world.mdx index c964461bb4..804d7954d9 100644 --- a/docs/internals/ant-only-world.mdx +++ b/docs/internals/ant-only-world.mdx @@ -11,13 +11,13 @@ keywords: ["Ant 特权", "USER_TYPE", "身份门控", "内部功能", "Anthropic `USER_TYPE` 是一个构建时常量,通过 Bun 打包器的 `--define` 注入。在 Anthropic 的内部构建中它被设为 `'ant'`,在公开发布的版本中是 `'external'`: ```typescript -// 反编译版本(src/entrypoints/cli.tsx 第 16 行) -(globalThis as any).BUILD_TARGET = "external"; +// 反编译版本(src/types/global.d.ts 第 63 行) +// Build-time constants BUILD_TARGET/BUILD_ENV/INTERFACE_TYPE — removed (zero runtime usage) ``` -由于这是编译时常量,Bun 会进行**常量折叠**——所有 `process.env.USER_TYPE === 'ant'` 在外部构建中直接变为 `false`,后续代码被 DCE 移除。但在反编译版本中,这些代码保留完整。 +`BUILD_TARGET` 等构建时常量在反编译版本中已被移除。`USER_TYPE` 通过 Bun 的 `--define` 或环境变量注入,Bun 会进行**常量折叠**——所有 `process.env.USER_TYPE === 'ant'` 在外部构建中直接变为 `false`,后续代码被 DCE 移除。但在反编译版本中,这些代码保留完整。 -`USER_TYPE === 'ant'` 出现在代码库的 **60+ 个位置**,控制着工具、命令、API、UI 等方方面面。 +`USER_TYPE === 'ant'` 在代码库中出现 **377+ 次**(含 `=== 'ant'` 291 次、`(process.env.USER_TYPE) === 'ant'` 86 次),另有 `!== 'ant'` 53 次、其他引用约 35 次,总计 **465 处引用**,控制着工具、命令、API、UI 等方方面面。 ## Ant-Only 工具 @@ -31,7 +31,9 @@ keywords: ["Ant 特权", "USER_TYPE", "身份门控", "内部功能", "Anthropic | **TungstenTool** | `src/tools/TungstenTool/` | 基于 tmux 的终端面板工具(反编译版中已 stub) | ```typescript -// src/tools.ts 第 16-24 行 +// src/tools.ts 第 14-24 行——条件导入 + Dead Code Elimination 标记 +// Dead code elimination: conditional import for ant-only tools +/* eslint-disable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */ const REPLTool = process.env.USER_TYPE === 'ant' ? require('./tools/REPLTool/REPLTool.js').REPLTool @@ -45,7 +47,7 @@ const SuggestBackgroundPRTool = ## Ant-Only 命令 -`src/commands.ts` 注册了 25+ 个仅在内部构建中可用的斜杠命令: +`src/commands.ts` 注册了 **28** 个仅在内部构建中可用的斜杠命令(`INTERNAL_ONLY_COMMANDS`,lines 225-254),在 `USER_TYPE === 'ant' && !IS_DEMO` 时才加载(line 343-345): @@ -55,6 +57,7 @@ const SuggestBackgroundPRTool = - `env` — 显示环境变量 - `mockLimits` — 模拟速率限制 - `resetLimits` — 重置速率限制 + - `resetLimitsNonInteractive` — 重置速率限制(非交互式) - `bughunter` — Bug 猎人模式 @@ -69,6 +72,9 @@ const SuggestBackgroundPRTool = - `autofixPr` — 自动修复 PR 中的问题 - `share` — 分享会话 - `summary` — 生成摘要 + - `subscribePr` — 订阅 PR(需要 `KAIROS_GITHUB_WEBHOOKS` feature flag) + - `forceSnip` — 强制截断历史(需要 `HISTORY_SNIP` feature flag) + - `ultraplan` — 超级规划(需要 `ULTRAPLAN` feature flag) - `backfillSessions` — 回填会话数据 @@ -88,30 +94,72 @@ const SuggestBackgroundPRTool = ## Beta API Headers -Claude Code 向 API 发送的 beta headers 也分为公开和内部两类: - -| Header | 功能 | 可见性 | -|--------|------|--------| -| `claude-code-20250219` | Claude Code 标识 | 公开 | -| `interleaved-thinking-2025-05-14` | 交错思考模式 | 公开 | -| `context-1m-2025-08-07` | 1M 上下文窗口 | 公开 | -| `context-management-2025-06-27` | 上下文管理 | 公开 | -| `web-search-2025-03-05` | 网页搜索 | 公开 | -| `effort-2025-11-24` | 推理强度控制 | 公开 | -| `fast-mode-2026-02-01` | 快速模式 | 公开 | -| `token-efficient-tools-2026-03-28` | Token 高效工具 | 公开 | -| `advisor-tool-2026-03-01` | 顾问工具 | 公开 | -| **`cli-internal-2026-02-09`** | 内部 CLI 功能 | **Ant-Only** | -| **`afk-mode-2026-01-31`** | AFK 模式(离开键盘自动审批) | **Feature Flag** | -| **`summarize-connector-text-2026-03-13`** | 连接器文本摘要 | **Feature Flag** | +Claude Code 向 API 发送的 beta headers 分布在 `src/constants/betas.ts`(主注册表)和其他文件中,按可见性分为以下几类: + +### 公开 Headers(所有构建均发送) + +| Header | 功能 | 额外条件 | +|--------|------|----------| +| `claude-code-20250219` | Claude Code 标识 | 非 Haiku 时始终发送;Haiku 在 agentic 模式下也发送 | +| `effort-2025-11-24` | 推理强度控制 | 动态注入 | +| `task-budgets-2026-03-13` | 任务预算 | 始终通过 `addAgenticBetas()` 注入 | +| `fast-mode-2026-02-01` | 快速模式 | 通过 sticky-on latch 动态注入 | +| `advisor-tool-2026-03-01` | 顾问工具 | 启用 advisor 时动态注入 | +| `advanced-tool-use-2025-11-20` | 工具搜索(1P) | Claude API / Foundry | +| `tool-search-tool-2025-10-19` | 工具搜索(3P) | Vertex / Bedrock | + +### 模型能力相关(有条件发送) + +| Header | 功能 | 条件 | +|--------|------|------| +| `interleaved-thinking-2025-05-14` | 交错思考模式 | 模型支持 ISP 且未禁用 | +| `context-1m-2025-08-07` | 1M 上下文窗口 | 模型支持 1M context | +| `context-management-2025-06-27` | 上下文管理 | Claude 4+ 或 ant 手动启用 | +| `structured-outputs-2025-12-15` | 结构化输出 | Claude 4.5/4.6 + GrowthBook `tengu_tool_pear` | +| `web-search-2025-03-05` | 网页搜索 | Vertex (Claude 4+) / Foundry | +| `redact-thinking-2026-02-12` | 思维摘要/脱敏 | ISP 模型 + 非交互 + 未强制显示思维 | +| `prompt-caching-scope-2026-01-05` | 提示缓存作用域 | firstParty/foundry + 全局缓存 | + +### Ant-Only Headers + +| Header | 功能 | 条件 | +|--------|------|------| +| **`cli-internal-2026-02-09`** | 内部 CLI 功能 | `USER_TYPE === 'ant'` + CLI 入口 | +| **`token-efficient-tools-2026-03-28`** | Token 高效工具 | `USER_TYPE === 'ant'` + GrowthBook `tengu_amber_json_tools` | + +### Feature Flag Gated + +| Header | 功能 | 条件 | +|--------|------|------| +| **`afk-mode-2026-01-31`** | AFK 模式(离开键盘自动审批) | `feature('TRANSCRIPT_CLASSIFIER')` | + +### 其他特殊 Headers + +| Header | 功能 | 来源 | +|--------|------|------| +| `oauth-2025-04-20` | OAuth 订阅者标识 | `src/constants/oauth.ts`,Pro/Max/Team/Enterprise | +| `environments-2025-11-01` | Bridge 环境 API | `src/bridge/bridgeApi.ts`,仅 Bridge 模式 | ```typescript -// src/constants/betas.ts 第 29-30 行 +// src/constants/betas.ts — 常量定义 +export const TOKEN_EFFICIENT_TOOLS_BETA_HEADER = + 'token-efficient-tools-2026-03-28' export const CLI_INTERNAL_BETA_HEADER = process.env.USER_TYPE === 'ant' ? 'cli-internal-2026-02-09' : '' ``` -`cli-internal` header 意味着 Anthropic 的 API 服务端也维护着一套 ant-only 的服务端行为——这不仅仅是客户端的门控。 +```typescript +// src/utils/betas.ts 第 315-321 行——TOKEN_EFFICIENT_TOOLS 的实际门控逻辑 +if ( + process.env.USER_TYPE === 'ant' && + includeFirstPartyOnlyBetas && + tokenEfficientToolsEnabled // GrowthBook 'tengu_amber_json_tools' flag +) { + betaHeaders.push(TOKEN_EFFICIENT_TOOLS_BETA_HEADER) +} +``` + +`cli-internal` header 意味着 Anthropic 的 API 服务端也维护着一套 ant-only 的服务端行为——这不仅仅是客户端的门控。`token-efficient-tools` 进一步需要 GrowthBook flag 开启,说明 Ant 员工内部也有分层灰度。 ## 内部代号体系 @@ -138,6 +186,8 @@ Anthropic 有浓厚的"动物命名"文化: - `DISABLE_AUTO_COMPACT` — 禁用自动压缩 - `CLAUDE_CODE_DISABLE_AUTO_MEMORY` — 禁用自动记忆 - `CLAUDE_CODE_DISABLE_BACKGROUND_TASKS` — 禁用后台任务 + - `CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS` — 禁用实验性 beta headers + - `USE_API_CONTEXT_MANAGEMENT` — 上下文管理工具清除(需 ant) - `CLAUDE_CODE_VERIFY_PLAN` — 启用 VerifyPlanExecutionTool @@ -151,6 +201,7 @@ Anthropic 有浓厚的"动物命名"文化: - `CLAUDE_CODE_COORDINATOR_MODE` — 启用 Coordinator 模式 - `CLAUDE_INTERNAL_FC_OVERRIDES` — GrowthBook flag 覆盖(ant-only) - `IS_DEMO` — 演示模式(隐藏内部命令和敏感信息) + - `CLAUDE_CODE_ENTRYPOINT` — 入口类型标识(`cli` | 其他) From e944633dd8b2a733b7cf180bb9fa42202351e490 Mon Sep 17 00:00:00 2001 From: claude-code-best Date: Fri, 3 Apr 2026 11:56:49 +0800 Subject: [PATCH 07/25] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20ERROR=20=20ge?= =?UTF-8?q?tAntModels=20is=20not=20defined=20Fixes=20#69?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/model/modelOptions.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/model/modelOptions.ts b/src/utils/model/modelOptions.ts index f8ef2966a8..30d8f0f0e4 100644 --- a/src/utils/model/modelOptions.ts +++ b/src/utils/model/modelOptions.ts @@ -6,6 +6,7 @@ import { isTeamPremiumSubscriber, } from '../auth.js' import { getModelStrings } from './modelStrings.js' +import { getAntModels } from './antModels.js' import { COST_TIER_3_15, COST_HAIKU_35, From a02a9fc4c2271c95bb7ee421bfc7a8363e72fea5 Mon Sep 17 00:00:00 2001 From: claude-code-best Date: Fri, 3 Apr 2026 14:14:35 +0800 Subject: [PATCH 08/25] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E5=AF=BC=E5=85=A5=E7=BC=BA=E5=A4=B1=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/PromptInput/PromptInputFooterLeftSide.tsx | 2 +- src/constants/prompts.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/PromptInput/PromptInputFooterLeftSide.tsx b/src/components/PromptInput/PromptInputFooterLeftSide.tsx index 381dcc570c..9480af9eb3 100644 --- a/src/components/PromptInput/PromptInputFooterLeftSide.tsx +++ b/src/components/PromptInput/PromptInputFooterLeftSide.tsx @@ -365,7 +365,7 @@ function ModeIndicator({ // its click-target Box isn't nested inside the // wrapper (reconciler throws on Box-in-Text). // Tmux pill (ant-only) — appears right after tasks in nav order - ...((process.env.USER_TYPE) === 'ant' && hasTmuxSession ? [] : []), ...(isAgentSwarmsEnabled() && hasTeams ? [] : []), ...(shouldShowPrStatus ? [] : [])]; + ...(process.env.USER_TYPE === 'ant' && hasTmuxSession && typeof TungstenPill === 'function' ? [] : []), ...(isAgentSwarmsEnabled() && hasTeams ? [] : []), ...(shouldShowPrStatus ? [] : [])]; // Check if any in-process teammates exist (for hint text cycling) const hasAnyInProcessTeammates = Object.values(tasks).some(t_2 => t_2.type === 'in_process_teammate' && t_2.status === 'running'); diff --git a/src/constants/prompts.ts b/src/constants/prompts.ts index 9eb49b36ed..02b6a22896 100644 --- a/src/constants/prompts.ts +++ b/src/constants/prompts.ts @@ -59,6 +59,7 @@ import { TICK_TAG } from './xml.js' import { logForDebugging } from '../utils/debug.js' import { loadMemoryPrompt } from '../memdir/memdir.js' import { isUndercover } from '../utils/undercover.js' +import { getAntModelOverrideConfig } from '../utils/model/antModels.js' import { isMcpInstructionsDeltaEnabled } from '../utils/mcpInstructionsDelta.js' // Dead code elimination: conditional imports for feature-gated modules From a7604f659194edbfbd11321a5b5d16780b84a02e Mon Sep 17 00:00:00 2001 From: claude-code-best Date: Fri, 3 Apr 2026 14:22:47 +0800 Subject: [PATCH 09/25] =?UTF-8?q?feat:=20/login=20=E5=91=BD=E4=BB=A4?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E8=87=AA=E5=AE=9A=E4=B9=89=20anthropic=20?= =?UTF-8?q?=E7=BB=88=E7=AB=AF=E7=99=BB=E9=99=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DEV-LOG.md | 34 +++++++ README.md | 35 +++++++ src/components/ConsoleOAuthFlow.tsx | 144 +++++++++++++++++++++++++++- 3 files changed, 208 insertions(+), 5 deletions(-) diff --git a/DEV-LOG.md b/DEV-LOG.md index 3c2a94fc08..c84f2e59ec 100644 --- a/DEV-LOG.md +++ b/DEV-LOG.md @@ -168,3 +168,37 @@ GrowthBook 功能开关系统原为 Anthropic 内部构建设计,硬编码 SDK 注意: - `USER_TYPE=ant` 启用 alt-screen 全屏模式,中心区域满屏是预期行为 - `global.d.ts` 中剩余未 stub 的全局函数(`getAntModels` 等)遇到 `X is not defined` 时按同样模式处理 + +--- + +## /login 添加 Custom Platform 选项 (2026-04-03) + +在 `/login` 命令的登录方式选择列表中新增 "Custom Platform" 选项(位于第一位),允许用户直接在终端配置第三方 API 兼容服务的 Base URL、API Key 和三种模型映射,保存到 `~/.claude/settings.json`。 + +**修改文件:** + +| 文件 | 变更 | +|------|------| +| `src/components/ConsoleOAuthFlow.tsx` | `OAuthStatus` 类型新增 `custom_platform` state(含 `baseUrl`、`apiKey`、`haikuModel`、`sonnetModel`、`opusModel`、`activeField`);`idle` case Select 选项新增 Custom Platform 并排第一位;新增 `custom_platform` case 渲染 5 字段表单(Tab/Shift+Tab 切换、focus 高亮、Enter 跳转/保存);Select onChange 处理 `custom_platform` 初始状态(从 `process.env` 预填当前值);`OAuthStatusMessageProps` 类型及调用处新增 `onDone` prop | +| `src/components/ConsoleOAuthFlow.tsx` | 新增 `updateSettingsForSource` import | + +**UI 交互:** +- 5 个字段同屏:Base URL、API Key、Haiku Model、Sonnet Model、Opus Model +- 当前活动字段的标签用 `suggestion` 背景色 + `inverseText` 反色高亮 +- Tab / Shift+Tab 在字段间切换,各自保留输入值 +- 每个字段按 Enter 跳到下一个,最后一个字段 (Opus) 按 Enter 保存 +- 模型字段自动从 `process.env` 读取当前配置作为预填值,无值则空 +- 保存时调用 `updateSettingsForSource('userSettings', { env })` 写入 settings.json,同时更新 `process.env` + +**保存的 settings.json env 字段:** +```json +{ + "ANTHROPIC_BASE_URL": "...", + "ANTHROPIC_AUTH_TOKEN": "...", + "ANTHROPIC_DEFAULT_HAIKU_MODEL": "...", + "ANTHROPIC_DEFAULT_SONNET_MODEL": "...", + "ANTHROPIC_DEFAULT_OPUS_MODEL": "..." +} +``` + +非空字段才写入,保存后立即生效(`onDone()` 触发 `onChangeAPIKey()` 刷新 API 客户端)。 diff --git a/README.md b/README.md index 1fa7e6c455..d1f8cf5767 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ - [x] 关闭自动更新; - [x] 添加自定义 sentry 错误上报支持 [文档](https://ccb.agent-aura.top/docs/internals/sentry-setup) - [x] 添加自定义 GrowthBook 支持 (GB 也是开源的, 现在你可以配置一个自定义的遥控平台) [文档](https://ccb.agent-aura.top/docs/internals/growthbook-adapter) + - [x] 自定义 login 模式, 大家可以用这个配置 Claude 的模型! - [ ] V6 大规模重构石山代码, 全面模块分包 - [ ] V6 将会为全新分支, 届时 main 分支将会封存为历史版本 @@ -72,6 +73,40 @@ bun run build 如果遇到 bug 请直接提一个 issues, 我们优先解决 +### 新人配置 /login + +首次运行后,在 REPL 中输入 `/login` 命令进入登录配置界面,选择 **Custom Platform** 即可对接第三方 API 兼容服务(无需 Anthropic 官方账号)。 + +需要填写的字段: + +| 字段 | 说明 | 示例 | +|------|------|------| +| Base URL | API 服务地址 | `https://api.example.com/v1` | +| API Key | 认证密钥 | `sk-xxx` | +| Haiku Model | 快速模型 ID | `claude-haiku-4-5-20251001` | +| Sonnet Model | 均衡模型 ID | `claude-sonnet-4-6` | +| Opus Model | 高性能模型 ID | `claude-opus-4-6` | + +- **Tab / Shift+Tab** 切换字段,**Enter** 确认并跳到下一个,最后一个字段按 Enter 保存 +- 模型字段会自动读取当前环境变量预填 +- 配置保存到 `~/.claude/settings.json` 的 `env` 字段,保存后立即生效 + +也可以直接编辑 `~/.claude/settings.json`: + +```json +{ + "env": { + "ANTHROPIC_BASE_URL": "https://api.example.com/v1", + "ANTHROPIC_AUTH_TOKEN": "sk-xxx", + "ANTHROPIC_DEFAULT_HAIKU_MODEL": "claude-haiku-4-5-20251001", + "ANTHROPIC_DEFAULT_SONNET_MODEL": "claude-sonnet-4-6", + "ANTHROPIC_DEFAULT_OPUS_MODEL": "claude-opus-4-6" + } +} +``` + +> 支持所有 Anthropic API 兼容服务(如 OpenRouter、AWS Bedrock 代理等),只要接口兼容 Messages API 即可。 + ## Feature Flags 所有功能开关通过 `FEATURE_=1` 环境变量启用,例如: diff --git a/src/components/ConsoleOAuthFlow.tsx b/src/components/ConsoleOAuthFlow.tsx index 8d5fc122ca..7d777cb189 100644 --- a/src/components/ConsoleOAuthFlow.tsx +++ b/src/components/ConsoleOAuthFlow.tsx @@ -12,7 +12,7 @@ import { sendNotification } from '../services/notifier.js'; import { OAuthService } from '../services/oauth/index.js'; import { getOauthAccountInfo, validateForceLoginOrg } from '../utils/auth.js'; import { logError } from '../utils/log.js'; -import { getSettings_DEPRECATED } from '../utils/settings/settings.js'; +import { getSettings_DEPRECATED, updateSettingsForSource } from '../utils/settings/settings.js'; import { Select } from './CustomSelect/select.js'; import { KeyboardShortcutHint } from './design-system/KeyboardShortcutHint.js'; import { Spinner } from './Spinner.js'; @@ -29,6 +29,15 @@ type OAuthStatus = { | { state: 'platform_setup'; } // Show platform setup info (Bedrock/Vertex/Foundry) +| { + state: 'custom_platform'; + baseUrl: string; + apiKey: string; + haikuModel: string; + sonnetModel: string; + opusModel: string; + activeField: 'base_url' | 'api_key' | 'haiku_model' | 'sonnet_model' | 'opus_model'; +} // Custom platform: configure API endpoint and model names | { state: 'ready_to_start'; } // Flow started, waiting for browser to open @@ -325,7 +334,7 @@ export function ConsoleOAuthFlow({ } - + ; } @@ -343,6 +352,7 @@ type OAuthStatusMessageProps = { handleSubmitCode: (value: string, url: string) => void; setOAuthStatus: (status: OAuthStatus) => void; setLoginWithClaudeAi: (value: boolean) => void; + onDone: () => void; }; function OAuthStatusMessage(t0) { const $ = _c(51); @@ -359,7 +369,8 @@ function OAuthStatusMessage(t0) { textInputColumns, handleSubmitCode, setOAuthStatus, - setLoginWithClaudeAi + setLoginWithClaudeAi, + onDone } = t0; switch (oauthStatus.state) { case "idle": @@ -402,7 +413,10 @@ function OAuthStatusMessage(t0) { } let t6; if ($[5] === Symbol.for("react.memo_cache_sentinel")) { - t6 = [t4, t5, { + t6 = [{ + label: Custom Platform ·{" "}Configure your own API endpoint{"\n"}, + value: "custom_platform" + }, t4, t5, { label: 3rd-party platform ·{" "}Amazon Bedrock, Microsoft Foundry, or Vertex AI{"\n"}, value: "platform" }]; @@ -413,7 +427,18 @@ function OAuthStatusMessage(t0) { let t7; if ($[6] !== setLoginWithClaudeAi || $[7] !== setOAuthStatus) { t7 =