mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-17 13:55:50 +00:00
Merge branch 'claude-code-best:main' into main
This commit is contained in:
@@ -362,15 +362,9 @@ const proactiveModule =
|
||||
feature('PROACTIVE') || feature('KAIROS')
|
||||
? (require('../proactive/index.js') as typeof import('../proactive/index.js'))
|
||||
: null
|
||||
const cronSchedulerModule = feature('AGENT_TRIGGERS')
|
||||
? (require('../utils/cronScheduler.js') as typeof import('../utils/cronScheduler.js'))
|
||||
: null
|
||||
const cronJitterConfigModule = feature('AGENT_TRIGGERS')
|
||||
? (require('../utils/cronJitterConfig.js') as typeof import('../utils/cronJitterConfig.js'))
|
||||
: null
|
||||
const cronGate = feature('AGENT_TRIGGERS')
|
||||
? (require('../tools/ScheduleCronTool/prompt.js') as typeof import('../tools/ScheduleCronTool/prompt.js'))
|
||||
: null
|
||||
const cronSchedulerModule = require('../utils/cronScheduler.js') as typeof import('../utils/cronScheduler.js')
|
||||
const cronJitterConfigModule = require('../utils/cronJitterConfig.js') as typeof import('../utils/cronJitterConfig.js')
|
||||
const cronGate = require('../tools/ScheduleCronTool/prompt.js') as typeof import('../tools/ScheduleCronTool/prompt.js')
|
||||
const extractMemoriesModule = feature('EXTRACT_MEMORIES')
|
||||
? (require('../services/extractMemories/extractMemories.js') as typeof import('../services/extractMemories/extractMemories.js'))
|
||||
: null
|
||||
@@ -2706,9 +2700,7 @@ function runHeadlessStreaming(
|
||||
let cronScheduler: import('../utils/cronScheduler.js').CronScheduler | null =
|
||||
null
|
||||
if (
|
||||
feature('AGENT_TRIGGERS') &&
|
||||
cronSchedulerModule &&
|
||||
cronGate?.isKairosCronEnabled()
|
||||
cronGate.isKairosCronEnabled()
|
||||
) {
|
||||
cronScheduler = cronSchedulerModule.createCronScheduler({
|
||||
onFire: prompt => {
|
||||
|
||||
@@ -95,8 +95,7 @@ export function Settings(t0) {
|
||||
}
|
||||
let t7;
|
||||
if ($[13] !== contentHeight) {
|
||||
const GatesComponent = Gates as any;
|
||||
t7 = false ? [<Tab key="gates" title="Gates"><GatesComponent onOwnsEscChange={setGatesOwnsEsc} contentHeight={contentHeight} /></Tab>] : [];
|
||||
t7 = [];
|
||||
$[13] = contentHeight;
|
||||
$[14] = t7;
|
||||
} else {
|
||||
|
||||
@@ -82,9 +82,7 @@ export const IN_PROCESS_TEAMMATE_ALLOWED_TOOLS = new Set([
|
||||
SEND_MESSAGE_TOOL_NAME,
|
||||
// Teammate-created crons are tagged with the creating agentId and routed to
|
||||
// that teammate's pendingUserMessages queue (see useScheduledTasks.ts).
|
||||
...(feature('AGENT_TRIGGERS')
|
||||
? [CRON_CREATE_TOOL_NAME, CRON_DELETE_TOOL_NAME, CRON_LIST_TOOL_NAME]
|
||||
: []),
|
||||
CRON_CREATE_TOOL_NAME, CRON_DELETE_TOOL_NAME, CRON_LIST_TOOL_NAME,
|
||||
])
|
||||
|
||||
/*
|
||||
|
||||
@@ -253,15 +253,14 @@ async function getFilesUsingGit(
|
||||
logForDebugging(`[FileIndex] getFilesUsingGit called`)
|
||||
|
||||
// Check if we're in a git repo. findGitRoot is LRU-memoized per path.
|
||||
const repoRoot = findGitRoot(getCwd())
|
||||
const cwd = getCwd()
|
||||
const repoRoot = findGitRoot(cwd)
|
||||
if (!repoRoot) {
|
||||
logForDebugging(`[FileIndex] not a git repo, returning null`)
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
const cwd = getCwd()
|
||||
|
||||
// Get tracked files (fast - reads from git index)
|
||||
// Run from repoRoot so paths are relative to repo root, not CWD
|
||||
const lsFilesStart = Date.now()
|
||||
@@ -634,7 +633,9 @@ function findMatchingFiles(
|
||||
*/
|
||||
const REFRESH_THROTTLE_MS = 5_000
|
||||
export function startBackgroundCacheRefresh(): void {
|
||||
if (fileListRefreshPromise) return
|
||||
if (fileListRefreshPromise) {
|
||||
return
|
||||
}
|
||||
|
||||
// Throttle only when a cache exists — cold start must always populate.
|
||||
// Refresh immediately when .git/index mtime changed (tracked files).
|
||||
|
||||
@@ -211,47 +211,88 @@ export class FileIndex {
|
||||
|
||||
const haystack = caseSensitive ? paths[i]! : lowerPaths[i]!
|
||||
|
||||
// Fused indexOf scan: find positions (SIMD-accelerated in JSC/V8) AND
|
||||
// accumulate gap/consecutive terms inline. The greedy-earliest positions
|
||||
// found here are identical to what the charCodeAt scorer would find, so
|
||||
// we score directly from them — no second scan.
|
||||
let pos = haystack.indexOf(needleChars[0]!)
|
||||
if (pos === -1) continue
|
||||
posBuf[0] = pos
|
||||
let gapPenalty = 0
|
||||
let consecBonus = 0
|
||||
let prev = pos
|
||||
for (let j = 1; j < nLen; j++) {
|
||||
pos = haystack.indexOf(needleChars[j]!, prev + 1)
|
||||
if (pos === -1) continue outer
|
||||
posBuf[j] = pos
|
||||
const gap = pos - prev - 1
|
||||
if (gap === 0) consecBonus += BONUS_CONSECUTIVE
|
||||
else gapPenalty += PENALTY_GAP_START + gap * PENALTY_GAP_EXTENSION
|
||||
prev = pos
|
||||
// Greedy-leftmost indexOf gives fast but suboptimal positions when the
|
||||
// first needle char appears early (e.g. 's' in "src/") while the real
|
||||
// match lives deeper (e.g. "settings/"). We score from multiple start
|
||||
// positions — the leftmost hit plus every word-boundary occurrence of
|
||||
// needle[0] — and keep the best. Typical paths have 2–4 boundary starts,
|
||||
// so the overhead is minimal.
|
||||
|
||||
// Collect candidate start positions for needle[0]
|
||||
const firstChar = needleChars[0]!
|
||||
let startCount = 0
|
||||
// startPositions is stack-allocated (reused array would add complexity
|
||||
// for marginal gain; paths rarely have >8 boundary starts)
|
||||
const startPositions: number[] = []
|
||||
|
||||
// Always try the leftmost occurrence
|
||||
const firstPos = haystack.indexOf(firstChar)
|
||||
if (firstPos === -1) continue
|
||||
startPositions[startCount++] = firstPos
|
||||
|
||||
// Also try every word-boundary position where needle[0] occurs
|
||||
for (let bp = firstPos + 1; bp < haystack.length; bp++) {
|
||||
if (haystack.charCodeAt(bp) !== firstChar.charCodeAt(0)) continue
|
||||
// Check if this position is at a word boundary
|
||||
const prevCode = haystack.charCodeAt(bp - 1)
|
||||
if (
|
||||
prevCode === 47 || // /
|
||||
prevCode === 92 || // \
|
||||
prevCode === 45 || // -
|
||||
prevCode === 95 || // _
|
||||
prevCode === 46 || // .
|
||||
prevCode === 32 // space
|
||||
) {
|
||||
startPositions[startCount++] = bp
|
||||
}
|
||||
}
|
||||
|
||||
// Gap-bound reject: if the best-case score (all boundary bonuses) minus
|
||||
// known gap penalties can't beat threshold, skip the boundary pass.
|
||||
if (
|
||||
topK.length === limit &&
|
||||
scoreCeiling + consecBonus - gapPenalty <= threshold
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Boundary/camelCase scoring: check the char before each match position.
|
||||
const path = paths[i]!
|
||||
const originalPath = paths[i]!
|
||||
const hLen = pathLens[i]!
|
||||
let score = nLen * SCORE_MATCH + consecBonus - gapPenalty
|
||||
score += scoreBonusAt(path, posBuf[0]!, true)
|
||||
for (let j = 1; j < nLen; j++) {
|
||||
score += scoreBonusAt(path, posBuf[j]!, false)
|
||||
const lengthBonus = Math.max(0, 32 - (hLen >> 2))
|
||||
let bestScore = -Infinity
|
||||
|
||||
for (let si = 0; si < startCount; si++) {
|
||||
posBuf[0] = startPositions[si]!
|
||||
let gapPenalty = 0
|
||||
let consecBonus = 0
|
||||
let prev = posBuf[0]!
|
||||
let matched = true
|
||||
for (let j = 1; j < nLen; j++) {
|
||||
const pos = haystack.indexOf(needleChars[j]!, prev + 1)
|
||||
if (pos === -1) { matched = false; break }
|
||||
posBuf[j] = pos
|
||||
const gap = pos - prev - 1
|
||||
if (gap === 0) consecBonus += BONUS_CONSECUTIVE
|
||||
else gapPenalty += PENALTY_GAP_START + gap * PENALTY_GAP_EXTENSION
|
||||
prev = pos
|
||||
}
|
||||
if (!matched) continue
|
||||
|
||||
// Gap-bound reject for this start position
|
||||
if (
|
||||
topK.length === limit &&
|
||||
scoreCeiling + consecBonus - gapPenalty + lengthBonus <= threshold
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Boundary/camelCase scoring
|
||||
let score = nLen * SCORE_MATCH + consecBonus - gapPenalty
|
||||
score += scoreBonusAt(originalPath, posBuf[0]!, true)
|
||||
for (let j = 1; j < nLen; j++) {
|
||||
score += scoreBonusAt(originalPath, posBuf[j]!, false)
|
||||
}
|
||||
score += lengthBonus
|
||||
|
||||
if (score > bestScore) bestScore = score
|
||||
}
|
||||
score += Math.max(0, 32 - (hLen >> 2))
|
||||
|
||||
if (bestScore === -Infinity) continue
|
||||
const score = bestScore
|
||||
|
||||
if (topK.length < limit) {
|
||||
topK.push({ path, fuzzScore: score })
|
||||
topK.push({ path: originalPath, fuzzScore: score })
|
||||
if (topK.length === limit) {
|
||||
topK.sort((a, b) => a.fuzzScore - b.fuzzScore)
|
||||
threshold = topK[0]!.fuzzScore
|
||||
@@ -264,7 +305,7 @@ export class FileIndex {
|
||||
if (topK[mid]!.fuzzScore < score) lo = mid + 1
|
||||
else hi = mid
|
||||
}
|
||||
topK.splice(lo, 0, { path, fuzzScore: score })
|
||||
topK.splice(lo, 0, { path: originalPath, fuzzScore: score })
|
||||
topK.shift()
|
||||
threshold = topK[0]!.fuzzScore
|
||||
}
|
||||
|
||||
@@ -197,7 +197,7 @@ const PROACTIVE_NO_OP_SUBSCRIBE = (_cb: () => void) => () => {};
|
||||
const PROACTIVE_FALSE = () => false;
|
||||
const SUGGEST_BG_PR_NOOP = (_p: string, _n: string): boolean => false;
|
||||
const useProactive = feature('PROACTIVE') || feature('KAIROS') ? require('../proactive/useProactive.js').useProactive : null;
|
||||
const useScheduledTasks = feature('AGENT_TRIGGERS') ? require('../hooks/useScheduledTasks.js').useScheduledTasks : null;
|
||||
const useScheduledTasks = require('../hooks/useScheduledTasks.js').useScheduledTasks;
|
||||
/* eslint-enable @typescript-eslint/no-require-imports */
|
||||
import { isAgentSwarmsEnabled } from '../utils/agentSwarmsEnabled.js';
|
||||
import { useTaskListWatcher } from '../hooks/useTaskListWatcher.js';
|
||||
@@ -4047,16 +4047,9 @@ export function REPL({
|
||||
});
|
||||
|
||||
// Scheduled tasks from .claude/scheduled_tasks.json (CronCreate/Delete/List)
|
||||
if (feature('AGENT_TRIGGERS')) {
|
||||
// Assistant mode bypasses the isLoading gate (the proactive tick →
|
||||
// Sleep → tick loop would otherwise starve the scheduler).
|
||||
// kairosEnabled is set once in initialState (main.tsx) and never mutated — no
|
||||
// subscription needed. The tengu_kairos_cron runtime gate is checked inside
|
||||
// useScheduledTasks's effect (not here) since wrapping a hook call in a dynamic
|
||||
// condition would break rules-of-hooks.
|
||||
{
|
||||
const assistantMode = store.getState().kairosEnabled;
|
||||
// biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant
|
||||
useScheduledTasks!({
|
||||
useScheduledTasks({
|
||||
isLoading,
|
||||
assistantMode,
|
||||
setMessages
|
||||
|
||||
@@ -9,6 +9,7 @@ import { registerRememberSkill } from './remember.js'
|
||||
import { registerSimplifySkill } from './simplify.js'
|
||||
import { registerSkillifySkill } from './skillify.js'
|
||||
import { registerStuckSkill } from './stuck.js'
|
||||
import { registerLoopSkill } from './loop.js'
|
||||
import { registerUpdateConfigSkill } from './updateConfig.js'
|
||||
import { registerVerifySkill } from './verify.js'
|
||||
|
||||
@@ -32,6 +33,7 @@ export function initBundledSkills(): void {
|
||||
registerSimplifySkill()
|
||||
registerBatchSkill()
|
||||
registerStuckSkill()
|
||||
registerLoopSkill()
|
||||
if (feature('KAIROS') || feature('KAIROS_DREAM')) {
|
||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||
const { registerDreamSkill } = require('./dream.js')
|
||||
@@ -44,15 +46,6 @@ export function initBundledSkills(): void {
|
||||
/* eslint-enable @typescript-eslint/no-require-imports */
|
||||
registerHunterSkill()
|
||||
}
|
||||
if (feature('AGENT_TRIGGERS')) {
|
||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||
const { registerLoopSkill } = require('./loop.js')
|
||||
/* eslint-enable @typescript-eslint/no-require-imports */
|
||||
// /loop's isEnabled delegates to isKairosCronEnabled() — same lazy
|
||||
// per-invocation pattern as the cron tools. Registered unconditionally;
|
||||
// the skill's own isEnabled callback decides visibility.
|
||||
registerLoopSkill()
|
||||
}
|
||||
if (feature('AGENT_TRIGGERS_REMOTE')) {
|
||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||
const {
|
||||
|
||||
12
src/tools.ts
12
src/tools.ts
@@ -26,13 +26,11 @@ const SleepTool =
|
||||
feature('PROACTIVE') || feature('KAIROS')
|
||||
? require('./tools/SleepTool/SleepTool.js').SleepTool
|
||||
: null
|
||||
const cronTools = feature('AGENT_TRIGGERS')
|
||||
? [
|
||||
require('./tools/ScheduleCronTool/CronCreateTool.js').CronCreateTool,
|
||||
require('./tools/ScheduleCronTool/CronDeleteTool.js').CronDeleteTool,
|
||||
require('./tools/ScheduleCronTool/CronListTool.js').CronListTool,
|
||||
]
|
||||
: []
|
||||
const cronTools = [
|
||||
require('./tools/ScheduleCronTool/CronCreateTool.js').CronCreateTool,
|
||||
require('./tools/ScheduleCronTool/CronDeleteTool.js').CronDeleteTool,
|
||||
require('./tools/ScheduleCronTool/CronListTool.js').CronListTool,
|
||||
]
|
||||
const RemoteTriggerTool = feature('AGENT_TRIGGERS_REMOTE')
|
||||
? require('./tools/RemoteTriggerTool/RemoteTriggerTool.js').RemoteTriggerTool
|
||||
: null
|
||||
|
||||
@@ -34,14 +34,7 @@ export const DEFAULT_MAX_AGE_DAYS =
|
||||
* `CLAUDE_CODE_DISABLE_CRON` is a local override that wins over GB.
|
||||
*/
|
||||
export function isKairosCronEnabled(): boolean {
|
||||
return feature('AGENT_TRIGGERS')
|
||||
? !isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_CRON) &&
|
||||
getFeatureValue_CACHED_WITH_REFRESH(
|
||||
'tengu_kairos_cron',
|
||||
true,
|
||||
KAIROS_CRON_REFRESH_MS,
|
||||
)
|
||||
: false
|
||||
return !isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_CRON)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -109,7 +109,7 @@ export function execFileNoThrowWithCwd(
|
||||
// Use execa for cross-platform .bat/.cmd compatibility on Windows
|
||||
execa(file, args, {
|
||||
maxBuffer,
|
||||
signal: abortSignal,
|
||||
cancelSignal: abortSignal,
|
||||
timeout: finalTimeout,
|
||||
cwd: finalCwd,
|
||||
env: finalEnv,
|
||||
|
||||
Reference in New Issue
Block a user