@xelauvas/xela-cli 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/local/README.md +112 -0
- package/local/start-qwen.sh +47 -0
- package/local/start-tunnel.sh +36 -0
- package/local/xela-local.sh +139 -0
- package/package.json +19 -5
- package/run.sh +33 -0
- package/src/Tool.ts +1 -1
- package/src/_shims/_generated_stubs/_universal_stub.mjs +168 -0
- package/src/_shims/_generated_stubs/stub_346f96741420f62caece15b196e233c6.mjs +19 -0
- package/src/_shims/_generated_stubs/stub_4b79af2a6ac1af5a53a55fd666b2bbb4.mjs +3 -0
- package/src/_shims/_generated_stubs/stub_94345ddeadacc184ff895597ec5abf71.mjs +6 -0
- package/src/_shims/_generated_stubs/stub_99fa0bc272643c549d8a2ec6d578436f.mjs +3 -0
- package/src/_shims/_generated_stubs/stub_e3451b131fcaf272a2a75270b9a427a3.mjs +9 -0
- package/src/_shims/_generated_stubs/text_stub.mjs +1 -0
- package/src/_shims/bun_bundle.js +79 -4
- package/src/_shims/cjs_stub.cjs +32 -10
- package/src/_shims/loader.js +104 -5
- package/src/_shims/openai_adapter.ts +446 -230
- package/src/_shims/register.js +16 -3
- package/src/assistant/gate.ts +15 -0
- package/src/assistant/index.ts +89 -0
- package/src/bootstrap/state.ts +6 -1
- package/src/bridge/peerSessions.ts +18 -0
- package/src/cli/print.ts +5 -0
- package/src/commands/assistant/assistant.ts +17 -0
- package/src/commands/assistant/index.ts +11 -0
- package/src/commands/brief.ts +3 -1
- package/src/commands/buddy/buddy.ts +88 -0
- package/src/commands/buddy/index.ts +12 -0
- package/src/commands/model/model.tsx +8 -23
- package/src/commands/peers/index.ts +11 -0
- package/src/commands/peers/peers.ts +24 -0
- package/src/commands/proactive/proactive.ts +64 -0
- package/src/commands/proactive.ts +12 -0
- package/src/commands/workflows/index.ts +12 -0
- package/src/commands/workflows/workflows.ts +36 -0
- package/src/commands.ts +4 -8
- package/src/components/Messages.tsx +1 -1
- package/src/components/ModelPicker.tsx +4 -34
- package/src/components/Spinner.tsx +54 -1
- package/src/components/StructuredDiff/colorDiff.ts +3 -1
- package/src/components/messages/SnipBoundaryMessage.tsx +15 -0
- package/src/components/messages/UserCrossSessionMessage.tsx +26 -0
- package/src/components/permissions/PermissionRequest.tsx +4 -2
- package/src/components/permissions/ReviewArtifactPermissionRequest/ReviewArtifactPermissionRequest.tsx +96 -0
- package/src/constants/prompts.ts +26 -0
- package/src/ink/terminal.ts +9 -1
- package/src/jobs/classifier.ts +103 -0
- package/src/memdir/paths.ts +13 -1
- package/src/proactive/index.ts +158 -0
- package/src/proactive/useProactive.ts +78 -0
- package/src/screens/REPL.tsx +28 -8
- package/src/services/api/claude.ts +6 -1
- package/src/services/compact/autoCompact.ts +18 -4
- package/src/services/compact/cachedMCConfig.ts +1 -0
- package/src/services/compact/cachedMicrocompact.ts +113 -0
- package/src/services/compact/compact.ts +22 -2
- package/src/services/compact/reactiveCompact.ts +251 -0
- package/src/services/compact/snipCompact.ts +282 -0
- package/src/services/compact/snipProjection.ts +50 -0
- package/src/services/contextCollapse/index.ts +322 -0
- package/src/services/contextCollapse/operations.ts +187 -0
- package/src/services/contextCollapse/persist.ts +40 -0
- package/src/services/extractMemories/extractMemories.ts +11 -2
- package/src/services/skillSearch/featureCheck.ts +12 -0
- package/src/services/skillSearch/localSearch.ts +98 -0
- package/src/services/skillSearch/prefetch.ts +125 -0
- package/src/services/skillSearch/remoteSkillLoader.ts +9 -0
- package/src/services/skillSearch/remoteSkillState.ts +21 -0
- package/src/services/skillSearch/signals.ts +7 -0
- package/src/services/skillSearch/telemetry.ts +9 -0
- package/src/setup.ts +10 -5
- package/src/skills/bundled/dream.ts +31 -0
- package/src/skills/bundled/hunter.ts +45 -0
- package/src/skills/bundled/index.ts +10 -23
- package/src/skills/bundled/runSkillGenerator.ts +61 -0
- package/src/skills/mcpSkills.ts +101 -0
- package/src/tasks/LocalWorkflowTask/LocalWorkflowTask.ts +301 -0
- package/src/tasks/MonitorMcpTask/MonitorMcpTask.ts +273 -0
- package/src/tasks.ts +22 -8
- package/src/tools/AgentTool/runAgent.ts +11 -0
- package/src/tools/BriefTool/BriefTool.ts +2 -0
- package/src/tools/CtxInspectTool/CtxInspectTool.ts +102 -0
- package/src/tools/DiscoverSkillsTool/DiscoverSkillsTool.ts +78 -0
- package/src/tools/DiscoverSkillsTool/prompt.ts +3 -0
- package/src/tools/ListPeersTool/ListPeersTool.ts +89 -0
- package/src/tools/MCPTool/MCPTool.ts +6 -0
- package/src/tools/MonitorTool/MonitorTool.ts +273 -0
- package/src/tools/MonitorTool/UI.tsx +25 -0
- package/src/tools/MonitorTool/events.ts +84 -0
- package/src/tools/MonitorTool/prompt.ts +45 -0
- package/src/tools/OverflowTestTool/OverflowTestTool.ts +73 -0
- package/src/tools/PushNotificationTool/PushNotificationTool.ts +119 -0
- package/src/tools/ReviewArtifactTool/ReviewArtifactTool.ts +93 -0
- package/src/tools/ReviewArtifactTool/prompt.ts +5 -0
- package/src/tools/SendUserFileTool/SendUserFileTool.ts +87 -0
- package/src/tools/SendUserFileTool/prompt.ts +7 -0
- package/src/tools/SleepTool/SleepTool.ts +126 -0
- package/src/tools/SnipTool/SnipTool.ts +90 -0
- package/src/tools/SnipTool/prompt.ts +5 -0
- package/src/tools/SubscribePRTool/SubscribePRTool.ts +115 -0
- package/src/tools/TerminalCaptureTool/TerminalCaptureTool.ts +129 -0
- package/src/tools/TerminalCaptureTool/prompt.ts +8 -0
- package/src/tools/ToolSearchTool/prompt.ts +1 -1
- package/src/tools/VerifyPlanExecutionTool/VerifyPlanExecutionTool.ts +99 -0
- package/src/tools/WebBrowserTool/WebBrowserPanel.tsx +10 -0
- package/src/tools/WebBrowserTool/WebBrowserTool.ts +190 -0
- package/src/tools/WebBrowserTool/prompt.ts +16 -0
- package/src/tools/WorkflowTool/UI.tsx +27 -0
- package/src/tools/WorkflowTool/WorkflowTool.ts +401 -0
- package/src/tools/WorkflowTool/bundled/index.ts +88 -0
- package/src/tools/WorkflowTool/constants.ts +7 -0
- package/src/tools/WorkflowTool/createWorkflowCommand.ts +84 -0
- package/src/tools/WorkflowTool/prompt.ts +38 -0
- package/src/tools/WorkflowTool/runtime.ts +331 -0
- package/src/tools.ts +36 -31
- package/src/utils/betas.ts +5 -0
- package/src/utils/context.ts +9 -0
- package/src/utils/conversationRecovery.ts +1 -1
- package/src/utils/forkedAgent.ts +4 -0
- package/src/utils/logoV2Utils.ts +7 -3
- package/src/utils/messageQueueManager.ts +23 -9
- package/src/utils/model/model.ts +10 -9
- package/src/utils/model/modelOptions.ts +0 -21
- package/src/utils/model/validateModel.ts +0 -19
- package/src/utils/permissions/permissionSetup.ts +5 -0
- package/src/utils/permissions/permissions.ts +25 -0
- package/src/utils/permissions/yolo-classifier-prompts/auto_mode_system_prompt.txt +38 -0
- package/src/utils/permissions/yolo-classifier-prompts/permissions_external.txt +25 -0
- package/src/utils/permissions/yoloClassifier.ts +28 -6
- package/src/utils/plugins/officialMarketplaceStartupCheck.ts +9 -0
- package/src/utils/systemThemeWatcher.ts +62 -0
- package/src/utils/udsClient.ts +115 -0
- package/src/utils/udsMessaging.ts +157 -0
- package/src/utils/yaml.ts +251 -5
- package/src/utils/model/providerModels.ts +0 -168
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { TICK_TAG } from '../constants/xml.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Proactive mode: the model keeps working between user prompts. The session
|
|
5
|
+
* receives periodic <tick> prompts (interactive: useProactive in the REPL;
|
|
6
|
+
* headless: print.ts scheduleProactiveTick) and either does something useful
|
|
7
|
+
* or calls Sleep, which schedules the next tick. State lives on globalThis
|
|
8
|
+
* because this module is reached both through ESM imports and the CJS
|
|
9
|
+
* require shim, and React reads it through subscribeToProactiveChanges.
|
|
10
|
+
*/
|
|
11
|
+
export type ProactiveSource = 'command' | 'flag' | 'sdk' | 'env'
|
|
12
|
+
|
|
13
|
+
type ProactiveState = {
|
|
14
|
+
active: boolean
|
|
15
|
+
paused: boolean
|
|
16
|
+
contextBlocked: boolean
|
|
17
|
+
source: ProactiveSource | null
|
|
18
|
+
activatedAt: number | null
|
|
19
|
+
tickCount: number
|
|
20
|
+
lastTickAt: number | null
|
|
21
|
+
/** When the next tick is due (set by Sleep or the ticker), null when none is scheduled. */
|
|
22
|
+
nextTickAt: number | null
|
|
23
|
+
listeners: Set<() => void>
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const state: ProactiveState = ((globalThis as unknown as { __xelaProactive?: ProactiveState })
|
|
27
|
+
.__xelaProactive ??= {
|
|
28
|
+
active: false,
|
|
29
|
+
paused: false,
|
|
30
|
+
contextBlocked: false,
|
|
31
|
+
source: null,
|
|
32
|
+
activatedAt: null,
|
|
33
|
+
tickCount: 0,
|
|
34
|
+
lastTickAt: null,
|
|
35
|
+
nextTickAt: null,
|
|
36
|
+
listeners: new Set(),
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
/** Default spacing between ticks when the model does not Sleep itself. */
|
|
40
|
+
export const PROACTIVE_TICK_INTERVAL_MS = Number(
|
|
41
|
+
process.env.CLAUDE_CODE_PROACTIVE_TICK_MS || 5 * 60_000,
|
|
42
|
+
)
|
|
43
|
+
/** First tick shortly after activation so the model greets and starts. */
|
|
44
|
+
export const PROACTIVE_FIRST_TICK_MS = 1_500
|
|
45
|
+
|
|
46
|
+
function notify(): void {
|
|
47
|
+
for (const l of state.listeners) {
|
|
48
|
+
try {
|
|
49
|
+
l()
|
|
50
|
+
} catch {
|
|
51
|
+
// listener errors must not break state updates
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function subscribeToProactiveChanges(listener: () => void): () => void {
|
|
57
|
+
state.listeners.add(listener)
|
|
58
|
+
return () => {
|
|
59
|
+
state.listeners.delete(listener)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function isProactiveActive(): boolean {
|
|
64
|
+
return state.active
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function isProactivePaused(): boolean {
|
|
68
|
+
return state.paused || state.contextBlocked
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function isContextBlocked(): boolean {
|
|
72
|
+
return state.contextBlocked
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function getNextTickAt(): number | null {
|
|
76
|
+
return state.active && !isProactivePaused() ? state.nextTickAt : null
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function getProactiveState(): Readonly<Omit<ProactiveState, 'listeners'>> {
|
|
80
|
+
const { listeners: _l, ...rest } = state
|
|
81
|
+
return rest
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function activateProactive(source: ProactiveSource = 'command'): void {
|
|
85
|
+
state.active = true
|
|
86
|
+
state.paused = false
|
|
87
|
+
state.contextBlocked = false
|
|
88
|
+
state.source = source
|
|
89
|
+
state.activatedAt = Date.now()
|
|
90
|
+
state.nextTickAt = Date.now() + PROACTIVE_FIRST_TICK_MS
|
|
91
|
+
notify()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function deactivateProactive(): void {
|
|
95
|
+
state.active = false
|
|
96
|
+
state.paused = false
|
|
97
|
+
state.source = null
|
|
98
|
+
state.nextTickAt = null
|
|
99
|
+
notify()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function pauseProactive(): void {
|
|
103
|
+
if (!state.paused) {
|
|
104
|
+
state.paused = true
|
|
105
|
+
notify()
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function resumeProactive(): void {
|
|
110
|
+
if (state.paused || state.contextBlocked) {
|
|
111
|
+
state.paused = false
|
|
112
|
+
state.contextBlocked = false
|
|
113
|
+
notify()
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Set when the context is too full to keep ticking (prompt-too-long with
|
|
119
|
+
* nothing left to compact). Cleared by /clear and by resumeProactive.
|
|
120
|
+
*/
|
|
121
|
+
export function setContextBlocked(blocked: boolean): void {
|
|
122
|
+
if (state.contextBlocked !== blocked) {
|
|
123
|
+
state.contextBlocked = blocked
|
|
124
|
+
notify()
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Schedule the next tick `ms` from now (Sleep tool, ticker). */
|
|
129
|
+
export function scheduleNextTick(ms: number): number {
|
|
130
|
+
const at = Date.now() + Math.max(0, ms)
|
|
131
|
+
state.nextTickAt = at
|
|
132
|
+
notify()
|
|
133
|
+
return at
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function clearNextTick(): void {
|
|
137
|
+
if (state.nextTickAt !== null) {
|
|
138
|
+
state.nextTickAt = null
|
|
139
|
+
notify()
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function recordTick(): number {
|
|
144
|
+
state.tickCount++
|
|
145
|
+
state.lastTickAt = Date.now()
|
|
146
|
+
state.nextTickAt = null
|
|
147
|
+
notify()
|
|
148
|
+
return state.tickCount
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** The prompt injected on every tick: the user's local time, as in headless mode. */
|
|
152
|
+
export function getTickPrompt(): string {
|
|
153
|
+
return `<${TICK_TAG}>${new Date().toLocaleTimeString()}</${TICK_TAG}>`
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function isTickPrompt(text: string): boolean {
|
|
157
|
+
return new RegExp(`^<${TICK_TAG}\\b`).test(text.trimStart())
|
|
158
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { useEffect, useRef, useSyncExternalStore } from 'react'
|
|
2
|
+
import { logForDebugging } from '../utils/debug.js'
|
|
3
|
+
import {
|
|
4
|
+
getNextTickAt,
|
|
5
|
+
getTickPrompt,
|
|
6
|
+
isProactiveActive,
|
|
7
|
+
isProactivePaused,
|
|
8
|
+
PROACTIVE_TICK_INTERVAL_MS,
|
|
9
|
+
recordTick,
|
|
10
|
+
scheduleNextTick,
|
|
11
|
+
subscribeToProactiveChanges,
|
|
12
|
+
} from './index.js'
|
|
13
|
+
|
|
14
|
+
type Props = {
|
|
15
|
+
isLoading: boolean
|
|
16
|
+
queuedCommandsLength: number
|
|
17
|
+
hasActiveLocalJsxUI: boolean
|
|
18
|
+
isInPlanMode: boolean
|
|
19
|
+
/** Submit a tick prompt right away (idle REPL). */
|
|
20
|
+
onSubmitTick: (prompt: string) => void
|
|
21
|
+
/** Queue a tick prompt behind the current turn (busy REPL). */
|
|
22
|
+
onQueueTick: (prompt: string) => void
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Interactive tick driver. While proactive mode is active and not paused,
|
|
27
|
+
* fires a <tick> prompt when the scheduled time arrives (set by Sleep or,
|
|
28
|
+
* failing that, the default interval). Ticks wait while a turn is running,
|
|
29
|
+
* while commands are queued, while a local dialog is open, or in plan mode.
|
|
30
|
+
*/
|
|
31
|
+
export function useProactive({
|
|
32
|
+
isLoading,
|
|
33
|
+
queuedCommandsLength,
|
|
34
|
+
hasActiveLocalJsxUI,
|
|
35
|
+
isInPlanMode,
|
|
36
|
+
onSubmitTick,
|
|
37
|
+
onQueueTick,
|
|
38
|
+
}: Props): void {
|
|
39
|
+
const active = useSyncExternalStore(subscribeToProactiveChanges, isProactiveActive)
|
|
40
|
+
const paused = useSyncExternalStore(subscribeToProactiveChanges, isProactivePaused)
|
|
41
|
+
const nextTickAt = useSyncExternalStore(subscribeToProactiveChanges, getNextTickAt)
|
|
42
|
+
|
|
43
|
+
const latest = useRef({ isLoading, queuedCommandsLength, hasActiveLocalJsxUI, isInPlanMode, onSubmitTick, onQueueTick })
|
|
44
|
+
latest.current = { isLoading, queuedCommandsLength, hasActiveLocalJsxUI, isInPlanMode, onSubmitTick, onQueueTick }
|
|
45
|
+
|
|
46
|
+
// Nothing scheduled while active and idle: fall back to the default interval
|
|
47
|
+
// so a turn that ended without Sleep still gets a follow-up.
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (!active || paused || nextTickAt !== null || isLoading) return
|
|
50
|
+
scheduleNextTick(PROACTIVE_TICK_INTERVAL_MS)
|
|
51
|
+
}, [active, paused, nextTickAt, isLoading])
|
|
52
|
+
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (!active || paused || nextTickAt === null) return
|
|
55
|
+
const fire = (): void => {
|
|
56
|
+
const l = latest.current
|
|
57
|
+
if (!isProactiveActive() || isProactivePaused()) {
|
|
58
|
+
logForDebugging('[proactive] tick skipped: inactive or paused')
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
if (l.hasActiveLocalJsxUI || l.isInPlanMode) {
|
|
62
|
+
// Try again shortly; the user is in a dialog or planning.
|
|
63
|
+
logForDebugging('[proactive] tick deferred: dialog open or plan mode')
|
|
64
|
+
scheduleNextTick(15_000)
|
|
65
|
+
return
|
|
66
|
+
}
|
|
67
|
+
const n = recordTick()
|
|
68
|
+
const prompt = getTickPrompt()
|
|
69
|
+
logForDebugging(`[proactive] tick ${n} ${l.isLoading || l.queuedCommandsLength > 0 ? 'queued' : 'submitted'}`)
|
|
70
|
+
if (l.isLoading || l.queuedCommandsLength > 0) l.onQueueTick(prompt)
|
|
71
|
+
else l.onSubmitTick(prompt)
|
|
72
|
+
}
|
|
73
|
+
const delay = Math.max(0, nextTickAt - Date.now())
|
|
74
|
+
logForDebugging(`[proactive] next tick in ${Math.round(delay / 1000)}s`)
|
|
75
|
+
const timer = setTimeout(fire, delay)
|
|
76
|
+
return () => clearTimeout(timer)
|
|
77
|
+
}, [active, paused, nextTickAt])
|
|
78
|
+
}
|
package/src/screens/REPL.tsx
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { c as _c } from "react/compiler-runtime";
|
|
2
|
+
// Static imports (not require): these touch React context / shared state and
|
|
3
|
+
// must be the same module instance as the rest of the ESM graph.
|
|
4
|
+
import * as proactiveModuleImpl from '../proactive/index.js';
|
|
5
|
+
import { useProactive as useProactiveImpl } from '../proactive/useProactive.js';
|
|
6
|
+
import { useScheduledTasks as useScheduledTasksImpl } from '../hooks/useScheduledTasks.js';
|
|
2
7
|
// biome-ignore-all assist/source/organizeImports: ANT-ONLY import markers must not be reordered
|
|
3
8
|
import { feature } from 'bun:bundle';
|
|
4
9
|
import { spawnSync } from 'child_process';
|
|
@@ -67,6 +72,7 @@ import { SkillImprovementSurvey } from '../components/SkillImprovementSurvey.js'
|
|
|
67
72
|
import { useSkillImprovementSurvey } from '../hooks/useSkillImprovementSurvey.js';
|
|
68
73
|
import { useMoreRight } from '../moreright/useMoreRight.js';
|
|
69
74
|
import { SpinnerWithVerb, BriefIdleStatus, type SpinnerMode } from '../components/Spinner.js';
|
|
75
|
+
import type { CompactProgressInfo } from '../components/Spinner.js';
|
|
70
76
|
import { getSystemPrompt } from '../constants/prompts.js';
|
|
71
77
|
import { buildEffectiveSystemPrompt } from '../utils/systemPrompt.js';
|
|
72
78
|
import { getSystemContext, getUserContext } from '../context.js';
|
|
@@ -191,12 +197,12 @@ import { restoreRemoteAgentTasks } from '../tasks/RemoteAgentTask/RemoteAgentTas
|
|
|
191
197
|
import { useInboxPoller } from '../hooks/useInboxPoller.js';
|
|
192
198
|
// Dead code elimination: conditional import for loop mode
|
|
193
199
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
194
|
-
const proactiveModule = feature('PROACTIVE') || feature('KAIROS') ?
|
|
200
|
+
const proactiveModule = feature('PROACTIVE') || feature('KAIROS') ? proactiveModuleImpl : null;
|
|
195
201
|
const PROACTIVE_NO_OP_SUBSCRIBE = (_cb: () => void) => () => {};
|
|
196
202
|
const PROACTIVE_FALSE = () => false;
|
|
197
203
|
const SUGGEST_BG_PR_NOOP = (_p: string, _n: string): boolean => false;
|
|
198
|
-
const useProactive = feature('PROACTIVE') || feature('KAIROS') ?
|
|
199
|
-
const useScheduledTasks = feature('AGENT_TRIGGERS') ?
|
|
204
|
+
const useProactive = feature('PROACTIVE') || feature('KAIROS') ? useProactiveImpl : null;
|
|
205
|
+
const useScheduledTasks = feature('AGENT_TRIGGERS') ? useScheduledTasksImpl : null;
|
|
200
206
|
/* eslint-enable @typescript-eslint/no-require-imports */
|
|
201
207
|
import { isAgentSwarmsEnabled } from '../utils/agentSwarmsEnabled.js';
|
|
202
208
|
import { useTaskListWatcher } from '../hooks/useTaskListWatcher.js';
|
|
@@ -1473,6 +1479,9 @@ export function REPL({
|
|
|
1473
1479
|
const visibleStreamingText = streamingText && showStreamingText ? streamingText.substring(0, streamingText.lastIndexOf('\n') + 1) || null : null;
|
|
1474
1480
|
const [lastQueryCompletionTime, setLastQueryCompletionTime] = useState(0);
|
|
1475
1481
|
const [spinnerMessage, setSpinnerMessage] = useState<string | null>(null);
|
|
1482
|
+
// Set while a compaction summary is being generated; drives the progress
|
|
1483
|
+
// bar under the spinner (see CompactProgressBar in Spinner.tsx).
|
|
1484
|
+
const [compactProgress, setCompactProgress] = useState<CompactProgressInfo | null>(null);
|
|
1476
1485
|
const [spinnerColor, setSpinnerColor] = useState<keyof Theme | null>(null);
|
|
1477
1486
|
const [spinnerShimmerColor, setSpinnerShimmerColor] = useState<keyof Theme | null>(null);
|
|
1478
1487
|
const [isMessageSelectorVisible, setIsMessageSelectorVisible] = useState(false);
|
|
@@ -1576,6 +1585,7 @@ export function REPL({
|
|
|
1576
1585
|
setStreamingText(null);
|
|
1577
1586
|
setStreamingToolUses([]);
|
|
1578
1587
|
setSpinnerMessage(null);
|
|
1588
|
+
setCompactProgress(null);
|
|
1579
1589
|
setSpinnerColor(null);
|
|
1580
1590
|
setSpinnerShimmerColor(null);
|
|
1581
1591
|
pickNewSpinnerTip();
|
|
@@ -2503,9 +2513,14 @@ export function REPL({
|
|
|
2503
2513
|
break;
|
|
2504
2514
|
case 'compact_start':
|
|
2505
2515
|
setSpinnerMessage('Compacting conversation');
|
|
2516
|
+
setCompactProgress({
|
|
2517
|
+
startedAt: Date.now(),
|
|
2518
|
+
preCompactTokenCount: event.preCompactTokenCount ?? 0
|
|
2519
|
+
});
|
|
2506
2520
|
break;
|
|
2507
2521
|
case 'compact_end':
|
|
2508
2522
|
setSpinnerMessage(null);
|
|
2523
|
+
setCompactProgress(null);
|
|
2509
2524
|
setSpinnerColor(null);
|
|
2510
2525
|
setSpinnerShimmerColor(null);
|
|
2511
2526
|
break;
|
|
@@ -3062,7 +3077,7 @@ export function REPL({
|
|
|
3062
3077
|
}
|
|
3063
3078
|
|
|
3064
3079
|
// Atomically: clear initial message, set permission mode and rules, and store plan for verification
|
|
3065
|
-
const shouldStorePlanForVerification = initialMsg.message.planContent &&
|
|
3080
|
+
const shouldStorePlanForVerification = initialMsg.message.planContent && process.env.CLAUDE_CODE_VERIFY_PLAN === 'true';
|
|
3066
3081
|
setAppState(prev => {
|
|
3067
3082
|
// Build and apply permission updates (mode + allowedPrompts rules)
|
|
3068
3083
|
let updatedToolPermissionContext = initialMsg.mode ? applyPermissionUpdates(prev.toolPermissionContext, buildPermissionUpdates(initialMsg.mode, initialMsg.allowedPrompts)) : prev.toolPermissionContext;
|
|
@@ -4073,10 +4088,15 @@ export function REPL({
|
|
|
4073
4088
|
onSubmitTask: handleIncomingPrompt
|
|
4074
4089
|
});
|
|
4075
4090
|
|
|
4076
|
-
|
|
4091
|
+
}
|
|
4092
|
+
|
|
4093
|
+
// Proactive mode: auto-tick when enabled (/proactive, --proactive). Upstream
|
|
4094
|
+
// this sat inside the ant-only block above; the tick driver is needed in
|
|
4095
|
+
// every build where PROACTIVE is on.
|
|
4096
|
+
if (feature('PROACTIVE') || feature('KAIROS')) {
|
|
4077
4097
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
4078
|
-
// biome-ignore lint/correctness/useHookAtTopLevel:
|
|
4079
|
-
useProactive
|
|
4098
|
+
// biome-ignore lint/correctness/useHookAtTopLevel: feature() is a compile-time constant
|
|
4099
|
+
useProactive!({
|
|
4080
4100
|
// Suppress ticks while an initial message is pending — the initial
|
|
4081
4101
|
// message will be processed asynchronously and a premature tick would
|
|
4082
4102
|
// race with it, causing concurrent-query enqueue of expanded skill text.
|
|
@@ -4584,7 +4604,7 @@ export function REPL({
|
|
|
4584
4604
|
{"external" === 'ant' && <TungstenLiveMonitor />}
|
|
4585
4605
|
{feature('WEB_BROWSER_TOOL') ? WebBrowserPanelModule && <WebBrowserPanelModule.WebBrowserPanel /> : null}
|
|
4586
4606
|
<Box flexGrow={1} />
|
|
4587
|
-
{showSpinner && <SpinnerWithVerb mode={streamMode} spinnerTip={spinnerTip} responseLengthRef={responseLengthRef} apiMetricsRef={apiMetricsRef} overrideMessage={spinnerMessage} spinnerSuffix={stopHookSpinnerSuffix} verbose={verbose} loadingStartTimeRef={loadingStartTimeRef} totalPausedMsRef={totalPausedMsRef} pauseStartTimeRef={pauseStartTimeRef} overrideColor={spinnerColor} overrideShimmerColor={spinnerShimmerColor} hasActiveTools={inProgressToolUseIDs.size > 0} leaderIsIdle={!isLoading} />}
|
|
4607
|
+
{showSpinner && <SpinnerWithVerb mode={streamMode} spinnerTip={spinnerTip} responseLengthRef={responseLengthRef} apiMetricsRef={apiMetricsRef} overrideMessage={spinnerMessage} compactProgress={compactProgress} spinnerSuffix={stopHookSpinnerSuffix} verbose={verbose} loadingStartTimeRef={loadingStartTimeRef} totalPausedMsRef={totalPausedMsRef} pauseStartTimeRef={pauseStartTimeRef} overrideColor={spinnerColor} overrideShimmerColor={spinnerShimmerColor} hasActiveTools={inProgressToolUseIDs.size > 0} leaderIsIdle={!isLoading} />}
|
|
4588
4608
|
{!showSpinner && !isLoading && !userInputOnProcessing && !hasRunningTeammates && isBriefOnly && !viewedAgentTask && <BriefIdleStatus />}
|
|
4589
4609
|
{isFullscreenEnvEnabled() && <PromptInputQueuedCommands />}
|
|
4590
4610
|
</>} bottom={<Box flexDirection={feature('BUDDY') && companionNarrow ? 'column' : 'row'} width="100%" alignItems={feature('BUDDY') && companionNarrow ? undefined : 'flex-end'}>
|
|
@@ -1214,7 +1214,7 @@ async function* queryModel(
|
|
|
1214
1214
|
cachedMCEnabled = featureEnabled && modelSupported
|
|
1215
1215
|
const config = getCachedMCConfig()
|
|
1216
1216
|
logForDebugging(
|
|
1217
|
-
`Cached MC gate: enabled=${featureEnabled} modelSupported=${modelSupported} model=${options.model} supportedModels=${jsonStringify(config
|
|
1217
|
+
`Cached MC gate: enabled=${featureEnabled} modelSupported=${modelSupported} model=${options.model} supportedModels=${jsonStringify(config?.supportedModels)}`,
|
|
1218
1218
|
)
|
|
1219
1219
|
}
|
|
1220
1220
|
|
|
@@ -1951,7 +1951,9 @@ async function* queryModel(
|
|
|
1951
1951
|
let totalStallTime = 0
|
|
1952
1952
|
let stallCount = 0
|
|
1953
1953
|
|
|
1954
|
+
let __xelaParts = 0
|
|
1954
1955
|
for await (const part of stream) {
|
|
1956
|
+
__xelaParts++
|
|
1955
1957
|
resetStreamIdleTimer()
|
|
1956
1958
|
const now = Date.now()
|
|
1957
1959
|
|
|
@@ -2316,6 +2318,9 @@ async function* queryModel(
|
|
|
2316
2318
|
...(part.type === 'message_start' ? { ttftMs } : undefined),
|
|
2317
2319
|
}
|
|
2318
2320
|
}
|
|
2321
|
+
if (process.env.XELA_LOADER_DEBUG) {
|
|
2322
|
+
process.stderr.write(`[claude.ts] stream loop exited: ${__xelaParts} parts, source=${options.querySource}, newMessages=${newMessages.length}\n`)
|
|
2323
|
+
}
|
|
2319
2324
|
// Clear the idle timeout watchdog now that the stream loop has exited
|
|
2320
2325
|
clearStreamIdleTimers()
|
|
2321
2326
|
|
|
@@ -31,11 +31,14 @@ const MAX_OUTPUT_TOKENS_FOR_SUMMARY = 20_000
|
|
|
31
31
|
|
|
32
32
|
// Returns the context window size minus the max output tokens for the model
|
|
33
33
|
export function getEffectiveContextWindowSize(model: string): number {
|
|
34
|
+
let contextWindow = getContextWindowForModel(model, getSdkBetas())
|
|
35
|
+
// The fixed reserves were sized for a 200k window. On a small local window
|
|
36
|
+
// (64k) they would leave almost nothing for the conversation, so scale them.
|
|
34
37
|
const reservedTokensForSummary = Math.min(
|
|
35
38
|
getMaxOutputTokensForModel(model),
|
|
36
39
|
MAX_OUTPUT_TOKENS_FOR_SUMMARY,
|
|
40
|
+
smallWindowCap(contextWindow, 8),
|
|
37
41
|
)
|
|
38
|
-
let contextWindow = getContextWindowForModel(model, getSdkBetas())
|
|
39
42
|
|
|
40
43
|
const autoCompactWindow = process.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW
|
|
41
44
|
if (autoCompactWindow) {
|
|
@@ -69,11 +72,20 @@ export const MANUAL_COMPACT_BUFFER_TOKENS = 3_000
|
|
|
69
72
|
// in a single session, wasting ~250K API calls/day globally.
|
|
70
73
|
const MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3
|
|
71
74
|
|
|
75
|
+
// OpenAI-compatible backends only: cap a fixed token reserve at 1/divisor of the
|
|
76
|
+
// window, so 64k-128k local models keep most of their context usable
|
|
77
|
+
function smallWindowCap(contextWindow: number, divisor: number): number {
|
|
78
|
+
return process.env.OPENAI_API_KEY
|
|
79
|
+
? Math.floor(contextWindow / divisor)
|
|
80
|
+
: Number.POSITIVE_INFINITY
|
|
81
|
+
}
|
|
82
|
+
|
|
72
83
|
export function getAutoCompactThreshold(model: string): number {
|
|
73
84
|
const effectiveContextWindow = getEffectiveContextWindowSize(model)
|
|
74
85
|
|
|
75
86
|
const autocompactThreshold =
|
|
76
|
-
effectiveContextWindow -
|
|
87
|
+
effectiveContextWindow -
|
|
88
|
+
Math.min(AUTOCOMPACT_BUFFER_TOKENS, smallWindowCap(effectiveContextWindow, 10))
|
|
77
89
|
|
|
78
90
|
// Override for easier testing of autocompact
|
|
79
91
|
const envPercent = process.env.CLAUDE_AUTOCOMPACT_PCT_OVERRIDE
|
|
@@ -110,8 +122,10 @@ export function calculateTokenWarningState(
|
|
|
110
122
|
Math.round(((threshold - tokenUsage) / threshold) * 100),
|
|
111
123
|
)
|
|
112
124
|
|
|
113
|
-
const warningThreshold =
|
|
114
|
-
|
|
125
|
+
const warningThreshold =
|
|
126
|
+
threshold - Math.min(WARNING_THRESHOLD_BUFFER_TOKENS, smallWindowCap(threshold, 8))
|
|
127
|
+
const errorThreshold =
|
|
128
|
+
threshold - Math.min(ERROR_THRESHOLD_BUFFER_TOKENS, smallWindowCap(threshold, 8))
|
|
115
129
|
|
|
116
130
|
const isAboveWarningThreshold = tokenUsage >= warningThreshold
|
|
117
131
|
const isAboveErrorThreshold = tokenUsage >= errorThreshold
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { getCachedMCConfig, type CachedMCConfig } from './cachedMicrocompact.js'
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cached microcompact: instead of rewriting old tool results locally, ask the
|
|
3
|
+
* API to drop them from the cached prompt with `cache_edits` blocks. That
|
|
4
|
+
* needs Anthropic's cache-editing beta, which no OpenAI-compatible backend
|
|
5
|
+
* offers, so this implementation keeps the bookkeeping but never enables the
|
|
6
|
+
* path. Enabling it requires CLAUDE_CODE_CACHED_MICROCOMPACT=1 and a model
|
|
7
|
+
* listed in the config.
|
|
8
|
+
*/
|
|
9
|
+
import { isEnvTruthy } from '../../utils/envUtils.js'
|
|
10
|
+
|
|
11
|
+
export type CacheEdit = {
|
|
12
|
+
type: 'clear_tool_uses_20250919'
|
|
13
|
+
tool_use_ids: string[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type CacheEditsBlock = {
|
|
17
|
+
type: 'cache_edits'
|
|
18
|
+
edits: CacheEdit[]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type PinnedCacheEdits = {
|
|
22
|
+
userMessageIndex: number
|
|
23
|
+
block: CacheEditsBlock
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type CachedMCState = {
|
|
27
|
+
/** tool_use_id -> whether the result has been sent to the API at least once */
|
|
28
|
+
toolResults: Map<string, { sent: boolean; order: number }>
|
|
29
|
+
/** tool_use_ids grouped by the user message that carried them, oldest first */
|
|
30
|
+
toolMessages: string[][]
|
|
31
|
+
/** ids already deleted via cache edits */
|
|
32
|
+
deleted: Set<string>
|
|
33
|
+
pinnedEdits: PinnedCacheEdits[]
|
|
34
|
+
nextOrder: number
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type CachedMCConfig = {
|
|
38
|
+
supportedModels: string[]
|
|
39
|
+
/** start deleting once this many sent tool results exist */
|
|
40
|
+
triggerThreshold: number
|
|
41
|
+
/** keep this many most recent tool results */
|
|
42
|
+
keepRecent: number
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const DEFAULT_CONFIG: CachedMCConfig = {
|
|
46
|
+
supportedModels: [],
|
|
47
|
+
triggerThreshold: 40,
|
|
48
|
+
keepRecent: 20,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function getCachedMCConfig(): CachedMCConfig {
|
|
52
|
+
const models = (process.env.CLAUDE_CODE_CACHED_MC_MODELS || '')
|
|
53
|
+
.split(',')
|
|
54
|
+
.map(s => s.trim())
|
|
55
|
+
.filter(Boolean)
|
|
56
|
+
return { ...DEFAULT_CONFIG, supportedModels: models }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function isCachedMicrocompactEnabled(): boolean {
|
|
60
|
+
return isEnvTruthy(process.env.CLAUDE_CODE_CACHED_MICROCOMPACT)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function isModelSupportedForCacheEditing(model: string): boolean {
|
|
64
|
+
if (!isCachedMicrocompactEnabled()) return false
|
|
65
|
+
return getCachedMCConfig().supportedModels.some(m => model.includes(m))
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function createCachedMCState(): CachedMCState {
|
|
69
|
+
return {
|
|
70
|
+
toolResults: new Map(),
|
|
71
|
+
toolMessages: [],
|
|
72
|
+
deleted: new Set(),
|
|
73
|
+
pinnedEdits: [],
|
|
74
|
+
nextOrder: 0,
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function resetCachedMCState(state: CachedMCState): void {
|
|
79
|
+
state.toolResults.clear()
|
|
80
|
+
state.toolMessages = []
|
|
81
|
+
state.deleted.clear()
|
|
82
|
+
state.pinnedEdits = []
|
|
83
|
+
state.nextOrder = 0
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function registerToolResult(state: CachedMCState, toolUseId: string): void {
|
|
87
|
+
if (!state.toolResults.has(toolUseId)) {
|
|
88
|
+
state.toolResults.set(toolUseId, { sent: false, order: state.nextOrder++ })
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function registerToolMessage(state: CachedMCState, toolUseIds: string[]): void {
|
|
93
|
+
if (toolUseIds.length > 0) state.toolMessages.push([...toolUseIds])
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function markToolsSentToAPI(state: CachedMCState): void {
|
|
97
|
+
for (const entry of state.toolResults.values()) entry.sent = true
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Oldest sent tool results beyond the keep window, not yet deleted. */
|
|
101
|
+
export function getToolResultsToDelete(state: CachedMCState): string[] {
|
|
102
|
+
const { triggerThreshold, keepRecent } = getCachedMCConfig()
|
|
103
|
+
const sent = [...state.toolResults.entries()]
|
|
104
|
+
.filter(([id, e]) => e.sent && !state.deleted.has(id))
|
|
105
|
+
.sort((a, b) => a[1].order - b[1].order)
|
|
106
|
+
if (sent.length < triggerThreshold) return []
|
|
107
|
+
return sent.slice(0, Math.max(0, sent.length - keepRecent)).map(([id]) => id)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function createCacheEditsBlock(state: CachedMCState, toolUseIds: string[]): CacheEditsBlock {
|
|
111
|
+
for (const id of toolUseIds) state.deleted.add(id)
|
|
112
|
+
return { type: 'cache_edits', edits: [{ type: 'clear_tool_uses_20250919', tool_use_ids: toolUseIds }] }
|
|
113
|
+
}
|
|
@@ -426,7 +426,7 @@ export async function compactConversation(
|
|
|
426
426
|
// Show requesting mode with up arrow and custom message
|
|
427
427
|
context.setStreamMode?.('requesting')
|
|
428
428
|
context.setResponseLength?.(() => 0)
|
|
429
|
-
context.onCompactProgress?.({ type: 'compact_start' })
|
|
429
|
+
context.onCompactProgress?.({ type: 'compact_start', preCompactTokenCount })
|
|
430
430
|
|
|
431
431
|
// 3P default: true — forked-agent path reuses main conversation's prompt cache.
|
|
432
432
|
// Experiment (Jan 2026) confirmed: false path is 98% cache miss, costs ~0.76% of
|
|
@@ -835,7 +835,7 @@ export async function partialCompactConversation(
|
|
|
835
835
|
|
|
836
836
|
context.setStreamMode?.('requesting')
|
|
837
837
|
context.setResponseLength?.(() => 0)
|
|
838
|
-
context.onCompactProgress?.({ type: 'compact_start' })
|
|
838
|
+
context.onCompactProgress?.({ type: 'compact_start', preCompactTokenCount })
|
|
839
839
|
|
|
840
840
|
const compactPrompt = getPartialCompactPrompt(customInstructions, direction)
|
|
841
841
|
const summaryRequest = createUserMessage({
|
|
@@ -1197,6 +1197,21 @@ async function streamCompactSummary({
|
|
|
1197
1197
|
// fork — same signal the streaming fallback uses at
|
|
1198
1198
|
// `signal: context.abortController.signal` below.
|
|
1199
1199
|
overrides: { abortController: context.abortController },
|
|
1200
|
+
// Feed streamed summary text into the parent's response counter so
|
|
1201
|
+
// the spinner's progress bar and token count move while the
|
|
1202
|
+
// summary is generated (the fork's own context is a no-op there).
|
|
1203
|
+
onStreamEvent: message => {
|
|
1204
|
+
const ev = message as { type?: string; event?: { type?: string; delta?: { type?: string; text?: string } } }
|
|
1205
|
+
if (
|
|
1206
|
+
ev.type === 'stream_event' &&
|
|
1207
|
+
ev.event?.type === 'content_block_delta' &&
|
|
1208
|
+
ev.event.delta?.type === 'text_delta' &&
|
|
1209
|
+
typeof ev.event.delta.text === 'string'
|
|
1210
|
+
) {
|
|
1211
|
+
const n = ev.event.delta.text.length
|
|
1212
|
+
context.setResponseLength?.(length => length + n)
|
|
1213
|
+
}
|
|
1214
|
+
},
|
|
1200
1215
|
})
|
|
1201
1216
|
const assistantMsg = getLastAssistantMessage(result.messages)
|
|
1202
1217
|
const assistantText = assistantMsg
|
|
@@ -1325,6 +1340,7 @@ async function streamCompactSummary({
|
|
|
1325
1340
|
},
|
|
1326
1341
|
})
|
|
1327
1342
|
const streamIter = streamingGen[Symbol.asyncIterator]()
|
|
1343
|
+
let summaryCharsStreamed = 0
|
|
1328
1344
|
let next = await streamIter.next()
|
|
1329
1345
|
|
|
1330
1346
|
while (!next.done) {
|
|
@@ -1346,6 +1362,7 @@ async function streamCompactSummary({
|
|
|
1346
1362
|
event.event.delta.type === 'text_delta'
|
|
1347
1363
|
) {
|
|
1348
1364
|
const charactersStreamed = event.event.delta.text.length
|
|
1365
|
+
summaryCharsStreamed += charactersStreamed
|
|
1349
1366
|
context.setResponseLength?.(length => length + charactersStreamed)
|
|
1350
1367
|
}
|
|
1351
1368
|
|
|
@@ -1357,6 +1374,9 @@ async function streamCompactSummary({
|
|
|
1357
1374
|
}
|
|
1358
1375
|
|
|
1359
1376
|
if (response) {
|
|
1377
|
+
logForDebugging(
|
|
1378
|
+
`[compact] summary streamed: ${summaryCharsStreamed} chars, hasStartedStreaming=${hasStartedStreaming}`,
|
|
1379
|
+
)
|
|
1360
1380
|
return response
|
|
1361
1381
|
}
|
|
1362
1382
|
|