replicas-engine 0.1.232 → 0.1.234

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.
Files changed (2) hide show
  1. package/dist/src/index.js +112 -95
  2. 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 - and surface a live noVNC viewer to the user via the \`replicas computer\` CLI. Every Replicas workspace boots with Xvfb / openbox / x11vnc / noVNC pre-installed.
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 - \`replicas computer start\` exposes a \`Desktop\` tab in the dashboard
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 it through the \`replicas computer\` CLI.
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.** \`replicas computer start\` exposes a live \`Desktop\` tab in the dashboard - they can watch you work in real time.
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) Make the desktop visible to the user (creates an authenticated noVNC
399
- # preview on port 6080, prints the viewer URL, adds a "Desktop" tab to the
400
- # dashboard).
401
- replicas computer start
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 start [--port N] [--display :N] [--size WxH]\`
430
- Ensures all desktop services are running and creates an authenticated noVNC preview. Prints the viewer URL (\`https://<port>-<hash>.tryreplicas.com/\`). The Replicas dashboard automatically shows a \`Desktop\` tab while this preview is live - point the user at it instead of pasting the URL.
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
- ### \`replicas computer stop [--port N]\`
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
- Always start the desktop session with \`replicas computer start\` *before* doing anything visual, even if you don't need the URL yourself. The Desktop tab appears in their dashboard. They get to watch and intervene if needed.
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 \`![\u2026](\u2026)\` 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) | **Lazy** - created by \`replicas computer start\` |
538
- | Dashboard \`Desktop\` tab | Appears when the preview URL is live, disappears on \`stop\` |
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.) without first calling \`start\` - they'll work since the daemons are running. \`start\` is only needed when you want the user to see the live stream.
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-v3";
1786
+ var DAYTONA_SNAPSHOT_ID = "29-05-2026-royal-york-v5";
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(config) {
2188
+ async doRefresh(_config) {
2163
2189
  console.log("[GitHubTokenManager] Refreshing GitHub token...");
2164
- const response = await fetch(`${config.monolithUrl}/v1/engine/github/refresh-token`, {
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(config) {
2237
- await this.refreshWithRequest(config);
2255
+ async doRefresh(_config) {
2256
+ await this.refreshWithRequest();
2238
2257
  }
2239
- async refreshWithRequest(config, request) {
2258
+ async refreshWithRequest(request) {
2240
2259
  console.log("[ClaudeTokenManager] Refreshing Claude credentials...");
2241
- const response = await fetch(`${config.monolithUrl}/v1/engine/claude/refresh-credentials`, {
2242
- method: "POST",
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(config, failedMethod && failedMethod !== "none" ? {
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(config) {
2349
- await this.refreshWithRequest(config);
2361
+ async doRefresh(_config) {
2362
+ await this.refreshWithRequest();
2350
2363
  }
2351
- async refreshWithRequest(config, request) {
2364
+ async refreshWithRequest(request) {
2352
2365
  console.log("[CodexTokenManager] Refreshing Codex credentials...");
2353
- const response = await fetch(`${config.monolithUrl}/v1/engine/codex/refresh-credentials`, {
2354
- method: "POST",
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(config, failedMethod === "oauth" || failedMethod === "api_key" ? {
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;
@@ -5368,6 +5391,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
5368
5391
  this.activePromptStream?.close();
5369
5392
  throw new ClaudeAuthError(authErrorMessage);
5370
5393
  }
5394
+ this.setAuthRetrying(false);
5371
5395
  await this.handleMessage(msg);
5372
5396
  if (linearSessionId) {
5373
5397
  linearForwarder.sendPlan(extractPlanFromClaudeEvent(msg));
@@ -8177,15 +8201,7 @@ var KeepAliveService = class _KeepAliveService {
8177
8201
  return;
8178
8202
  }
8179
8203
  try {
8180
- const response = await fetch(`${ENGINE_ENV.MONOLITH_URL}/v1/engine/keep-alive`, {
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
- });
8204
+ const response = await monolithRequest("/v1/engine/keep-alive", { body: {} });
8189
8205
  if (!response.ok) {
8190
8206
  console.warn(`[KeepAlive] Ping returned ${response.status}`);
8191
8207
  }
@@ -10510,6 +10526,7 @@ serve(
10510
10526
  await chatService.initialize();
10511
10527
  await previewService.initialize();
10512
10528
  engineReady = true;
10529
+ void registerDesktopPreview();
10513
10530
  if (!IS_WARMING_MODE) {
10514
10531
  await githubTokenManager.start();
10515
10532
  await claudeTokenManager.start();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.232",
3
+ "version": "0.1.234",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",