mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-17 13:55:50 +00:00
* feat: 删除垃圾更改
* fix: 消除生产代码中的 as any 类型不安全模式
- API 兼容层(openai/grok/gemini): 利用 BetaRawMessageStreamEvent 的
discriminated union 在 switch/case 中直接属性访问,消除 ~29 个 as any
- ConsoleOAuthFlow: 用 as unknown as Parameters<typeof> 替代 as any
- performanceShim: 用 Record<string, unknown> 和显式类型断言替代 as any
- companionReact/auth: 直接访问已有类型属性消除 as any
- sliceAnsi/textHighlighting: 用 as Char 替代 as any(Token 联合类型收窄)
- ccrClient: 利用 RequestResult 类型收窄直接访问 retryAfterMs
- outputsScanner: 用 TurnStartTime.turnStartTime 属性访问替代双重断言
- plans: 用显式数组类型替代 as any[]
- FeedbackSurvey: 用 in 操作符和 Parameters<typeof> 替代 as any
- messageQueueManager: 用 Record<string, unknown> 替代 as any
- mcp.ts: 用 in 操作符类型守卫替代 as any
precheck 通过: typecheck 零错误 + 5420 测试全部通过 + lint 通过
* fix: 将 pipeIpc 添加到 AppState 类型声明,消除 4 个 as any
- AppStateStore: 添加 pipeIpc?: PipeIpcState 可选字段
- PromptInputFooter: 直接访问 s.pipeIpc
- useBackgroundTaskNavigation: 直接访问 s.pipeIpc
- usePipeRouter: 直接访问 store.getState().pipeIpc
- REPL.tsx: 移除 getPipeIpc(s as any) 中的 as any
precheck 通过
* fix: 消除 UltraplanChoiceDialog 中的 wheelDown/wheelUp as any
Ink Key 类型已包含 wheelDown/wheelUp 属性,直接访问即可。
* fix: 消除 sideQuestion.ts 中的 2 个 as any
- toolUse.name: 使用 as unknown as { name: string } 双重断言
- apiErr.error: 使用 as Parameters<typeof formatAPIError>[0] 类型参数
* fix: 为 auto dream 添加 maxTurns: 20 限制,防止单次执行消耗过多 token
* fix: 补充 SAFE_ENV_VARS 中缺失的 OpenAI/Gemini/Grok provider 环境变量
项目级 settings.local.json 的 env 字段在 trust dialog 之前只有
SAFE_ENV_VARS 白名单中的变量会被应用到 process.env。
OPENAI_API_KEY、OPENAI_BASE_URL 等关键变量不在白名单中,
导致容器中通过 settings.local.json 配置 OpenAI 协议时认证失败。
* fix: 修复 goalState.js 模块不存在的类型错误
* fix: 增强 providers 测试的环境变量隔离,防止 mock 污染
* fix: 内联 providers 测试逻辑,彻底隔离 mock 污染
测试不再 import providers.ts(其默认参数触发 getInitialSettings 全链),
改为内联纯函数逻辑,从根源消除 CI 上其他测试 mock.module 污染。
* fix: 添加 goalState 模块存根,修复 CI 构建打包解析失败
CI 中的 autonomy-lifecycle-user-flow 集成测试会执行 build.ts 打包 CLI。
此前 PromptInputFooterLeftSide.tsx 中 require('../../services/goal/goalState.js')
的路径在源码中不存在,打包器报 Could not resolve,导致 (unnamed) 测试失败。
新增 src/services/goal/goalState.ts 存根模块(getGoal 返回 null,组件不渲染),
让打包器在构建期可以解析该 require 路径。同时把 PromptInputFooterLeftSide.tsx
里两处 as unknown as 内联类型签名换成 as typeof import(...),让类型直接来自
存根模块,避免类型定义重复。
99 lines
3.4 KiB
TypeScript
99 lines
3.4 KiB
TypeScript
import {
|
|
type AnsiCode,
|
|
type Char,
|
|
ansiCodesToString,
|
|
reduceAnsiCodes,
|
|
tokenize,
|
|
undoAnsiCodes,
|
|
} from '@alcalzone/ansi-tokenize'
|
|
import { stringWidth } from '@anthropic/ink'
|
|
|
|
// A code is an "end code" if its code equals its endCode (e.g., hyperlink close)
|
|
function isEndCode(code: AnsiCode): boolean {
|
|
return code.code === code.endCode
|
|
}
|
|
|
|
// Filter to only include "start codes" (not end codes)
|
|
function filterStartCodes(codes: AnsiCode[]): AnsiCode[] {
|
|
return codes.filter(c => !isEndCode(c))
|
|
}
|
|
|
|
/**
|
|
* Slice a string containing ANSI escape codes.
|
|
*
|
|
* Unlike the slice-ansi package, this properly handles OSC 8 hyperlink
|
|
* sequences because @alcalzone/ansi-tokenize tokenizes them correctly.
|
|
*/
|
|
export default function sliceAnsi(
|
|
str: string,
|
|
start: number,
|
|
end?: number,
|
|
): string {
|
|
// Don't pass `end` to tokenize — it counts code units, not display cells,
|
|
// so it drops tokens early for text with zero-width combining marks.
|
|
const tokens = tokenize(str)
|
|
let activeCodes: AnsiCode[] = []
|
|
let position = 0
|
|
let result = ''
|
|
let include = false
|
|
|
|
for (const token of tokens) {
|
|
// Advance by display width, not code units. Combining marks (Devanagari
|
|
// matras, virama, diacritics) are width 0 — counting them via .length
|
|
// advanced position past `end` early and truncated the slice. Callers
|
|
// pass start/end in display cells (via stringWidth), so position must
|
|
// track the same units.
|
|
const width =
|
|
token.type === 'ansi'
|
|
? 0
|
|
: token.type === 'char'
|
|
? token.fullWidth
|
|
? 2
|
|
: stringWidth(token.value)
|
|
: 0
|
|
|
|
// Break AFTER trailing zero-width marks — a combining mark attaches to
|
|
// the preceding base char, so "भा" (भ + ा, 1 display cell) sliced at
|
|
// end=1 must include the ा. Breaking on position >= end BEFORE the
|
|
// zero-width check would drop it and render भ bare. ANSI codes are
|
|
// width 0 but must NOT be included past end (they open new style runs
|
|
// that leak into the undo sequence), so gate on char type too. The
|
|
// !include guard ensures empty slices (start===end) stay empty even
|
|
// when the string starts with a zero-width char (BOM, ZWJ).
|
|
if (end !== undefined && position >= end) {
|
|
if (token.type === 'ansi' || width > 0 || !include) break
|
|
}
|
|
|
|
if (token.type === 'ansi') {
|
|
activeCodes.push(token)
|
|
if (include) {
|
|
// Emit all ANSI codes during the slice
|
|
result += token.code
|
|
}
|
|
} else {
|
|
if (!include && position >= start) {
|
|
// Skip leading zero-width marks at the start boundary — they belong
|
|
// to the preceding base char in the left half. Without this, the
|
|
// mark appears in BOTH halves: left+right ≠ original. Only applies
|
|
// when start > 0 (otherwise there's no preceding char to own it).
|
|
if (start > 0 && width === 0) continue
|
|
include = true
|
|
// Reduce and filter to only active start codes
|
|
activeCodes = filterStartCodes(reduceAnsiCodes(activeCodes))
|
|
result = ansiCodesToString(activeCodes)
|
|
}
|
|
|
|
if (include) {
|
|
result += (token as Char).value
|
|
}
|
|
|
|
position += width
|
|
}
|
|
}
|
|
|
|
// Only undo start codes that are still active
|
|
const activeStartCodes = filterStartCodes(reduceAnsiCodes(activeCodes))
|
|
result += ansiCodesToString(undoAnsiCodes(activeStartCodes))
|
|
return result
|
|
}
|