@@ -5,12 +5,16 @@ import path from "node:path";
55import { Readable , Transform } from "node:stream" ;
66import { pipeline } from "node:stream/promises" ;
77import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime" ;
8- import { readProviderJsonResponse } from "openclaw/plugin-sdk/provider-http" ;
8+ import { readProviderJsonObjectResponse } from "openclaw/plugin-sdk/provider-http" ;
99import { runPluginCommandWithTimeout } from "openclaw/plugin-sdk/run-command" ;
1010import type { RuntimeEnv } from "openclaw/plugin-sdk/runtime-env" ;
1111import { CONFIG_DIR , extractArchive , resolveBrewExecutable } from "openclaw/plugin-sdk/setup-tools" ;
1212import { fetchWithSsrFGuard } from "openclaw/plugin-sdk/ssrf-runtime" ;
13- import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime" ;
13+ import {
14+ isRecord ,
15+ normalizeLowercaseStringOrEmpty ,
16+ normalizeOptionalString ,
17+ } from "openclaw/plugin-sdk/string-coerce-runtime" ;
1418import { withTempDownloadPath } from "openclaw/plugin-sdk/temp-path" ;
1519import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime" ;
1620
@@ -24,9 +28,9 @@ export type NamedAsset = {
2428 browser_download_url : string ;
2529} ;
2630
27- type ReleaseResponse = {
28- tag_name ? : string ;
29- assets ?: ReleaseAsset [ ] ;
31+ type SignalCliRelease = {
32+ version : string ;
33+ assets : NamedAsset [ ] ;
3034} ;
3135
3236const MAX_SIGNAL_CLI_ARCHIVE_BYTES = 256 * 1024 * 1024 ;
@@ -87,6 +91,32 @@ async function cancelUnusedResponseBody(response: Response): Promise<void> {
8791 await response . body ?. cancel ( ) . catch ( ( ) => undefined ) ;
8892}
8993
94+ function normalizeReleaseAsset ( value : unknown ) : NamedAsset | undefined {
95+ if ( ! isRecord ( value ) ) {
96+ return undefined ;
97+ }
98+ const name = normalizeOptionalString ( value . name ) ;
99+ const browserDownloadUrl = normalizeOptionalString ( value . browser_download_url ) ;
100+ return name && browserDownloadUrl
101+ ? { name, browser_download_url : browserDownloadUrl }
102+ : undefined ;
103+ }
104+
105+ function normalizeSignalCliRelease ( value : Record < string , unknown > ) : SignalCliRelease | undefined {
106+ const tagName = normalizeOptionalString ( value . tag_name ) ;
107+ const version = normalizeOptionalString ( tagName ?. replace ( / ^ v / , "" ) ) ;
108+ if ( ! version || ! Array . isArray ( value . assets ) ) {
109+ return undefined ;
110+ }
111+ return {
112+ version,
113+ assets : value . assets . flatMap ( ( asset ) => {
114+ const normalized = normalizeReleaseAsset ( asset ) ;
115+ return normalized ? [ normalized ] : [ ] ;
116+ } ) ,
117+ } ;
118+ }
119+
90120/**
91121 * Pick a native release asset from the official GitHub releases.
92122 *
@@ -315,7 +345,7 @@ export async function installSignalCliFromRelease(
315345 } ,
316346 } ) ;
317347
318- let payload : ReleaseResponse ;
348+ let releaseInfo : SignalCliRelease ;
319349 try {
320350 if ( ! response . ok ) {
321351 await cancelUnusedResponseBody ( response ) ;
@@ -325,7 +355,12 @@ export async function installSignalCliFromRelease(
325355 } ;
326356 }
327357 try {
328- payload = await readProviderJsonResponse < ReleaseResponse > ( response , "signal.release-info" ) ;
358+ const payload = await readProviderJsonObjectResponse ( response , "signal.release-info" ) ;
359+ const normalized = normalizeSignalCliRelease ( payload ) ;
360+ if ( ! normalized ) {
361+ throw new Error ( "Unexpected signal-cli release info" ) ;
362+ }
363+ releaseInfo = normalized ;
329364 } catch {
330365 return {
331366 ok : false ,
@@ -335,9 +370,7 @@ export async function installSignalCliFromRelease(
335370 } finally {
336371 await release ( ) ;
337372 }
338- const version = payload . tag_name ?. replace ( / ^ v / , "" ) ?? "unknown" ;
339- const assets = payload . assets ?? [ ] ;
340- const asset = pickAsset ( assets , process . platform , process . arch ) ;
373+ const asset = pickAsset ( releaseInfo . assets , process . platform , process . arch ) ;
341374
342375 if ( ! asset ) {
343376 return {
@@ -351,10 +384,10 @@ export async function installSignalCliFromRelease(
351384 return await withTempDownloadPath (
352385 { prefix : "openclaw-signal" , fileName : asset . name } ,
353386 async ( archivePath ) => {
354- runtime . log ( `Downloading signal-cli ${ version } (${ asset . name } )…` ) ;
387+ runtime . log ( `Downloading signal-cli ${ releaseInfo . version } (${ asset . name } )…` ) ;
355388 await downloadToFile ( asset . browser_download_url , archivePath ) ;
356389
357- const installRoot = path . join ( CONFIG_DIR , "tools" , "signal-cli" , version ) ;
390+ const installRoot = path . join ( CONFIG_DIR , "tools" , "signal-cli" , releaseInfo . version ) ;
358391 await fs . mkdir ( installRoot , { recursive : true } ) ;
359392
360393 if ( ! looksLikeArchive ( normalizeLowercaseStringOrEmpty ( asset . name ) ) ) {
@@ -383,7 +416,7 @@ export async function installSignalCliFromRelease(
383416 return {
384417 ok : true ,
385418 cliPath,
386- version,
419+ version : releaseInfo . version ,
387420 } ;
388421 } ,
389422 ) ;
0 commit comments