yzcode-cli 1.0.2 → 1.0.3
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/assistant/sessionHistory.ts +87 -0
- package/bootstrap/state.ts +1769 -0
- package/bridge/bridgeApi.ts +539 -0
- package/bridge/bridgeConfig.ts +48 -0
- package/bridge/bridgeDebug.ts +135 -0
- package/bridge/bridgeEnabled.ts +202 -0
- package/bridge/bridgeMain.ts +2999 -0
- package/bridge/bridgeMessaging.ts +461 -0
- package/bridge/bridgePermissionCallbacks.ts +43 -0
- package/bridge/bridgePointer.ts +210 -0
- package/bridge/bridgeStatusUtil.ts +163 -0
- package/bridge/bridgeUI.ts +530 -0
- package/bridge/capacityWake.ts +56 -0
- package/bridge/codeSessionApi.ts +168 -0
- package/bridge/createSession.ts +384 -0
- package/bridge/debugUtils.ts +141 -0
- package/bridge/envLessBridgeConfig.ts +165 -0
- package/bridge/flushGate.ts +71 -0
- package/bridge/inboundAttachments.ts +175 -0
- package/bridge/inboundMessages.ts +80 -0
- package/bridge/initReplBridge.ts +569 -0
- package/bridge/jwtUtils.ts +256 -0
- package/bridge/pollConfig.ts +110 -0
- package/bridge/pollConfigDefaults.ts +82 -0
- package/bridge/remoteBridgeCore.ts +1008 -0
- package/bridge/replBridge.ts +2406 -0
- package/bridge/replBridgeHandle.ts +36 -0
- package/bridge/replBridgeTransport.ts +370 -0
- package/bridge/sessionIdCompat.ts +57 -0
- package/bridge/sessionRunner.ts +550 -0
- package/bridge/trustedDevice.ts +210 -0
- package/bridge/types.ts +262 -0
- package/bridge/workSecret.ts +127 -0
- package/buddy/CompanionSprite.tsx +371 -0
- package/buddy/companion.ts +133 -0
- package/buddy/prompt.ts +36 -0
- package/buddy/sprites.ts +514 -0
- package/buddy/types.ts +148 -0
- package/buddy/useBuddyNotification.tsx +98 -0
- package/coordinator/coordinatorMode.ts +369 -0
- package/memdir/findRelevantMemories.ts +141 -0
- package/memdir/memdir.ts +507 -0
- package/memdir/memoryAge.ts +53 -0
- package/memdir/memoryScan.ts +94 -0
- package/memdir/memoryTypes.ts +271 -0
- package/memdir/paths.ts +278 -0
- package/memdir/teamMemPaths.ts +292 -0
- package/memdir/teamMemPrompts.ts +100 -0
- package/migrations/migrateAutoUpdatesToSettings.ts +61 -0
- package/migrations/migrateBypassPermissionsAcceptedToSettings.ts +40 -0
- package/migrations/migrateEnableAllProjectMcpServersToSettings.ts +118 -0
- package/migrations/migrateFennecToOpus.ts +45 -0
- package/migrations/migrateLegacyOpusToCurrent.ts +57 -0
- package/migrations/migrateOpusToOpus1m.ts +43 -0
- package/migrations/migrateReplBridgeEnabledToRemoteControlAtStartup.ts +22 -0
- package/migrations/migrateSonnet1mToSonnet45.ts +48 -0
- package/migrations/migrateSonnet45ToSonnet46.ts +67 -0
- package/migrations/resetAutoModeOptInForDefaultOffer.ts +51 -0
- package/migrations/resetProToOpusDefault.ts +51 -0
- package/native-ts/color-diff/index.ts +999 -0
- package/native-ts/file-index/index.ts +370 -0
- package/native-ts/yoga-layout/enums.ts +134 -0
- package/native-ts/yoga-layout/index.ts +2578 -0
- package/outputStyles/loadOutputStylesDir.ts +98 -0
- package/package.json +19 -2
- package/plugins/builtinPlugins.ts +159 -0
- package/plugins/bundled/index.ts +23 -0
- package/schemas/hooks.ts +222 -0
- package/screens/Doctor.tsx +575 -0
- package/screens/REPL.tsx +5006 -0
- package/screens/ResumeConversation.tsx +399 -0
- package/server/createDirectConnectSession.ts +88 -0
- package/server/directConnectManager.ts +213 -0
- package/server/types.ts +57 -0
- package/skills/bundled/batch.ts +124 -0
- package/skills/bundled/claudeApi.ts +196 -0
- package/skills/bundled/claudeApiContent.ts +75 -0
- package/skills/bundled/claudeInChrome.ts +34 -0
- package/skills/bundled/debug.ts +103 -0
- package/skills/bundled/index.ts +79 -0
- package/skills/bundled/keybindings.ts +339 -0
- package/skills/bundled/loop.ts +92 -0
- package/skills/bundled/loremIpsum.ts +282 -0
- package/skills/bundled/remember.ts +82 -0
- package/skills/bundled/scheduleRemoteAgents.ts +447 -0
- package/skills/bundled/simplify.ts +69 -0
- package/skills/bundled/skillify.ts +197 -0
- package/skills/bundled/stuck.ts +79 -0
- package/skills/bundled/updateConfig.ts +475 -0
- package/skills/bundled/verify/SKILL.md +3 -0
- package/skills/bundled/verify/examples/cli.md +3 -0
- package/skills/bundled/verify/examples/server.md +3 -0
- package/skills/bundled/verify.ts +30 -0
- package/skills/bundled/verifyContent.ts +13 -0
- package/skills/bundledSkills.ts +220 -0
- package/skills/loadSkillsDir.ts +1086 -0
- package/skills/mcpSkillBuilders.ts +44 -0
- package/tasks/DreamTask/DreamTask.ts +157 -0
- package/tasks/InProcessTeammateTask/InProcessTeammateTask.tsx +126 -0
- package/tasks/InProcessTeammateTask/types.ts +121 -0
- package/tasks/LocalAgentTask/LocalAgentTask.tsx +683 -0
- package/tasks/LocalMainSessionTask.ts +479 -0
- package/tasks/LocalShellTask/LocalShellTask.tsx +523 -0
- package/tasks/LocalShellTask/guards.ts +41 -0
- package/tasks/LocalShellTask/killShellTasks.ts +76 -0
- package/tasks/RemoteAgentTask/RemoteAgentTask.tsx +856 -0
- package/tasks/pillLabel.ts +82 -0
- package/tasks/stopTask.ts +100 -0
- package/tasks/types.ts +46 -0
- package/upstreamproxy/relay.ts +455 -0
- package/upstreamproxy/upstreamproxy.ts +285 -0
- package/vim/motions.ts +82 -0
- package/vim/operators.ts +556 -0
- package/vim/textObjects.ts +186 -0
- package/vim/transitions.ts +490 -0
- package/vim/types.ts +199 -0
- package/voice/voiceModeEnabled.ts +54 -0
|
@@ -0,0 +1,1769 @@
|
|
|
1
|
+
import type { BetaMessageStreamParams } from '@anthropic-ai/sdk/resources/beta/messages/messages.mjs'
|
|
2
|
+
import type { Attributes, Meter, MetricOptions } from '@opentelemetry/api'
|
|
3
|
+
import type { logs } from '@opentelemetry/api-logs'
|
|
4
|
+
import type { LoggerProvider } from '@opentelemetry/sdk-logs'
|
|
5
|
+
import type { MeterProvider } from '@opentelemetry/sdk-metrics'
|
|
6
|
+
import type { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'
|
|
7
|
+
import { realpathSync } from 'fs'
|
|
8
|
+
import sumBy from 'lodash-es/sumBy.js'
|
|
9
|
+
import { cwd } from 'process'
|
|
10
|
+
import type { HookEvent, ModelUsage } from 'src/entrypoints/agentSdkTypes.js'
|
|
11
|
+
import type { AgentColorName } from 'src/tools/AgentTool/agentColorManager.js'
|
|
12
|
+
import type { HookCallbackMatcher } from 'src/types/hooks.js'
|
|
13
|
+
// Indirection for browser-sdk build (package.json "browser" field swaps
|
|
14
|
+
// crypto.ts for crypto.browser.ts). Pure leaf re-export of node:crypto —
|
|
15
|
+
// zero circular-dep risk. Path-alias import bypasses bootstrap-isolation
|
|
16
|
+
// (rule only checks ./ and / prefixes); explicit disable documents intent.
|
|
17
|
+
// eslint-disable-next-line custom-rules/bootstrap-isolation
|
|
18
|
+
import { randomUUID } from 'src/utils/crypto.js'
|
|
19
|
+
import type { ModelSetting } from 'src/utils/model/model.js'
|
|
20
|
+
import type { ModelStrings } from 'src/utils/model/modelStrings.js'
|
|
21
|
+
import type { SettingSource } from 'src/utils/settings/constants.js'
|
|
22
|
+
import { resetSettingsCache } from 'src/utils/settings/settingsCache.js'
|
|
23
|
+
import type { PluginHookMatcher } from 'src/utils/settings/types.js'
|
|
24
|
+
import { createSignal } from 'src/utils/signal.js'
|
|
25
|
+
|
|
26
|
+
// Union type for registered hooks - can be SDK callbacks or native plugin hooks
|
|
27
|
+
type RegisteredHookMatcher = HookCallbackMatcher | PluginHookMatcher
|
|
28
|
+
|
|
29
|
+
import type { SessionId } from 'src/types/ids.js'
|
|
30
|
+
|
|
31
|
+
// DO NOT ADD MORE STATE HERE - BE JUDICIOUS WITH GLOBAL STATE
|
|
32
|
+
|
|
33
|
+
// dev: true on entries that came via --dangerously-load-development-channels.
|
|
34
|
+
// The allowlist gate checks this per-entry (not the session-wide
|
|
35
|
+
// hasDevChannels bit) so passing both flags doesn't let the dev dialog's
|
|
36
|
+
// acceptance leak allowlist-bypass to the --channels entries.
|
|
37
|
+
export type ChannelEntry =
|
|
38
|
+
| { kind: 'plugin'; name: string; marketplace: string; dev?: boolean }
|
|
39
|
+
| { kind: 'server'; name: string; dev?: boolean }
|
|
40
|
+
|
|
41
|
+
export type AttributedCounter = {
|
|
42
|
+
add(value: number, additionalAttributes?: Attributes): void
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
type State = {
|
|
46
|
+
originalCwd: string
|
|
47
|
+
// Stable project root - set once at startup (including by --worktree flag),
|
|
48
|
+
// never updated by mid-session EnterWorktreeTool.
|
|
49
|
+
// Use for project identity (history, skills, sessions) not file operations.
|
|
50
|
+
projectRoot: string
|
|
51
|
+
totalCostUSD: number
|
|
52
|
+
totalAPIDuration: number
|
|
53
|
+
totalAPIDurationWithoutRetries: number
|
|
54
|
+
totalToolDuration: number
|
|
55
|
+
turnHookDurationMs: number
|
|
56
|
+
turnToolDurationMs: number
|
|
57
|
+
turnClassifierDurationMs: number
|
|
58
|
+
turnToolCount: number
|
|
59
|
+
turnHookCount: number
|
|
60
|
+
turnClassifierCount: number
|
|
61
|
+
startTime: number
|
|
62
|
+
lastInteractionTime: number
|
|
63
|
+
totalLinesAdded: number
|
|
64
|
+
totalLinesRemoved: number
|
|
65
|
+
hasUnknownModelCost: boolean
|
|
66
|
+
cwd: string
|
|
67
|
+
modelUsage: { [modelName: string]: ModelUsage }
|
|
68
|
+
mainLoopModelOverride: ModelSetting | undefined
|
|
69
|
+
initialMainLoopModel: ModelSetting
|
|
70
|
+
modelStrings: ModelStrings | null
|
|
71
|
+
isInteractive: boolean
|
|
72
|
+
kairosActive: boolean
|
|
73
|
+
// When true, ensureToolResultPairing throws on mismatch instead of
|
|
74
|
+
// repairing with synthetic placeholders. HFI opts in at startup so
|
|
75
|
+
// trajectories fail fast rather than conditioning the model on fake
|
|
76
|
+
// tool_results.
|
|
77
|
+
strictToolResultPairing: boolean
|
|
78
|
+
sdkAgentProgressSummariesEnabled: boolean
|
|
79
|
+
userMsgOptIn: boolean
|
|
80
|
+
clientType: string
|
|
81
|
+
sessionSource: string | undefined
|
|
82
|
+
questionPreviewFormat: 'markdown' | 'html' | undefined
|
|
83
|
+
flagSettingsPath: string | undefined
|
|
84
|
+
flagSettingsInline: Record<string, unknown> | null
|
|
85
|
+
allowedSettingSources: SettingSource[]
|
|
86
|
+
sessionIngressToken: string | null | undefined
|
|
87
|
+
oauthTokenFromFd: string | null | undefined
|
|
88
|
+
apiKeyFromFd: string | null | undefined
|
|
89
|
+
// Telemetry state
|
|
90
|
+
meter: Meter | null
|
|
91
|
+
sessionCounter: AttributedCounter | null
|
|
92
|
+
locCounter: AttributedCounter | null
|
|
93
|
+
prCounter: AttributedCounter | null
|
|
94
|
+
commitCounter: AttributedCounter | null
|
|
95
|
+
costCounter: AttributedCounter | null
|
|
96
|
+
tokenCounter: AttributedCounter | null
|
|
97
|
+
codeEditToolDecisionCounter: AttributedCounter | null
|
|
98
|
+
activeTimeCounter: AttributedCounter | null
|
|
99
|
+
statsStore: { observe(name: string, value: number): void } | null
|
|
100
|
+
sessionId: SessionId
|
|
101
|
+
// Parent session ID for tracking session lineage (e.g., plan mode -> implementation)
|
|
102
|
+
parentSessionId: SessionId | undefined
|
|
103
|
+
// Logger state
|
|
104
|
+
loggerProvider: LoggerProvider | null
|
|
105
|
+
eventLogger: ReturnType<typeof logs.getLogger> | null
|
|
106
|
+
// Meter provider state
|
|
107
|
+
meterProvider: MeterProvider | null
|
|
108
|
+
// Tracer provider state
|
|
109
|
+
tracerProvider: BasicTracerProvider | null
|
|
110
|
+
// Agent color state
|
|
111
|
+
agentColorMap: Map<string, AgentColorName>
|
|
112
|
+
agentColorIndex: number
|
|
113
|
+
// Last API request for bug reports
|
|
114
|
+
lastAPIRequest: Omit<BetaMessageStreamParams, 'messages'> | null
|
|
115
|
+
// Messages from the last API request (ant-only; reference, not clone).
|
|
116
|
+
// Captures the exact post-compaction, CLAUDE.md-injected message set sent
|
|
117
|
+
// to the API so /share's serialized_conversation.json reflects reality.
|
|
118
|
+
lastAPIRequestMessages: BetaMessageStreamParams['messages'] | null
|
|
119
|
+
// Last auto-mode classifier request(s) for /share transcript
|
|
120
|
+
lastClassifierRequests: unknown[] | null
|
|
121
|
+
// CLAUDE.md content cached by context.ts for the auto-mode classifier.
|
|
122
|
+
// Breaks the yoloClassifier → claudemd → filesystem → permissions cycle.
|
|
123
|
+
cachedClaudeMdContent: string | null
|
|
124
|
+
// In-memory error log for recent errors
|
|
125
|
+
inMemoryErrorLog: Array<{ error: string; timestamp: string }>
|
|
126
|
+
// Session-only plugins from --plugin-dir flag
|
|
127
|
+
inlinePlugins: Array<string>
|
|
128
|
+
// Explicit --chrome / --no-chrome flag value (undefined = not set on CLI)
|
|
129
|
+
chromeFlagOverride: boolean | undefined
|
|
130
|
+
// Use cowork_plugins directory instead of plugins (--cowork flag or env var)
|
|
131
|
+
useCoworkPlugins: boolean
|
|
132
|
+
// Session-only bypass permissions mode flag (not persisted)
|
|
133
|
+
sessionBypassPermissionsMode: boolean
|
|
134
|
+
// Session-only flag gating the .claude/scheduled_tasks.json watcher
|
|
135
|
+
// (useScheduledTasks). Set by cronScheduler.start() when the JSON has
|
|
136
|
+
// entries, or by CronCreateTool. Not persisted.
|
|
137
|
+
scheduledTasksEnabled: boolean
|
|
138
|
+
// Session-only cron tasks created via CronCreate with durable: false.
|
|
139
|
+
// Fire on schedule like file-backed tasks but are never written to
|
|
140
|
+
// .claude/scheduled_tasks.json — they die with the process. Typed via
|
|
141
|
+
// SessionCronTask below (not importing from cronTasks.ts keeps
|
|
142
|
+
// bootstrap a leaf of the import DAG).
|
|
143
|
+
sessionCronTasks: SessionCronTask[]
|
|
144
|
+
// Teams created this session via TeamCreate. cleanupSessionTeams()
|
|
145
|
+
// removes these on gracefulShutdown so subagent-created teams don't
|
|
146
|
+
// persist on disk forever (gh-32730). TeamDelete removes entries to
|
|
147
|
+
// avoid double-cleanup. Lives here (not teamHelpers.ts) so
|
|
148
|
+
// resetStateForTests() clears it between tests.
|
|
149
|
+
sessionCreatedTeams: Set<string>
|
|
150
|
+
// Session-only trust flag for home directory (not persisted to disk)
|
|
151
|
+
// When running from home dir, trust dialog is shown but not saved to disk.
|
|
152
|
+
// This flag allows features requiring trust to work during the session.
|
|
153
|
+
sessionTrustAccepted: boolean
|
|
154
|
+
// Session-only flag to disable session persistence to disk
|
|
155
|
+
sessionPersistenceDisabled: boolean
|
|
156
|
+
// Track if user has exited plan mode in this session (for re-entry guidance)
|
|
157
|
+
hasExitedPlanMode: boolean
|
|
158
|
+
// Track if we need to show the plan mode exit attachment (one-time notification)
|
|
159
|
+
needsPlanModeExitAttachment: boolean
|
|
160
|
+
// Track if we need to show the auto mode exit attachment (one-time notification)
|
|
161
|
+
needsAutoModeExitAttachment: boolean
|
|
162
|
+
// Track if LSP plugin recommendation has been shown this session (only show once)
|
|
163
|
+
lspRecommendationShownThisSession: boolean
|
|
164
|
+
// SDK init event state - jsonSchema for structured output
|
|
165
|
+
initJsonSchema: Record<string, unknown> | null
|
|
166
|
+
// Registered hooks - SDK callbacks and plugin native hooks
|
|
167
|
+
registeredHooks: Partial<Record<HookEvent, RegisteredHookMatcher[]>> | null
|
|
168
|
+
// Cache for plan slugs: sessionId -> wordSlug
|
|
169
|
+
planSlugCache: Map<string, string>
|
|
170
|
+
// Track teleported session for reliability logging
|
|
171
|
+
teleportedSessionInfo: {
|
|
172
|
+
isTeleported: boolean
|
|
173
|
+
hasLoggedFirstMessage: boolean
|
|
174
|
+
sessionId: string | null
|
|
175
|
+
} | null
|
|
176
|
+
// Track invoked skills for preservation across compaction
|
|
177
|
+
// Keys are composite: `${agentId ?? ''}:${skillName}` to prevent cross-agent overwrites
|
|
178
|
+
invokedSkills: Map<
|
|
179
|
+
string,
|
|
180
|
+
{
|
|
181
|
+
skillName: string
|
|
182
|
+
skillPath: string
|
|
183
|
+
content: string
|
|
184
|
+
invokedAt: number
|
|
185
|
+
agentId: string | null
|
|
186
|
+
}
|
|
187
|
+
>
|
|
188
|
+
// Track slow operations for dev bar display (ant-only)
|
|
189
|
+
slowOperations: Array<{
|
|
190
|
+
operation: string
|
|
191
|
+
durationMs: number
|
|
192
|
+
timestamp: number
|
|
193
|
+
}>
|
|
194
|
+
// SDK-provided betas (e.g., context-1m-2025-08-07)
|
|
195
|
+
sdkBetas: string[] | undefined
|
|
196
|
+
// Main thread agent type (from --agent flag or settings)
|
|
197
|
+
mainThreadAgentType: string | undefined
|
|
198
|
+
// Remote mode (--remote flag)
|
|
199
|
+
isRemoteMode: boolean
|
|
200
|
+
// Direct connect server URL (for display in header)
|
|
201
|
+
directConnectServerUrl: string | undefined
|
|
202
|
+
// System prompt section cache state
|
|
203
|
+
systemPromptSectionCache: Map<string, string | null>
|
|
204
|
+
// Last date emitted to the model (for detecting midnight date changes)
|
|
205
|
+
lastEmittedDate: string | null
|
|
206
|
+
// Additional directories from --add-dir flag (for CLAUDE.md loading)
|
|
207
|
+
additionalDirectoriesForClaudeMd: string[]
|
|
208
|
+
// Channel server allowlist from --channels flag (servers whose channel
|
|
209
|
+
// notifications should register this session). Parsed once in main.tsx —
|
|
210
|
+
// the tag decides trust model: 'plugin' → marketplace verification +
|
|
211
|
+
// allowlist, 'server' → allowlist always fails (schema is plugin-only).
|
|
212
|
+
// Either kind needs entry.dev to bypass allowlist.
|
|
213
|
+
allowedChannels: ChannelEntry[]
|
|
214
|
+
// True if any entry in allowedChannels came from
|
|
215
|
+
// --dangerously-load-development-channels (so ChannelsNotice can name the
|
|
216
|
+
// right flag in policy-blocked messages)
|
|
217
|
+
hasDevChannels: boolean
|
|
218
|
+
// Dir containing the session's `.jsonl`; null = derive from originalCwd.
|
|
219
|
+
sessionProjectDir: string | null
|
|
220
|
+
// Cached prompt cache 1h TTL allowlist from GrowthBook (session-stable)
|
|
221
|
+
promptCache1hAllowlist: string[] | null
|
|
222
|
+
// Cached 1h TTL user eligibility (session-stable). Latched on first
|
|
223
|
+
// evaluation so mid-session overage flips don't change the cache_control
|
|
224
|
+
// TTL, which would bust the server-side prompt cache.
|
|
225
|
+
promptCache1hEligible: boolean | null
|
|
226
|
+
// Sticky-on latch for AFK_MODE_BETA_HEADER. Once auto mode is first
|
|
227
|
+
// activated, keep sending the header for the rest of the session so
|
|
228
|
+
// Shift+Tab toggles don't bust the ~50-70K token prompt cache.
|
|
229
|
+
afkModeHeaderLatched: boolean | null
|
|
230
|
+
// Sticky-on latch for FAST_MODE_BETA_HEADER. Once fast mode is first
|
|
231
|
+
// enabled, keep sending the header so cooldown enter/exit doesn't
|
|
232
|
+
// double-bust the prompt cache. The `speed` body param stays dynamic.
|
|
233
|
+
fastModeHeaderLatched: boolean | null
|
|
234
|
+
// Sticky-on latch for the cache-editing beta header. Once cached
|
|
235
|
+
// microcompact is first enabled, keep sending the header so mid-session
|
|
236
|
+
// GrowthBook/settings toggles don't bust the prompt cache.
|
|
237
|
+
cacheEditingHeaderLatched: boolean | null
|
|
238
|
+
// Sticky-on latch for clearing thinking from prior tool loops. Triggered
|
|
239
|
+
// when >1h since last API call (confirmed cache miss — no cache-hit
|
|
240
|
+
// benefit to keeping thinking). Once latched, stays on so the newly-warmed
|
|
241
|
+
// thinking-cleared cache isn't busted by flipping back to keep:'all'.
|
|
242
|
+
thinkingClearLatched: boolean | null
|
|
243
|
+
// Current prompt ID (UUID) correlating a user prompt with subsequent OTel events
|
|
244
|
+
promptId: string | null
|
|
245
|
+
// Last API requestId for the main conversation chain (not subagents).
|
|
246
|
+
// Updated after each successful API response for main-session queries.
|
|
247
|
+
// Read at shutdown to send cache eviction hints to inference.
|
|
248
|
+
lastMainRequestId: string | undefined
|
|
249
|
+
// Timestamp (Date.now()) of the last successful API call completion.
|
|
250
|
+
// Used to compute timeSinceLastApiCallMs in tengu_api_success for
|
|
251
|
+
// correlating cache misses with idle time (cache TTL is ~5min).
|
|
252
|
+
lastApiCompletionTimestamp: number | null
|
|
253
|
+
// Set to true after compaction (auto or manual /compact). Consumed by
|
|
254
|
+
// logAPISuccess to tag the first post-compaction API call so we can
|
|
255
|
+
// distinguish compaction-induced cache misses from TTL expiry.
|
|
256
|
+
pendingPostCompaction: boolean
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// ALSO HERE - THINK THRICE BEFORE MODIFYING
|
|
260
|
+
function getInitialState(): State {
|
|
261
|
+
// Resolve symlinks in cwd to match behavior of shell.ts setCwd
|
|
262
|
+
// This ensures consistency with how paths are sanitized for session storage
|
|
263
|
+
let resolvedCwd = ''
|
|
264
|
+
if (
|
|
265
|
+
typeof process !== 'undefined' &&
|
|
266
|
+
typeof process.cwd === 'function' &&
|
|
267
|
+
typeof realpathSync === 'function'
|
|
268
|
+
) {
|
|
269
|
+
const rawCwd = cwd()
|
|
270
|
+
try {
|
|
271
|
+
resolvedCwd = realpathSync(rawCwd).normalize('NFC')
|
|
272
|
+
} catch {
|
|
273
|
+
// File Provider EPERM on CloudStorage mounts (lstat per path component).
|
|
274
|
+
resolvedCwd = rawCwd.normalize('NFC')
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
const state: State = {
|
|
278
|
+
originalCwd: resolvedCwd,
|
|
279
|
+
projectRoot: resolvedCwd,
|
|
280
|
+
totalCostUSD: 0,
|
|
281
|
+
totalAPIDuration: 0,
|
|
282
|
+
totalAPIDurationWithoutRetries: 0,
|
|
283
|
+
totalToolDuration: 0,
|
|
284
|
+
turnHookDurationMs: 0,
|
|
285
|
+
turnToolDurationMs: 0,
|
|
286
|
+
turnClassifierDurationMs: 0,
|
|
287
|
+
turnToolCount: 0,
|
|
288
|
+
turnHookCount: 0,
|
|
289
|
+
turnClassifierCount: 0,
|
|
290
|
+
startTime: Date.now(),
|
|
291
|
+
lastInteractionTime: Date.now(),
|
|
292
|
+
totalLinesAdded: 0,
|
|
293
|
+
totalLinesRemoved: 0,
|
|
294
|
+
hasUnknownModelCost: false,
|
|
295
|
+
cwd: resolvedCwd,
|
|
296
|
+
modelUsage: {},
|
|
297
|
+
mainLoopModelOverride: undefined,
|
|
298
|
+
initialMainLoopModel: null,
|
|
299
|
+
modelStrings: null,
|
|
300
|
+
isInteractive: false,
|
|
301
|
+
kairosActive: false,
|
|
302
|
+
strictToolResultPairing: false,
|
|
303
|
+
sdkAgentProgressSummariesEnabled: false,
|
|
304
|
+
userMsgOptIn: false,
|
|
305
|
+
clientType: 'cli',
|
|
306
|
+
sessionSource: undefined,
|
|
307
|
+
questionPreviewFormat: undefined,
|
|
308
|
+
sessionIngressToken: undefined,
|
|
309
|
+
oauthTokenFromFd: undefined,
|
|
310
|
+
apiKeyFromFd: undefined,
|
|
311
|
+
flagSettingsPath: undefined,
|
|
312
|
+
flagSettingsInline: null,
|
|
313
|
+
allowedSettingSources: [
|
|
314
|
+
'userSettings',
|
|
315
|
+
'projectSettings',
|
|
316
|
+
'localSettings',
|
|
317
|
+
'flagSettings',
|
|
318
|
+
'policySettings',
|
|
319
|
+
],
|
|
320
|
+
// Telemetry state
|
|
321
|
+
meter: null,
|
|
322
|
+
sessionCounter: null,
|
|
323
|
+
locCounter: null,
|
|
324
|
+
prCounter: null,
|
|
325
|
+
commitCounter: null,
|
|
326
|
+
costCounter: null,
|
|
327
|
+
tokenCounter: null,
|
|
328
|
+
codeEditToolDecisionCounter: null,
|
|
329
|
+
activeTimeCounter: null,
|
|
330
|
+
statsStore: null,
|
|
331
|
+
sessionId: randomUUID() as SessionId,
|
|
332
|
+
parentSessionId: undefined,
|
|
333
|
+
// Logger state
|
|
334
|
+
loggerProvider: null,
|
|
335
|
+
eventLogger: null,
|
|
336
|
+
// Meter provider state
|
|
337
|
+
meterProvider: null,
|
|
338
|
+
tracerProvider: null,
|
|
339
|
+
// Agent color state
|
|
340
|
+
agentColorMap: new Map(),
|
|
341
|
+
agentColorIndex: 0,
|
|
342
|
+
// Last API request for bug reports
|
|
343
|
+
lastAPIRequest: null,
|
|
344
|
+
lastAPIRequestMessages: null,
|
|
345
|
+
// Last auto-mode classifier request(s) for /share transcript
|
|
346
|
+
lastClassifierRequests: null,
|
|
347
|
+
cachedClaudeMdContent: null,
|
|
348
|
+
// In-memory error log for recent errors
|
|
349
|
+
inMemoryErrorLog: [],
|
|
350
|
+
// Session-only plugins from --plugin-dir flag
|
|
351
|
+
inlinePlugins: [],
|
|
352
|
+
// Explicit --chrome / --no-chrome flag value (undefined = not set on CLI)
|
|
353
|
+
chromeFlagOverride: undefined,
|
|
354
|
+
// Use cowork_plugins directory instead of plugins
|
|
355
|
+
useCoworkPlugins: false,
|
|
356
|
+
// Session-only bypass permissions mode flag (not persisted)
|
|
357
|
+
sessionBypassPermissionsMode: false,
|
|
358
|
+
// Scheduled tasks disabled until flag or dialog enables them
|
|
359
|
+
scheduledTasksEnabled: false,
|
|
360
|
+
sessionCronTasks: [],
|
|
361
|
+
sessionCreatedTeams: new Set(),
|
|
362
|
+
// Session-only trust flag (not persisted to disk)
|
|
363
|
+
sessionTrustAccepted: false,
|
|
364
|
+
// Session-only flag to disable session persistence to disk
|
|
365
|
+
sessionPersistenceDisabled: false,
|
|
366
|
+
// Track if user has exited plan mode in this session
|
|
367
|
+
hasExitedPlanMode: false,
|
|
368
|
+
// Track if we need to show the plan mode exit attachment
|
|
369
|
+
needsPlanModeExitAttachment: false,
|
|
370
|
+
// Track if we need to show the auto mode exit attachment
|
|
371
|
+
needsAutoModeExitAttachment: false,
|
|
372
|
+
// Track if LSP plugin recommendation has been shown this session
|
|
373
|
+
lspRecommendationShownThisSession: false,
|
|
374
|
+
// SDK init event state
|
|
375
|
+
initJsonSchema: null,
|
|
376
|
+
registeredHooks: null,
|
|
377
|
+
// Cache for plan slugs
|
|
378
|
+
planSlugCache: new Map(),
|
|
379
|
+
// Track teleported session for reliability logging
|
|
380
|
+
teleportedSessionInfo: null,
|
|
381
|
+
// Track invoked skills for preservation across compaction
|
|
382
|
+
invokedSkills: new Map(),
|
|
383
|
+
// Track slow operations for dev bar display
|
|
384
|
+
slowOperations: [],
|
|
385
|
+
// SDK-provided betas
|
|
386
|
+
sdkBetas: undefined,
|
|
387
|
+
// Main thread agent type
|
|
388
|
+
mainThreadAgentType: undefined,
|
|
389
|
+
// Remote mode
|
|
390
|
+
isRemoteMode: false,
|
|
391
|
+
...(process.env.USER_TYPE === 'ant'
|
|
392
|
+
? {
|
|
393
|
+
replBridgeActive: false,
|
|
394
|
+
}
|
|
395
|
+
: {}),
|
|
396
|
+
// Direct connect server URL
|
|
397
|
+
directConnectServerUrl: undefined,
|
|
398
|
+
// System prompt section cache state
|
|
399
|
+
systemPromptSectionCache: new Map(),
|
|
400
|
+
// Last date emitted to the model
|
|
401
|
+
lastEmittedDate: null,
|
|
402
|
+
// Additional directories from --add-dir flag (for CLAUDE.md loading)
|
|
403
|
+
additionalDirectoriesForClaudeMd: [],
|
|
404
|
+
// Channel server allowlist from --channels flag
|
|
405
|
+
allowedChannels: [],
|
|
406
|
+
hasDevChannels: false,
|
|
407
|
+
// Session project dir (null = derive from originalCwd)
|
|
408
|
+
sessionProjectDir: null,
|
|
409
|
+
// Prompt cache 1h allowlist (null = not yet fetched from GrowthBook)
|
|
410
|
+
promptCache1hAllowlist: null,
|
|
411
|
+
// Prompt cache 1h eligibility (null = not yet evaluated)
|
|
412
|
+
promptCache1hEligible: null,
|
|
413
|
+
// Beta header latches (null = not yet triggered)
|
|
414
|
+
afkModeHeaderLatched: null,
|
|
415
|
+
fastModeHeaderLatched: null,
|
|
416
|
+
cacheEditingHeaderLatched: null,
|
|
417
|
+
thinkingClearLatched: null,
|
|
418
|
+
// Current prompt ID
|
|
419
|
+
promptId: null,
|
|
420
|
+
lastMainRequestId: undefined,
|
|
421
|
+
lastApiCompletionTimestamp: null,
|
|
422
|
+
pendingPostCompaction: false,
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return state
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// AND ESPECIALLY HERE
|
|
429
|
+
const STATE: State = getInitialState()
|
|
430
|
+
|
|
431
|
+
export function getSessionId(): SessionId {
|
|
432
|
+
return STATE.sessionId
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
export function regenerateSessionId(
|
|
436
|
+
options: { setCurrentAsParent?: boolean } = {},
|
|
437
|
+
): SessionId {
|
|
438
|
+
if (options.setCurrentAsParent) {
|
|
439
|
+
STATE.parentSessionId = STATE.sessionId
|
|
440
|
+
}
|
|
441
|
+
// Drop the outgoing session's plan-slug entry so the Map doesn't
|
|
442
|
+
// accumulate stale keys. Callers that need to carry the slug across
|
|
443
|
+
// (REPL.tsx clearContext) read it before calling clearConversation.
|
|
444
|
+
STATE.planSlugCache.delete(STATE.sessionId)
|
|
445
|
+
// Regenerated sessions live in the current project: reset projectDir to
|
|
446
|
+
// null so getTranscriptPath() derives from originalCwd.
|
|
447
|
+
STATE.sessionId = randomUUID() as SessionId
|
|
448
|
+
STATE.sessionProjectDir = null
|
|
449
|
+
return STATE.sessionId
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
export function getParentSessionId(): SessionId | undefined {
|
|
453
|
+
return STATE.parentSessionId
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Atomically switch the active session. `sessionId` and `sessionProjectDir`
|
|
458
|
+
* always change together — there is no separate setter for either, so they
|
|
459
|
+
* cannot drift out of sync (CC-34).
|
|
460
|
+
*
|
|
461
|
+
* @param projectDir — directory containing `<sessionId>.jsonl`. Omit (or
|
|
462
|
+
* pass `null`) for sessions in the current project — the path will derive
|
|
463
|
+
* from originalCwd at read time. Pass `dirname(transcriptPath)` when the
|
|
464
|
+
* session lives in a different project directory (git worktrees,
|
|
465
|
+
* cross-project resume). Every call resets the project dir; it never
|
|
466
|
+
* carries over from the previous session.
|
|
467
|
+
*/
|
|
468
|
+
export function switchSession(
|
|
469
|
+
sessionId: SessionId,
|
|
470
|
+
projectDir: string | null = null,
|
|
471
|
+
): void {
|
|
472
|
+
// Drop the outgoing session's plan-slug entry so the Map stays bounded
|
|
473
|
+
// across repeated /resume. Only the current session's slug is ever read
|
|
474
|
+
// (plans.ts getPlanSlug defaults to getSessionId()).
|
|
475
|
+
STATE.planSlugCache.delete(STATE.sessionId)
|
|
476
|
+
STATE.sessionId = sessionId
|
|
477
|
+
STATE.sessionProjectDir = projectDir
|
|
478
|
+
sessionSwitched.emit(sessionId)
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const sessionSwitched = createSignal<[id: SessionId]>()
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Register a callback that fires when switchSession changes the active
|
|
485
|
+
* sessionId. bootstrap can't import listeners directly (DAG leaf), so
|
|
486
|
+
* callers register themselves. concurrentSessions.ts uses this to keep the
|
|
487
|
+
* PID file's sessionId in sync with --resume.
|
|
488
|
+
*/
|
|
489
|
+
export const onSessionSwitch = sessionSwitched.subscribe
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Project directory the current session's transcript lives in, or `null` if
|
|
493
|
+
* the session was created in the current project (common case — derive from
|
|
494
|
+
* originalCwd). See `switchSession()`.
|
|
495
|
+
*/
|
|
496
|
+
export function getSessionProjectDir(): string | null {
|
|
497
|
+
return STATE.sessionProjectDir
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
export function getOriginalCwd(): string {
|
|
501
|
+
return STATE.originalCwd
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Get the stable project root directory.
|
|
506
|
+
* Unlike getOriginalCwd(), this is never updated by mid-session EnterWorktreeTool
|
|
507
|
+
* (so skills/history stay stable when entering a throwaway worktree).
|
|
508
|
+
* It IS set at startup by --worktree, since that worktree is the session's project.
|
|
509
|
+
* Use for project identity (history, skills, sessions) not file operations.
|
|
510
|
+
*/
|
|
511
|
+
export function getProjectRoot(): string {
|
|
512
|
+
return STATE.projectRoot
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
export function setOriginalCwd(cwd: string): void {
|
|
516
|
+
STATE.originalCwd = cwd.normalize('NFC')
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/**
|
|
520
|
+
* Only for --worktree startup flag. Mid-session EnterWorktreeTool must NOT
|
|
521
|
+
* call this — skills/history should stay anchored to where the session started.
|
|
522
|
+
*/
|
|
523
|
+
export function setProjectRoot(cwd: string): void {
|
|
524
|
+
STATE.projectRoot = cwd.normalize('NFC')
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
export function getCwdState(): string {
|
|
528
|
+
return STATE.cwd
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
export function setCwdState(cwd: string): void {
|
|
532
|
+
STATE.cwd = cwd.normalize('NFC')
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
export function getDirectConnectServerUrl(): string | undefined {
|
|
536
|
+
return STATE.directConnectServerUrl
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
export function setDirectConnectServerUrl(url: string): void {
|
|
540
|
+
STATE.directConnectServerUrl = url
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
export function addToTotalDurationState(
|
|
544
|
+
duration: number,
|
|
545
|
+
durationWithoutRetries: number,
|
|
546
|
+
): void {
|
|
547
|
+
STATE.totalAPIDuration += duration
|
|
548
|
+
STATE.totalAPIDurationWithoutRetries += durationWithoutRetries
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
export function resetTotalDurationStateAndCost_FOR_TESTS_ONLY(): void {
|
|
552
|
+
STATE.totalAPIDuration = 0
|
|
553
|
+
STATE.totalAPIDurationWithoutRetries = 0
|
|
554
|
+
STATE.totalCostUSD = 0
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
export function addToTotalCostState(
|
|
558
|
+
cost: number,
|
|
559
|
+
modelUsage: ModelUsage,
|
|
560
|
+
model: string,
|
|
561
|
+
): void {
|
|
562
|
+
STATE.modelUsage[model] = modelUsage
|
|
563
|
+
STATE.totalCostUSD += cost
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
export function getTotalCostUSD(): number {
|
|
567
|
+
return STATE.totalCostUSD
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
export function getTotalAPIDuration(): number {
|
|
571
|
+
return STATE.totalAPIDuration
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
export function getTotalDuration(): number {
|
|
575
|
+
return Date.now() - STATE.startTime
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
export function getTotalAPIDurationWithoutRetries(): number {
|
|
579
|
+
return STATE.totalAPIDurationWithoutRetries
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
export function getTotalToolDuration(): number {
|
|
583
|
+
return STATE.totalToolDuration
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
export function addToToolDuration(duration: number): void {
|
|
587
|
+
STATE.totalToolDuration += duration
|
|
588
|
+
STATE.turnToolDurationMs += duration
|
|
589
|
+
STATE.turnToolCount++
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
export function getTurnHookDurationMs(): number {
|
|
593
|
+
return STATE.turnHookDurationMs
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
export function addToTurnHookDuration(duration: number): void {
|
|
597
|
+
STATE.turnHookDurationMs += duration
|
|
598
|
+
STATE.turnHookCount++
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
export function resetTurnHookDuration(): void {
|
|
602
|
+
STATE.turnHookDurationMs = 0
|
|
603
|
+
STATE.turnHookCount = 0
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
export function getTurnHookCount(): number {
|
|
607
|
+
return STATE.turnHookCount
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
export function getTurnToolDurationMs(): number {
|
|
611
|
+
return STATE.turnToolDurationMs
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
export function resetTurnToolDuration(): void {
|
|
615
|
+
STATE.turnToolDurationMs = 0
|
|
616
|
+
STATE.turnToolCount = 0
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
export function getTurnToolCount(): number {
|
|
620
|
+
return STATE.turnToolCount
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
export function getTurnClassifierDurationMs(): number {
|
|
624
|
+
return STATE.turnClassifierDurationMs
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
export function addToTurnClassifierDuration(duration: number): void {
|
|
628
|
+
STATE.turnClassifierDurationMs += duration
|
|
629
|
+
STATE.turnClassifierCount++
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
export function resetTurnClassifierDuration(): void {
|
|
633
|
+
STATE.turnClassifierDurationMs = 0
|
|
634
|
+
STATE.turnClassifierCount = 0
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
export function getTurnClassifierCount(): number {
|
|
638
|
+
return STATE.turnClassifierCount
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
export function getStatsStore(): {
|
|
642
|
+
observe(name: string, value: number): void
|
|
643
|
+
} | null {
|
|
644
|
+
return STATE.statsStore
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
export function setStatsStore(
|
|
648
|
+
store: { observe(name: string, value: number): void } | null,
|
|
649
|
+
): void {
|
|
650
|
+
STATE.statsStore = store
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Marks that an interaction occurred.
|
|
655
|
+
*
|
|
656
|
+
* By default the actual Date.now() call is deferred until the next Ink render
|
|
657
|
+
* frame (via flushInteractionTime()) so we avoid calling Date.now() on every
|
|
658
|
+
* single keypress.
|
|
659
|
+
*
|
|
660
|
+
* Pass `immediate = true` when calling from React useEffect callbacks or
|
|
661
|
+
* other code that runs *after* the Ink render cycle has already flushed.
|
|
662
|
+
* Without it the timestamp stays stale until the next render, which may never
|
|
663
|
+
* come if the user is idle (e.g. permission dialog waiting for input).
|
|
664
|
+
*/
|
|
665
|
+
let interactionTimeDirty = false
|
|
666
|
+
|
|
667
|
+
export function updateLastInteractionTime(immediate?: boolean): void {
|
|
668
|
+
if (immediate) {
|
|
669
|
+
flushInteractionTime_inner()
|
|
670
|
+
} else {
|
|
671
|
+
interactionTimeDirty = true
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* If an interaction was recorded since the last flush, update the timestamp
|
|
677
|
+
* now. Called by Ink before each render cycle so we batch many keypresses into
|
|
678
|
+
* a single Date.now() call.
|
|
679
|
+
*/
|
|
680
|
+
export function flushInteractionTime(): void {
|
|
681
|
+
if (interactionTimeDirty) {
|
|
682
|
+
flushInteractionTime_inner()
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
function flushInteractionTime_inner(): void {
|
|
687
|
+
STATE.lastInteractionTime = Date.now()
|
|
688
|
+
interactionTimeDirty = false
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
export function addToTotalLinesChanged(added: number, removed: number): void {
|
|
692
|
+
STATE.totalLinesAdded += added
|
|
693
|
+
STATE.totalLinesRemoved += removed
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
export function getTotalLinesAdded(): number {
|
|
697
|
+
return STATE.totalLinesAdded
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
export function getTotalLinesRemoved(): number {
|
|
701
|
+
return STATE.totalLinesRemoved
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
export function getTotalInputTokens(): number {
|
|
705
|
+
return sumBy(Object.values(STATE.modelUsage), 'inputTokens')
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
export function getTotalOutputTokens(): number {
|
|
709
|
+
return sumBy(Object.values(STATE.modelUsage), 'outputTokens')
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
export function getTotalCacheReadInputTokens(): number {
|
|
713
|
+
return sumBy(Object.values(STATE.modelUsage), 'cacheReadInputTokens')
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
export function getTotalCacheCreationInputTokens(): number {
|
|
717
|
+
return sumBy(Object.values(STATE.modelUsage), 'cacheCreationInputTokens')
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
export function getTotalWebSearchRequests(): number {
|
|
721
|
+
return sumBy(Object.values(STATE.modelUsage), 'webSearchRequests')
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
let outputTokensAtTurnStart = 0
|
|
725
|
+
let currentTurnTokenBudget: number | null = null
|
|
726
|
+
export function getTurnOutputTokens(): number {
|
|
727
|
+
return getTotalOutputTokens() - outputTokensAtTurnStart
|
|
728
|
+
}
|
|
729
|
+
export function getCurrentTurnTokenBudget(): number | null {
|
|
730
|
+
return currentTurnTokenBudget
|
|
731
|
+
}
|
|
732
|
+
let budgetContinuationCount = 0
|
|
733
|
+
export function snapshotOutputTokensForTurn(budget: number | null): void {
|
|
734
|
+
outputTokensAtTurnStart = getTotalOutputTokens()
|
|
735
|
+
currentTurnTokenBudget = budget
|
|
736
|
+
budgetContinuationCount = 0
|
|
737
|
+
}
|
|
738
|
+
export function getBudgetContinuationCount(): number {
|
|
739
|
+
return budgetContinuationCount
|
|
740
|
+
}
|
|
741
|
+
export function incrementBudgetContinuationCount(): void {
|
|
742
|
+
budgetContinuationCount++
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
export function setHasUnknownModelCost(): void {
|
|
746
|
+
STATE.hasUnknownModelCost = true
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
export function hasUnknownModelCost(): boolean {
|
|
750
|
+
return STATE.hasUnknownModelCost
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
export function getLastMainRequestId(): string | undefined {
|
|
754
|
+
return STATE.lastMainRequestId
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
export function setLastMainRequestId(requestId: string): void {
|
|
758
|
+
STATE.lastMainRequestId = requestId
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
export function getLastApiCompletionTimestamp(): number | null {
|
|
762
|
+
return STATE.lastApiCompletionTimestamp
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
export function setLastApiCompletionTimestamp(timestamp: number): void {
|
|
766
|
+
STATE.lastApiCompletionTimestamp = timestamp
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/** Mark that a compaction just occurred. The next API success event will
|
|
770
|
+
* include isPostCompaction=true, then the flag auto-resets. */
|
|
771
|
+
export function markPostCompaction(): void {
|
|
772
|
+
STATE.pendingPostCompaction = true
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/** Consume the post-compaction flag. Returns true once after compaction,
|
|
776
|
+
* then returns false until the next compaction. */
|
|
777
|
+
export function consumePostCompaction(): boolean {
|
|
778
|
+
const was = STATE.pendingPostCompaction
|
|
779
|
+
STATE.pendingPostCompaction = false
|
|
780
|
+
return was
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
export function getLastInteractionTime(): number {
|
|
784
|
+
return STATE.lastInteractionTime
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// Scroll drain suspension — background intervals check this before doing work
|
|
788
|
+
// so they don't compete with scroll frames for the event loop. Set by
|
|
789
|
+
// ScrollBox scrollBy/scrollTo, cleared SCROLL_DRAIN_IDLE_MS after the last
|
|
790
|
+
// scroll event. Module-scope (not in STATE) — ephemeral hot-path flag, no
|
|
791
|
+
// test-reset needed since the debounce timer self-clears.
|
|
792
|
+
let scrollDraining = false
|
|
793
|
+
let scrollDrainTimer: ReturnType<typeof setTimeout> | undefined
|
|
794
|
+
const SCROLL_DRAIN_IDLE_MS = 150
|
|
795
|
+
|
|
796
|
+
/** Mark that a scroll event just happened. Background intervals gate on
|
|
797
|
+
* getIsScrollDraining() and skip their work until the debounce clears. */
|
|
798
|
+
export function markScrollActivity(): void {
|
|
799
|
+
scrollDraining = true
|
|
800
|
+
if (scrollDrainTimer) clearTimeout(scrollDrainTimer)
|
|
801
|
+
scrollDrainTimer = setTimeout(() => {
|
|
802
|
+
scrollDraining = false
|
|
803
|
+
scrollDrainTimer = undefined
|
|
804
|
+
}, SCROLL_DRAIN_IDLE_MS)
|
|
805
|
+
scrollDrainTimer.unref?.()
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
/** True while scroll is actively draining (within 150ms of last event).
|
|
809
|
+
* Intervals should early-return when this is set — the work picks up next
|
|
810
|
+
* tick after scroll settles. */
|
|
811
|
+
export function getIsScrollDraining(): boolean {
|
|
812
|
+
return scrollDraining
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
/** Await this before expensive one-shot work (network, subprocess) that could
|
|
816
|
+
* coincide with scroll. Resolves immediately if not scrolling; otherwise
|
|
817
|
+
* polls at the idle interval until the flag clears. */
|
|
818
|
+
export async function waitForScrollIdle(): Promise<void> {
|
|
819
|
+
while (scrollDraining) {
|
|
820
|
+
// bootstrap-isolation forbids importing sleep() from src/utils/
|
|
821
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
822
|
+
await new Promise(r => setTimeout(r, SCROLL_DRAIN_IDLE_MS).unref?.())
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
export function getModelUsage(): { [modelName: string]: ModelUsage } {
|
|
827
|
+
return STATE.modelUsage
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
export function getUsageForModel(model: string): ModelUsage | undefined {
|
|
831
|
+
return STATE.modelUsage[model]
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/**
|
|
835
|
+
* Gets the model override set from the --model CLI flag or after the user
|
|
836
|
+
* updates their configured model.
|
|
837
|
+
*/
|
|
838
|
+
export function getMainLoopModelOverride(): ModelSetting | undefined {
|
|
839
|
+
return STATE.mainLoopModelOverride
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
export function getInitialMainLoopModel(): ModelSetting {
|
|
843
|
+
return STATE.initialMainLoopModel
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
export function setMainLoopModelOverride(
|
|
847
|
+
model: ModelSetting | undefined,
|
|
848
|
+
): void {
|
|
849
|
+
STATE.mainLoopModelOverride = model
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
export function setInitialMainLoopModel(model: ModelSetting): void {
|
|
853
|
+
STATE.initialMainLoopModel = model
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
export function getSdkBetas(): string[] | undefined {
|
|
857
|
+
return STATE.sdkBetas
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
export function setSdkBetas(betas: string[] | undefined): void {
|
|
861
|
+
STATE.sdkBetas = betas
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
export function resetCostState(): void {
|
|
865
|
+
STATE.totalCostUSD = 0
|
|
866
|
+
STATE.totalAPIDuration = 0
|
|
867
|
+
STATE.totalAPIDurationWithoutRetries = 0
|
|
868
|
+
STATE.totalToolDuration = 0
|
|
869
|
+
STATE.startTime = Date.now()
|
|
870
|
+
STATE.totalLinesAdded = 0
|
|
871
|
+
STATE.totalLinesRemoved = 0
|
|
872
|
+
STATE.hasUnknownModelCost = false
|
|
873
|
+
STATE.modelUsage = {}
|
|
874
|
+
STATE.promptId = null
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
/**
|
|
878
|
+
* Sets cost state values for session restore.
|
|
879
|
+
* Called by restoreCostStateForSession in cost-tracker.ts.
|
|
880
|
+
*/
|
|
881
|
+
export function setCostStateForRestore({
|
|
882
|
+
totalCostUSD,
|
|
883
|
+
totalAPIDuration,
|
|
884
|
+
totalAPIDurationWithoutRetries,
|
|
885
|
+
totalToolDuration,
|
|
886
|
+
totalLinesAdded,
|
|
887
|
+
totalLinesRemoved,
|
|
888
|
+
lastDuration,
|
|
889
|
+
modelUsage,
|
|
890
|
+
}: {
|
|
891
|
+
totalCostUSD: number
|
|
892
|
+
totalAPIDuration: number
|
|
893
|
+
totalAPIDurationWithoutRetries: number
|
|
894
|
+
totalToolDuration: number
|
|
895
|
+
totalLinesAdded: number
|
|
896
|
+
totalLinesRemoved: number
|
|
897
|
+
lastDuration: number | undefined
|
|
898
|
+
modelUsage: { [modelName: string]: ModelUsage } | undefined
|
|
899
|
+
}): void {
|
|
900
|
+
STATE.totalCostUSD = totalCostUSD
|
|
901
|
+
STATE.totalAPIDuration = totalAPIDuration
|
|
902
|
+
STATE.totalAPIDurationWithoutRetries = totalAPIDurationWithoutRetries
|
|
903
|
+
STATE.totalToolDuration = totalToolDuration
|
|
904
|
+
STATE.totalLinesAdded = totalLinesAdded
|
|
905
|
+
STATE.totalLinesRemoved = totalLinesRemoved
|
|
906
|
+
|
|
907
|
+
// Restore per-model usage breakdown
|
|
908
|
+
if (modelUsage) {
|
|
909
|
+
STATE.modelUsage = modelUsage
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// Adjust startTime to make wall duration accumulate
|
|
913
|
+
if (lastDuration) {
|
|
914
|
+
STATE.startTime = Date.now() - lastDuration
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
// Only used in tests
|
|
919
|
+
export function resetStateForTests(): void {
|
|
920
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
921
|
+
throw new Error('resetStateForTests can only be called in tests')
|
|
922
|
+
}
|
|
923
|
+
Object.entries(getInitialState()).forEach(([key, value]) => {
|
|
924
|
+
STATE[key as keyof State] = value as never
|
|
925
|
+
})
|
|
926
|
+
outputTokensAtTurnStart = 0
|
|
927
|
+
currentTurnTokenBudget = null
|
|
928
|
+
budgetContinuationCount = 0
|
|
929
|
+
sessionSwitched.clear()
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// You shouldn't use this directly. See src/utils/model/modelStrings.ts::getModelStrings()
|
|
933
|
+
export function getModelStrings(): ModelStrings | null {
|
|
934
|
+
return STATE.modelStrings
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
// You shouldn't use this directly. See src/utils/model/modelStrings.ts
|
|
938
|
+
export function setModelStrings(modelStrings: ModelStrings): void {
|
|
939
|
+
STATE.modelStrings = modelStrings
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// Test utility function to reset model strings for re-initialization.
|
|
943
|
+
// Separate from setModelStrings because we only want to accept 'null' in tests.
|
|
944
|
+
export function resetModelStringsForTestingOnly() {
|
|
945
|
+
STATE.modelStrings = null
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
export function setMeter(
|
|
949
|
+
meter: Meter,
|
|
950
|
+
createCounter: (name: string, options: MetricOptions) => AttributedCounter,
|
|
951
|
+
): void {
|
|
952
|
+
STATE.meter = meter
|
|
953
|
+
|
|
954
|
+
// Initialize all counters using the provided factory
|
|
955
|
+
STATE.sessionCounter = createCounter('claude_code.session.count', {
|
|
956
|
+
description: 'Count of CLI sessions started',
|
|
957
|
+
})
|
|
958
|
+
STATE.locCounter = createCounter('claude_code.lines_of_code.count', {
|
|
959
|
+
description:
|
|
960
|
+
"Count of lines of code modified, with the 'type' attribute indicating whether lines were added or removed",
|
|
961
|
+
})
|
|
962
|
+
STATE.prCounter = createCounter('claude_code.pull_request.count', {
|
|
963
|
+
description: 'Number of pull requests created',
|
|
964
|
+
})
|
|
965
|
+
STATE.commitCounter = createCounter('claude_code.commit.count', {
|
|
966
|
+
description: 'Number of git commits created',
|
|
967
|
+
})
|
|
968
|
+
STATE.costCounter = createCounter('claude_code.cost.usage', {
|
|
969
|
+
description: 'Cost of the Claude Code session',
|
|
970
|
+
unit: 'USD',
|
|
971
|
+
})
|
|
972
|
+
STATE.tokenCounter = createCounter('claude_code.token.usage', {
|
|
973
|
+
description: 'Number of tokens used',
|
|
974
|
+
unit: 'tokens',
|
|
975
|
+
})
|
|
976
|
+
STATE.codeEditToolDecisionCounter = createCounter(
|
|
977
|
+
'claude_code.code_edit_tool.decision',
|
|
978
|
+
{
|
|
979
|
+
description:
|
|
980
|
+
'Count of code editing tool permission decisions (accept/reject) for Edit, Write, and NotebookEdit tools',
|
|
981
|
+
},
|
|
982
|
+
)
|
|
983
|
+
STATE.activeTimeCounter = createCounter('claude_code.active_time.total', {
|
|
984
|
+
description: 'Total active time in seconds',
|
|
985
|
+
unit: 's',
|
|
986
|
+
})
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
export function getMeter(): Meter | null {
|
|
990
|
+
return STATE.meter
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
export function getSessionCounter(): AttributedCounter | null {
|
|
994
|
+
return STATE.sessionCounter
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
export function getLocCounter(): AttributedCounter | null {
|
|
998
|
+
return STATE.locCounter
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
export function getPrCounter(): AttributedCounter | null {
|
|
1002
|
+
return STATE.prCounter
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
export function getCommitCounter(): AttributedCounter | null {
|
|
1006
|
+
return STATE.commitCounter
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
export function getCostCounter(): AttributedCounter | null {
|
|
1010
|
+
return STATE.costCounter
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
export function getTokenCounter(): AttributedCounter | null {
|
|
1014
|
+
return STATE.tokenCounter
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
export function getCodeEditToolDecisionCounter(): AttributedCounter | null {
|
|
1018
|
+
return STATE.codeEditToolDecisionCounter
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
export function getActiveTimeCounter(): AttributedCounter | null {
|
|
1022
|
+
return STATE.activeTimeCounter
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
export function getLoggerProvider(): LoggerProvider | null {
|
|
1026
|
+
return STATE.loggerProvider
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
export function setLoggerProvider(provider: LoggerProvider | null): void {
|
|
1030
|
+
STATE.loggerProvider = provider
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
export function getEventLogger(): ReturnType<typeof logs.getLogger> | null {
|
|
1034
|
+
return STATE.eventLogger
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
export function setEventLogger(
|
|
1038
|
+
logger: ReturnType<typeof logs.getLogger> | null,
|
|
1039
|
+
): void {
|
|
1040
|
+
STATE.eventLogger = logger
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
export function getMeterProvider(): MeterProvider | null {
|
|
1044
|
+
return STATE.meterProvider
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
export function setMeterProvider(provider: MeterProvider | null): void {
|
|
1048
|
+
STATE.meterProvider = provider
|
|
1049
|
+
}
|
|
1050
|
+
export function getTracerProvider(): BasicTracerProvider | null {
|
|
1051
|
+
return STATE.tracerProvider
|
|
1052
|
+
}
|
|
1053
|
+
export function setTracerProvider(provider: BasicTracerProvider | null): void {
|
|
1054
|
+
STATE.tracerProvider = provider
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
export function getIsNonInteractiveSession(): boolean {
|
|
1058
|
+
return !STATE.isInteractive
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
export function getIsInteractive(): boolean {
|
|
1062
|
+
return STATE.isInteractive
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
export function setIsInteractive(value: boolean): void {
|
|
1066
|
+
STATE.isInteractive = value
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
export function getClientType(): string {
|
|
1070
|
+
return STATE.clientType
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
export function setClientType(type: string): void {
|
|
1074
|
+
STATE.clientType = type
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
export function getSdkAgentProgressSummariesEnabled(): boolean {
|
|
1078
|
+
return STATE.sdkAgentProgressSummariesEnabled
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
export function setSdkAgentProgressSummariesEnabled(value: boolean): void {
|
|
1082
|
+
STATE.sdkAgentProgressSummariesEnabled = value
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
export function getKairosActive(): boolean {
|
|
1086
|
+
return STATE.kairosActive
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
export function setKairosActive(value: boolean): void {
|
|
1090
|
+
STATE.kairosActive = value
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
export function getStrictToolResultPairing(): boolean {
|
|
1094
|
+
return STATE.strictToolResultPairing
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
export function setStrictToolResultPairing(value: boolean): void {
|
|
1098
|
+
STATE.strictToolResultPairing = value
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
// Field name 'userMsgOptIn' avoids excluded-string substrings ('BriefTool',
|
|
1102
|
+
// 'SendUserMessage' — case-insensitive). All callers are inside feature()
|
|
1103
|
+
// guards so these accessors don't need their own (matches getKairosActive).
|
|
1104
|
+
export function getUserMsgOptIn(): boolean {
|
|
1105
|
+
return STATE.userMsgOptIn
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
export function setUserMsgOptIn(value: boolean): void {
|
|
1109
|
+
STATE.userMsgOptIn = value
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
export function getSessionSource(): string | undefined {
|
|
1113
|
+
return STATE.sessionSource
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
export function setSessionSource(source: string): void {
|
|
1117
|
+
STATE.sessionSource = source
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
export function getQuestionPreviewFormat(): 'markdown' | 'html' | undefined {
|
|
1121
|
+
return STATE.questionPreviewFormat
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
export function setQuestionPreviewFormat(format: 'markdown' | 'html'): void {
|
|
1125
|
+
STATE.questionPreviewFormat = format
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
export function isReplBridgeActive(): boolean {
|
|
1129
|
+
// replBridgeActive only exists when USER_TYPE === 'ant'
|
|
1130
|
+
return 'replBridgeActive' in STATE ? STATE.replBridgeActive : false
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
export function setReplBridgeActive(value: boolean): void {
|
|
1134
|
+
if ('replBridgeActive' in STATE) {
|
|
1135
|
+
STATE.replBridgeActive = value
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
export function getAgentColorMap(): Map<string, AgentColorName> {
|
|
1140
|
+
return STATE.agentColorMap
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
export function getFlagSettingsPath(): string | undefined {
|
|
1144
|
+
return STATE.flagSettingsPath
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
export function setFlagSettingsPath(path: string | undefined): void {
|
|
1148
|
+
STATE.flagSettingsPath = path
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
export function getFlagSettingsInline(): Record<string, unknown> | null {
|
|
1152
|
+
return STATE.flagSettingsInline
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
export function setFlagSettingsInline(
|
|
1156
|
+
settings: Record<string, unknown> | null,
|
|
1157
|
+
): void {
|
|
1158
|
+
STATE.flagSettingsInline = settings
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
export function getSessionIngressToken(): string | null | undefined {
|
|
1162
|
+
return STATE.sessionIngressToken
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
export function setSessionIngressToken(token: string | null): void {
|
|
1166
|
+
STATE.sessionIngressToken = token
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
export function getOauthTokenFromFd(): string | null | undefined {
|
|
1170
|
+
return STATE.oauthTokenFromFd
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
export function setOauthTokenFromFd(token: string | null): void {
|
|
1174
|
+
STATE.oauthTokenFromFd = token
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
export function getApiKeyFromFd(): string | null | undefined {
|
|
1178
|
+
return STATE.apiKeyFromFd
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
export function setApiKeyFromFd(key: string | null): void {
|
|
1182
|
+
STATE.apiKeyFromFd = key
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
export function setLastAPIRequest(
|
|
1186
|
+
params: Omit<BetaMessageStreamParams, 'messages'> | null,
|
|
1187
|
+
): void {
|
|
1188
|
+
STATE.lastAPIRequest = params
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
export function getLastAPIRequest(): Omit<
|
|
1192
|
+
BetaMessageStreamParams,
|
|
1193
|
+
'messages'
|
|
1194
|
+
> | null {
|
|
1195
|
+
return STATE.lastAPIRequest
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
export function setLastAPIRequestMessages(
|
|
1199
|
+
messages: BetaMessageStreamParams['messages'] | null,
|
|
1200
|
+
): void {
|
|
1201
|
+
STATE.lastAPIRequestMessages = messages
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
export function getLastAPIRequestMessages():
|
|
1205
|
+
| BetaMessageStreamParams['messages']
|
|
1206
|
+
| null {
|
|
1207
|
+
return STATE.lastAPIRequestMessages
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
export function setLastClassifierRequests(requests: unknown[] | null): void {
|
|
1211
|
+
STATE.lastClassifierRequests = requests
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
export function getLastClassifierRequests(): unknown[] | null {
|
|
1215
|
+
return STATE.lastClassifierRequests
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
export function setCachedClaudeMdContent(content: string | null): void {
|
|
1219
|
+
STATE.cachedClaudeMdContent = content
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
export function getCachedClaudeMdContent(): string | null {
|
|
1223
|
+
return STATE.cachedClaudeMdContent
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
export function addToInMemoryErrorLog(errorInfo: {
|
|
1227
|
+
error: string
|
|
1228
|
+
timestamp: string
|
|
1229
|
+
}): void {
|
|
1230
|
+
const MAX_IN_MEMORY_ERRORS = 100
|
|
1231
|
+
if (STATE.inMemoryErrorLog.length >= MAX_IN_MEMORY_ERRORS) {
|
|
1232
|
+
STATE.inMemoryErrorLog.shift() // Remove oldest error
|
|
1233
|
+
}
|
|
1234
|
+
STATE.inMemoryErrorLog.push(errorInfo)
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
export function getAllowedSettingSources(): SettingSource[] {
|
|
1238
|
+
return STATE.allowedSettingSources
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
export function setAllowedSettingSources(sources: SettingSource[]): void {
|
|
1242
|
+
STATE.allowedSettingSources = sources
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
export function preferThirdPartyAuthentication(): boolean {
|
|
1246
|
+
// IDE extension should behave as 1P for authentication reasons.
|
|
1247
|
+
return getIsNonInteractiveSession() && STATE.clientType !== 'claude-vscode'
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
export function setInlinePlugins(plugins: Array<string>): void {
|
|
1251
|
+
STATE.inlinePlugins = plugins
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
export function getInlinePlugins(): Array<string> {
|
|
1255
|
+
return STATE.inlinePlugins
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
export function setChromeFlagOverride(value: boolean | undefined): void {
|
|
1259
|
+
STATE.chromeFlagOverride = value
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
export function getChromeFlagOverride(): boolean | undefined {
|
|
1263
|
+
return STATE.chromeFlagOverride
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
export function setUseCoworkPlugins(value: boolean): void {
|
|
1267
|
+
STATE.useCoworkPlugins = value
|
|
1268
|
+
resetSettingsCache()
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
export function getUseCoworkPlugins(): boolean {
|
|
1272
|
+
return STATE.useCoworkPlugins
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
export function setSessionBypassPermissionsMode(enabled: boolean): void {
|
|
1276
|
+
STATE.sessionBypassPermissionsMode = enabled
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
export function getSessionBypassPermissionsMode(): boolean {
|
|
1280
|
+
return STATE.sessionBypassPermissionsMode
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
export function setScheduledTasksEnabled(enabled: boolean): void {
|
|
1284
|
+
STATE.scheduledTasksEnabled = enabled
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
export function getScheduledTasksEnabled(): boolean {
|
|
1288
|
+
return STATE.scheduledTasksEnabled
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
export type SessionCronTask = {
|
|
1292
|
+
id: string
|
|
1293
|
+
cron: string
|
|
1294
|
+
prompt: string
|
|
1295
|
+
createdAt: number
|
|
1296
|
+
recurring?: boolean
|
|
1297
|
+
/**
|
|
1298
|
+
* When set, the task was created by an in-process teammate (not the team lead).
|
|
1299
|
+
* The scheduler routes fires to that teammate's pendingUserMessages queue
|
|
1300
|
+
* instead of the main REPL command queue. Session-only — never written to disk.
|
|
1301
|
+
*/
|
|
1302
|
+
agentId?: string
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
export function getSessionCronTasks(): SessionCronTask[] {
|
|
1306
|
+
return STATE.sessionCronTasks
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
export function addSessionCronTask(task: SessionCronTask): void {
|
|
1310
|
+
STATE.sessionCronTasks.push(task)
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
/**
|
|
1314
|
+
* Returns the number of tasks actually removed. Callers use this to skip
|
|
1315
|
+
* downstream work (e.g. the disk read in removeCronTasks) when all ids
|
|
1316
|
+
* were accounted for here.
|
|
1317
|
+
*/
|
|
1318
|
+
export function removeSessionCronTasks(ids: readonly string[]): number {
|
|
1319
|
+
if (ids.length === 0) return 0
|
|
1320
|
+
const idSet = new Set(ids)
|
|
1321
|
+
const remaining = STATE.sessionCronTasks.filter(t => !idSet.has(t.id))
|
|
1322
|
+
const removed = STATE.sessionCronTasks.length - remaining.length
|
|
1323
|
+
if (removed === 0) return 0
|
|
1324
|
+
STATE.sessionCronTasks = remaining
|
|
1325
|
+
return removed
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
export function setSessionTrustAccepted(accepted: boolean): void {
|
|
1329
|
+
STATE.sessionTrustAccepted = accepted
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
export function getSessionTrustAccepted(): boolean {
|
|
1333
|
+
return STATE.sessionTrustAccepted
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
export function setSessionPersistenceDisabled(disabled: boolean): void {
|
|
1337
|
+
STATE.sessionPersistenceDisabled = disabled
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
export function isSessionPersistenceDisabled(): boolean {
|
|
1341
|
+
return STATE.sessionPersistenceDisabled
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
export function hasExitedPlanModeInSession(): boolean {
|
|
1345
|
+
return STATE.hasExitedPlanMode
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
export function setHasExitedPlanMode(value: boolean): void {
|
|
1349
|
+
STATE.hasExitedPlanMode = value
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
export function needsPlanModeExitAttachment(): boolean {
|
|
1353
|
+
return STATE.needsPlanModeExitAttachment
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
export function setNeedsPlanModeExitAttachment(value: boolean): void {
|
|
1357
|
+
STATE.needsPlanModeExitAttachment = value
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
export function handlePlanModeTransition(
|
|
1361
|
+
fromMode: string,
|
|
1362
|
+
toMode: string,
|
|
1363
|
+
): void {
|
|
1364
|
+
// If switching TO plan mode, clear any pending exit attachment
|
|
1365
|
+
// This prevents sending both plan_mode and plan_mode_exit when user toggles quickly
|
|
1366
|
+
if (toMode === 'plan' && fromMode !== 'plan') {
|
|
1367
|
+
STATE.needsPlanModeExitAttachment = false
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
// If switching out of plan mode, trigger the plan_mode_exit attachment
|
|
1371
|
+
if (fromMode === 'plan' && toMode !== 'plan') {
|
|
1372
|
+
STATE.needsPlanModeExitAttachment = true
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
export function needsAutoModeExitAttachment(): boolean {
|
|
1377
|
+
return STATE.needsAutoModeExitAttachment
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
export function setNeedsAutoModeExitAttachment(value: boolean): void {
|
|
1381
|
+
STATE.needsAutoModeExitAttachment = value
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
export function handleAutoModeTransition(
|
|
1385
|
+
fromMode: string,
|
|
1386
|
+
toMode: string,
|
|
1387
|
+
): void {
|
|
1388
|
+
// Auto↔plan transitions are handled by prepareContextForPlanMode (auto may
|
|
1389
|
+
// stay active through plan if opted in) and ExitPlanMode (restores mode).
|
|
1390
|
+
// Skip both directions so this function only handles direct auto transitions.
|
|
1391
|
+
if (
|
|
1392
|
+
(fromMode === 'auto' && toMode === 'plan') ||
|
|
1393
|
+
(fromMode === 'plan' && toMode === 'auto')
|
|
1394
|
+
) {
|
|
1395
|
+
return
|
|
1396
|
+
}
|
|
1397
|
+
const fromIsAuto = fromMode === 'auto'
|
|
1398
|
+
const toIsAuto = toMode === 'auto'
|
|
1399
|
+
|
|
1400
|
+
// If switching TO auto mode, clear any pending exit attachment
|
|
1401
|
+
// This prevents sending both auto_mode and auto_mode_exit when user toggles quickly
|
|
1402
|
+
if (toIsAuto && !fromIsAuto) {
|
|
1403
|
+
STATE.needsAutoModeExitAttachment = false
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
// If switching out of auto mode, trigger the auto_mode_exit attachment
|
|
1407
|
+
if (fromIsAuto && !toIsAuto) {
|
|
1408
|
+
STATE.needsAutoModeExitAttachment = true
|
|
1409
|
+
}
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
// LSP plugin recommendation session tracking
|
|
1413
|
+
export function hasShownLspRecommendationThisSession(): boolean {
|
|
1414
|
+
return STATE.lspRecommendationShownThisSession
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
export function setLspRecommendationShownThisSession(value: boolean): void {
|
|
1418
|
+
STATE.lspRecommendationShownThisSession = value
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
// SDK init event state
|
|
1422
|
+
export function setInitJsonSchema(schema: Record<string, unknown>): void {
|
|
1423
|
+
STATE.initJsonSchema = schema
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
export function getInitJsonSchema(): Record<string, unknown> | null {
|
|
1427
|
+
return STATE.initJsonSchema
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
export function registerHookCallbacks(
|
|
1431
|
+
hooks: Partial<Record<HookEvent, RegisteredHookMatcher[]>>,
|
|
1432
|
+
): void {
|
|
1433
|
+
if (!STATE.registeredHooks) {
|
|
1434
|
+
STATE.registeredHooks = {}
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
// `registerHookCallbacks` may be called multiple times, so we need to merge (not overwrite)
|
|
1438
|
+
for (const [event, matchers] of Object.entries(hooks)) {
|
|
1439
|
+
const eventKey = event as HookEvent
|
|
1440
|
+
if (!STATE.registeredHooks[eventKey]) {
|
|
1441
|
+
STATE.registeredHooks[eventKey] = []
|
|
1442
|
+
}
|
|
1443
|
+
STATE.registeredHooks[eventKey]!.push(...matchers)
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
export function getRegisteredHooks(): Partial<
|
|
1448
|
+
Record<HookEvent, RegisteredHookMatcher[]>
|
|
1449
|
+
> | null {
|
|
1450
|
+
return STATE.registeredHooks
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
export function clearRegisteredHooks(): void {
|
|
1454
|
+
STATE.registeredHooks = null
|
|
1455
|
+
}
|
|
1456
|
+
|
|
1457
|
+
export function clearRegisteredPluginHooks(): void {
|
|
1458
|
+
if (!STATE.registeredHooks) {
|
|
1459
|
+
return
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
const filtered: Partial<Record<HookEvent, RegisteredHookMatcher[]>> = {}
|
|
1463
|
+
for (const [event, matchers] of Object.entries(STATE.registeredHooks)) {
|
|
1464
|
+
// Keep only callback hooks (those without pluginRoot)
|
|
1465
|
+
const callbackHooks = matchers.filter(m => !('pluginRoot' in m))
|
|
1466
|
+
if (callbackHooks.length > 0) {
|
|
1467
|
+
filtered[event as HookEvent] = callbackHooks
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
STATE.registeredHooks = Object.keys(filtered).length > 0 ? filtered : null
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
export function resetSdkInitState(): void {
|
|
1475
|
+
STATE.initJsonSchema = null
|
|
1476
|
+
STATE.registeredHooks = null
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
export function getPlanSlugCache(): Map<string, string> {
|
|
1480
|
+
return STATE.planSlugCache
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
export function getSessionCreatedTeams(): Set<string> {
|
|
1484
|
+
return STATE.sessionCreatedTeams
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
// Teleported session tracking for reliability logging
|
|
1488
|
+
export function setTeleportedSessionInfo(info: {
|
|
1489
|
+
sessionId: string | null
|
|
1490
|
+
}): void {
|
|
1491
|
+
STATE.teleportedSessionInfo = {
|
|
1492
|
+
isTeleported: true,
|
|
1493
|
+
hasLoggedFirstMessage: false,
|
|
1494
|
+
sessionId: info.sessionId,
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
export function getTeleportedSessionInfo(): {
|
|
1499
|
+
isTeleported: boolean
|
|
1500
|
+
hasLoggedFirstMessage: boolean
|
|
1501
|
+
sessionId: string | null
|
|
1502
|
+
} | null {
|
|
1503
|
+
return STATE.teleportedSessionInfo
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
export function markFirstTeleportMessageLogged(): void {
|
|
1507
|
+
if (STATE.teleportedSessionInfo) {
|
|
1508
|
+
STATE.teleportedSessionInfo.hasLoggedFirstMessage = true
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
// Invoked skills tracking for preservation across compaction
|
|
1513
|
+
export type InvokedSkillInfo = {
|
|
1514
|
+
skillName: string
|
|
1515
|
+
skillPath: string
|
|
1516
|
+
content: string
|
|
1517
|
+
invokedAt: number
|
|
1518
|
+
agentId: string | null
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
export function addInvokedSkill(
|
|
1522
|
+
skillName: string,
|
|
1523
|
+
skillPath: string,
|
|
1524
|
+
content: string,
|
|
1525
|
+
agentId: string | null = null,
|
|
1526
|
+
): void {
|
|
1527
|
+
const key = `${agentId ?? ''}:${skillName}`
|
|
1528
|
+
STATE.invokedSkills.set(key, {
|
|
1529
|
+
skillName,
|
|
1530
|
+
skillPath,
|
|
1531
|
+
content,
|
|
1532
|
+
invokedAt: Date.now(),
|
|
1533
|
+
agentId,
|
|
1534
|
+
})
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
export function getInvokedSkills(): Map<string, InvokedSkillInfo> {
|
|
1538
|
+
return STATE.invokedSkills
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
export function getInvokedSkillsForAgent(
|
|
1542
|
+
agentId: string | undefined | null,
|
|
1543
|
+
): Map<string, InvokedSkillInfo> {
|
|
1544
|
+
const normalizedId = agentId ?? null
|
|
1545
|
+
const filtered = new Map<string, InvokedSkillInfo>()
|
|
1546
|
+
for (const [key, skill] of STATE.invokedSkills) {
|
|
1547
|
+
if (skill.agentId === normalizedId) {
|
|
1548
|
+
filtered.set(key, skill)
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
return filtered
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
export function clearInvokedSkills(
|
|
1555
|
+
preservedAgentIds?: ReadonlySet<string>,
|
|
1556
|
+
): void {
|
|
1557
|
+
if (!preservedAgentIds || preservedAgentIds.size === 0) {
|
|
1558
|
+
STATE.invokedSkills.clear()
|
|
1559
|
+
return
|
|
1560
|
+
}
|
|
1561
|
+
for (const [key, skill] of STATE.invokedSkills) {
|
|
1562
|
+
if (skill.agentId === null || !preservedAgentIds.has(skill.agentId)) {
|
|
1563
|
+
STATE.invokedSkills.delete(key)
|
|
1564
|
+
}
|
|
1565
|
+
}
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
export function clearInvokedSkillsForAgent(agentId: string): void {
|
|
1569
|
+
for (const [key, skill] of STATE.invokedSkills) {
|
|
1570
|
+
if (skill.agentId === agentId) {
|
|
1571
|
+
STATE.invokedSkills.delete(key)
|
|
1572
|
+
}
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
// Slow operations tracking for dev bar
|
|
1577
|
+
const MAX_SLOW_OPERATIONS = 10
|
|
1578
|
+
const SLOW_OPERATION_TTL_MS = 10000
|
|
1579
|
+
|
|
1580
|
+
export function addSlowOperation(operation: string, durationMs: number): void {
|
|
1581
|
+
if (process.env.USER_TYPE !== 'ant') return
|
|
1582
|
+
// Skip tracking for editor sessions (user editing a prompt file in $EDITOR)
|
|
1583
|
+
// These are intentionally slow since the user is drafting text
|
|
1584
|
+
if (operation.includes('exec') && operation.includes('claude-prompt-')) {
|
|
1585
|
+
return
|
|
1586
|
+
}
|
|
1587
|
+
const now = Date.now()
|
|
1588
|
+
// Remove stale operations
|
|
1589
|
+
STATE.slowOperations = STATE.slowOperations.filter(
|
|
1590
|
+
op => now - op.timestamp < SLOW_OPERATION_TTL_MS,
|
|
1591
|
+
)
|
|
1592
|
+
// Add new operation
|
|
1593
|
+
STATE.slowOperations.push({ operation, durationMs, timestamp: now })
|
|
1594
|
+
// Keep only the most recent operations
|
|
1595
|
+
if (STATE.slowOperations.length > MAX_SLOW_OPERATIONS) {
|
|
1596
|
+
STATE.slowOperations = STATE.slowOperations.slice(-MAX_SLOW_OPERATIONS)
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
const EMPTY_SLOW_OPERATIONS: ReadonlyArray<{
|
|
1601
|
+
operation: string
|
|
1602
|
+
durationMs: number
|
|
1603
|
+
timestamp: number
|
|
1604
|
+
}> = []
|
|
1605
|
+
|
|
1606
|
+
export function getSlowOperations(): ReadonlyArray<{
|
|
1607
|
+
operation: string
|
|
1608
|
+
durationMs: number
|
|
1609
|
+
timestamp: number
|
|
1610
|
+
}> {
|
|
1611
|
+
// Most common case: nothing tracked. Return a stable reference so the
|
|
1612
|
+
// caller's setState() can bail via Object.is instead of re-rendering at 2fps.
|
|
1613
|
+
if (STATE.slowOperations.length === 0) {
|
|
1614
|
+
return EMPTY_SLOW_OPERATIONS
|
|
1615
|
+
}
|
|
1616
|
+
const now = Date.now()
|
|
1617
|
+
// Only allocate a new array when something actually expired; otherwise keep
|
|
1618
|
+
// the reference stable across polls while ops are still fresh.
|
|
1619
|
+
if (
|
|
1620
|
+
STATE.slowOperations.some(op => now - op.timestamp >= SLOW_OPERATION_TTL_MS)
|
|
1621
|
+
) {
|
|
1622
|
+
STATE.slowOperations = STATE.slowOperations.filter(
|
|
1623
|
+
op => now - op.timestamp < SLOW_OPERATION_TTL_MS,
|
|
1624
|
+
)
|
|
1625
|
+
if (STATE.slowOperations.length === 0) {
|
|
1626
|
+
return EMPTY_SLOW_OPERATIONS
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
// Safe to return directly: addSlowOperation() reassigns STATE.slowOperations
|
|
1630
|
+
// before pushing, so the array held in React state is never mutated.
|
|
1631
|
+
return STATE.slowOperations
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
export function getMainThreadAgentType(): string | undefined {
|
|
1635
|
+
return STATE.mainThreadAgentType
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
export function setMainThreadAgentType(agentType: string | undefined): void {
|
|
1639
|
+
STATE.mainThreadAgentType = agentType
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
export function getIsRemoteMode(): boolean {
|
|
1643
|
+
return STATE.isRemoteMode
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
export function setIsRemoteMode(value: boolean): void {
|
|
1647
|
+
STATE.isRemoteMode = value
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
// System prompt section accessors
|
|
1651
|
+
|
|
1652
|
+
export function getSystemPromptSectionCache(): Map<string, string | null> {
|
|
1653
|
+
return STATE.systemPromptSectionCache
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
export function setSystemPromptSectionCacheEntry(
|
|
1657
|
+
name: string,
|
|
1658
|
+
value: string | null,
|
|
1659
|
+
): void {
|
|
1660
|
+
STATE.systemPromptSectionCache.set(name, value)
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
export function clearSystemPromptSectionState(): void {
|
|
1664
|
+
STATE.systemPromptSectionCache.clear()
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
// Last emitted date accessors (for detecting midnight date changes)
|
|
1668
|
+
|
|
1669
|
+
export function getLastEmittedDate(): string | null {
|
|
1670
|
+
return STATE.lastEmittedDate
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
export function setLastEmittedDate(date: string | null): void {
|
|
1674
|
+
STATE.lastEmittedDate = date
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
export function getAdditionalDirectoriesForClaudeMd(): string[] {
|
|
1678
|
+
return STATE.additionalDirectoriesForClaudeMd
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
export function setAdditionalDirectoriesForClaudeMd(
|
|
1682
|
+
directories: string[],
|
|
1683
|
+
): void {
|
|
1684
|
+
STATE.additionalDirectoriesForClaudeMd = directories
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
export function getAllowedChannels(): ChannelEntry[] {
|
|
1688
|
+
return STATE.allowedChannels
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
export function setAllowedChannels(entries: ChannelEntry[]): void {
|
|
1692
|
+
STATE.allowedChannels = entries
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
export function getHasDevChannels(): boolean {
|
|
1696
|
+
return STATE.hasDevChannels
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
export function setHasDevChannels(value: boolean): void {
|
|
1700
|
+
STATE.hasDevChannels = value
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
export function getPromptCache1hAllowlist(): string[] | null {
|
|
1704
|
+
return STATE.promptCache1hAllowlist
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
export function setPromptCache1hAllowlist(allowlist: string[] | null): void {
|
|
1708
|
+
STATE.promptCache1hAllowlist = allowlist
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
export function getPromptCache1hEligible(): boolean | null {
|
|
1712
|
+
return STATE.promptCache1hEligible
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
export function setPromptCache1hEligible(eligible: boolean | null): void {
|
|
1716
|
+
STATE.promptCache1hEligible = eligible
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
export function getAfkModeHeaderLatched(): boolean | null {
|
|
1720
|
+
return STATE.afkModeHeaderLatched
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
export function setAfkModeHeaderLatched(v: boolean): void {
|
|
1724
|
+
STATE.afkModeHeaderLatched = v
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
export function getFastModeHeaderLatched(): boolean | null {
|
|
1728
|
+
return STATE.fastModeHeaderLatched
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
export function setFastModeHeaderLatched(v: boolean): void {
|
|
1732
|
+
STATE.fastModeHeaderLatched = v
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
export function getCacheEditingHeaderLatched(): boolean | null {
|
|
1736
|
+
return STATE.cacheEditingHeaderLatched
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
export function setCacheEditingHeaderLatched(v: boolean): void {
|
|
1740
|
+
STATE.cacheEditingHeaderLatched = v
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
export function getThinkingClearLatched(): boolean | null {
|
|
1744
|
+
return STATE.thinkingClearLatched
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
export function setThinkingClearLatched(v: boolean): void {
|
|
1748
|
+
STATE.thinkingClearLatched = v
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
/**
|
|
1752
|
+
* Reset beta header latches to null. Called on /clear and /compact so a
|
|
1753
|
+
* fresh conversation gets fresh header evaluation.
|
|
1754
|
+
*/
|
|
1755
|
+
export function clearBetaHeaderLatches(): void {
|
|
1756
|
+
STATE.afkModeHeaderLatched = null
|
|
1757
|
+
STATE.fastModeHeaderLatched = null
|
|
1758
|
+
STATE.cacheEditingHeaderLatched = null
|
|
1759
|
+
STATE.thinkingClearLatched = null
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
export function getPromptId(): string | null {
|
|
1763
|
+
return STATE.promptId
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
export function setPromptId(id: string | null): void {
|
|
1767
|
+
STATE.promptId = id
|
|
1768
|
+
}
|
|
1769
|
+
|