@xortex/xcode 3.0.8 → 3.1.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.
Files changed (71) hide show
  1. package/INSTALLATION.md +285 -0
  2. package/QUICKSTART.md +151 -0
  3. package/SYSTEM_PROMPT.md +583 -0
  4. package/SYSTEM_PROMPT_EXTRACTED.md +1 -0
  5. package/Untitled +1 -0
  6. package/bin/xcode +33 -85
  7. package/bootstrap/state.ts +1758 -0
  8. package/bun.lock +645 -0
  9. package/context/QueuedMessageContext.tsx +63 -0
  10. package/context/fpsMetrics.tsx +30 -0
  11. package/context/mailbox.tsx +38 -0
  12. package/context/modalContext.tsx +58 -0
  13. package/context/notifications.tsx +240 -0
  14. package/context/overlayContext.tsx +151 -0
  15. package/context/promptOverlayContext.tsx +125 -0
  16. package/context/stats.tsx +220 -0
  17. package/context/voice.tsx +88 -0
  18. package/coordinator/coordinatorMode.ts +369 -0
  19. package/costHook.ts +22 -0
  20. package/dialogLaunchers.tsx +133 -0
  21. package/entrypoints/cli.tsx +1 -1
  22. package/extract_prompt.ts +304 -0
  23. package/ink.ts +85 -0
  24. package/install.sh +221 -0
  25. package/interactiveHelpers.tsx +366 -0
  26. package/macro.ts +1 -1
  27. package/memdir/findRelevantMemories.ts +141 -0
  28. package/memdir/memdir.ts +511 -0
  29. package/memdir/memoryAge.ts +53 -0
  30. package/memdir/memoryScan.ts +94 -0
  31. package/memdir/memoryTypes.ts +271 -0
  32. package/memdir/paths.ts +291 -0
  33. package/memdir/teamMemPaths.ts +292 -0
  34. package/memdir/teamMemPrompts.ts +100 -0
  35. package/moreright/useMoreRight.tsx +26 -0
  36. package/native-ts/color-diff/index.ts +999 -0
  37. package/native-ts/file-index/index.ts +370 -0
  38. package/native-ts/yoga-layout/enums.ts +134 -0
  39. package/native-ts/yoga-layout/index.ts +2578 -0
  40. package/outputStyles/loadOutputStylesDir.ts +98 -0
  41. package/package.json +3 -42
  42. package/plugins/builtinPlugins.ts +159 -0
  43. package/plugins/bundled/index.ts +23 -0
  44. package/projectOnboardingState.ts +83 -0
  45. package/public/claude-files.png +0 -0
  46. package/public/leak-tweet.png +0 -0
  47. package/query/config.ts +46 -0
  48. package/query/deps.ts +40 -0
  49. package/query/stopHooks.ts +470 -0
  50. package/query/tokenBudget.ts +93 -0
  51. package/replLauncher.tsx +27 -0
  52. package/schemas/hooks.ts +222 -0
  53. package/screens/Doctor.tsx +575 -0
  54. package/screens/REPL.tsx +7107 -0
  55. package/screens/ResumeConversation.tsx +399 -0
  56. package/scripts/postinstall.js +90 -0
  57. package/server/createDirectConnectSession.ts +88 -0
  58. package/server/directConnectManager.ts +213 -0
  59. package/server/types.ts +57 -0
  60. package/setup.ts +477 -0
  61. package/stub_types.sh +13 -0
  62. package/tasks.ts +39 -0
  63. package/tools.ts +396 -0
  64. package/upstreamproxy/relay.ts +455 -0
  65. package/upstreamproxy/upstreamproxy.ts +285 -0
  66. package/vim/motions.ts +82 -0
  67. package/vim/operators.ts +556 -0
  68. package/vim/textObjects.ts +186 -0
  69. package/vim/transitions.ts +490 -0
  70. package/vim/types.ts +199 -0
  71. package/voice/voiceModeEnabled.ts +54 -0
package/setup.ts ADDED
@@ -0,0 +1,477 @@
1
+ /* eslint-disable custom-rules/no-process-exit */
2
+
3
+ import { feature } from 'bun:bundle'
4
+ import chalk from 'chalk'
5
+ import {
6
+ type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
7
+ logEvent,
8
+ } from 'src/services/analytics/index.js'
9
+ import { getCwd } from 'src/utils/cwd.js'
10
+ import { checkForReleaseNotes } from 'src/utils/releaseNotes.js'
11
+ import { setCwd } from 'src/utils/Shell.js'
12
+ import { initSinks } from 'src/utils/sinks.js'
13
+ import {
14
+ getIsNonInteractiveSession,
15
+ getProjectRoot,
16
+ getSessionId,
17
+ setOriginalCwd,
18
+ setProjectRoot,
19
+ switchSession,
20
+ } from './bootstrap/state.js'
21
+ import { getCommands } from './commands.js'
22
+ import { initSessionMemory } from './services/SessionMemory/sessionMemory.js'
23
+ import { asSessionId } from './types/ids.js'
24
+ import { isAgentSwarmsEnabled } from './utils/agentSwarmsEnabled.js'
25
+ import { checkAndRestoreTerminalBackup } from './utils/appleTerminalBackup.js'
26
+ import { prefetchApiKeyFromApiKeyHelperIfSafe } from './utils/auth.js'
27
+ import { clearMemoryFileCaches } from './utils/claudemd.js'
28
+ import { getCurrentProjectConfig, getGlobalConfig } from './utils/config.js'
29
+ import { logForDiagnosticsNoPII } from './utils/diagLogs.js'
30
+ import { env } from './utils/env.js'
31
+ import { envDynamic } from './utils/envDynamic.js'
32
+ import { isBareMode, isEnvTruthy } from './utils/envUtils.js'
33
+ import { errorMessage } from './utils/errors.js'
34
+ import { findCanonicalGitRoot, findGitRoot, getIsGit } from './utils/git.js'
35
+ import { initializeFileChangedWatcher } from './utils/hooks/fileChangedWatcher.js'
36
+ import {
37
+ captureHooksConfigSnapshot,
38
+ updateHooksConfigSnapshot,
39
+ } from './utils/hooks/hooksConfigSnapshot.js'
40
+ import { hasWorktreeCreateHook } from './utils/hooks.js'
41
+ import { checkAndRestoreITerm2Backup } from './utils/iTermBackup.js'
42
+ import { logError } from './utils/log.js'
43
+ import { getRecentActivity } from './utils/logoV2Utils.js'
44
+ import { lockCurrentVersion } from './utils/nativeInstaller/index.js'
45
+ import type { PermissionMode } from './utils/permissions/PermissionMode.js'
46
+ import { getPlanSlug } from './utils/plans.js'
47
+ import { saveWorktreeState } from './utils/sessionStorage.js'
48
+ import { profileCheckpoint } from './utils/startupProfiler.js'
49
+ import {
50
+ createTmuxSessionForWorktree,
51
+ createWorktreeForSession,
52
+ generateTmuxSessionName,
53
+ worktreeBranchName,
54
+ } from './utils/worktree.js'
55
+
56
+ export async function setup(
57
+ cwd: string,
58
+ permissionMode: PermissionMode,
59
+ allowDangerouslySkipPermissions: boolean,
60
+ worktreeEnabled: boolean,
61
+ worktreeName: string | undefined,
62
+ tmuxEnabled: boolean,
63
+ customSessionId?: string | null,
64
+ worktreePRNumber?: number,
65
+ messagingSocketPath?: string,
66
+ ): Promise<void> {
67
+ logForDiagnosticsNoPII('info', 'setup_started')
68
+
69
+ // Check for Node.js version < 18
70
+ const nodeVersion = process.version.match(/^v(\d+)\./)?.[1]
71
+ if (!nodeVersion || parseInt(nodeVersion) < 18) {
72
+ // biome-ignore lint/suspicious/noConsole:: intentional console output
73
+ console.error(
74
+ chalk.bold.red(
75
+ 'Error: Claude Code requires Node.js version 18 or higher.',
76
+ ),
77
+ )
78
+ process.exit(1)
79
+ }
80
+
81
+ // Set custom session ID if provided
82
+ if (customSessionId) {
83
+ switchSession(asSessionId(customSessionId))
84
+ }
85
+
86
+ // --bare / SIMPLE: skip UDS messaging server and teammate snapshot.
87
+ // Scripted calls don't receive injected messages and don't use swarm teammates.
88
+ // Explicit --messaging-socket-path is the escape hatch (per #23222 gate pattern).
89
+ if (!isBareMode() || messagingSocketPath !== undefined) {
90
+ // Start UDS messaging server (Mac/Linux only).
91
+ // Enabled by default for ants — creates a socket in tmpdir if no
92
+ // --messaging-socket-path is passed. Awaited so the server is bound
93
+ // and $CLAUDE_CODE_MESSAGING_SOCKET is exported before any hook
94
+ // (SessionStart in particular) can spawn and snapshot process.env.
95
+ if (feature('UDS_INBOX')) {
96
+ const m = await import('./utils/udsMessaging.js')
97
+ await m.startUdsMessaging(
98
+ messagingSocketPath ?? m.getDefaultUdsSocketPath(),
99
+ { isExplicit: messagingSocketPath !== undefined },
100
+ )
101
+ }
102
+ }
103
+
104
+ // Teammate snapshot — SIMPLE-only gate (no escape hatch, swarm not used in bare)
105
+ if (!isBareMode() && isAgentSwarmsEnabled()) {
106
+ const { captureTeammateModeSnapshot } = await import(
107
+ './utils/swarm/backends/teammateModeSnapshot.js'
108
+ )
109
+ captureTeammateModeSnapshot()
110
+ }
111
+
112
+ // Terminal backup restoration — interactive only. Print mode doesn't
113
+ // interact with terminal settings; the next interactive session will
114
+ // detect and restore any interrupted setup.
115
+ if (!getIsNonInteractiveSession()) {
116
+ // iTerm2 backup check only when swarms enabled
117
+ if (isAgentSwarmsEnabled()) {
118
+ const restoredIterm2Backup = await checkAndRestoreITerm2Backup()
119
+ if (restoredIterm2Backup.status === 'restored') {
120
+ // biome-ignore lint/suspicious/noConsole:: intentional console output
121
+ console.log(
122
+ chalk.yellow(
123
+ 'Detected an interrupted iTerm2 setup. Your original settings have been restored. You may need to restart iTerm2 for the changes to take effect.',
124
+ ),
125
+ )
126
+ } else if (restoredIterm2Backup.status === 'failed') {
127
+ // biome-ignore lint/suspicious/noConsole:: intentional console output
128
+ console.error(
129
+ chalk.red(
130
+ `Failed to restore iTerm2 settings. Please manually restore your original settings with: defaults import com.googlecode.iterm2 ${restoredIterm2Backup.backupPath}.`,
131
+ ),
132
+ )
133
+ }
134
+ }
135
+
136
+ // Check and restore Terminal.app backup if setup was interrupted
137
+ try {
138
+ const restoredTerminalBackup = await checkAndRestoreTerminalBackup()
139
+ if (restoredTerminalBackup.status === 'restored') {
140
+ // biome-ignore lint/suspicious/noConsole:: intentional console output
141
+ console.log(
142
+ chalk.yellow(
143
+ 'Detected an interrupted Terminal.app setup. Your original settings have been restored. You may need to restart Terminal.app for the changes to take effect.',
144
+ ),
145
+ )
146
+ } else if (restoredTerminalBackup.status === 'failed') {
147
+ // biome-ignore lint/suspicious/noConsole:: intentional console output
148
+ console.error(
149
+ chalk.red(
150
+ `Failed to restore Terminal.app settings. Please manually restore your original settings with: defaults import com.apple.Terminal ${restoredTerminalBackup.backupPath}.`,
151
+ ),
152
+ )
153
+ }
154
+ } catch (error) {
155
+ // Log but don't crash if Terminal.app backup restoration fails
156
+ logError(error)
157
+ }
158
+ }
159
+
160
+ // IMPORTANT: setCwd() must be called before any other code that depends on the cwd
161
+ setCwd(cwd)
162
+
163
+ // Capture hooks configuration snapshot to avoid hidden hook modifications.
164
+ // IMPORTANT: Must be called AFTER setCwd() so hooks are loaded from the correct directory
165
+ const hooksStart = Date.now()
166
+ captureHooksConfigSnapshot()
167
+ logForDiagnosticsNoPII('info', 'setup_hooks_captured', {
168
+ duration_ms: Date.now() - hooksStart,
169
+ })
170
+
171
+ // Initialize FileChanged hook watcher — sync, reads hook config snapshot
172
+ initializeFileChangedWatcher(cwd)
173
+
174
+ // Handle worktree creation if requested
175
+ // IMPORTANT: this must be called befiore getCommands(), otherwise /eject won't be available.
176
+ if (worktreeEnabled) {
177
+ // Mirrors bridgeMain.ts: hook-configured sessions can proceed without git
178
+ // so createWorktreeForSession() can delegate to the hook (non-git VCS).
179
+ const hasHook = hasWorktreeCreateHook()
180
+ const inGit = await getIsGit()
181
+ if (!hasHook && !inGit) {
182
+ process.stderr.write(
183
+ chalk.red(
184
+ `Error: Can only use --worktree in a git repository, but ${chalk.bold(cwd)} is not a git repository. ` +
185
+ `Configure a WorktreeCreate hook in settings.json to use --worktree with other VCS systems.\n`,
186
+ ),
187
+ )
188
+ process.exit(1)
189
+ }
190
+
191
+ const slug = worktreePRNumber
192
+ ? `pr-${worktreePRNumber}`
193
+ : (worktreeName ?? getPlanSlug())
194
+
195
+ // Git preamble runs whenever we're in a git repo — even if a hook is
196
+ // configured — so --tmux keeps working for git users who also have a
197
+ // WorktreeCreate hook. Only hook-only (non-git) mode skips it.
198
+ let tmuxSessionName: string | undefined
199
+ if (inGit) {
200
+ // Resolve to main repo root (handles being invoked from within a worktree).
201
+ // findCanonicalGitRoot is sync/filesystem-only/memoized; the underlying
202
+ // findGitRoot cache was already warmed by getIsGit() above, so this is ~free.
203
+ const mainRepoRoot = findCanonicalGitRoot(getCwd())
204
+ if (!mainRepoRoot) {
205
+ process.stderr.write(
206
+ chalk.red(
207
+ `Error: Could not determine the main git repository root.\n`,
208
+ ),
209
+ )
210
+ process.exit(1)
211
+ }
212
+
213
+ // If we're inside a worktree, switch to the main repo for worktree creation
214
+ if (mainRepoRoot !== (findGitRoot(getCwd()) ?? getCwd())) {
215
+ logForDiagnosticsNoPII('info', 'worktree_resolved_to_main_repo')
216
+ process.chdir(mainRepoRoot)
217
+ setCwd(mainRepoRoot)
218
+ }
219
+
220
+ tmuxSessionName = tmuxEnabled
221
+ ? generateTmuxSessionName(mainRepoRoot, worktreeBranchName(slug))
222
+ : undefined
223
+ } else {
224
+ // Non-git hook mode: no canonical root to resolve, so name the tmux
225
+ // session from cwd — generateTmuxSessionName only basenames the path.
226
+ tmuxSessionName = tmuxEnabled
227
+ ? generateTmuxSessionName(getCwd(), worktreeBranchName(slug))
228
+ : undefined
229
+ }
230
+
231
+ let worktreeSession: Awaited<ReturnType<typeof createWorktreeForSession>>
232
+ try {
233
+ worktreeSession = await createWorktreeForSession(
234
+ getSessionId(),
235
+ slug,
236
+ tmuxSessionName,
237
+ worktreePRNumber ? { prNumber: worktreePRNumber } : undefined,
238
+ )
239
+ } catch (error) {
240
+ process.stderr.write(
241
+ chalk.red(`Error creating worktree: ${errorMessage(error)}\n`),
242
+ )
243
+ process.exit(1)
244
+ }
245
+
246
+ logEvent('tengu_worktree_created', { tmux_enabled: tmuxEnabled })
247
+
248
+ // Create tmux session for the worktree if enabled
249
+ if (tmuxEnabled && tmuxSessionName) {
250
+ const tmuxResult = await createTmuxSessionForWorktree(
251
+ tmuxSessionName,
252
+ worktreeSession.worktreePath,
253
+ )
254
+ if (tmuxResult.created) {
255
+ // biome-ignore lint/suspicious/noConsole:: intentional console output
256
+ console.log(
257
+ chalk.green(
258
+ `Created tmux session: ${chalk.bold(tmuxSessionName)}\nTo attach: ${chalk.bold(`tmux attach -t ${tmuxSessionName}`)}`,
259
+ ),
260
+ )
261
+ } else {
262
+ // biome-ignore lint/suspicious/noConsole:: intentional console output
263
+ console.error(
264
+ chalk.yellow(
265
+ `Warning: Failed to create tmux session: ${tmuxResult.error}`,
266
+ ),
267
+ )
268
+ }
269
+ }
270
+
271
+ process.chdir(worktreeSession.worktreePath)
272
+ setCwd(worktreeSession.worktreePath)
273
+ setOriginalCwd(getCwd())
274
+ // --worktree means the worktree IS the session's project, so skills/hooks/
275
+ // cron/etc. should resolve here. (EnterWorktreeTool mid-session does NOT
276
+ // touch projectRoot — that's a throwaway worktree, project stays stable.)
277
+ setProjectRoot(getCwd())
278
+ saveWorktreeState(worktreeSession)
279
+ // Clear memory files cache since originalCwd has changed
280
+ clearMemoryFileCaches()
281
+ // Settings cache was populated in init() (via applySafeConfigEnvironmentVariables)
282
+ // and again at captureHooksConfigSnapshot() above, both from the original dir's
283
+ // .claude/settings.json. Re-read from the worktree and re-capture hooks.
284
+ updateHooksConfigSnapshot()
285
+ }
286
+
287
+ // Background jobs - only critical registrations that must happen before first query
288
+ logForDiagnosticsNoPII('info', 'setup_background_jobs_starting')
289
+ // Bundled skills/plugins are registered in main.tsx before the parallel
290
+ // getCommands() kick — see comment there. Moved out of setup() because
291
+ // the await points above (startUdsMessaging, ~20ms) meant getCommands()
292
+ // raced ahead and memoized an empty bundledSkills list.
293
+ if (!isBareMode()) {
294
+ initSessionMemory() // Synchronous - registers hook, gate check happens lazily
295
+ if (feature('CONTEXT_COLLAPSE')) {
296
+ /* eslint-disable @typescript-eslint/no-require-imports */
297
+ ;(
298
+ require('./services/contextCollapse/index.js') as typeof import('./services/contextCollapse/index.js')
299
+ ).initContextCollapse()
300
+ /* eslint-enable @typescript-eslint/no-require-imports */
301
+ }
302
+ }
303
+ void lockCurrentVersion() // Lock current version to prevent deletion by other processes
304
+ logForDiagnosticsNoPII('info', 'setup_background_jobs_launched')
305
+
306
+ profileCheckpoint('setup_before_prefetch')
307
+ // Pre-fetch promises - only items needed before render
308
+ logForDiagnosticsNoPII('info', 'setup_prefetch_starting')
309
+ // When CLAUDE_CODE_SYNC_PLUGIN_INSTALL is set, skip all plugin prefetch.
310
+ // The sync install path in print.ts calls refreshPluginState() after
311
+ // installing, which reloads commands, hooks, and agents. Prefetching here
312
+ // races with the install (concurrent copyPluginToVersionedCache / cachePlugin
313
+ // on the same directories), and the hot-reload handler fires clearPluginCache()
314
+ // mid-install when policySettings arrives.
315
+ const skipPluginPrefetch =
316
+ (getIsNonInteractiveSession() &&
317
+ isEnvTruthy(process.env.CLAUDE_CODE_SYNC_PLUGIN_INSTALL)) ||
318
+ // --bare: loadPluginHooks → loadAllPlugins is filesystem work that's
319
+ // wasted when executeHooks early-returns under --bare anyway.
320
+ isBareMode()
321
+ if (!skipPluginPrefetch) {
322
+ void getCommands(getProjectRoot())
323
+ }
324
+ void import('./utils/plugins/loadPluginHooks.js').then(m => {
325
+ if (!skipPluginPrefetch) {
326
+ void m.loadPluginHooks() // Pre-load plugin hooks (consumed by processSessionStartHooks before render)
327
+ m.setupPluginHookHotReload() // Set up hot reload for plugin hooks when settings change
328
+ }
329
+ })
330
+ // --bare: skip attribution hook install + repo classification +
331
+ // session-file-access analytics + team memory watcher. These are background
332
+ // bookkeeping for commit attribution + usage metrics — scripted calls don't
333
+ // commit code, and the 49ms attribution hook stat check (measured) is pure
334
+ // overhead. NOT an early-return: the --dangerously-skip-permissions safety
335
+ // gate, tengu_started beacon, and apiKeyHelper prefetch below must still run.
336
+ if (!isBareMode()) {
337
+ if (process.env.USER_TYPE === 'ant') {
338
+ // Prime repo classification cache for auto-undercover mode. Default is
339
+ // undercover ON until proven internal; if this resolves to internal, clear
340
+ // the prompt cache so the next turn picks up the OFF state.
341
+ void import('./utils/commitAttribution.js').then(async m => {
342
+ if (await m.isInternalModelRepo()) {
343
+ const { clearSystemPromptSections } = await import(
344
+ './constants/systemPromptSections.js'
345
+ )
346
+ clearSystemPromptSections()
347
+ }
348
+ })
349
+ }
350
+ if (feature('COMMIT_ATTRIBUTION')) {
351
+ // Dynamic import to enable dead code elimination (module contains excluded strings).
352
+ // Defer to next tick so the git subprocess spawn runs after first render
353
+ // rather than during the setup() microtask window.
354
+ setImmediate(() => {
355
+ void import('./utils/attributionHooks.js').then(
356
+ ({ registerAttributionHooks }) => {
357
+ registerAttributionHooks() // Register attribution tracking hooks (ant-only feature)
358
+ },
359
+ )
360
+ })
361
+ }
362
+ void import('./utils/sessionFileAccessHooks.js').then(m =>
363
+ m.registerSessionFileAccessHooks(),
364
+ ) // Register session file access analytics hooks
365
+ if (feature('TEAMMEM')) {
366
+ void import('./services/teamMemorySync/watcher.js').then(m =>
367
+ m.startTeamMemoryWatcher(),
368
+ ) // Start team memory sync watcher
369
+ }
370
+ }
371
+ initSinks() // Attach error log + analytics sinks and drain queued events
372
+
373
+ // Session-success-rate denominator. Emit immediately after the analytics
374
+ // sink is attached — before any parsing, fetching, or I/O that could throw.
375
+ // inc-3694 (P0 CHANGELOG crash) threw at checkForReleaseNotes below; every
376
+ // event after this point was dead. This beacon is the earliest reliable
377
+ // "process started" signal for release health monitoring.
378
+ logEvent('tengu_started', {})
379
+
380
+ void prefetchApiKeyFromApiKeyHelperIfSafe(getIsNonInteractiveSession()) // Prefetch safely - only executes if trust already confirmed
381
+ profileCheckpoint('setup_after_prefetch')
382
+
383
+ // Pre-fetch data for Logo v2 - await to ensure it's ready before logo renders.
384
+ // --bare / SIMPLE: skip — release notes are interactive-UI display data,
385
+ // and getRecentActivity() reads up to 10 session JSONL files.
386
+ if (!isBareMode()) {
387
+ const { hasReleaseNotes } = await checkForReleaseNotes(
388
+ getGlobalConfig().lastReleaseNotesSeen,
389
+ )
390
+ if (hasReleaseNotes) {
391
+ await getRecentActivity()
392
+ }
393
+ }
394
+
395
+ // If permission mode is set to bypass, verify we're in a safe environment
396
+ if (
397
+ permissionMode === 'bypassPermissions' ||
398
+ allowDangerouslySkipPermissions
399
+ ) {
400
+ // Check if running as root/sudo on Unix-like systems
401
+ // Allow root if in a sandbox (e.g., TPU devspaces that require root)
402
+ if (
403
+ process.platform !== 'win32' &&
404
+ typeof process.getuid === 'function' &&
405
+ process.getuid() === 0 &&
406
+ process.env.IS_SANDBOX !== '1' &&
407
+ !isEnvTruthy(process.env.CLAUDE_CODE_BUBBLEWRAP)
408
+ ) {
409
+ // biome-ignore lint/suspicious/noConsole:: intentional console output
410
+ console.error(
411
+ `--dangerously-skip-permissions cannot be used with root/sudo privileges for security reasons`,
412
+ )
413
+ process.exit(1)
414
+ }
415
+
416
+ if (
417
+ process.env.USER_TYPE === 'ant' &&
418
+ // Skip for Desktop's local agent mode — same trust model as CCR/BYOC
419
+ // (trusted Anthropic-managed launcher intentionally pre-approving everything).
420
+ // Precedent: permissionSetup.ts:861, applySettingsChange.ts:55 (PR #19116)
421
+ process.env.CLAUDE_CODE_ENTRYPOINT !== 'local-agent' &&
422
+ // Same for CCD (Claude Code in Desktop) — apps#29127 passes the flag
423
+ // unconditionally to unlock mid-session bypass switching
424
+ process.env.CLAUDE_CODE_ENTRYPOINT !== 'claude-desktop'
425
+ ) {
426
+ // Only await if permission mode is set to bypass
427
+ const [isDocker, hasInternet] = await Promise.all([
428
+ envDynamic.getIsDocker(),
429
+ env.hasInternetAccess(),
430
+ ])
431
+ const isBubblewrap = envDynamic.getIsBubblewrapSandbox()
432
+ const isSandbox = process.env.IS_SANDBOX === '1'
433
+ const isSandboxed = isDocker || isBubblewrap || isSandbox
434
+ if (!isSandboxed || hasInternet) {
435
+ // biome-ignore lint/suspicious/noConsole:: intentional console output
436
+ console.error(
437
+ `--dangerously-skip-permissions can only be used in Docker/sandbox containers with no internet access but got Docker: ${isDocker}, Bubblewrap: ${isBubblewrap}, IS_SANDBOX: ${isSandbox}, hasInternet: ${hasInternet}`,
438
+ )
439
+ process.exit(1)
440
+ }
441
+ }
442
+ }
443
+
444
+ if (process.env.NODE_ENV === 'test') {
445
+ return
446
+ }
447
+
448
+ // Log tengu_exit event from the last session?
449
+ const projectConfig = getCurrentProjectConfig()
450
+ if (
451
+ projectConfig.lastCost !== undefined &&
452
+ projectConfig.lastDuration !== undefined
453
+ ) {
454
+ logEvent('tengu_exit', {
455
+ last_session_cost: projectConfig.lastCost,
456
+ last_session_api_duration: projectConfig.lastAPIDuration,
457
+ last_session_tool_duration: projectConfig.lastToolDuration,
458
+ last_session_duration: projectConfig.lastDuration,
459
+ last_session_lines_added: projectConfig.lastLinesAdded,
460
+ last_session_lines_removed: projectConfig.lastLinesRemoved,
461
+ last_session_total_input_tokens: projectConfig.lastTotalInputTokens,
462
+ last_session_total_output_tokens: projectConfig.lastTotalOutputTokens,
463
+ last_session_total_cache_creation_input_tokens:
464
+ projectConfig.lastTotalCacheCreationInputTokens,
465
+ last_session_total_cache_read_input_tokens:
466
+ projectConfig.lastTotalCacheReadInputTokens,
467
+ last_session_fps_average: projectConfig.lastFpsAverage,
468
+ last_session_fps_low_1_pct: projectConfig.lastFpsLow1Pct,
469
+ last_session_id:
470
+ projectConfig.lastSessionId as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
471
+ ...projectConfig.lastSessionMetrics,
472
+ })
473
+ // Note: We intentionally don't clear these values after logging.
474
+ // They're needed for cost restoration when resuming sessions.
475
+ // The values will be overwritten when the next session exits.
476
+ }
477
+ }
package/stub_types.sh ADDED
@@ -0,0 +1,13 @@
1
+ #!/bin/bash
2
+ # Auto-generate stub types.ts files for all missing modules
3
+
4
+ cd /Users/vedantmahajan/Desktop/Xortex/claude-code
5
+
6
+ # Find all directories that need types.ts
7
+ find . -name "*.ts" -o -name "*.tsx" | xargs grep -h "from './types.js'" 2>/dev/null | sed "s/.*from '\([^']*\)'.*/\1/" | sort -u | while read path; do
8
+ dir=$(dirname "$path")
9
+ if [ ! -f "$dir/types.ts" ] && [ ! -f "$dir/types.js" ]; then
10
+ echo "// Stub types file for $dir" > "$dir/types.ts"
11
+ echo "Created stub: $dir/types.ts"
12
+ fi
13
+ done
package/tasks.ts ADDED
@@ -0,0 +1,39 @@
1
+ import { feature } from 'bun:bundle'
2
+ import type { Task, TaskType } from './Task.js'
3
+ import { DreamTask } from './tasks/DreamTask/DreamTask.js'
4
+ import { LocalAgentTask } from './tasks/LocalAgentTask/LocalAgentTask.js'
5
+ import { LocalShellTask } from './tasks/LocalShellTask/LocalShellTask.js'
6
+ import { RemoteAgentTask } from './tasks/RemoteAgentTask/RemoteAgentTask.js'
7
+
8
+ /* eslint-disable @typescript-eslint/no-require-imports */
9
+ const LocalWorkflowTask: Task | null = feature('WORKFLOW_SCRIPTS')
10
+ ? require('./tasks/LocalWorkflowTask/LocalWorkflowTask.js').LocalWorkflowTask
11
+ : null
12
+ const MonitorMcpTask: Task | null = feature('MONITOR_TOOL')
13
+ ? require('./tasks/MonitorMcpTask/MonitorMcpTask.js').MonitorMcpTask
14
+ : null
15
+ /* eslint-enable @typescript-eslint/no-require-imports */
16
+
17
+ /**
18
+ * Get all tasks.
19
+ * Mirrors the pattern from tools.ts
20
+ * Note: Returns array inline to avoid circular dependency issues with top-level const
21
+ */
22
+ export function getAllTasks(): Task[] {
23
+ const tasks: Task[] = [
24
+ LocalShellTask,
25
+ LocalAgentTask,
26
+ RemoteAgentTask,
27
+ DreamTask,
28
+ ]
29
+ if (LocalWorkflowTask) tasks.push(LocalWorkflowTask)
30
+ if (MonitorMcpTask) tasks.push(MonitorMcpTask)
31
+ return tasks
32
+ }
33
+
34
+ /**
35
+ * Get a task by its type.
36
+ */
37
+ export function getTaskByType(type: TaskType): Task | undefined {
38
+ return getAllTasks().find(t => t.type === type)
39
+ }