* 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>
68 lines
2.0 KiB
TypeScript
68 lines
2.0 KiB
TypeScript
import { readFileSync, writeFileSync } from 'fs'
|
|
import { join } from 'path'
|
|
import type { AssistantMessage } from '../types/message.js'
|
|
|
|
/**
|
|
* Classify the job status from the turn's assistant messages and update state.json.
|
|
*
|
|
* Called by stopHooks.ts after each repl_main_thread turn when CLAUDE_JOB_DIR is set.
|
|
* Only the main thread calls this (not subagents).
|
|
*
|
|
* @param jobDir - Path to the job directory (from CLAUDE_JOB_DIR env)
|
|
* @param assistantMessages - Assistant messages from this turn
|
|
*/
|
|
export async function classifyAndWriteState(
|
|
jobDir: string,
|
|
assistantMessages: AssistantMessage[],
|
|
): Promise<void> {
|
|
const stateFile = join(jobDir, 'state.json')
|
|
|
|
let state: Record<string, unknown>
|
|
try {
|
|
state = JSON.parse(readFileSync(stateFile, 'utf-8'))
|
|
} catch {
|
|
// No state file or corrupt — not a valid job directory
|
|
return
|
|
}
|
|
|
|
const newStatus = classifyStatus(assistantMessages)
|
|
state.status = newStatus
|
|
state.updatedAt = new Date().toISOString()
|
|
|
|
writeFileSync(stateFile, JSON.stringify(state, null, 2), 'utf-8')
|
|
}
|
|
|
|
/**
|
|
* Determine job status from assistant messages.
|
|
*
|
|
* - Has tool_use blocks → still running (tools executing)
|
|
* - stop_reason === 'end_turn' → completed (model finished)
|
|
* - Otherwise → running
|
|
*/
|
|
function classifyStatus(messages: AssistantMessage[]): string {
|
|
if (messages.length === 0) return 'running'
|
|
|
|
const lastMessage = messages[messages.length - 1]!
|
|
const content = lastMessage.message?.content
|
|
|
|
// Check if the last message has tool_use blocks (still executing)
|
|
if (Array.isArray(content)) {
|
|
const hasToolUse = content.some(
|
|
block =>
|
|
typeof block === 'object' &&
|
|
block !== null &&
|
|
'type' in block &&
|
|
block.type === 'tool_use',
|
|
)
|
|
if (hasToolUse) return 'running'
|
|
}
|
|
|
|
// Check stop_reason via index signature
|
|
const stopReason = (lastMessage.message as Record<string, unknown>)
|
|
?.stop_reason
|
|
if (stopReason === 'end_turn') return 'completed'
|
|
if (stopReason === 'max_tokens') return 'running'
|
|
|
|
return 'running'
|
|
}
|