feat: 添加工具类命令(teleport、recap、break-cache、env、tui 等)

- /teleport: 从 claude.ai 恢复会话
- /recap: 生成会话摘要
- /break-cache: 提示缓存管理(once/always/off/status)
- /env: 环境信息展示(含密钥脱敏)
- /tui: 无闪烁 TUI 模式管理
- /onboarding: 引导流程
- /perf-issue: 性能问题诊断
- /debug-tool-call: 工具调用调试
- /usage: 用量统计(合并 /cost 和 /stats 别名)

Co-Authored-By: glm-5-turbo <zai-org@claude-code-best.win>
This commit is contained in:
claude-code-best
2026-05-09 23:04:31 +08:00
parent 6766f08e47
commit fdddb6dbe8
38 changed files with 5494 additions and 43 deletions

View File

@@ -1,6 +1,7 @@
import axios from 'axios';
import chalk from 'chalk';
import { randomUUID } from 'crypto';
import React from 'react';
import { getOriginalCwd, getSessionId } from 'src/bootstrap/state.js';
import { checkGate_CACHED_OR_BLOCKING } from 'src/services/analytics/growthbook.js';
import {
@@ -877,6 +878,13 @@ export async function teleportToRemote(options: {
* identify the PR associated with this session.
*/
githubPr?: { owner: string; repo: string; number: number };
/**
* Identifies which command/flow originated this teleport. CCR backend
* uses this for routing/observability. Known values: 'autofix_pr',
* 'ultrareview', 'ultraplan'. Pass-through field — not interpreted
* client-side; if backend doesn't recognize it, it's silently ignored.
*/
source?: string;
}): Promise<TeleportToRemoteResponse | null> {
const { initialMessage, signal } = options;
try {
@@ -1227,6 +1235,7 @@ export async function teleportToRemote(options: {
model: options.model ?? getMainLoopModel(),
...(options.reuseOutcomeBranch && { reuse_outcome_branches: true }),
...(options.githubPr && { github_pr: options.githubPr }),
...(options.source && { source: options.source }),
};
// CreateCCRSessionPayload has no permission_mode field — a top-level

View File

@@ -0,0 +1,76 @@
/**
* L2 regression tests for prepareWorkspaceApiRequest (codecov-100 audit #12):
* pins the cleared-vs-never-set predicate that distinguishes the two error
* messages.
*
* NOTE on isolation: several other test files in this repo
* (`src/commands/vault/__tests__/api.test.ts`,
* `src/commands/agents-platform/__tests__/agentsApi.test.ts`, etc.) call
* `mock.module('src/utils/teleport/api.js', ...)` to stub
* `prepareWorkspaceApiRequest`. Bun's mock registry is process-wide, so
* full-suite imports of `../api.js` from this test file return the stubbed
* module — we cannot exercise the real prepareWorkspaceApiRequest here.
*
* Workaround: we replicate the predicate logic from api.ts and pin it as
* a pure unit test. The predicate is small and self-contained; if api.ts
* ever changes the cleared-vs-never-set logic, both this replicated
* function and the test must be updated together. End-to-end coverage of
* the message text continues to come through the prepareWorkspaceApiRequest
* call sites in the wider integration tests.
*/
import { describe, test, expect } from 'bun:test'
// ── Replicated from src/utils/teleport/api.ts (keep in sync) ────────────────
// L2 fix: detect "was cleared" (null / empty / whitespace) vs "never set"
// (undefined / missing field) so the user gets an actionable error message.
function isWorkspaceKeyCleared(rawValue: unknown): boolean {
return (
rawValue === null ||
(typeof rawValue === 'string' && rawValue.trim() === '')
)
}
describe('isWorkspaceKeyCleared (audit #12: cleared vs never-set predicate)', () => {
test('undefined → not cleared (never set)', () => {
expect(isWorkspaceKeyCleared(undefined)).toBe(false)
})
test('missing field on config object → not cleared (never set)', () => {
const config: { workspaceApiKey?: string | null } = {}
expect(isWorkspaceKeyCleared(config.workspaceApiKey)).toBe(false)
})
test('null → cleared', () => {
expect(isWorkspaceKeyCleared(null)).toBe(true)
})
test('empty string → cleared', () => {
expect(isWorkspaceKeyCleared('')).toBe(true)
})
test('whitespace-only string → cleared', () => {
expect(isWorkspaceKeyCleared(' ')).toBe(true)
expect(isWorkspaceKeyCleared('\t\n \r')).toBe(true)
})
test('valid key string → not cleared', () => {
expect(isWorkspaceKeyCleared('sk-ant-api03-validkey')).toBe(false)
})
test('whitespace-padded valid key → not cleared (real prepare trims and uses it)', () => {
// The function only tests the trimmed value; non-empty after trim
// means a usable key exists, not a cleared one.
expect(isWorkspaceKeyCleared(' sk-ant-api03-key ')).toBe(false)
})
test('non-string non-null types are conservatively treated as not-cleared', () => {
// Defensive: only literal null + empty/whitespace strings count as
// "cleared". Other unexpected types fall through to the standard
// "required" message rather than misleading the user with
// "was cleared" when the underlying state is corrupt.
expect(isWorkspaceKeyCleared(0)).toBe(false)
expect(isWorkspaceKeyCleared(false)).toBe(false)
expect(isWorkspaceKeyCleared({})).toBe(false)
expect(isWorkspaceKeyCleared([])).toBe(false)
})
})

View File

@@ -4,6 +4,7 @@ import { getOauthConfig } from 'src/constants/oauth.js'
import { getOrganizationUUID } from 'src/services/oauth/client.js'
import z from 'zod/v4'
import { getClaudeAIOAuthTokens } from '../auth.js'
import { getGlobalConfig } from '../config.js'
import { logForDebugging } from '../debug.js'
import { parseGitHubRepository } from '../detectRepository.js'
import { errorMessage, toError } from '../errors.js'
@@ -174,6 +175,83 @@ export const CodeSessionSchema = lazySchema(() =>
// Export the inferred type from the Zod schema
export type CodeSession = z.infer<ReturnType<typeof CodeSessionSchema>>
/**
* L2 fix (codecov-100 audit #12): predicate for "was the workspace API key
* explicitly cleared" vs "was it never set". Treats workspaceApiKey
* present-but-falsy (null, '', whitespace) as cleared, and absent
* (undefined, missing field) as never-set. The TypeScript type is
* `string | undefined` but the JSON file can legally hold null if a user
* manually edited it, so we handle null defensively via runtime check.
*
* Other types (number, boolean, object, etc.) conservatively fall through
* to "not cleared" — the underlying state is corrupt, and the standard
* "required" message is less misleading than claiming the user cleared a
* value they never set.
*
* Exported so unit tests can pin the predicate directly without needing
* to bypass the process-wide mock.module() registrations on
* `src/utils/teleport/api.js` from sibling test files.
*/
export function isWorkspaceKeyCleared(rawValue: unknown): boolean {
return (
rawValue === null ||
(typeof rawValue === 'string' && rawValue.trim() === '')
)
}
/**
* Validates and prepares for workspace API key requests (agents, vaults, memory_stores, skills).
*
* Reads the workspace API key from two sources in priority order:
* 1. ANTHROPIC_API_KEY environment variable (takes precedence)
* 2. workspaceApiKey field in ~/.claude.json (set via /login UI, no restart needed)
*
* Validates the sk-ant-api03-* prefix and returns the key for use in `x-api-key` headers.
* Configuration errors (missing or wrong-prefix key) are surfaced as thrown errors so
* callers can convert them to 501.
*
* @throws {Error} when no workspace key is found in env or settings, or the key does not
* start with sk-ant-api03-
*/
export async function prepareWorkspaceApiRequest(): Promise<{
apiKey: string
}> {
// Dual-source: env var takes precedence, then settings (saved via /login UI)
const config = getGlobalConfig()
const apiKey =
process.env['ANTHROPIC_API_KEY']?.trim() || config.workspaceApiKey?.trim()
if (!apiKey) {
// L2 fix (codecov-100 audit #12): when the user previously had a
// workspace key and explicitly cleared it (set to null/empty), the
// generic "required" error doesn't tell them what changed. Detect
// the cleared-vs-never-set distinction so the prompt is actionable.
const rawValue = (config as { workspaceApiKey?: string | null })
.workspaceApiKey
const wasCleared = isWorkspaceKeyCleared(rawValue)
const preface = wasCleared
? 'Your workspace API key was cleared. '
: 'A workspace API key (sk-ant-api03-*) is required to use workspace endpoints ' +
'(/v1/agents, /v1/vaults, /v1/memory_stores, /v1/skills). '
throw new Error(
preface +
'Press W in /login to save your key directly (no restart needed), or ' +
'set ANTHROPIC_API_KEY=<key> and restart. ' +
'Obtain a key from https://console.anthropic.com/settings/keys. ' +
'Subscription OAuth (claude.ai login) cannot reach these endpoints.',
)
}
if (!apiKey.startsWith('sk-ant-api03-')) {
// D5: expose at most first 4 chars to avoid leaking high-entropy secret bits into error logs/reports
throw new Error(
`Workspace API key must start with sk-ant-api03-, got prefix "${apiKey.slice(0, 4)}...". ` +
'Obtain a workspace API key from https://console.anthropic.com/settings/keys. ' +
'Press W in /login to save your key, or set ANTHROPIC_API_KEY.',
)
}
return { apiKey }
}
/**
* Validates and prepares for API requests
* @returns Object containing access token and organization UUID