Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
## Amazon Q for Eclipse

> [!IMPORTANT]
> **Amazon Q Developer IDE plugins: end of support**
>
> On April 30, 2027, AWS will discontinue support for Amazon Q Developer IDE plugins. For capabilities similar to Amazon Q Developer IDE plugins, [explore Kiro](https://kiro.dev) to access the latest models and features, including agentic coding, chat and MCP support.
>
> [Learn more](https://aws.amazon.com/blogs/devops/amazon-q-developer-end-of-support-announcement/)

Amazon Q Developer is an advanced AI-powered coding assistant designed to enhance developer productivity and streamline software development processes.

### Key Features
Expand Down
4 changes: 2 additions & 2 deletions feature/feature.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<feature
id="software.aws.toolkits.eclipse.amazonq.feature"
label="Amazon Q for Eclipse"
version="2.7.5.qualifier">
version="2.7.6.qualifier">

<description>
Amazon Q Developer helps users build faster across the entire software development lifecycle by providing tailored responses and code recommendations that conform to their team's internal libraries, proprietary algorithmic techniques, and enterprise code style.
Expand Down Expand Up @@ -198,6 +198,6 @@ https://github.com/aws/amazon-q-eclipse/blob/main/attribution.xml
id="amazon-q-eclipse"
download-size="11000"
install-size="0"
version="2.7.5.qualifier"
version="2.7.6.qualifier"
unpack="false"/>
</feature>
2 changes: 1 addition & 1 deletion feature/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>software.aws.toolkits.eclipse</groupId>
<artifactId>amazon-q-eclipse-group</artifactId>
<version>2.7.5-SNAPSHOT</version>
<version>2.7.6-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>

Expand Down
2 changes: 1 addition & 1 deletion plugin/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Bundle-Name: Amazon Q for Eclipse
Bundle-Provider: Amazon Web Services
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-SymbolicName: amazon-q-eclipse;singleton:=true
Bundle-Version: 2.7.5.qualifier
Bundle-Version: 2.7.6.qualifier
Automatic-Module-Name: amazon.q.eclipse
Bundle-ActivationPolicy: lazy
Bundle-Activator: software.aws.toolkits.eclipse.amazonq.plugin.Activator
Expand Down
23 changes: 23 additions & 0 deletions plugin/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,13 @@
name="Amazon Q Code Reference"
restorable="true">
</view>
<view
category="amazonq"
class="software.aws.toolkits.eclipse.amazonq.views.DeprecationBannerView"
id="software.aws.toolkits.eclipse.amazonq.views.DeprecationBannerView"
name="Amazon Q Developer IDE plugins: end of support"
restorable="false">
</view>
</extension>
<extension point="org.eclipse.ui.activities">
<!-- These activities and activityPatternBindings prevent the view from showing up in the Show View menu. Logic exists that will filter these views out. -->
Expand All @@ -146,6 +153,11 @@
isEqualityPattern="true"
pattern="amazon-q-eclipse/software.aws.toolkits.eclipse.amazonq.views.AmazonQViewContainer">
</activityPatternBinding>
<activityPatternBinding
activityId="software.aws.toolkits.eclipse.amazonq.activity.AmazonQViewContainer"
isEqualityPattern="true"
pattern="amazon-q-eclipse/software.aws.toolkits.eclipse.amazonq.views.DeprecationBannerView">
</activityPatternBinding>
</extension>
<extension
point="org.eclipse.ui.perspectiveExtensions">
Expand All @@ -158,6 +170,17 @@
relationship="right"
visible="false">
</view>
<view
closeable="false"
id="software.aws.toolkits.eclipse.amazonq.views.DeprecationBannerView"
moveable="false"
ratio="0.15"
relative="org.eclipse.ui.editorss"
relationship="top"
showTitle="false"
standalone="true"
visible="false">
</view>
</perspectiveExtension>
</extension>
<extension point="org.eclipse.core.contenttype.contentTypes">
Expand Down
2 changes: 1 addition & 1 deletion plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>software.aws.toolkits.eclipse</groupId>
<artifactId>amazon-q-eclipse-group</artifactId>
<version>2.7.5-SNAPSHOT</version>
<version>2.7.6-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
public enum AmazonQViewType {

TOOLKIT_LOGIN_VIEW, CHAT_VIEW, DEPENDENCY_MISSING_VIEW, RE_AUTHENTICATE_VIEW, CHAT_ASSET_MISSING_VIEW,
LSP_STARTUP_FAILED_VIEW
LSP_STARTUP_FAILED_VIEW, Q_DEV_ACCESS_BLOCKED_VIEW

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.eclipse.amazonq.broker.events;

/**
* Whether Amazon Q Developer has refused this identity at sign-in.
*
* <p>Reacting to the refusal signs the user out, which on its own would route to the ordinary login
* view and lose the explanation. This state is therefore resolved ahead of the logged-out state by
* {@code ViewRouter}, so the user lands on a screen that says what happened.
*/
public enum QDevAccessBlockedState {
NOT_BLOCKED, BLOCKED
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@

public record ViewRouterPluginState(AuthState authState, AmazonQLspState lspState, BrowserCompatibilityState browserCompatibilityState,
ChatWebViewAssetState chatWebViewAssetState, ToolkitLoginWebViewAssetState toolkitLoginWebViewAssetState,
QDeveloperProfileState qDeveloperProfileState) {
QDeveloperProfileState qDeveloperProfileState, QDevAccessBlockedState qDevAccessBlockedState) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package software.aws.toolkits.eclipse.amazonq.configuration;

import org.eclipse.core.runtime.preferences.ConfigurationScope;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.osgi.service.prefs.BackingStoreException;

import software.aws.toolkits.eclipse.amazonq.plugin.Activator;
import software.aws.toolkits.eclipse.amazonq.util.LoggingService;

/**
* Persists deprecation acknowledgements at the Eclipse installation level when possible.
*
* <p>Some Eclipse installations do not allow writes to the configuration scope. In those
* environments, acknowledgements fall back to the existing workspace-scoped plugin store.
*/
public final class DeprecationAcknowledgmentStore {

private static final String STORAGE_PROBE_KEY = "deprecationAcknowledgmentStorageProbe";

private final IEclipsePreferences installationPreferences;
private final PluginStore workspaceFallback;
private final LoggingService logger;

private boolean storageChecked;
private boolean useWorkspaceFallback;

public DeprecationAcknowledgmentStore(final IEclipsePreferences installationPreferences,
final PluginStore workspaceFallback, final LoggingService logger) {
this.installationPreferences = installationPreferences;
this.workspaceFallback = workspaceFallback;
this.logger = logger;
}

public static DeprecationAcknowledgmentStore getInstance() {
return InstanceHolder.INSTANCE;
}

public synchronized boolean isAcknowledged(final String key) {
ensureStorageAvailable();
if (useWorkspaceFallback) {
return "true".equals(workspaceFallback.get(key));
}
try {
return "true".equals(installationPreferences.get(key, null));
} catch (RuntimeException e) {
switchToWorkspaceFallback("reading", key, e);
return "true".equals(workspaceFallback.get(key));
}
}

public synchronized void acknowledge(final String key) {
ensureStorageAvailable();
if (useWorkspaceFallback) {
workspaceFallback.put(key, "true");
return;
}
try {
installationPreferences.put(key, "true");
installationPreferences.flush();
} catch (BackingStoreException | RuntimeException e) {
switchToWorkspaceFallback("saving", key, e);
workspaceFallback.put(key, "true");
}
}

private void ensureStorageAvailable() {
if (storageChecked) {
return;
}
storageChecked = true;
try {
installationPreferences.put(STORAGE_PROBE_KEY, "true");
installationPreferences.flush();
installationPreferences.remove(STORAGE_PROBE_KEY);
installationPreferences.flush();
} catch (BackingStoreException | RuntimeException e) {
try {
installationPreferences.remove(STORAGE_PROBE_KEY);
} catch (RuntimeException ignored) {
// The installation scope is already known to be unavailable.
}
switchToWorkspaceFallback("validating", STORAGE_PROBE_KEY, e);
}
}

private void switchToWorkspaceFallback(final String operation, final String key, final Exception exception) {
useWorkspaceFallback = true;
if (logger != null) {
logger.warn(String.format(
"Unable to use installation-level preferences while %s deprecation acknowledgement '%s'; using workspace preferences",
operation, key), exception);
}
}

private static final class InstanceHolder {
private static final DeprecationAcknowledgmentStore INSTANCE = new DeprecationAcknowledgmentStore(
ConfigurationScope.INSTANCE.getNode(Activator.PLUGIN_ID),
Activator.getPluginStore(),
Activator.getLogger());

private InstanceHolder() {
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,7 @@ private PluginStoreKeys() {

public static final String CHAT_DISCLAIMER_ACKNOWLEDGED = "qchatDisclaimerAcknowledged";
public static final String PAIR_PROGRAMMING_ACKNOWLEDGED = "qchatPairProgrammingAcknowledged";
public static final String CHAT_DEPRECATION_NOTICE_ACKNOWLEDGED = "qchatDeprecationNoticeAcknowledged";
public static final String EDITOR_DEPRECATION_BANNER_DISMISSED = "editorDeprecationBannerDismissed";

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import software.aws.toolkits.eclipse.amazonq.lsp.auth.model.SsoTokenChangedParams;
import software.aws.toolkits.eclipse.amazonq.lsp.model.ConnectionMetadata;
import software.aws.toolkits.eclipse.amazonq.lsp.model.OpenFileDiffParams;
import software.aws.toolkits.eclipse.amazonq.lsp.model.ShowNotificationParams;

public interface AmazonQLspClient extends LanguageClient {

Expand Down Expand Up @@ -61,6 +62,9 @@ public interface AmazonQLspClient extends LanguageClient {
@JsonNotification("aws/didCreateDirectory")
void didCreateDirectory(Object params);

@JsonNotification("aws/window/showNotification")
void showNotification(ShowNotificationParams params);

@JsonNotification("aws/chat/sendPinnedContext")
void sendPinnedContext(Object params);
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@
import software.aws.toolkits.eclipse.amazonq.lsp.auth.model.SsoTokenChangedParams;
import software.aws.toolkits.eclipse.amazonq.lsp.model.ConnectionMetadata;
import software.aws.toolkits.eclipse.amazonq.lsp.model.OpenFileDiffParams;
import software.aws.toolkits.eclipse.amazonq.lsp.model.ShowNotificationParams;
import software.aws.toolkits.eclipse.amazonq.broker.events.QDevAccessBlockedState;
import software.aws.toolkits.eclipse.amazonq.lsp.model.OpenTabUiResponse;
import software.aws.toolkits.eclipse.amazonq.lsp.model.SsoProfileData;
import software.aws.toolkits.eclipse.amazonq.lsp.model.TelemetryEvent;
Expand Down Expand Up @@ -257,6 +259,37 @@ public final void ssoTokenChanged(final SsoTokenChangedParams params) {
}
}

/**
* Handles the language server's generic notification channel. Today the only notification this
* plugin acts on is the report that Amazon Q Developer has refused this identity at sign-in.
*
* <p>Reacting means signing the user out and routing to an explanation. Sign-out happens here
* rather than in the view because the session is already useless: every Q request from this
* identity is refused, so leaving the user signed in would show a working-looking IDE that
* silently does nothing.
*
* <p>Never throws. This runs on the LSP message thread and is shared by every future
* notification, so a failure to classify one must not take the channel down.
*/
@Override
public final void showNotification(final ShowNotificationParams params) {
try {
if (!QDevAccessBlockedNotification.isAccessBlocked(params)) {
return;
}

String message = params.content() == null ? null : params.content().text();
Activator.getLogger().info("Amazon Q Developer access is blocked for this identity: " + message);

// Publish before signing out. Sign-out makes the router re-evaluate, and the blocked
// state has to be in place by then or the router resolves the ordinary login view.
Activator.getEventBroker().post(QDevAccessBlockedState.class, QDevAccessBlockedState.BLOCKED);
Activator.getLoginService().logout();
} catch (Exception e) {
Activator.getLogger().error("Failed to handle showNotification", e);
}
}

@Override
public final void sendContextCommands(final Object params) {
var command = ChatUIInboundCommand.createCommand("aws/chat/sendContextCommands", params);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ private Map<String, Object> getInitializationOptions(final ClientMetadata metada
awsClientCapabilities.put("q", qOptions);
Map<String, Object> window = new HashMap<>();
window.put("showSaveFileDialog", true);
// Required. The runtime builds no notification router unless the client asks for one, and
// then drops aws/window/showNotification silently -- no error, no log.
window.put("notifications", true);
awsClientCapabilities.put("window", window);
awsInitOptions.put("awsClientCapabilities", awsClientCapabilities);
initOptions.put("aws", awsInitOptions);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@
import software.aws.toolkits.eclipse.amazonq.util.AutoTriggerPartListener;
import software.aws.toolkits.eclipse.amazonq.util.AutoTriggerTopLevelListener;
import software.aws.toolkits.eclipse.amazonq.util.Constants;
import software.aws.toolkits.eclipse.amazonq.util.KiroSunsetNotification;
import software.aws.toolkits.eclipse.amazonq.util.ThreadingUtils;
import software.aws.toolkits.eclipse.amazonq.util.ToolkitNotification;
import software.aws.toolkits.eclipse.amazonq.util.UpdateUtils;
import software.aws.toolkits.eclipse.amazonq.views.DeprecationBannerManager;
import software.aws.toolkits.eclipse.amazonq.views.ViewConstants;
import software.aws.toolkits.eclipse.amazonq.views.ViewVisibilityManager;
import software.aws.toolkits.eclipse.amazonq.views.actions.AmazonQToolbarActions;
Expand All @@ -41,6 +41,8 @@ public class LspStartupActivity implements IStartup {

@Override
public final void earlyStartup() {
Display.getDefault().asyncExec(
() -> DeprecationBannerManager.getInstance().showOncePerSession());
Job startupJob = new Job("Amazon Q Startup") {
@Override
protected IStatus run(final IProgressMonitor monitor) {
Expand Down Expand Up @@ -82,23 +84,11 @@ private void schedulePostStartupJobs() {
Display.getDefault().asyncExec(() -> launchWebview());
}
Display.getDefault().asyncExec(() -> attachAutoTriggerListenersIfApplicable());
Display.getDefault().asyncExec(() -> showKiroSunsetNotification());
checkForUpdates();
NotificationPollingService.getInstance().start();
});
}

private void showKiroSunsetNotification() {
if (Activator.getPluginStore().get(Constants.KIRO_SUNSET_NOTIFICATION_DISMISSED_KEY) != null) {
return;
}
AbstractNotificationPopup notification = new KiroSunsetNotification(Display.getCurrent(),
Constants.KIRO_SUNSET_NOTIFICATION_TITLE,
Constants.KIRO_SUNSET_NOTIFICATION_BODY,
() -> Activator.getPluginStore().put(Constants.KIRO_SUNSET_NOTIFICATION_DISMISSED_KEY, "true"));
notification.open();
}

private void checkForUpdates() {
Job updateCheckJob = new Job("Check for updates") {
@Override
Expand Down
Loading
Loading