fix: route openclaw tasks via bridge gateway
This commit is contained in:
parent
6b542acc9a
commit
50c20eec16
@ -609,21 +609,19 @@ extension AppControllerDesktopRuntimeHelpers on AppController {
|
||||
|
||||
var wroteArtifact = false;
|
||||
for (final artifact in artifacts) {
|
||||
if (!artifact.hasInlineContent) {
|
||||
continue;
|
||||
}
|
||||
final relativePath = _sanitizeArtifactRelativePathInternal(
|
||||
artifact.relativePath,
|
||||
);
|
||||
if (relativePath.isEmpty) {
|
||||
continue;
|
||||
}
|
||||
final bytes = await artifactBytesInternal(artifact);
|
||||
if (bytes == null) {
|
||||
continue;
|
||||
}
|
||||
final target = await _nextArtifactTargetFileInternal(root, relativePath);
|
||||
await target.parent.create(recursive: true);
|
||||
await target.writeAsBytes(
|
||||
_decodeArtifactContentInternal(artifact),
|
||||
flush: true,
|
||||
);
|
||||
await target.writeAsBytes(bytes, flush: true);
|
||||
wroteArtifact = true;
|
||||
}
|
||||
|
||||
@ -635,6 +633,51 @@ extension AppControllerDesktopRuntimeHelpers on AppController {
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<int>?> artifactBytesInternal(
|
||||
GoTaskServiceArtifact artifact,
|
||||
) async {
|
||||
if (artifact.hasInlineContent) {
|
||||
return _decodeArtifactContentInternal(artifact);
|
||||
}
|
||||
final rawDownloadUrl = artifact.downloadUrl.trim();
|
||||
if (rawDownloadUrl.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
final uri = Uri.tryParse(rawDownloadUrl);
|
||||
if (uri == null || (uri.scheme != 'http' && uri.scheme != 'https')) {
|
||||
return null;
|
||||
}
|
||||
final bridgeEndpoint = resolveBridgeAcpEndpointInternal();
|
||||
final sameBridgeHost =
|
||||
bridgeEndpoint != null &&
|
||||
uri.host.trim().toLowerCase() ==
|
||||
bridgeEndpoint.host.trim().toLowerCase();
|
||||
if (!sameBridgeHost) {
|
||||
return null;
|
||||
}
|
||||
final authorization = await resolveGatewayAcpAuthorizationHeaderInternal(
|
||||
uri,
|
||||
);
|
||||
if (authorization == null || authorization.trim().isEmpty) {
|
||||
return null;
|
||||
}
|
||||
final client = HttpClient();
|
||||
try {
|
||||
final request = await client.getUrl(uri);
|
||||
request.headers.set(HttpHeaders.authorizationHeader, authorization);
|
||||
final response = await request.close();
|
||||
if (response.statusCode != HttpStatus.ok) {
|
||||
return null;
|
||||
}
|
||||
return response.fold<List<int>>(
|
||||
<int>[],
|
||||
(buffer, chunk) => buffer..addAll(chunk),
|
||||
);
|
||||
} finally {
|
||||
client.close(force: true);
|
||||
}
|
||||
}
|
||||
|
||||
Uri? resolveGatewayAcpEndpointInternal() {
|
||||
return resolveBridgeAcpEndpointInternal();
|
||||
}
|
||||
@ -677,7 +720,14 @@ extension AppControllerDesktopRuntimeHelpers on AppController {
|
||||
Uri? resolveExternalAcpEndpointForRequestInternal(
|
||||
GoTaskServiceRequest request,
|
||||
) {
|
||||
return resolveBridgeAcpEndpointInternal();
|
||||
final bridgeEndpoint = resolveBridgeAcpEndpointInternal();
|
||||
if (bridgeEndpoint == null) {
|
||||
return null;
|
||||
}
|
||||
if (request.target.isGateway) {
|
||||
return bridgeEndpoint.replace(path: '/gateway/openclaw');
|
||||
}
|
||||
return bridgeEndpoint;
|
||||
}
|
||||
|
||||
Uri? gatewayProfileBaseUriInternal(GatewayConnectionProfile profile) {
|
||||
|
||||
@ -94,6 +94,17 @@ Uri? resolveAcpHttpRpcEndpoint(Uri? endpoint) {
|
||||
if (endpoint == null || endpoint.host.trim().isEmpty) {
|
||||
return null;
|
||||
}
|
||||
if (_isGatewayOpenClawPath(endpoint.path)) {
|
||||
final scheme = endpoint.scheme.trim().toLowerCase();
|
||||
if (scheme != 'http' && scheme != 'https') {
|
||||
return null;
|
||||
}
|
||||
return endpoint.replace(
|
||||
path: '/gateway/openclaw',
|
||||
query: null,
|
||||
fragment: null,
|
||||
);
|
||||
}
|
||||
if (AcpEndpointPaths.isProviderMappingPath(endpoint.path)) {
|
||||
return null;
|
||||
}
|
||||
@ -104,3 +115,12 @@ Uri? resolveAcpHttpRpcEndpoint(Uri? endpoint) {
|
||||
final paths = AcpEndpointPaths.fromBaseEndpoint(endpoint);
|
||||
return endpoint.replace(path: paths.httpRpcPath, query: null, fragment: null);
|
||||
}
|
||||
|
||||
bool _isGatewayOpenClawPath(String rawPath) {
|
||||
var path = rawPath.trim();
|
||||
if (!path.startsWith('/')) {
|
||||
path = '/$path';
|
||||
}
|
||||
path = path.replaceFirst(RegExp(r'/+$'), '');
|
||||
return path == '/gateway/openclaw';
|
||||
}
|
||||
|
||||
@ -11,16 +11,23 @@ void main() {
|
||||
expect(endpoint.toString(), 'https://xworkmate-bridge.svc.plus/acp/rpc');
|
||||
});
|
||||
|
||||
test('keeps OpenClaw gateway submit path as HTTP endpoint', () {
|
||||
final endpoint = resolveAcpHttpRpcEndpoint(
|
||||
Uri.parse('https://xworkmate-bridge.svc.plus/gateway/openclaw'),
|
||||
);
|
||||
|
||||
expect(
|
||||
endpoint.toString(),
|
||||
'https://xworkmate-bridge.svc.plus/gateway/openclaw',
|
||||
);
|
||||
});
|
||||
|
||||
test('rejects provider mapping paths as app RPC bases', () {
|
||||
final codexEndpoint = resolveAcpHttpRpcEndpoint(
|
||||
Uri.parse('https://xworkmate-bridge.svc.plus/acp-server/codex'),
|
||||
);
|
||||
final gatewayEndpoint = resolveAcpHttpRpcEndpoint(
|
||||
Uri.parse('https://xworkmate-bridge.svc.plus/gateway/openclaw'),
|
||||
);
|
||||
|
||||
expect(codexEndpoint, isNull);
|
||||
expect(gatewayEndpoint, isNull);
|
||||
});
|
||||
|
||||
test('rejects provider mapping paths even when ACP suffix is present', () {
|
||||
|
||||
@ -10,7 +10,9 @@ void main() {
|
||||
test(
|
||||
'keeps local workspace binding separate from remote execution workspace',
|
||||
() {
|
||||
final controller = AppController(environmentOverride: const <String, String>{});
|
||||
final controller = AppController(
|
||||
environmentOverride: const <String, String>{},
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
final localWorkspace = Directory.systemTemp.createTempSync(
|
||||
@ -62,7 +64,9 @@ void main() {
|
||||
);
|
||||
|
||||
test('writes inline ACP artifacts into the local thread workspace', () async {
|
||||
final controller = AppController(environmentOverride: const <String, String>{});
|
||||
final controller = AppController(
|
||||
environmentOverride: const <String, String>{},
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
final localWorkspace = await Directory.systemTemp.createTemp(
|
||||
|
||||
@ -583,7 +583,7 @@ void main() {
|
||||
);
|
||||
|
||||
test(
|
||||
'desktop task execution routes OpenClaw through bridge RPC with gateway params',
|
||||
'desktop task execution routes OpenClaw through dedicated bridge gateway path',
|
||||
() async {
|
||||
final capture = await _startAcpHttpServer();
|
||||
addTearDown(capture.close);
|
||||
@ -595,7 +595,8 @@ void main() {
|
||||
final transport = ExternalCodeAgentAcpDesktopTransport(
|
||||
client: client,
|
||||
endpointResolver: (_) => capture.baseEndpoint,
|
||||
taskEndpointResolver: (_) => capture.baseEndpoint,
|
||||
taskEndpointResolver: (_) =>
|
||||
capture.baseEndpoint.replace(path: '/gateway/openclaw'),
|
||||
);
|
||||
|
||||
await transport.executeTask(
|
||||
@ -607,10 +608,9 @@ void main() {
|
||||
);
|
||||
|
||||
expect(capture.authorizationHeader, 'Bearer bridge-token');
|
||||
expect(capture.requestPath, '/acp/rpc');
|
||||
expect(capture.requestPath, '/gateway/openclaw');
|
||||
expect(capture.requestPath, isNot(contains('/acp-server')));
|
||||
expect(capture.requestPath, isNot(contains('/acp-server/gateway')));
|
||||
expect(capture.requestPath, isNot(contains('/gateway/openclaw')));
|
||||
final params = _lastRequestParams(capture);
|
||||
final routing = params['routing'] as Map<String, dynamic>;
|
||||
expect(params.containsKey('gatewayProvider'), isFalse);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user