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();
}