* 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>
164 lines
4.5 KiB
TypeScript
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,
|
|
};
|
|
}
|
|
}
|
|
}
|