Files
continue/core/config/ProfileLifecycleManager.ts
Patrick Erichsen 85994118e8 feat: implement Sentry error tracking with privacy-first anonymization (#6881)
* feat: add sentry to core

* feat: implement comprehensive Sentry error tracking with privacy-first anonymization

- Add Sentry ErrorBoundary components following React best practices
- Implement shared anonymization logic between core and GUI environments
- Create organized sentry directory structure with clean exports
- Add Continue team member access control (@continue.dev email check)
- Preserve node_modules package names for debugging while anonymizing user paths
- Include comprehensive test coverage for all anonymization scenarios
- Set up source maps for readable production error stack traces
- Add browser-compatible hashing without crypto module dependency

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* feat: complete Sentry integration with GUI components and tests

- Add GUI TelemetryProviders with Sentry ErrorBoundary
- Create shared isContinueTeamMember utilities for both core and GUI
- Update all imports and dependencies for new sentry structure
- Add comprehensive test coverage and fix all TypeScript issues

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* prettier

* fix: resolve GUI build errors by removing sentry index.ts and using specific imports

- Remove problematic index.ts that was pulling Node.js-specific Sentry code into browser bundle
- Update GUI to import specifically from anonymization.ts and constants.ts files
- Update core imports to use specific SentryLogger.ts import path
- GUI build now succeeds with proper source map upload to Sentry

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: move TelemetryProviders inside AuthProvider to fix test failures

- Move TelemetryProviders from main.tsx to Layout.tsx inside AuthProvider
- Fixes "useAuth must be used within an AuthProvider" error in tests
- TelemetryProviders now has access to auth context for Continue team member check
- GUI build and functionality remain working correctly

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* prettier

* merge main

* Update package.json

* fix tests

* fix errors

* Update extension.ts

* disable sentry in tests/local dev

* add additional error handling

* Update SentryLogger.ts

* remove global try/catch

* remove test code

* linting

* Update SentryLogger.ts

* more logs

* Update core.ts

* lower sampling rate

* fix tests

* feat: use `Logger`

* Update logger.ts

* Update logger.ts

Co-Authored-By: Claude <noreply@anthropic.com>

* lint fixes

Co-Authored-By: Claude <noreply@anthropic.com>

* fix: rename logger.ts to Logger.ts for case sensitivity

This fixes TypeScript compilation errors in CI where the file
was tracked as lowercase logger.ts but referenced as Logger.ts
in imports.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* address ting-wai's feedback

* Update core.ts

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-08-14 16:54:21 -07:00

164 lines
4.5 KiB
TypeScript

import {
ConfigResult,
ConfigValidationError,
FullSlug,
Policy,
} from "@continuedev/config-yaml";
import {
BrowserSerializedContinueConfig,
ContinueConfig,
IContextProvider,
IDE,
} from "../index.js";
import { Logger } from "../util/Logger.js";
import { finalToBrowserConfig } from "./load.js";
import { IProfileLoader } from "./profile/IProfileLoader.js";
export interface ProfileDescription {
fullSlug: FullSlug;
profileType: "control-plane" | "local" | "platform";
title: string;
id: string;
iconUrl: string;
errors: ConfigValidationError[] | undefined;
uri: string;
rawYaml?: string;
}
export interface OrganizationDescription {
id: string;
iconUrl: string;
name: string;
slug: string | undefined; // TODO: This doesn't need to be undefined, just doing while transitioning the backend
policy?: Policy;
}
export type OrgWithProfiles = OrganizationDescription & {
profiles: ProfileLifecycleManager[];
currentProfile: ProfileLifecycleManager | null;
};
export type SerializedOrgWithProfiles = OrganizationDescription & {
profiles: ProfileDescription[];
selectedProfileId: string | null;
};
export class ProfileLifecycleManager {
private savedConfigResult: ConfigResult<ContinueConfig> | undefined;
private savedBrowserConfigResult?: ConfigResult<BrowserSerializedContinueConfig>;
private pendingConfigPromise?: Promise<ConfigResult<ContinueConfig>>;
constructor(
private readonly profileLoader: IProfileLoader,
private readonly ide: IDE,
) {}
get profileDescription(): ProfileDescription {
return this.profileLoader.description;
}
clearConfig() {
this.savedConfigResult = undefined;
this.savedBrowserConfigResult = undefined;
this.pendingConfigPromise = undefined;
}
// Clear saved config and reload
async reloadConfig(
additionalContextProviders: IContextProvider[] = [],
): Promise<ConfigResult<ContinueConfig>> {
this.savedConfigResult = undefined;
this.savedBrowserConfigResult = undefined;
this.pendingConfigPromise = undefined;
return this.loadConfig(additionalContextProviders, true);
}
async loadConfig(
additionalContextProviders: IContextProvider[],
forceReload: boolean = false,
): Promise<ConfigResult<ContinueConfig>> {
// If we already have a config, return it
if (!forceReload) {
if (this.savedConfigResult) {
return this.savedConfigResult;
} else if (this.pendingConfigPromise) {
return this.pendingConfigPromise;
}
}
// Set pending config promise
this.pendingConfigPromise = new Promise((resolve) => {
void (async () => {
let result: ConfigResult<ContinueConfig>;
// This try catch is expected to catch high-level errors that aren't block-specific
// Like invalid json, invalid yaml, file read errors, etc.
// NOT block-specific loading errors
try {
result = await this.profileLoader.doLoadConfig();
} catch (e) {
// Capture config loading system failures to Sentry
Logger.error(e, {
context: "profile_config_loading",
});
const message =
e instanceof Error
? `${e.message}\n${e.stack ? e.stack : ""}`
: "Error loading config";
result = {
errors: [
{
fatal: true,
message,
},
],
config: undefined,
configLoadInterrupted: true,
};
}
if (result.config) {
// Add registered context providers
result.config.contextProviders = (
result.config.contextProviders ?? []
).concat(additionalContextProviders);
}
resolve(result);
})();
});
// Wait for the config promise to resolve
this.savedConfigResult = await this.pendingConfigPromise;
this.pendingConfigPromise = undefined;
return this.savedConfigResult;
}
async getSerializedConfig(
additionalContextProviders: IContextProvider[],
): Promise<ConfigResult<BrowserSerializedContinueConfig>> {
if (this.savedBrowserConfigResult) {
return this.savedBrowserConfigResult;
} else {
const result = await this.loadConfig(additionalContextProviders);
if (!result.config) {
return {
...result,
config: undefined,
};
}
const serializedConfig = await finalToBrowserConfig(
result.config,
this.ide,
);
return {
...result,
config: serializedConfig,
};
}
}
}