mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-17 13:55:50 +00:00
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:
@@ -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
|
||||
|
||||
76
src/utils/teleport/__tests__/api.test.ts
Normal file
76
src/utils/teleport/__tests__/api.test.ts
Normal 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)
|
||||
})
|
||||
})
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user