replicas-engine 0.1.232 → 0.1.233
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/index.js +111 -95
- package/package.json +1 -1
package/dist/src/index.js
CHANGED
|
@@ -357,18 +357,18 @@ var GIT_PARTIAL_CLONE_FILTER = "blob:none";
|
|
|
357
357
|
|
|
358
358
|
// ../shared/src/default-skills/replicas-agent/abilities/computer.ts
|
|
359
359
|
var SECTION = `### Computer use (Linux desktop control)
|
|
360
|
-
Drive the workspace's Linux desktop - open a browser, click, type, scroll, screenshot, record -
|
|
360
|
+
Drive the workspace's Linux desktop - open a browser, click, type, scroll, screenshot, record - via the \`replicas computer\` CLI. Every Replicas workspace boots with Xvfb / openbox / x11vnc / noVNC pre-installed and the live noVNC viewer is automatically published as an authenticated preview, so a \`Desktop\` tab is always available in the dashboard. Use \`replicas computer info\` to get the viewer URL when you want to share it (e.g. point a user on Slack at the live stream).
|
|
361
361
|
|
|
362
362
|
**Reference:** \`references/COMPUTER-USE.md\`
|
|
363
363
|
|
|
364
364
|
Use this when:
|
|
365
365
|
- A task requires interacting with a website, web app, or desktop application that has no usable API
|
|
366
|
-
- You want the user to watch the agent work -
|
|
366
|
+
- You want the user to watch the agent work - they can open the \`Desktop\` tab, or you can share the URL from \`replicas computer info\`
|
|
367
367
|
- You're testing UI changes in a browser before reporting them as done
|
|
368
368
|
- You want to record a screen capture of a task as proof to share back`;
|
|
369
369
|
var REFERENCE = `# Computer use (Linux desktop control)
|
|
370
370
|
|
|
371
|
-
Every Replicas workspace boots with a full Linux desktop stack - Xvfb (1920\xD71080), openbox, tint2, x11vnc, noVNC, ffmpeg, and Google Chrome. You drive
|
|
371
|
+
Every Replicas workspace boots with a full Linux desktop stack - Xvfb (1920\xD71080), openbox, tint2, x11vnc, noVNC, ffmpeg, and Google Chrome. The engine also auto-registers the noVNC viewer (port 6080) as an authenticated preview at startup, so the dashboard \`Desktop\` tab is live the moment the workspace is up. You drive the desktop through the \`replicas computer\` CLI.
|
|
372
372
|
|
|
373
373
|
Use this for anything the user can't reasonably do via an API - clicking around web apps, filling forms, testing UI changes, dragging files between desktop apps, recording a walkthrough.
|
|
374
374
|
|
|
@@ -376,7 +376,7 @@ Use this for anything the user can't reasonably do via an API - clicking around
|
|
|
376
376
|
|
|
377
377
|
- **Prefer real APIs first.** If a task has a CLI or HTTP API (GitHub, Linear, Slack, Replicas itself), use that. Driving a UI is slower, flakier, and less auditable.
|
|
378
378
|
- **Use it when there's no API**: testing a frontend you just changed, navigating a vendor portal, demonstrating a flow on video.
|
|
379
|
-
- **Use it when the user wants to watch.**
|
|
379
|
+
- **Use it when the user wants to watch.** The dashboard already shows a \`Desktop\` tab. If you're talking to the user somewhere else (e.g. Slack), call \`replicas computer info\` to get the viewer URL and paste it in.
|
|
380
380
|
|
|
381
381
|
## Never use raw \`xdotool\` / \`scrot\` / \`ffmpeg\` directly
|
|
382
382
|
|
|
@@ -395,10 +395,10 @@ scrot /tmp/state.png
|
|
|
395
395
|
## Quickstart
|
|
396
396
|
|
|
397
397
|
\`\`\`bash
|
|
398
|
-
# 1)
|
|
399
|
-
#
|
|
400
|
-
#
|
|
401
|
-
replicas computer
|
|
398
|
+
# 1) (Optional) Get the live viewer URL so you can share it with the user.
|
|
399
|
+
# The desktop is already running and the preview is already registered -
|
|
400
|
+
# this just looks up the URL.
|
|
401
|
+
replicas computer info
|
|
402
402
|
|
|
403
403
|
# 2) Launch a browser on the workspace display.
|
|
404
404
|
replicas computer launch chrome
|
|
@@ -419,20 +419,14 @@ replicas computer record start /tmp/demo.mp4 --fps 60
|
|
|
419
419
|
# ... do stuff ...
|
|
420
420
|
replicas computer record stop
|
|
421
421
|
replicas media upload /tmp/demo.mp4
|
|
422
|
-
|
|
423
|
-
# 6) Tear down the live preview when done (services keep running for next time).
|
|
424
|
-
replicas computer stop
|
|
425
422
|
\`\`\`
|
|
426
423
|
|
|
427
424
|
## Command reference
|
|
428
425
|
|
|
429
|
-
### \`replicas computer
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
Idempotent - safe to call repeatedly. Use it as the first computer-use command in any session.
|
|
426
|
+
### \`replicas computer info\`
|
|
427
|
+
Prints the live noVNC viewer URL (\`https://6080-<hash>.tryreplicas.com/\`) for the workspace desktop. The engine auto-registers this authenticated preview on startup; \`info\` just looks it up. Use it when you want to share the live stream URL with the user out-of-band (e.g. in a Slack reply) - the dashboard \`Desktop\` tab already points at the same URL automatically.
|
|
433
428
|
|
|
434
|
-
|
|
435
|
-
Tears down the noVNC preview (the \`Desktop\` tab disappears). The underlying Xvfb / openbox / x11vnc / browser keep running so the next \`start\` is instant.
|
|
429
|
+
If invoked very early in the workspace lifecycle, \`info\` will poll briefly while the engine finishes registering the preview, then error if it's still not available.
|
|
436
430
|
|
|
437
431
|
### \`replicas computer status\`
|
|
438
432
|
Prints which desktop services are running and the active preview URL (if any). Useful for debugging when a tool call seems to be doing nothing.
|
|
@@ -505,12 +499,11 @@ replicas computer screenshot /tmp/loaded.png
|
|
|
505
499
|
The display is 1920\xD71080. Screenshot pixels map 1:1 to click coordinates - if your Read tool shows a button at pixel (520, 700), click \`replicas computer click 520 700\`. **No translation needed.** Modern image-reading models often imagine the screenshot is at a different resolution; trust the \`xdpyinfo\` value (\`replicas computer status\` shows the real size).
|
|
506
500
|
|
|
507
501
|
### Letting the user watch
|
|
508
|
-
|
|
502
|
+
The Desktop tab is already live in the dashboard - the user can open it any time. If you're communicating with the user somewhere else (Slack, PR comment, etc.), grab the URL with \`replicas computer info\` and share it inline so they can watch you work.
|
|
509
503
|
|
|
510
504
|
### Recording a deliverable
|
|
511
505
|
For tasks the user wants proof of:
|
|
512
506
|
\`\`\`bash
|
|
513
|
-
replicas computer start # makes it visible live too
|
|
514
507
|
replicas computer record start /tmp/walkthrough.mp4
|
|
515
508
|
# ... your work ...
|
|
516
509
|
replicas computer record stop
|
|
@@ -518,15 +511,13 @@ replicas media upload /tmp/walkthrough.mp4
|
|
|
518
511
|
\`\`\`
|
|
519
512
|
Then embed the printed \`\` line in your chat reply. See \`MEDIA.md\`.
|
|
520
513
|
|
|
521
|
-
### Cleaning up
|
|
522
|
-
Call \`replicas computer stop\` when you're done with the visual demo so the live preview URL goes away. The services keep running so the next \`start\` is instant.
|
|
523
|
-
|
|
524
514
|
## Failure modes
|
|
525
515
|
|
|
526
516
|
- **"Desktop services script missing"**: workspace image is older than this skill. Tell the user - nothing you can do from the CLI side.
|
|
527
517
|
- **\`xdotool ... failed: Can't open display\`**: Xvfb didn't come up. \`replicas computer status\` will show which service is dead. Re-running any CLI command auto-attempts to start it.
|
|
528
518
|
- **Browser doesn't appear after \`launch chrome\`**: give it 1-2s, then screenshot. Chrome cold-start on the virtual display takes ~500ms but bigger pages take longer.
|
|
529
519
|
- **Live preview shows static / black screen**: the browser may have crashed. \`replicas computer status\` should show no Chrome process - re-launch.
|
|
520
|
+
- **\`replicas computer info\` errors with "not registered"**: engine couldn't register the preview at startup (transient monolith error, or warming mode). Re-running the engine usually fixes it. Until it's registered, the Desktop tab will show a placeholder.
|
|
530
521
|
|
|
531
522
|
## What gets baked in vs. lazy
|
|
532
523
|
|
|
@@ -534,10 +525,10 @@ Call \`replicas computer stop\` when you're done with the visual demo so the liv
|
|
|
534
525
|
|---|---|
|
|
535
526
|
| \`xvfb\`, \`openbox\`, \`tint2\`, \`x11vnc\`, \`websockify\`, \`xdotool\`, \`scrot\`, \`ffmpeg\`, \`google-chrome\` | Baked into the workspace image |
|
|
536
527
|
| Xvfb / openbox / tint2 / x11vnc / websockify processes | Started at workspace boot (\`replicas-start-desktop-services\`) |
|
|
537
|
-
| noVNC preview URL (port 6080) |
|
|
538
|
-
| Dashboard \`Desktop\` tab |
|
|
528
|
+
| noVNC preview URL (port 6080) | Registered by the engine on startup (authenticated) |
|
|
529
|
+
| Dashboard \`Desktop\` tab | Always available once the engine has registered the preview |
|
|
539
530
|
|
|
540
|
-
You can call any of the input tools (\`click\`, \`type\`, \`screenshot\`, etc.)
|
|
531
|
+
You can call any of the input tools (\`click\`, \`type\`, \`screenshot\`, etc.) immediately - the daemons are already running.
|
|
541
532
|
`;
|
|
542
533
|
var COMPUTER_ABILITY = {
|
|
543
534
|
label: "Computer use",
|
|
@@ -1792,7 +1783,8 @@ function isClaudeAuthErrorText(text) {
|
|
|
1792
1783
|
}
|
|
1793
1784
|
|
|
1794
1785
|
// ../shared/src/engine/environment.ts
|
|
1795
|
-
var DAYTONA_SNAPSHOT_ID = "29-05-2026-royal-york-
|
|
1786
|
+
var DAYTONA_SNAPSHOT_ID = "29-05-2026-royal-york-v4";
|
|
1787
|
+
var DESKTOP_NOVNC_PORT = 6080;
|
|
1796
1788
|
|
|
1797
1789
|
// ../shared/src/engine/types.ts
|
|
1798
1790
|
var DEFAULT_CHAT_TITLES = {
|
|
@@ -2154,21 +2146,48 @@ var BaseRefreshManager = class {
|
|
|
2154
2146
|
}
|
|
2155
2147
|
};
|
|
2156
2148
|
|
|
2149
|
+
// src/services/monolith-service.ts
|
|
2150
|
+
async function monolithRequest(path4, init = {}) {
|
|
2151
|
+
if (!ENGINE_ENV.WORKSPACE_ID) {
|
|
2152
|
+
throw new Error("WORKSPACE_ID is not set; cannot call monolith");
|
|
2153
|
+
}
|
|
2154
|
+
return fetch(`${ENGINE_ENV.MONOLITH_URL}${path4}`, {
|
|
2155
|
+
method: init.method ?? "POST",
|
|
2156
|
+
headers: {
|
|
2157
|
+
Authorization: `Bearer ${ENGINE_ENV.REPLICAS_ENGINE_SECRET}`,
|
|
2158
|
+
"X-Workspace-Id": ENGINE_ENV.WORKSPACE_ID,
|
|
2159
|
+
"Content-Type": "application/json"
|
|
2160
|
+
},
|
|
2161
|
+
body: init.body === void 0 ? void 0 : JSON.stringify(init.body),
|
|
2162
|
+
signal: init.signal
|
|
2163
|
+
});
|
|
2164
|
+
}
|
|
2165
|
+
var MonolithService = class {
|
|
2166
|
+
async sendEvent(event) {
|
|
2167
|
+
if (!ENGINE_ENV.WORKSPACE_ID) {
|
|
2168
|
+
return;
|
|
2169
|
+
}
|
|
2170
|
+
try {
|
|
2171
|
+
const response = await monolithRequest("/v1/engine/webhook", { body: event });
|
|
2172
|
+
if (!response.ok) {
|
|
2173
|
+
const errorText = await response.text();
|
|
2174
|
+
console.error(`[MonolithService] Failed to send event: ${response.status} ${errorText}`);
|
|
2175
|
+
}
|
|
2176
|
+
} catch (error) {
|
|
2177
|
+
console.error("[MonolithService] Failed to send event:", error);
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
};
|
|
2181
|
+
var monolithService = new MonolithService();
|
|
2182
|
+
|
|
2157
2183
|
// src/managers/github-token-manager.ts
|
|
2158
2184
|
var GitHubTokenManager = class extends BaseRefreshManager {
|
|
2159
2185
|
constructor() {
|
|
2160
2186
|
super("GitHubTokenManager");
|
|
2161
2187
|
}
|
|
2162
|
-
async doRefresh(
|
|
2188
|
+
async doRefresh(_config) {
|
|
2163
2189
|
console.log("[GitHubTokenManager] Refreshing GitHub token...");
|
|
2164
|
-
const response = await
|
|
2165
|
-
method: "POST",
|
|
2166
|
-
headers: {
|
|
2167
|
-
Authorization: `Bearer ${config.engineSecret}`,
|
|
2168
|
-
"X-Workspace-Id": config.workspaceId,
|
|
2169
|
-
"Content-Type": "application/json"
|
|
2170
|
-
}
|
|
2171
|
-
});
|
|
2190
|
+
const response = await monolithRequest("/v1/engine/github/refresh-token");
|
|
2172
2191
|
if (!response.ok) {
|
|
2173
2192
|
const errorText = await response.text();
|
|
2174
2193
|
throw new Error(`Token refresh failed: ${response.status} ${errorText}`);
|
|
@@ -2233,19 +2252,13 @@ var ClaudeTokenManager = class extends BaseRefreshManager {
|
|
|
2233
2252
|
}
|
|
2234
2253
|
return null;
|
|
2235
2254
|
}
|
|
2236
|
-
async doRefresh(
|
|
2237
|
-
await this.refreshWithRequest(
|
|
2255
|
+
async doRefresh(_config) {
|
|
2256
|
+
await this.refreshWithRequest();
|
|
2238
2257
|
}
|
|
2239
|
-
async refreshWithRequest(
|
|
2258
|
+
async refreshWithRequest(request) {
|
|
2240
2259
|
console.log("[ClaudeTokenManager] Refreshing Claude credentials...");
|
|
2241
|
-
const response = await
|
|
2242
|
-
|
|
2243
|
-
headers: {
|
|
2244
|
-
Authorization: `Bearer ${config.engineSecret}`,
|
|
2245
|
-
"X-Workspace-Id": config.workspaceId,
|
|
2246
|
-
"Content-Type": "application/json"
|
|
2247
|
-
},
|
|
2248
|
-
...request ? { body: JSON.stringify(request) } : {}
|
|
2260
|
+
const response = await monolithRequest("/v1/engine/claude/refresh-credentials", {
|
|
2261
|
+
body: request
|
|
2249
2262
|
});
|
|
2250
2263
|
if (!response.ok) {
|
|
2251
2264
|
const errorText = await response.text();
|
|
@@ -2261,7 +2274,7 @@ var ClaudeTokenManager = class extends BaseRefreshManager {
|
|
|
2261
2274
|
try {
|
|
2262
2275
|
console.log("[ClaudeTokenManager] Fetching fresh credentials from monolith after auth failure...");
|
|
2263
2276
|
const failedMethod = ENGINE_ENV.REPLICAS_CLAUDE_AUTH_METHOD;
|
|
2264
|
-
await this.refreshWithRequest(
|
|
2277
|
+
await this.refreshWithRequest(failedMethod && failedMethod !== "none" ? {
|
|
2265
2278
|
failedMethod,
|
|
2266
2279
|
failureReason
|
|
2267
2280
|
} : void 0);
|
|
@@ -2345,19 +2358,13 @@ var CodexTokenManager = class extends BaseRefreshManager {
|
|
|
2345
2358
|
}
|
|
2346
2359
|
return null;
|
|
2347
2360
|
}
|
|
2348
|
-
async doRefresh(
|
|
2349
|
-
await this.refreshWithRequest(
|
|
2361
|
+
async doRefresh(_config) {
|
|
2362
|
+
await this.refreshWithRequest();
|
|
2350
2363
|
}
|
|
2351
|
-
async refreshWithRequest(
|
|
2364
|
+
async refreshWithRequest(request) {
|
|
2352
2365
|
console.log("[CodexTokenManager] Refreshing Codex credentials...");
|
|
2353
|
-
const response = await
|
|
2354
|
-
|
|
2355
|
-
headers: {
|
|
2356
|
-
Authorization: `Bearer ${config.engineSecret}`,
|
|
2357
|
-
"X-Workspace-Id": config.workspaceId,
|
|
2358
|
-
"Content-Type": "application/json"
|
|
2359
|
-
},
|
|
2360
|
-
...request ? { body: JSON.stringify(request) } : {}
|
|
2366
|
+
const response = await monolithRequest("/v1/engine/codex/refresh-credentials", {
|
|
2367
|
+
body: request
|
|
2361
2368
|
});
|
|
2362
2369
|
if (!response.ok) {
|
|
2363
2370
|
const errorText = await response.text();
|
|
@@ -2373,7 +2380,7 @@ var CodexTokenManager = class extends BaseRefreshManager {
|
|
|
2373
2380
|
try {
|
|
2374
2381
|
console.log("[CodexTokenManager] Fetching fresh credentials from monolith after auth failure...");
|
|
2375
2382
|
const failedMethod = ENGINE_ENV.REPLICAS_CODEX_AUTH_METHOD;
|
|
2376
|
-
await this.refreshWithRequest(
|
|
2383
|
+
await this.refreshWithRequest(failedMethod === "oauth" || failedMethod === "api_key" ? {
|
|
2377
2384
|
failedMethod,
|
|
2378
2385
|
failureReason
|
|
2379
2386
|
} : void 0);
|
|
@@ -3838,6 +3845,49 @@ var PreviewService = class {
|
|
|
3838
3845
|
};
|
|
3839
3846
|
var previewService = new PreviewService();
|
|
3840
3847
|
|
|
3848
|
+
// src/services/desktop-preview-service.ts
|
|
3849
|
+
var REGISTRATION_TIMEOUT_MS = 3e4;
|
|
3850
|
+
var RETRY_DELAY_MS = 2e3;
|
|
3851
|
+
var ATTEMPT_TIMEOUT_MS = 8e3;
|
|
3852
|
+
async function attemptRegistration() {
|
|
3853
|
+
try {
|
|
3854
|
+
const response = await monolithRequest("/v1/previews", {
|
|
3855
|
+
body: { port: DESKTOP_NOVNC_PORT, authenticated: true },
|
|
3856
|
+
signal: AbortSignal.timeout(ATTEMPT_TIMEOUT_MS)
|
|
3857
|
+
});
|
|
3858
|
+
if (response.ok || response.status === 409) {
|
|
3859
|
+
console.log(
|
|
3860
|
+
`[DesktopPreview] Registered port ${DESKTOP_NOVNC_PORT} (status ${response.status}).`
|
|
3861
|
+
);
|
|
3862
|
+
return true;
|
|
3863
|
+
}
|
|
3864
|
+
const errorText = await response.text().catch(() => "");
|
|
3865
|
+
console.error(
|
|
3866
|
+
`[DesktopPreview] Registration failed: ${response.status} ${errorText}`
|
|
3867
|
+
);
|
|
3868
|
+
return false;
|
|
3869
|
+
} catch (error) {
|
|
3870
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3871
|
+
console.error(`[DesktopPreview] Registration error: ${message}`);
|
|
3872
|
+
return false;
|
|
3873
|
+
}
|
|
3874
|
+
}
|
|
3875
|
+
async function registerDesktopPreview() {
|
|
3876
|
+
if (IS_WARMING_MODE || !ENGINE_ENV.WORKSPACE_ID) {
|
|
3877
|
+
return;
|
|
3878
|
+
}
|
|
3879
|
+
const deadline = Date.now() + REGISTRATION_TIMEOUT_MS;
|
|
3880
|
+
while (Date.now() < deadline) {
|
|
3881
|
+
if (await attemptRegistration()) {
|
|
3882
|
+
return;
|
|
3883
|
+
}
|
|
3884
|
+
await new Promise((resolve3) => setTimeout(resolve3, RETRY_DELAY_MS));
|
|
3885
|
+
}
|
|
3886
|
+
console.error(
|
|
3887
|
+
`[DesktopPreview] Gave up registering port ${DESKTOP_NOVNC_PORT} after ${REGISTRATION_TIMEOUT_MS}ms`
|
|
3888
|
+
);
|
|
3889
|
+
}
|
|
3890
|
+
|
|
3841
3891
|
// src/services/chat/chat-service.ts
|
|
3842
3892
|
import { existsSync as existsSync7 } from "fs";
|
|
3843
3893
|
import { appendFile as appendFile5, copyFile, mkdir as mkdir11, readFile as readFile8, rename as rename2, rm } from "fs/promises";
|
|
@@ -4631,33 +4681,6 @@ var MessageQueueService = class {
|
|
|
4631
4681
|
}
|
|
4632
4682
|
};
|
|
4633
4683
|
|
|
4634
|
-
// src/services/monolith-service.ts
|
|
4635
|
-
var MonolithService = class {
|
|
4636
|
-
async sendEvent(event) {
|
|
4637
|
-
if (!ENGINE_ENV.WORKSPACE_ID) {
|
|
4638
|
-
return;
|
|
4639
|
-
}
|
|
4640
|
-
try {
|
|
4641
|
-
const response = await fetch(`${ENGINE_ENV.MONOLITH_URL}/v1/engine/webhook`, {
|
|
4642
|
-
method: "POST",
|
|
4643
|
-
headers: {
|
|
4644
|
-
Authorization: `Bearer ${ENGINE_ENV.REPLICAS_ENGINE_SECRET}`,
|
|
4645
|
-
"X-Workspace-Id": ENGINE_ENV.WORKSPACE_ID,
|
|
4646
|
-
"Content-Type": "application/json"
|
|
4647
|
-
},
|
|
4648
|
-
body: JSON.stringify(event)
|
|
4649
|
-
});
|
|
4650
|
-
if (!response.ok) {
|
|
4651
|
-
const errorText = await response.text();
|
|
4652
|
-
console.error(`[MonolithService] Failed to send event: ${response.status} ${errorText}`);
|
|
4653
|
-
}
|
|
4654
|
-
} catch (error) {
|
|
4655
|
-
console.error("[MonolithService] Failed to send event:", error);
|
|
4656
|
-
}
|
|
4657
|
-
}
|
|
4658
|
-
};
|
|
4659
|
-
var monolithService = new MonolithService();
|
|
4660
|
-
|
|
4661
4684
|
// src/managers/coding-agent-manager.ts
|
|
4662
4685
|
var MAX_INTERRUPT_QUEUE_ITEMS = 1e3;
|
|
4663
4686
|
var MAX_INTERRUPT_QUEUE_CHARS = 2e5;
|
|
@@ -8177,15 +8200,7 @@ var KeepAliveService = class _KeepAliveService {
|
|
|
8177
8200
|
return;
|
|
8178
8201
|
}
|
|
8179
8202
|
try {
|
|
8180
|
-
const response = await
|
|
8181
|
-
method: "POST",
|
|
8182
|
-
headers: {
|
|
8183
|
-
Authorization: `Bearer ${ENGINE_ENV.REPLICAS_ENGINE_SECRET}`,
|
|
8184
|
-
"X-Workspace-Id": ENGINE_ENV.WORKSPACE_ID,
|
|
8185
|
-
"Content-Type": "application/json"
|
|
8186
|
-
},
|
|
8187
|
-
body: JSON.stringify({})
|
|
8188
|
-
});
|
|
8203
|
+
const response = await monolithRequest("/v1/engine/keep-alive", { body: {} });
|
|
8189
8204
|
if (!response.ok) {
|
|
8190
8205
|
console.warn(`[KeepAlive] Ping returned ${response.status}`);
|
|
8191
8206
|
}
|
|
@@ -10510,6 +10525,7 @@ serve(
|
|
|
10510
10525
|
await chatService.initialize();
|
|
10511
10526
|
await previewService.initialize();
|
|
10512
10527
|
engineReady = true;
|
|
10528
|
+
void registerDesktopPreview();
|
|
10513
10529
|
if (!IS_WARMING_MODE) {
|
|
10514
10530
|
await githubTokenManager.start();
|
|
10515
10531
|
await claudeTokenManager.start();
|