-
Notifications
You must be signed in to change notification settings - Fork 122
Feature/channel integrations #426
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weβll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
40 commits
Select commit
Hold shift + click to select a range
35704a9
feat(channels): add ChannelIntegrationConfiguration resource + store β¦
ginccc 53e8224
feat(channels): add ChannelTargetRouter with colon-required trigger mβ¦
ginccc 9f2b330
docs: add conversation cancel & lifecycle control plan
ginccc a69d5fa
refactor(slack): replace SlackChannelRouter with ChannelTargetRouter
ginccc 5175926
feat(channels): add MCP admin tools + migration helper for channel inβ¦
ginccc ed684db
docs: update changelog and task list for channel integration work
ginccc cebdcde
fix(channels): fix 4 bugs found in code review
ginccc e4e493a
fix(channels): address code review findings #1-12
ginccc a45bee1
docs(channels): add code review hardening changelog entry
ginccc 39a37a8
fix(channels): harden migration tool β N1-N7 from re-review
ginccc 63050b8
docs(channels): add migration tool hardening changelog entry (N1-N7)
ginccc 6abb1f7
test(channels): add 19 tests + polish migration edge cases
ginccc 1925a01
refactor(channels): replace MCP migration with startup migration, depβ¦
ginccc 1200ebd
docs(channels): add startup migration & deprecation changelog entry
ginccc aa0d1bf
fix(channels): readAgentβread on IAgentStore, signing secret from resβ¦
ginccc c861cb0
test(channels): add 31 tests for refresh, public API, secrets, legacyβ¦
ginccc ded7110
docs(changelog): review hardening and test coverage entry
ginccc aa759f0
chore(channels): remove unused IResourceStore import
ginccc 4733411
fix(channels): address review bugs β legacy posting, duplicate channeβ¦
ginccc 2e3c831
docs(changelog): external review round 4 β 6 bugs fixed, 80 tests
ginccc 2121f18
test(channels): comprehensive test coverage for channel integration sβ¦
ginccc 9d44453
chore(merge): resolve merge conflicts from main into feature/channel-β¦
ginccc 0537232
fix(channels): reject duplicate target names in validation
ginccc dd9a6fd
fix(channels): address PR review β defensive copies, migration retry,β¦
ginccc 67ab9ba
fix(channels): fix duplicateChannel regression from defensive copying
ginccc 95f2759
fix(ci): harden Postgres integration tests against CI flakiness
ginccc 94d6d0f
fix(migration): inject IMigrationLogStore interface instead of concreβ¦
ginccc fed27e7
fix(channels): address review findings β idempotency, intent keys, heβ¦
ginccc 4fa03f9
Merge remote-tracking branch 'origin/main' into feature/channel-integβ¦
ginccc ead310d
Merge remote-tracking branch 'origin/main' into feature/channel-integβ¦
ginccc 4c209b2
fix(channels): address code review β ThreadLocal removal, vault prefiβ¦
ginccc dc7b570
fix(channels): second-pass review β log sanitization, defensive copieβ¦
ginccc 65ec272
docs(changelog): add second-pass review fixes entry
ginccc 7711817
Merge remote-tracking branch 'origin/main' into feature/channel-integβ¦
ginccc f6ded96
refactor(slack): remove eddi.slack.enabled server toggle β config-driβ¦
ginccc 50fe538
fix(slack): sanitize attacker-controlled headers in log statements
ginccc 31ca10a
feat(slack): DM support, test hardening, docs overhaul
ginccc bf7a94c
fix(llm): update LlmTaskTest template counts for TEMPLATE_SKIP_PARAMS
ginccc 86b0694
fix(slack): close table fence before real code block in mrkdwn converter
ginccc a01e4f7
chore: merge origin/main into feature/channel-integrations
ginccc File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
17 changes: 17 additions & 0 deletions
17
src/main/java/ai/labs/eddi/configs/channels/IChannelIntegrationStore.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| /* | ||
| * Copyright EDDI contributors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
| package ai.labs.eddi.configs.channels; | ||
|
|
||
| import ai.labs.eddi.configs.channels.model.ChannelIntegrationConfiguration; | ||
| import ai.labs.eddi.datastore.IResourceStore; | ||
|
|
||
| /** | ||
| * Store interface for channel integration configurations. Uses the DB-agnostic | ||
| * {@code AbstractResourceStore} via {@code IResourceStorageFactory}. | ||
| * | ||
| * @since 6.1.0 | ||
| */ | ||
| public interface IChannelIntegrationStore extends IResourceStore<ChannelIntegrationConfiguration> { | ||
| } |
86 changes: 86 additions & 0 deletions
86
src/main/java/ai/labs/eddi/configs/channels/IRestChannelIntegrationStore.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,86 @@ | ||
| /* | ||
| * Copyright EDDI contributors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
| package ai.labs.eddi.configs.channels; | ||
|
|
||
| import ai.labs.eddi.configs.IRestVersionInfo; | ||
| import ai.labs.eddi.configs.channels.model.ChannelIntegrationConfiguration; | ||
| import ai.labs.eddi.configs.descriptors.model.DocumentDescriptor; | ||
| import jakarta.annotation.security.RolesAllowed; | ||
| import jakarta.ws.rs.*; | ||
| import jakarta.ws.rs.core.MediaType; | ||
| import jakarta.ws.rs.core.Response; | ||
| import org.eclipse.microprofile.openapi.annotations.Operation; | ||
| import org.eclipse.microprofile.openapi.annotations.parameters.Parameter; | ||
| import org.eclipse.microprofile.openapi.annotations.tags.Tag; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| /** | ||
| * JAX-RS interface for channel integration configuration CRUD. | ||
| * <p> | ||
| * Admin-only β channel configurations expose target topology and vault | ||
| * references. Same security posture as {@code IRestAgentStore}. | ||
| * | ||
| * @since 6.1.0 | ||
| */ | ||
| @Path("/channelstore/channels") | ||
| @Tag(name = "Channel Integrations") | ||
| @RolesAllowed({"eddi-admin", "eddi-editor"}) | ||
| public interface IRestChannelIntegrationStore extends IRestVersionInfo { | ||
| String resourceBaseType = "eddi://ai.labs.channel"; | ||
| String resourceURI = resourceBaseType + "/channelstore/channels/"; | ||
|
|
||
| @GET | ||
| @Path("/descriptors") | ||
| @Produces(MediaType.APPLICATION_JSON) | ||
| @Operation(description = "Read list of channel integration descriptors.") | ||
| List<DocumentDescriptor> readChannelDescriptors( | ||
| @QueryParam("filter") | ||
| @DefaultValue("") String filter, | ||
| @QueryParam("index") | ||
| @DefaultValue("0") Integer index, | ||
| @QueryParam("limit") | ||
| @DefaultValue("20") Integer limit); | ||
|
|
||
| @GET | ||
| @Path("/{id}") | ||
| @Produces(MediaType.APPLICATION_JSON) | ||
| @Operation(description = "Read channel integration configuration.") | ||
| ChannelIntegrationConfiguration readChannel( | ||
| @PathParam("id") String id, | ||
| @Parameter(name = "version", required = true, example = "1") | ||
| @QueryParam("version") Integer version); | ||
|
|
||
| @PUT | ||
| @Path("/{id}") | ||
| @Consumes(MediaType.APPLICATION_JSON) | ||
| @Operation(description = "Update channel integration configuration.") | ||
| Response updateChannel( | ||
| @PathParam("id") String id, | ||
| @Parameter(name = "version", required = true, example = "1") | ||
| @QueryParam("version") Integer version, | ||
| ChannelIntegrationConfiguration channelConfiguration); | ||
|
|
||
| @POST | ||
| @Consumes(MediaType.APPLICATION_JSON) | ||
| @Operation(description = "Create channel integration configuration.") | ||
| Response createChannel(ChannelIntegrationConfiguration channelConfiguration); | ||
|
|
||
| @POST | ||
| @Path("/{id}") | ||
| @Operation(description = "Duplicate this channel integration configuration.") | ||
| Response duplicateChannel(@PathParam("id") String id, | ||
| @QueryParam("version") Integer version); | ||
|
|
||
| @DELETE | ||
| @Path("/{id}") | ||
| @Operation(description = "Delete channel integration configuration.") | ||
| Response deleteChannel( | ||
| @PathParam("id") String id, | ||
| @Parameter(name = "version", required = true, example = "1") | ||
| @QueryParam("version") Integer version, | ||
| @QueryParam("permanent") | ||
| @DefaultValue("false") Boolean permanent); | ||
| } |
109 changes: 109 additions & 0 deletions
109
src/main/java/ai/labs/eddi/configs/channels/model/ChannelIntegrationConfiguration.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| /* | ||
| * Copyright EDDI contributors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
| package ai.labs.eddi.configs.channels.model; | ||
|
|
||
| import com.fasterxml.jackson.annotation.JsonInclude; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.HashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
|
|
||
| /** | ||
| * Standalone configuration for a channel integration. Decouples channel routing | ||
| * and credentials from {@code AgentConfiguration}, supporting multi-target | ||
| * channels and cross-platform extensibility (Slack, Teams, Discord). | ||
| * <p> | ||
| * One {@code ChannelIntegrationConfiguration} represents one platform channel | ||
| * (e.g., a single Slack channel) with one or more {@link ChannelTarget}s that | ||
| * map trigger keywords to agents or groups. | ||
| * | ||
| * @since 6.1.0 | ||
| */ | ||
| @JsonInclude(JsonInclude.Include.NON_NULL) | ||
| public class ChannelIntegrationConfiguration { | ||
|
|
||
| private String name; | ||
| private String channelType; | ||
| private Map<String, String> platformConfig; | ||
| private List<ChannelTarget> targets; | ||
| private String defaultTargetName; | ||
|
|
||
| public ChannelIntegrationConfiguration() { | ||
| this.platformConfig = new HashMap<>(); | ||
| this.targets = new ArrayList<>(); | ||
| } | ||
|
|
||
| /** | ||
| * Human-readable name for this integration (e.g., "Engineering AI Hub"). | ||
| */ | ||
| public String getName() { | ||
| return name; | ||
| } | ||
|
|
||
| public void setName(String name) { | ||
| this.name = name; | ||
| } | ||
|
|
||
| /** | ||
| * Platform type. Validated server-side against registered adapters. Currently | ||
| * supported: {@code "slack"}. Future: {@code "teams"}, {@code "discord"}. | ||
| * <p> | ||
| * Deliberately a String (not an enum) so downstream forks can register custom | ||
| * adapters without recompiling core. | ||
| */ | ||
| public String getChannelType() { | ||
| return channelType; | ||
| } | ||
|
|
||
| public void setChannelType(String channelType) { | ||
| this.channelType = channelType; | ||
| } | ||
|
|
||
| /** | ||
| * Platform-specific credentials and identifiers. Keys depend on | ||
| * {@link #channelType}: | ||
| * <ul> | ||
| * <li><b>slack:</b> {@code channelId}, {@code botToken}, | ||
| * {@code signingSecret}</li> | ||
| * <li><b>teams:</b> {@code channelId}, {@code appId}, {@code appPassword}, | ||
| * {@code serviceUrl}</li> | ||
| * <li><b>discord:</b> {@code guildId}, {@code channelId}, {@code botToken}, | ||
| * {@code publicKey}</li> | ||
| * </ul> | ||
| * Secret values should use vault references: {@code ${vault:key-name}}. | ||
| */ | ||
| public Map<String, String> getPlatformConfig() { | ||
| return new HashMap<>(platformConfig); | ||
| } | ||
|
|
||
| public void setPlatformConfig(Map<String, String> platformConfig) { | ||
| this.platformConfig = platformConfig == null ? new HashMap<>() : new HashMap<>(platformConfig); | ||
| } | ||
|
|
||
| /** | ||
| * Available targets in this channel. Each target maps trigger keywords to an | ||
| * agent or group. At least one target is required. | ||
| */ | ||
| public List<ChannelTarget> getTargets() { | ||
| return new ArrayList<>(targets); | ||
| } | ||
|
|
||
| public void setTargets(List<ChannelTarget> targets) { | ||
| this.targets = targets == null ? new ArrayList<>() : new ArrayList<>(targets); | ||
| } | ||
|
|
||
| /** | ||
| * Name of the target to use when no trigger keyword matches. Must reference an | ||
| * existing target in {@link #targets}. Required. | ||
| */ | ||
| public String getDefaultTargetName() { | ||
| return defaultTargetName; | ||
| } | ||
|
|
||
| public void setDefaultTargetName(String defaultTargetName) { | ||
| this.defaultTargetName = defaultTargetName; | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.