From a7894278e43f3ec755b0fadc54a993ab3ca4c9d4 Mon Sep 17 00:00:00 2001 From: "li.zhiwen" Date: Thu, 19 Mar 2026 16:20:20 +0800 Subject: [PATCH 1/3] feat(storage): add compatibility file ops APIs to StorageClient --- .../agent/storage/core/StorageClient.java | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/linkwork-storage-core/src/main/java/com/linkwork/agent/storage/core/StorageClient.java b/linkwork-storage-core/src/main/java/com/linkwork/agent/storage/core/StorageClient.java index d4f47ec..7f4695d 100644 --- a/linkwork-storage-core/src/main/java/com/linkwork/agent/storage/core/StorageClient.java +++ b/linkwork-storage-core/src/main/java/com/linkwork/agent/storage/core/StorageClient.java @@ -2,6 +2,12 @@ import com.linkwork.agent.storage.core.model.StorageVolumeDef; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; + public class StorageClient { private final StorageProvider storageProvider; @@ -20,4 +26,70 @@ public byte[] download(String path) { public StorageVolumeDef generateSandboxMountConfig(String workspaceId) { return storageProvider.generateSandboxMountConfig(workspaceId); } + + public boolean isConfigured() { + return storageProvider != null; + } + + public boolean supportsFileStorageOps() { + return isConfigured(); + } + + public Path resolvePath(String path) { + if (path == null || path.isBlank()) { + throw new StorageException("path cannot be blank"); + } + return Path.of(path).toAbsolutePath().normalize(); + } + + public Path uploadToPath(InputStream inputStream, String targetPath, long size) { + if (inputStream == null) { + throw new StorageException("inputStream cannot be null"); + } + Path target = resolvePath(targetPath); + try { + Path parent = target.getParent(); + if (parent != null) { + Files.createDirectories(parent); + } + Files.copy(inputStream, target, StandardCopyOption.REPLACE_EXISTING); + return target; + } catch (IOException ex) { + throw new StorageException("failed to upload to path: " + target, ex); + } + } + + public Path downloadToTempFile(String sourcePath) { + Path source = resolvePath(sourcePath); + try { + Path temp = Files.createTempFile("linkwork-storage-", ".tmp"); + Files.copy(source, temp, StandardCopyOption.REPLACE_EXISTING); + return temp; + } catch (IOException ex) { + throw new StorageException("failed to download to temp file: " + source, ex); + } + } + + public void deleteObject(String path) { + Path target = resolvePath(path); + try { + Files.deleteIfExists(target); + } catch (IOException ex) { + throw new StorageException("failed to delete object: " + target, ex); + } + } + + public void copyObject(String sourcePath, String targetPath) { + Path source = resolvePath(sourcePath); + Path target = resolvePath(targetPath); + try { + Path parent = target.getParent(); + if (parent != null) { + Files.createDirectories(parent); + } + Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING); + } catch (IOException ex) { + throw new StorageException("failed to copy object from " + source + " to " + target, ex); + } + } } From 1a0609a779ee613f6a888a0d9549593d95baf6fa Mon Sep 17 00:00:00 2001 From: "li.zhiwen" Date: Thu, 19 Mar 2026 17:26:01 +0800 Subject: [PATCH 2/3] fix(mcp): support gateway probe status contract --- .../agent/mcp/client/GatewayMcpClient.java | 40 +++++++++++++++++-- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/linkwork-mcp-starter/src/main/java/com/linkwork/agent/mcp/client/GatewayMcpClient.java b/linkwork-mcp-starter/src/main/java/com/linkwork/agent/mcp/client/GatewayMcpClient.java index a9aca9d..96886bc 100644 --- a/linkwork-mcp-starter/src/main/java/com/linkwork/agent/mcp/client/GatewayMcpClient.java +++ b/linkwork-mcp-starter/src/main/java/com/linkwork/agent/mcp/client/GatewayMcpClient.java @@ -64,12 +64,24 @@ public McpProbeResponse probe(McpEndpoint endpoint) { try { Map payload = endpointPayload(endpoint); Map response = post(baseUrl + "/proxy/probe", payload); - boolean success = readSuccess(response); int latencyMs = readLatency(response, (int) (System.currentTimeMillis() - start)); - if (!success) { + + Boolean success = readSuccessNullable(response); + if (success != null) { + if (!success) { + return McpProbeResponse.failure(readMessage(response), latencyMs); + } + return McpProbeResponse.success(latencyMs); + } + + String status = readStatus(response); + if ("online".equals(status) || "degraded".equals(status)) { + return McpProbeResponse.success(latencyMs); + } + if (StringUtils.hasText(status)) { return McpProbeResponse.failure(readMessage(response), latencyMs); } - return McpProbeResponse.success(latencyMs); + return McpProbeResponse.failure("mcp gateway probe returned unknown response", latencyMs); } catch (Exception ex) { int latencyMs = (int) Math.max(1, System.currentTimeMillis() - start); return McpProbeResponse.failure(ex.getMessage(), latencyMs); @@ -144,6 +156,28 @@ private boolean readSuccess(Map body) { return Boolean.TRUE.equals(success); } + private Boolean readSuccessNullable(Map body) { + if (body == null || !body.containsKey("success")) { + return null; + } + Object success = body.get("success"); + if (success instanceof Boolean bool) { + return bool; + } + if (success == null) { + return null; + } + return Boolean.parseBoolean(String.valueOf(success)); + } + + private String readStatus(Map body) { + Object status = body.get("status"); + if (status == null) { + return null; + } + return String.valueOf(status).trim().toLowerCase(); + } + private String readMessage(Map body) { Object message = body.get("message"); if (message == null) { From d8b5e0d5b3c46e2a909ce330545dc4f712032d54 Mon Sep 17 00:00:00 2001 From: wangfenghe Date: Fri, 20 Mar 2026 11:02:49 +0800 Subject: [PATCH 3/3] refactor(starter): unify storage/skill config prefix to linkwork.agent --- .../linkwork/agent/skill/core/UnsupportedSkillProvider.java | 2 +- .../java/com/linkwork/agent/skill/AgentSkillProperties.java | 2 +- .../com/linkwork/agent/skill/SkillAutoConfiguration.java | 6 +++--- .../agent/skill/provider/gitlab/GitLabProviderImpl.java | 6 +++--- .../agent/storage/core/UnsupportedStorageProvider.java | 2 +- .../com/linkwork/agent/storage/AgentStorageProperties.java | 2 +- .../linkwork/agent/storage/StorageAutoConfiguration.java | 4 ++-- .../agent/storage/provider/nfs/NfsStorageProviderImpl.java | 2 +- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/linkwork-skill-core/src/main/java/com/linkwork/agent/skill/core/UnsupportedSkillProvider.java b/linkwork-skill-core/src/main/java/com/linkwork/agent/skill/core/UnsupportedSkillProvider.java index d56a501..dc99394 100644 --- a/linkwork-skill-core/src/main/java/com/linkwork/agent/skill/core/UnsupportedSkillProvider.java +++ b/linkwork-skill-core/src/main/java/com/linkwork/agent/skill/core/UnsupportedSkillProvider.java @@ -44,6 +44,6 @@ public List listCommits(String skillName, int page, int pageSize) { } private SkillException unsupported() { - return new SkillException("agent.skill.provider='" + provider + "' is not supported yet"); + return new SkillException("linkwork.agent.skill.provider='" + provider + "' is not supported yet"); } } diff --git a/linkwork-skill-starter/src/main/java/com/linkwork/agent/skill/AgentSkillProperties.java b/linkwork-skill-starter/src/main/java/com/linkwork/agent/skill/AgentSkillProperties.java index 905f867..fb1f893 100644 --- a/linkwork-skill-starter/src/main/java/com/linkwork/agent/skill/AgentSkillProperties.java +++ b/linkwork-skill-starter/src/main/java/com/linkwork/agent/skill/AgentSkillProperties.java @@ -3,7 +3,7 @@ import com.linkwork.agent.skill.provider.gitlab.GitLabProperties; import org.springframework.boot.context.properties.ConfigurationProperties; -@ConfigurationProperties(prefix = "agent.skill") +@ConfigurationProperties(prefix = "linkwork.agent.skill") public class AgentSkillProperties { private boolean enabled = true; private String provider = "gitlab"; diff --git a/linkwork-skill-starter/src/main/java/com/linkwork/agent/skill/SkillAutoConfiguration.java b/linkwork-skill-starter/src/main/java/com/linkwork/agent/skill/SkillAutoConfiguration.java index 5d24fb1..77ecd48 100644 --- a/linkwork-skill-starter/src/main/java/com/linkwork/agent/skill/SkillAutoConfiguration.java +++ b/linkwork-skill-starter/src/main/java/com/linkwork/agent/skill/SkillAutoConfiguration.java @@ -16,12 +16,12 @@ @AutoConfiguration @EnableConfigurationProperties(AgentSkillProperties.class) -@ConditionalOnProperty(prefix = "agent.skill", name = "enabled", havingValue = "true", matchIfMissing = true) +@ConditionalOnProperty(prefix = "linkwork.agent.skill", name = "enabled", havingValue = "true", matchIfMissing = true) public class SkillAutoConfiguration { @Bean @ConditionalOnMissingBean(name = "skillGitLabRestClient") - @ConditionalOnProperty(prefix = "agent.skill", name = "provider", havingValue = "gitlab", matchIfMissing = true) + @ConditionalOnProperty(prefix = "linkwork.agent.skill", name = "provider", havingValue = "gitlab", matchIfMissing = true) public RestClient skillGitLabRestClient(AgentSkillProperties properties, ObjectProvider builderProvider) { String baseUrl = properties.getGitlab().effectiveUrl(); @@ -36,7 +36,7 @@ public RestClient skillGitLabRestClient(AgentSkillProperties properties, @Bean @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = "agent.skill", name = "provider", havingValue = "gitlab", matchIfMissing = true) + @ConditionalOnProperty(prefix = "linkwork.agent.skill", name = "provider", havingValue = "gitlab", matchIfMissing = true) public SkillProvider skillProvider(RestClient skillGitLabRestClient, AgentSkillProperties properties) { return new GitLabProviderImpl(skillGitLabRestClient, properties.getGitlab()); diff --git a/linkwork-skill-starter/src/main/java/com/linkwork/agent/skill/provider/gitlab/GitLabProviderImpl.java b/linkwork-skill-starter/src/main/java/com/linkwork/agent/skill/provider/gitlab/GitLabProviderImpl.java index e8cb0db..ec80ab2 100644 --- a/linkwork-skill-starter/src/main/java/com/linkwork/agent/skill/provider/gitlab/GitLabProviderImpl.java +++ b/linkwork-skill-starter/src/main/java/com/linkwork/agent/skill/provider/gitlab/GitLabProviderImpl.java @@ -342,13 +342,13 @@ private String trimSlashes(String value) { private void validate() { if (properties.effectiveUrl() == null || properties.effectiveUrl().isBlank()) { - throw new SkillException("agent.skill.gitlab.url or agent.skill.gitlab.repo-url is required"); + throw new SkillException("linkwork.agent.skill.gitlab.url or linkwork.agent.skill.gitlab.repo-url is required"); } if (properties.effectiveToken() == null || properties.effectiveToken().isBlank()) { - throw new SkillException("agent.skill.gitlab.token or agent.skill.gitlab.deploy-token is required"); + throw new SkillException("linkwork.agent.skill.gitlab.token or linkwork.agent.skill.gitlab.deploy-token is required"); } if (properties.getProjectId() == null || properties.getProjectId().isBlank()) { - throw new SkillException("agent.skill.gitlab.project-id is required"); + throw new SkillException("linkwork.agent.skill.gitlab.project-id is required"); } } } diff --git a/linkwork-storage-core/src/main/java/com/linkwork/agent/storage/core/UnsupportedStorageProvider.java b/linkwork-storage-core/src/main/java/com/linkwork/agent/storage/core/UnsupportedStorageProvider.java index 7aacad1..77aa28b 100644 --- a/linkwork-storage-core/src/main/java/com/linkwork/agent/storage/core/UnsupportedStorageProvider.java +++ b/linkwork-storage-core/src/main/java/com/linkwork/agent/storage/core/UnsupportedStorageProvider.java @@ -25,6 +25,6 @@ public StorageVolumeDef generateSandboxMountConfig(String workspaceId) { } private StorageException unsupported() { - return new StorageException("agent.storage.provider='" + provider + "' is not supported yet"); + return new StorageException("linkwork.agent.storage.provider='" + provider + "' is not supported yet"); } } diff --git a/linkwork-storage-starter/src/main/java/com/linkwork/agent/storage/AgentStorageProperties.java b/linkwork-storage-starter/src/main/java/com/linkwork/agent/storage/AgentStorageProperties.java index fd7af8a..13848fa 100644 --- a/linkwork-storage-starter/src/main/java/com/linkwork/agent/storage/AgentStorageProperties.java +++ b/linkwork-storage-starter/src/main/java/com/linkwork/agent/storage/AgentStorageProperties.java @@ -3,7 +3,7 @@ import com.linkwork.agent.storage.provider.nfs.NfsStorageProperties; import org.springframework.boot.context.properties.ConfigurationProperties; -@ConfigurationProperties(prefix = "agent.storage") +@ConfigurationProperties(prefix = "linkwork.agent.storage") public class AgentStorageProperties { private boolean enabled = true; private String provider = "nfs"; diff --git a/linkwork-storage-starter/src/main/java/com/linkwork/agent/storage/StorageAutoConfiguration.java b/linkwork-storage-starter/src/main/java/com/linkwork/agent/storage/StorageAutoConfiguration.java index 15a8918..5a69031 100644 --- a/linkwork-storage-starter/src/main/java/com/linkwork/agent/storage/StorageAutoConfiguration.java +++ b/linkwork-storage-starter/src/main/java/com/linkwork/agent/storage/StorageAutoConfiguration.java @@ -12,12 +12,12 @@ @AutoConfiguration @EnableConfigurationProperties(AgentStorageProperties.class) -@ConditionalOnProperty(prefix = "agent.storage", name = "enabled", havingValue = "true", matchIfMissing = true) +@ConditionalOnProperty(prefix = "linkwork.agent.storage", name = "enabled", havingValue = "true", matchIfMissing = true) public class StorageAutoConfiguration { @Bean @ConditionalOnMissingBean - @ConditionalOnProperty(prefix = "agent.storage", name = "provider", havingValue = "nfs", matchIfMissing = true) + @ConditionalOnProperty(prefix = "linkwork.agent.storage", name = "provider", havingValue = "nfs", matchIfMissing = true) public StorageProvider storageProvider(AgentStorageProperties properties) { return new NfsStorageProviderImpl(properties.getNfs()); } diff --git a/linkwork-storage-starter/src/main/java/com/linkwork/agent/storage/provider/nfs/NfsStorageProviderImpl.java b/linkwork-storage-starter/src/main/java/com/linkwork/agent/storage/provider/nfs/NfsStorageProviderImpl.java index 5924768..b098178 100644 --- a/linkwork-storage-starter/src/main/java/com/linkwork/agent/storage/provider/nfs/NfsStorageProviderImpl.java +++ b/linkwork-storage-starter/src/main/java/com/linkwork/agent/storage/provider/nfs/NfsStorageProviderImpl.java @@ -240,7 +240,7 @@ private void assertWithinBase(Path path) { private Path resolveBaseRoot(String basePath) { if (basePath == null || basePath.isBlank()) { - throw new StorageException("agent.storage.nfs.base-path is required"); + throw new StorageException("linkwork.agent.storage.nfs.base-path is required"); } return Path.of(basePath).toAbsolutePath().normalize(); }