mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-17 22:05:50 +00:00
Feat/integrate lint preview (#285)
* feat: 适配 zed acp 协议 * docs: 完善 acp 文档 * feat: integrate feature branches + daemon/job 命令层级化 + 跨平台后台引擎 Cherry-picked from origin/lint/preview (637c908), excluding lint-only changes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: correct detectMimeFromBase64 to decode raw bytes from base64 Cherry-picked from origin/lint/preview (ee36954). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: daemon 子进程 spawn 跨平台修复 + CliLaunchSpec 集中化重构 Cherry-picked from origin/lint/preview (c5f52cd), excluding lint-only formatting changes. - 新建 src/utils/cliLaunch.ts: 集中化 CLI 子进程启动层 - 修复 --daemon-worker=kind 等号格式解析 - 修复 daemon/bg fast path 缺少 setShellIfWindows() - 修复 checkPathExists 用 existsSync 替代 execSync('dir') - 7 个 spawn 站点迁移到 CliLaunchSpec Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: merge tsconfig.base.json into tsconfig.json with full compiler options The cherry-pick from637c908dropped jsx/strict/etc settings when removing tsconfig.base.json. This commit restores them in a single tsconfig.json. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: merge tsconfig.base.json into tsconfig.json with full compiler options The cherry-pick from637c908dropped jsx/strict/etc settings when removing tsconfig.base.json. This commit restores them in a single tsconfig.json. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,12 @@ import { fileHistoryEnabled, fileHistoryMakeSnapshot } from './fileHistory.js'
|
||||
import { gracefulShutdownSync } from './gracefulShutdown.js'
|
||||
import { enqueue } from './messageQueueManager.js'
|
||||
import { resolveSkillModelOverride } from './model/model.js'
|
||||
import {
|
||||
finalizeAutonomyRunCompleted,
|
||||
finalizeAutonomyRunFailed,
|
||||
markAutonomyRunFailed,
|
||||
markAutonomyRunRunning,
|
||||
} from './autonomyRuns.js'
|
||||
import type { ProcessUserInputContext } from './processUserInput/processUserInput.js'
|
||||
import { processUserInput } from './processUserInput/processUserInput.js'
|
||||
import type { QueryGuard } from './QueryGuard.js'
|
||||
@@ -460,6 +466,7 @@ async function executeUserInput(params: ExecuteUserInputParams): Promise<void> {
|
||||
commands.every(c => c.workload === firstWorkload)
|
||||
? firstWorkload
|
||||
: undefined
|
||||
let autonomyRunIds: string[] | undefined
|
||||
|
||||
// Wrap the entire turn (processUserInput loop + onQuery) in an
|
||||
// AsyncLocalStorage context. This is the ONLY way to correctly
|
||||
@@ -469,131 +476,159 @@ async function executeUserInput(params: ExecuteUserInputParams): Promise<void> {
|
||||
// context — isolated from the parent's continuation. A process-global
|
||||
// mutable slot would be clobbered at the detached closure's first
|
||||
// await by this function's synchronous return path. See state.ts.
|
||||
await runWithWorkload(turnWorkload, async () => {
|
||||
for (let i = 0; i < commands.length; i++) {
|
||||
const cmd = commands[i]!
|
||||
const isFirst = i === 0
|
||||
const result = await processUserInput({
|
||||
input: cmd.value,
|
||||
preExpansionInput: cmd.preExpansionValue,
|
||||
mode: cmd.mode,
|
||||
setToolJSX,
|
||||
context: makeContext(),
|
||||
pastedContents: isFirst ? cmd.pastedContents : undefined,
|
||||
messages,
|
||||
setUserInputOnProcessing: isFirst
|
||||
? setUserInputOnProcessing
|
||||
: undefined,
|
||||
isAlreadyProcessing: !isFirst,
|
||||
querySource,
|
||||
canUseTool,
|
||||
uuid: cmd.uuid,
|
||||
ideSelection: isFirst ? ideSelection : undefined,
|
||||
skipSlashCommands: cmd.skipSlashCommands,
|
||||
bridgeOrigin: cmd.bridgeOrigin,
|
||||
isMeta: cmd.isMeta,
|
||||
skipAttachments: !isFirst,
|
||||
})
|
||||
// Stamp origin here rather than threading another arg through
|
||||
// processUserInput → processUserInputBase → processTextPrompt → createUserMessage.
|
||||
// Derive origin from mode for task-notifications — mirrors the origin
|
||||
// derivation at messages.ts (case 'queued_command'); intentionally
|
||||
// does NOT mirror its isMeta:true so idle-dequeued notifications stay
|
||||
// visible in the transcript via UserAgentNotificationMessage.
|
||||
const origin =
|
||||
cmd.origin ??
|
||||
(cmd.mode === 'task-notification'
|
||||
? ({ kind: 'task-notification' } as const)
|
||||
: undefined)
|
||||
if (origin) {
|
||||
for (const m of result.messages) {
|
||||
if (m.type === 'user') m.origin = origin
|
||||
try {
|
||||
await runWithWorkload(turnWorkload, async () => {
|
||||
for (let i = 0; i < commands.length; i++) {
|
||||
const cmd = commands[i]!
|
||||
const isFirst = i === 0
|
||||
if (cmd.autonomy?.runId) {
|
||||
;(autonomyRunIds ??= []).push(cmd.autonomy.runId)
|
||||
await markAutonomyRunRunning(cmd.autonomy.runId)
|
||||
}
|
||||
const result = await processUserInput({
|
||||
input: cmd.value,
|
||||
preExpansionInput: cmd.preExpansionValue,
|
||||
mode: cmd.mode,
|
||||
setToolJSX,
|
||||
context: makeContext(),
|
||||
pastedContents: isFirst ? cmd.pastedContents : undefined,
|
||||
messages,
|
||||
setUserInputOnProcessing: isFirst
|
||||
? setUserInputOnProcessing
|
||||
: undefined,
|
||||
isAlreadyProcessing: !isFirst,
|
||||
querySource,
|
||||
canUseTool,
|
||||
uuid: cmd.uuid,
|
||||
ideSelection: isFirst ? ideSelection : undefined,
|
||||
skipSlashCommands: cmd.skipSlashCommands,
|
||||
bridgeOrigin: cmd.bridgeOrigin,
|
||||
isMeta: cmd.isMeta,
|
||||
skipAttachments: !isFirst,
|
||||
})
|
||||
// Stamp origin here rather than threading another arg through
|
||||
// processUserInput → processUserInputBase → processTextPrompt → createUserMessage.
|
||||
// Derive origin from mode for task-notifications — mirrors the origin
|
||||
// derivation at messages.ts (case 'queued_command'); intentionally
|
||||
// does NOT mirror its isMeta:true so idle-dequeued notifications stay
|
||||
// visible in the transcript via UserAgentNotificationMessage.
|
||||
const origin =
|
||||
cmd.origin ??
|
||||
(cmd.mode === 'task-notification'
|
||||
? ({ kind: 'task-notification' } as const)
|
||||
: undefined)
|
||||
if (origin) {
|
||||
for (const m of result.messages) {
|
||||
if (m.type === 'user') m.origin = origin
|
||||
}
|
||||
}
|
||||
newMessages.push(...result.messages)
|
||||
if (isFirst) {
|
||||
shouldQuery = result.shouldQuery
|
||||
allowedTools = result.allowedTools
|
||||
model = result.model
|
||||
effort = result.effort
|
||||
nextInput = result.nextInput
|
||||
submitNextInput = result.submitNextInput
|
||||
}
|
||||
}
|
||||
newMessages.push(...result.messages)
|
||||
if (isFirst) {
|
||||
shouldQuery = result.shouldQuery
|
||||
allowedTools = result.allowedTools
|
||||
model = result.model
|
||||
effort = result.effort
|
||||
nextInput = result.nextInput
|
||||
submitNextInput = result.submitNextInput
|
||||
}
|
||||
}
|
||||
|
||||
queryCheckpoint('query_process_user_input_end')
|
||||
if (fileHistoryEnabled()) {
|
||||
queryCheckpoint('query_file_history_snapshot_start')
|
||||
newMessages.filter(selectableUserMessagesFilter).forEach(message => {
|
||||
void fileHistoryMakeSnapshot(
|
||||
(updater: (prev: FileHistoryState) => FileHistoryState) => {
|
||||
setAppState(prev => ({
|
||||
...prev,
|
||||
fileHistory: updater(prev.fileHistory),
|
||||
}))
|
||||
},
|
||||
message.uuid,
|
||||
queryCheckpoint('query_process_user_input_end')
|
||||
if (fileHistoryEnabled()) {
|
||||
queryCheckpoint('query_file_history_snapshot_start')
|
||||
newMessages.filter(selectableUserMessagesFilter).forEach(message => {
|
||||
void fileHistoryMakeSnapshot(
|
||||
(updater: (prev: FileHistoryState) => FileHistoryState) => {
|
||||
setAppState(prev => ({
|
||||
...prev,
|
||||
fileHistory: updater(prev.fileHistory),
|
||||
}))
|
||||
},
|
||||
message.uuid,
|
||||
)
|
||||
})
|
||||
queryCheckpoint('query_file_history_snapshot_end')
|
||||
}
|
||||
|
||||
if (newMessages.length) {
|
||||
// History is now added in the caller (onSubmit) for direct user submissions.
|
||||
// This ensures queued command processing (notifications, already-queued user input)
|
||||
// doesn't add to history, since those either shouldn't be in history or were
|
||||
// already added when originally queued.
|
||||
resetHistory()
|
||||
setToolJSX({
|
||||
jsx: null,
|
||||
shouldHidePromptInput: false,
|
||||
clearLocalJSX: true,
|
||||
})
|
||||
|
||||
const primaryCmd = commands[0]
|
||||
const primaryMode = primaryCmd?.mode ?? 'prompt'
|
||||
const primaryInput =
|
||||
primaryCmd && typeof primaryCmd.value === 'string'
|
||||
? primaryCmd.value
|
||||
: undefined
|
||||
const shouldCallBeforeQuery = primaryMode === 'prompt'
|
||||
await onQuery(
|
||||
newMessages,
|
||||
abortController,
|
||||
shouldQuery,
|
||||
allowedTools ?? [],
|
||||
model
|
||||
? resolveSkillModelOverride(model, mainLoopModel)
|
||||
: mainLoopModel,
|
||||
shouldCallBeforeQuery ? onBeforeQuery : undefined,
|
||||
primaryInput,
|
||||
effort,
|
||||
)
|
||||
})
|
||||
queryCheckpoint('query_file_history_snapshot_end')
|
||||
}
|
||||
|
||||
if (newMessages.length) {
|
||||
// History is now added in the caller (onSubmit) for direct user submissions.
|
||||
// This ensures queued command processing (notifications, already-queued user input)
|
||||
// doesn't add to history, since those either shouldn't be in history or were
|
||||
// already added when originally queued.
|
||||
resetHistory()
|
||||
setToolJSX({
|
||||
jsx: null,
|
||||
shouldHidePromptInput: false,
|
||||
clearLocalJSX: true,
|
||||
})
|
||||
|
||||
const primaryCmd = commands[0]
|
||||
const primaryMode = primaryCmd?.mode ?? 'prompt'
|
||||
const primaryInput =
|
||||
primaryCmd && typeof primaryCmd.value === 'string'
|
||||
? primaryCmd.value
|
||||
: undefined
|
||||
const shouldCallBeforeQuery = primaryMode === 'prompt'
|
||||
await onQuery(
|
||||
newMessages,
|
||||
abortController,
|
||||
shouldQuery,
|
||||
allowedTools ?? [],
|
||||
model
|
||||
? resolveSkillModelOverride(model, mainLoopModel)
|
||||
: mainLoopModel,
|
||||
shouldCallBeforeQuery ? onBeforeQuery : undefined,
|
||||
primaryInput,
|
||||
effort,
|
||||
)
|
||||
} else {
|
||||
// Local slash commands that skip messages (e.g., /model, /theme).
|
||||
// Release the guard BEFORE clearing toolJSX to prevent spinner flash —
|
||||
// the spinner formula checks: (!toolJSX || showSpinner) && isLoading.
|
||||
// If we clear toolJSX while the guard is still reserved, spinner briefly
|
||||
// shows. The finally below also calls cancelReservation (no-op if idle).
|
||||
queryGuard.cancelReservation()
|
||||
setToolJSX({
|
||||
jsx: null,
|
||||
shouldHidePromptInput: false,
|
||||
clearLocalJSX: true,
|
||||
})
|
||||
resetHistory()
|
||||
setAbortController(null)
|
||||
}
|
||||
|
||||
// Handle nextInput from commands that want to chain (e.g., /discover activation)
|
||||
if (nextInput) {
|
||||
if (submitNextInput) {
|
||||
enqueue({ value: nextInput, mode: 'prompt' })
|
||||
} else {
|
||||
params.onInputChange(nextInput)
|
||||
// Local slash commands that skip messages (e.g., /model, /theme).
|
||||
// Release the guard BEFORE clearing toolJSX to prevent spinner flash —
|
||||
// the spinner formula checks: (!toolJSX || showSpinner) && isLoading.
|
||||
// If we clear toolJSX while the guard is still reserved, spinner briefly
|
||||
// shows. The finally below also calls cancelReservation (no-op if idle).
|
||||
queryGuard.cancelReservation()
|
||||
setToolJSX({
|
||||
jsx: null,
|
||||
shouldHidePromptInput: false,
|
||||
clearLocalJSX: true,
|
||||
})
|
||||
resetHistory()
|
||||
setAbortController(null)
|
||||
}
|
||||
|
||||
// Handle nextInput from commands that want to chain (e.g., /discover activation)
|
||||
if (nextInput) {
|
||||
if (submitNextInput) {
|
||||
enqueue({ value: nextInput, mode: 'prompt' })
|
||||
} else {
|
||||
params.onInputChange(nextInput)
|
||||
}
|
||||
}
|
||||
}) // end runWithWorkload — ALS context naturally scoped, no finally needed
|
||||
if (autonomyRunIds?.length) {
|
||||
for (const runId of autonomyRunIds) {
|
||||
const nextCommands = await finalizeAutonomyRunCompleted({
|
||||
runId,
|
||||
priority: 'later',
|
||||
workload: turnWorkload,
|
||||
})
|
||||
for (const nextCommand of nextCommands) {
|
||||
enqueue(nextCommand)
|
||||
}
|
||||
}
|
||||
}
|
||||
}) // end runWithWorkload — ALS context naturally scoped, no finally needed
|
||||
} catch (error) {
|
||||
if (autonomyRunIds?.length) {
|
||||
for (const runId of autonomyRunIds) {
|
||||
await finalizeAutonomyRunFailed({
|
||||
runId,
|
||||
error: String(error),
|
||||
})
|
||||
}
|
||||
}
|
||||
throw error
|
||||
}
|
||||
} finally {
|
||||
// Safety net: release the guard reservation if processUserInput threw
|
||||
// or onQuery was skipped. No-op if onQuery already ran (guard is idle
|
||||
|
||||
Reference in New Issue
Block a user