Files
claude-code/src/proactive/useProactive.ts
claude-code-best c8d08d235b 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 from 637c908 dropped 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 from 637c908 dropped 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>
2026-04-16 20:59:29 +08:00

110 lines
3.0 KiB
TypeScript

/**
* useProactive — React hook that drives tick generation for proactive mode.
*
* Mounted inside REPL.tsx when feature('PROACTIVE') || feature('KAIROS').
* Generates <tick>HH:MM:SS</tick> prompts at a fixed interval while
* proactive mode is active and not blocked.
*/
import { useEffect, useRef } from 'react'
import type { QueuedCommand } from '../types/textInputTypes.js'
import { TICK_TAG } from '../constants/xml.js'
import { getCwd } from '../utils/cwd.js'
import { createProactiveAutonomyCommands } from '../utils/autonomyRuns.js'
import {
isProactiveActive,
isProactivePaused,
isContextBlocked,
setNextTickAt,
shouldTick,
} from './index.js'
/** Default interval between ticks (ms). Prompt cache TTL is ~5 min so we
* stay well under that to keep the cache warm. */
const TICK_INTERVAL_MS = 30_000
type UseProactiveOpts = {
isLoading: boolean
queuedCommandsLength: number
hasActiveLocalJsxUI: boolean
isInPlanMode: boolean
onQueueTick: (command: QueuedCommand) => void
}
export function useProactive(opts: UseProactiveOpts): void {
const optsRef = useRef(opts)
optsRef.current = opts
useEffect(() => {
if (!isProactiveActive()) return
let timer: ReturnType<typeof setTimeout> | null = null
function scheduleTick(): void {
const nextTs = Date.now() + TICK_INTERVAL_MS
setNextTickAt(nextTs)
timer = setTimeout(() => {
timer = null
// Guard: skip tick if any blocking condition is met
if (!shouldTick()) {
// Reschedule — conditions may clear later
scheduleTick()
return
}
const {
isLoading,
queuedCommandsLength,
hasActiveLocalJsxUI,
isInPlanMode,
} = optsRef.current
// Don't fire while a query is in-flight, plan mode is active,
// a local JSX UI is showing, or commands are queued
if (
isLoading ||
isInPlanMode ||
hasActiveLocalJsxUI ||
queuedCommandsLength > 0
) {
scheduleTick()
return
}
void (async () => {
const commands = await createProactiveAutonomyCommands({
basePrompt: `<${TICK_TAG}>${new Date().toLocaleTimeString()}</${TICK_TAG}>`,
currentDir: getCwd(),
})
for (const command of commands) {
// Always queue proactive turns. This avoids races where the prompt
// is built asynchronously, a user turn starts meanwhile, and a
// direct-submit path would silently drop the autonomy turn after
// consuming its heartbeat due-state.
optsRef.current.onQueueTick(command)
}
})()
// Schedule next tick
scheduleTick()
}, TICK_INTERVAL_MS)
}
scheduleTick()
return () => {
if (timer !== null) {
clearTimeout(timer)
timer = null
}
setNextTickAt(null)
}
}, [
// Re-mount when proactive state changes
isProactiveActive(),
isProactivePaused(),
isContextBlocked(),
])
}