Skip to content

Commit 5b0cbe0

Browse files
authored
Merge branch 'main' into chore/add-browser-download
2 parents 889071d + 78641bb commit 5b0cbe0

File tree

182 files changed

+47372
-2349
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

182 files changed

+47372
-2349
lines changed

apps/agent-tars/dev-app-update.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
provider: github
1+
provider: custom
22
owner: bytedance
33
repo: UI-TARS-desktop
44
updaterCacheDirName: agent-tars-updater

apps/agent-tars/forge.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ const config: ForgeConfig = {
188188
{
189189
name: '@electron-forge/publisher-github',
190190
config: {
191-
repository: { owner: 'bytedance', name: 'ui-tars-desktop' },
191+
repository: { owner: 'bytedance', name: 'UI-TARS-desktop' },
192192
draft: true,
193193
force: true,
194194
generateReleaseNotes: true,

apps/agent-tars/package.json

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "agent-tars-app",
33
"private": true,
4-
"version": "1.0.0-alpha.8",
4+
"version": "1.0.0-alpha.9",
55
"description": "A multimodal AI agent that revolutionizes GUI interaction",
66
"main": "./dist/main/index.js",
77
"author": "ByteDance",
@@ -49,13 +49,15 @@
4949
"@agent-infra/mcp-server-browser": "workspace:*"
5050
},
5151
"devDependencies": {
52+
"semver": "7.7.2",
53+
"builder-util-runtime": "9.3.1",
5254
"jsonrepair": "3.12.0",
5355
"serialize-javascript": "6.0.2",
5456
"@modelcontextprotocol/sdk": "^1.11.2",
5557
"@electron-toolkit/preload": "^3.0.1",
5658
"@electron-toolkit/utils": "^4.0.0",
5759
"@electron/asar": "^3.2.18",
58-
"electron-updater": "^6.3.9",
60+
"electron-updater": "^6.6.2",
5961
"openai": "^4.86.2",
6062
"dotenv": "16.4.7",
6163
"@agent-infra/mcp-shared": "workspace:*",
@@ -105,13 +107,6 @@
105107
"vite-plugin-singlefile": "2.2.0"
106108
},
107109
"build": {
108-
"publish": [
109-
{
110-
"provider": "github",
111-
"owner": "bytedance",
112-
"repo": "UI-TARS-desktop"
113-
}
114-
],
115110
"electronDownload": {
116111
"mirror": "https://npmmirror.com/mirrors/electron/"
117112
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
provider: github
1+
provider: custom
22
owner: bytedance
33
repo: UI-TARS-desktop
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import {
2+
CancellationToken,
3+
GithubOptions,
4+
HttpError,
5+
newError,
6+
ReleaseNoteInfo,
7+
UpdateInfo,
8+
XElement,
9+
} from 'builder-util-runtime';
10+
import * as semver from 'semver';
11+
import { URL } from 'url';
12+
import { AppUpdater, ResolvedUpdateFileInfo } from 'electron-updater';
13+
import {
14+
getChannelFilename,
15+
newUrlFromBase,
16+
extractTagFromGithubDownloadURL,
17+
} from './util';
18+
import {
19+
parseUpdateInfo,
20+
resolveFiles,
21+
ProviderRuntimeOptions,
22+
} from 'electron-updater/out/providers/Provider';
23+
import { BaseGitHubProvider } from 'electron-updater/out/providers/GitHubProvider';
24+
25+
interface GithubUpdateInfo extends UpdateInfo {
26+
tag: string;
27+
}
28+
29+
export class CustomGitHubProvider extends BaseGitHubProvider<GithubUpdateInfo> {
30+
constructor(
31+
protected readonly options: GithubOptions,
32+
private readonly updater: AppUpdater,
33+
runtimeOptions: ProviderRuntimeOptions,
34+
) {
35+
super(options, 'github.com', runtimeOptions);
36+
}
37+
38+
private get channel(): string {
39+
const result = this.updater.channel || this.options.channel;
40+
return result == null
41+
? this.getDefaultChannelName()
42+
: this.getCustomChannelName(result);
43+
}
44+
45+
async getLatestVersion(): Promise<GithubUpdateInfo> {
46+
const cancellationToken = new CancellationToken();
47+
48+
let tag: string | null = null;
49+
try {
50+
const requestOptions = this.createRequestOptions(
51+
new URL('https://formulae.brew.sh/api/cask/agent-tars.json'),
52+
);
53+
const result = JSON.parse(
54+
(await this.executor.request(requestOptions, cancellationToken)) || '',
55+
);
56+
57+
// @ts-ignore
58+
if (result?.url) {
59+
// @ts-ignore
60+
tag = extractTagFromGithubDownloadURL(result.url);
61+
}
62+
} catch (e: any) {
63+
throw newError(
64+
`Cannot parse releases feed: ${e.stack || e.message}`,
65+
'ERR_UPDATER_INVALID_RELEASE_FEED',
66+
);
67+
}
68+
69+
if (tag == null) {
70+
throw newError(
71+
`No published versions on GitHub`,
72+
'ERR_UPDATER_NO_PUBLISHED_VERSIONS',
73+
);
74+
}
75+
76+
let rawData: string;
77+
let channelFile = '';
78+
let channelFileUrl: any = '';
79+
const fetchData = async (channelName: string) => {
80+
channelFile = getChannelFilename(channelName);
81+
channelFileUrl = newUrlFromBase(
82+
this.getBaseDownloadPath(String(tag), channelFile),
83+
this.baseUrl,
84+
);
85+
const requestOptions = this.createRequestOptions(channelFileUrl);
86+
try {
87+
return (await this.executor.request(
88+
requestOptions,
89+
cancellationToken,
90+
))!;
91+
} catch (e: any) {
92+
if (e instanceof HttpError && e.statusCode === 404) {
93+
throw newError(
94+
`Cannot find ${channelFile} in the latest release artifacts (${channelFileUrl}): ${e.stack || e.message}`,
95+
'ERR_UPDATER_CHANNEL_FILE_NOT_FOUND',
96+
);
97+
}
98+
throw e;
99+
}
100+
};
101+
102+
try {
103+
let channel = this.channel;
104+
if (this.updater.allowPrerelease && semver.prerelease(tag)?.[0]) {
105+
channel = this.getCustomChannelName(
106+
String(semver.prerelease(tag)?.[0]),
107+
);
108+
}
109+
rawData = await fetchData(channel);
110+
} catch (e: any) {
111+
if (this.updater.allowPrerelease) {
112+
// Allow fallback to `latest.yml`
113+
rawData = await fetchData(this.getDefaultChannelName());
114+
} else {
115+
throw e;
116+
}
117+
}
118+
119+
const result = parseUpdateInfo(rawData, channelFile, channelFileUrl);
120+
121+
if (result.releaseNotes == null || result.releaseName == null) {
122+
try {
123+
const requestOptions = this.createRequestOptions(
124+
newUrlFromBase(`/repos${this.basePath}/tags/${tag}`, this.baseApiUrl),
125+
);
126+
const releaseInfo = JSON.parse(
127+
(await this.executor.request(requestOptions, cancellationToken)) ||
128+
'',
129+
);
130+
131+
result.releaseName = result.releaseName || releaseInfo.name;
132+
result.releaseNotes = result.releaseNotes || releaseInfo.body;
133+
} catch (e: any) {
134+
console.error('Error fetching release info', e);
135+
result.releaseName = tag;
136+
result.releaseNotes = '';
137+
}
138+
}
139+
console.log('result', result);
140+
return {
141+
tag: tag,
142+
...result,
143+
};
144+
}
145+
146+
private get basePath(): string {
147+
return `/${this.options.owner}/${this.options.repo}/releases`;
148+
}
149+
150+
resolveFiles(updateInfo: GithubUpdateInfo): Array<ResolvedUpdateFileInfo> {
151+
// still replace space to - due to backward compatibility
152+
return resolveFiles(updateInfo, this.baseUrl, (p) =>
153+
this.getBaseDownloadPath(updateInfo.tag, p.replace(/ /g, '-')),
154+
);
155+
}
156+
157+
private getBaseDownloadPath(tag: string, fileName: string): string {
158+
return `${this.basePath}/download/${tag}/${fileName}`;
159+
}
160+
}
161+
162+
function getNoteValue(parent: XElement): string {
163+
const result = parent.elementValueOrEmpty('content');
164+
// GitHub reports empty notes as <content>No content.</content>
165+
return result === 'No content.' ? '' : result;
166+
}
167+
168+
export function computeReleaseNotes(
169+
currentVersion: semver.SemVer,
170+
isFullChangelog: boolean,
171+
feed: XElement,
172+
latestRelease: any,
173+
): string | Array<ReleaseNoteInfo> | null {
174+
if (!isFullChangelog) {
175+
return getNoteValue(latestRelease);
176+
}
177+
178+
const releaseNotes: Array<ReleaseNoteInfo> = [];
179+
for (const release of feed.getElements('entry')) {
180+
// noinspection TypeScriptValidateJSTypes
181+
const versionRelease = /\/tag\/v?([^/]+)$/.exec(
182+
release.element('link').attribute('href'),
183+
)![1];
184+
if (semver.lt(currentVersion, versionRelease)) {
185+
releaseNotes.push({
186+
version: versionRelease,
187+
note: getNoteValue(release),
188+
});
189+
}
190+
}
191+
return releaseNotes.sort((a, b) => semver.rcompare(a.version, b.version));
192+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// if baseUrl path doesn't ends with /, this path will be not prepended to passed pathname for new URL(input, base)
2+
import { URL } from 'url';
3+
4+
// addRandomQueryToAvoidCaching is false by default because in most cases URL already contains version number,
5+
// so, it makes sense only for Generic Provider for channel files
6+
export function newUrlFromBase(
7+
pathname: string,
8+
baseUrl: URL,
9+
addRandomQueryToAvoidCaching = false,
10+
): URL {
11+
const result = new URL(pathname, baseUrl);
12+
// search is not propagated (search is an empty string if not specified)
13+
const search = baseUrl.search;
14+
if (search != null && search.length !== 0) {
15+
result.search = search;
16+
} else if (addRandomQueryToAvoidCaching) {
17+
result.search = `noCache=${Date.now().toString(32)}`;
18+
}
19+
return result;
20+
}
21+
22+
export function getChannelFilename(channel: string): string {
23+
return `${channel}.yml`;
24+
}
25+
26+
export function extractTagFromGithubDownloadURL(url: string) {
27+
const match = url.match(/\/download\/([^/]+)\//);
28+
return match ? match[1] : null;
29+
}

apps/agent-tars/src/main/utils/updateApp.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
AppUpdater as ElectronAppUpdater,
66
autoUpdater,
77
} from 'electron-updater';
8+
import { CustomGitHubProvider } from '@main/electron-updater/GitHubProvider';
89

910
export class AppUpdater {
1011
autoUpdater: ElectronAppUpdater = autoUpdater;
@@ -19,6 +20,15 @@ export class AppUpdater {
1920
autoUpdater.logger = logger;
2021
autoUpdater.autoDownload = false;
2122

23+
autoUpdater.setFeedURL({
24+
// hack for custom provider
25+
provider: 'custom' as 'github',
26+
owner: 'bytedance',
27+
repo: 'UI-TARS-desktop',
28+
// @ts-expect-error hack for custom provider
29+
updateProvider: CustomGitHubProvider,
30+
});
31+
2232
autoUpdater.on('error', (error) => {
2333
logger.error('Update_Error', error);
2434
mainWindow.webContents.send('main:error', error);

apps/ui-tars/dev-app-update.yml

Lines changed: 0 additions & 3 deletions
This file was deleted.

apps/ui-tars/package.json

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,6 @@
3636
"publish": "npm run build && electron-forge publish"
3737
},
3838
"build": {
39-
"publish": [
40-
{
41-
"provider": "github",
42-
"owner": "bytedance",
43-
"repo": "UI-TARS-desktop"
44-
}
45-
],
4639
"electronDownload": {
4740
"mirror": "https://npmmirror.com/mirrors/electron/"
4841
}
@@ -123,7 +116,7 @@
123116
"ts-node": "^10.9.2",
124117
"tsx": "^4.19.2",
125118
"typescript": "^5.7.2",
126-
"update-electron-app": "^3.1.0",
119+
"electron-updater": "^6.6.2",
127120
"vite": "^6.1.0",
128121
"vite-tsconfig-paths": "^5.1.4",
129122
"vitest": "^3.0.8",

apps/ui-tars/resources/app-update.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
provider: github
2-
owner: bytedance
3-
repo: UI-TARS-desktop
1+
provider: custom
2+
owner: ycjcl868
3+
repo: test-electron-update

0 commit comments

Comments
 (0)