replicas-engine 0.1.256 → 0.1.258

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 +344 -238
  2. package/package.json +1 -1
package/dist/src/index.js CHANGED
@@ -3,22 +3,11 @@
3
3
  // src/index.ts
4
4
  import { serve } from "@hono/node-server";
5
5
  import { Hono as Hono2 } from "hono";
6
- import { readFile as readFile14 } from "fs/promises";
7
- import { execSync as execSync2 } from "child_process";
8
6
  import { randomUUID as randomUUID6 } from "crypto";
9
7
 
10
- // src/managers/github-token-manager.ts
11
- import { promises as fs } from "fs";
12
- import path from "path";
13
-
14
- // src/engine-env.ts
15
- import { homedir as homedir2 } from "os";
16
- import { join as join2 } from "path";
17
-
18
- // src/runtime-env-loader.ts
19
- import { readFileSync } from "fs";
20
- import { homedir } from "os";
21
- import { join } from "path";
8
+ // src/utils/exec.ts
9
+ import { exec, execFile } from "child_process";
10
+ import { promisify } from "util";
22
11
 
23
12
  // ../shared/src/type-guards.ts
24
13
  function isRecord(value) {
@@ -297,7 +286,7 @@ var WORKSPACE_SIZES = ["small", "large"];
297
286
  var INVALID_WORKSPACE_SIZE_ERROR = `Invalid size: must be one of ${WORKSPACE_SIZES.join(", ")}`;
298
287
 
299
288
  // ../shared/src/e2b.ts
300
- var E2B_TEMPLATE_NAME = "replicas-sandbox-2026-06-03-v1";
289
+ var E2B_TEMPLATE_NAME = "replicas-sandbox-2026-06-03-v3";
301
290
 
302
291
  // ../shared/src/runtime-env.ts
303
292
  function parsePosixEnvFile(content) {
@@ -358,6 +347,25 @@ function parsePosixEnvFile(content) {
358
347
  // ../shared/src/git.ts
359
348
  var GIT_PARTIAL_CLONE_FILTER = "blob:none";
360
349
 
350
+ // ../shared/src/in-flight.ts
351
+ var InFlightMap = class {
352
+ pending = /* @__PURE__ */ new Map();
353
+ run(key, producer) {
354
+ const existing = this.pending.get(key);
355
+ if (existing) return existing;
356
+ const promise = (async () => producer())().finally(() => {
357
+ if (this.pending.get(key) === promise) {
358
+ this.pending.delete(key);
359
+ }
360
+ });
361
+ this.pending.set(key, promise);
362
+ return promise;
363
+ }
364
+ get(key) {
365
+ return this.pending.get(key);
366
+ }
367
+ };
368
+
361
369
  // ../shared/src/default-skills/replicas-agent/abilities/computer.ts
362
370
  var SECTION = `### Computer use (Linux desktop control)
363
371
  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).
@@ -2234,7 +2242,30 @@ var MEDIA_KIND = {
2234
2242
  };
2235
2243
  var MEDIA_KINDS = [MEDIA_KIND.IMAGE, MEDIA_KIND.VIDEO, MEDIA_KIND.AUDIO];
2236
2244
 
2245
+ // ../shared/src/github/url.ts
2246
+ var GITHUB_HOST_RE = /(^|@|\/\/)github\.com[:/]/;
2247
+ function isGitHubUrl(url) {
2248
+ if (!url) return false;
2249
+ return GITHUB_HOST_RE.test(url);
2250
+ }
2251
+
2252
+ // src/utils/exec.ts
2253
+ var execAsync = promisify(exec);
2254
+ var execFileAsync = promisify(execFile);
2255
+ var SUBPROCESS_MAX_BUFFER = HOOK_EXEC_MAX_BUFFER_BYTES;
2256
+
2257
+ // src/managers/github-token-manager.ts
2258
+ import { promises as fs } from "fs";
2259
+ import path from "path";
2260
+
2261
+ // src/engine-env.ts
2262
+ import { homedir as homedir2 } from "os";
2263
+ import { join as join2 } from "path";
2264
+
2237
2265
  // src/runtime-env-loader.ts
2266
+ import { readFileSync } from "fs";
2267
+ import { homedir } from "os";
2268
+ import { join } from "path";
2238
2269
  function loadRuntimeEnvFile() {
2239
2270
  let content;
2240
2271
  try {
@@ -2730,9 +2761,9 @@ var CodexTokenManager = class extends BaseRefreshManager {
2730
2761
  var codexTokenManager = new CodexTokenManager();
2731
2762
 
2732
2763
  // src/git/service.ts
2733
- import { readdir, stat } from "fs/promises";
2734
- import { existsSync as existsSync2, readFileSync as readFileSync3, unlinkSync } from "fs";
2735
- import { execFileSync as execFileSync2, spawnSync } from "child_process";
2764
+ import { readdir, readFile as readFile2, stat } from "fs/promises";
2765
+ import { existsSync as existsSync2, unlinkSync } from "fs";
2766
+ import { spawn } from "child_process";
2736
2767
  import { join as join5 } from "path";
2737
2768
 
2738
2769
  // src/utils/state.ts
@@ -2863,15 +2894,15 @@ async function saveRepoState(repoName, state, fallbackState) {
2863
2894
  }
2864
2895
 
2865
2896
  // src/git/commands.ts
2866
- import { execFileSync } from "child_process";
2867
2897
  import { readFileSync as readFileSync2 } from "fs";
2868
2898
  import { join as join4 } from "path";
2869
- function runGitCommand(args, cwd) {
2870
- return execFileSync("git", args, {
2899
+ async function runGitCommand(args, cwd, options = {}) {
2900
+ const { stdout } = await execFileAsync("git", args, {
2871
2901
  cwd,
2872
2902
  encoding: "utf-8",
2873
- stdio: ["pipe", "pipe", "pipe"]
2874
- }).trim();
2903
+ maxBuffer: options.maxBuffer ?? SUBPROCESS_MAX_BUFFER
2904
+ });
2905
+ return stdout.trim();
2875
2906
  }
2876
2907
  function readRepoHeadBranch(repoPath) {
2877
2908
  try {
@@ -2882,17 +2913,17 @@ function readRepoHeadBranch(repoPath) {
2882
2913
  return null;
2883
2914
  }
2884
2915
  }
2885
- function branchExists(branchName, cwd) {
2916
+ async function branchExists(branchName, cwd) {
2886
2917
  try {
2887
- runGitCommand(["rev-parse", "--verify", branchName], cwd);
2918
+ await runGitCommand(["rev-parse", "--verify", branchName], cwd);
2888
2919
  return true;
2889
2920
  } catch {
2890
2921
  return false;
2891
2922
  }
2892
2923
  }
2893
- function getCurrentBranch(cwd) {
2924
+ async function getCurrentBranch(cwd) {
2894
2925
  try {
2895
- return runGitCommand(["rev-parse", "--abbrev-ref", "HEAD"], cwd);
2926
+ return await runGitCommand(["rev-parse", "--abbrev-ref", "HEAD"], cwd);
2896
2927
  } catch (error) {
2897
2928
  console.error("Error getting current branch:", error);
2898
2929
  return null;
@@ -2900,12 +2931,20 @@ function getCurrentBranch(cwd) {
2900
2931
  }
2901
2932
 
2902
2933
  // src/git/service.ts
2934
+ var FULL_DIFF_MAX_BUFFER = 50 * 1024 * 1024;
2903
2935
  function appendUniqueUrl(urls, url) {
2904
2936
  return urls.includes(url) ? urls : [...urls, url];
2905
2937
  }
2906
2938
  var GitService = class {
2907
2939
  defaultBranchCache = /* @__PURE__ */ new Map();
2908
2940
  cachedPrByRepo = /* @__PURE__ */ new Map();
2941
+ // No invalidation on purpose — `git remote set-url` mid-session is rare and
2942
+ // the worst case is `gh pr view` stays skipped until the next engine restart.
2943
+ originIsGitHubCache = /* @__PURE__ */ new Map();
2944
+ // Broadcaster + UI /repos requests fan in here every ~2s; one shared run.
2945
+ listReposInFlight = new InFlightMap();
2946
+ diffStatsInFlight = new InFlightMap();
2947
+ fullDiffInFlight = new InFlightMap();
2909
2948
  getWorkspaceRoot() {
2910
2949
  return ENGINE_ENV.WORKSPACE_ROOT;
2911
2950
  }
@@ -2931,7 +2970,7 @@ var GitService = class {
2931
2970
  repos.push({
2932
2971
  name: entry,
2933
2972
  path: fullPath,
2934
- defaultBranch: this.resolveDefaultBranch(fullPath)
2973
+ defaultBranch: await this.resolveDefaultBranch(fullPath)
2935
2974
  });
2936
2975
  } catch {
2937
2976
  }
@@ -2940,13 +2979,21 @@ var GitService = class {
2940
2979
  }
2941
2980
  async listRepos(options) {
2942
2981
  const includeDiffs = options?.includeDiffs === true;
2982
+ const key = includeDiffs ? "diffs" : "base";
2983
+ return this.listReposInFlight.run(key, () => this.computeListRepos(includeDiffs));
2984
+ }
2985
+ async computeListRepos(includeDiffs) {
2943
2986
  const repos = await this.listRepositories();
2944
2987
  const states = [];
2945
2988
  for (const repo of repos) {
2946
2989
  try {
2947
- const persistedState = await loadRepoState(repo.name);
2948
- const currentBranch = getCurrentBranch(repo.path) ?? repo.defaultBranch;
2949
- const gitDiff = this.getGitDiffStats(repo.path, repo.defaultBranch);
2990
+ const [persistedState, currentBranchRaw, gitDiff] = await Promise.all([
2991
+ loadRepoState(repo.name),
2992
+ getCurrentBranch(repo.path),
2993
+ this.getGitDiffStats(repo.path, repo.defaultBranch)
2994
+ ]);
2995
+ const currentBranch = currentBranchRaw ?? repo.defaultBranch;
2996
+ const fullDiff = includeDiffs && gitDiff ? await this.getFullGitDiff(repo.path, repo.defaultBranch) : void 0;
2950
2997
  states.push({
2951
2998
  name: repo.name,
2952
2999
  path: repo.path,
@@ -2954,7 +3001,7 @@ var GitService = class {
2954
3001
  currentBranch,
2955
3002
  prUrls: persistedState?.prUrls ?? [],
2956
3003
  // fullDiff may be empty if the diff subprocess fails.
2957
- gitDiff: includeDiffs && gitDiff ? { ...gitDiff, fullDiff: this.getFullGitDiff(repo.path, repo.defaultBranch) } : gitDiff,
3004
+ gitDiff: includeDiffs && gitDiff ? { ...gitDiff, fullDiff: fullDiff ?? "" } : gitDiff,
2958
3005
  startHooksCompleted: persistedState?.startHooksCompleted ?? false
2959
3006
  });
2960
3007
  } catch {
@@ -2968,7 +3015,7 @@ var GitService = class {
2968
3015
  for (const repo of repos) {
2969
3016
  try {
2970
3017
  const persistedState = await loadRepoState(repo.name);
2971
- const currentBranch = getCurrentBranch(repo.path) ?? repo.defaultBranch;
3018
+ const currentBranch = await getCurrentBranch(repo.path) ?? repo.defaultBranch;
2972
3019
  const startHooksCompleted = persistedState?.startHooksCompleted ?? false;
2973
3020
  const observed = observedBranchesByRepo?.get(repo.name);
2974
3021
  states.push(
@@ -3033,12 +3080,12 @@ var GitService = class {
3033
3080
  const persistedState = await loadRepoState(repo.name);
3034
3081
  const persistedBranch = persistedState?.currentBranch;
3035
3082
  if (!skipNetworkRefresh) {
3036
- runGitCommand(["fetch", "--all", "--prune", `--filter=${GIT_PARTIAL_CLONE_FILTER}`], repo.path);
3083
+ await runGitCommand(["fetch", "--all", "--prune", `--filter=${GIT_PARTIAL_CLONE_FILTER}`], repo.path);
3037
3084
  }
3038
- if (persistedBranch && branchExists(persistedBranch, repo.path)) {
3039
- const currentBranch = getCurrentBranch(repo.path);
3085
+ if (persistedBranch && await branchExists(persistedBranch, repo.path)) {
3086
+ const currentBranch = await getCurrentBranch(repo.path);
3040
3087
  if (currentBranch !== persistedBranch) {
3041
- runGitCommand(["checkout", persistedBranch], repo.path);
3088
+ await runGitCommand(["checkout", persistedBranch], repo.path);
3042
3089
  }
3043
3090
  results.push({
3044
3091
  name: repo.name,
@@ -3048,15 +3095,15 @@ var GitService = class {
3048
3095
  });
3049
3096
  continue;
3050
3097
  }
3051
- runGitCommand(["checkout", repo.defaultBranch], repo.path);
3098
+ await runGitCommand(["checkout", repo.defaultBranch], repo.path);
3052
3099
  if (!skipNetworkRefresh) {
3053
3100
  try {
3054
- runGitCommand(["pull", "--rebase", "--autostash"], repo.path);
3101
+ await runGitCommand(["pull", "--rebase", "--autostash"], repo.path);
3055
3102
  } catch {
3056
3103
  }
3057
3104
  }
3058
- const branchName = this.findAvailableBranchName(workspaceName, repo.path);
3059
- runGitCommand(["checkout", "-b", branchName], repo.path);
3105
+ const branchName = await this.findAvailableBranchName(workspaceName, repo.path);
3106
+ await runGitCommand(["checkout", "-b", branchName], repo.path);
3060
3107
  await saveRepoState(repo.name, { currentBranch: branchName }, baselineState);
3061
3108
  results.push({
3062
3109
  name: repo.name,
@@ -3094,109 +3141,149 @@ var GitService = class {
3094
3141
  return true;
3095
3142
  }
3096
3143
  getGitDiffStats(repoPath, defaultBranch) {
3097
- try {
3098
- const diffBase = this.getDiffBase(repoPath, defaultBranch);
3099
- const shortstat = runGitCommand(["diff", diffBase, "--shortstat", "-M"], repoPath);
3100
- let added = 0;
3101
- let removed = 0;
3102
- const addedMatch = shortstat.match(/(\d+) insertion/);
3103
- const removedMatch = shortstat.match(/(\d+) deletion/);
3104
- if (addedMatch) {
3105
- added = parseInt(addedMatch[1], 10);
3106
- }
3107
- if (removedMatch) {
3108
- removed = parseInt(removedMatch[1], 10);
3109
- }
3110
- added += this.countUntrackedAddedLines(repoPath);
3111
- return {
3112
- added,
3113
- removed
3114
- };
3115
- } catch (error) {
3116
- console.error("Error getting git diff:", error);
3117
- return null;
3118
- }
3144
+ return this.diffStatsInFlight.run(repoPath, async () => {
3145
+ try {
3146
+ const diffBase = await this.getDiffBase(repoPath, defaultBranch);
3147
+ const shortstat = await runGitCommand(["diff", diffBase, "--shortstat", "-M"], repoPath);
3148
+ let added = 0;
3149
+ let removed = 0;
3150
+ const addedMatch = shortstat.match(/(\d+) insertion/);
3151
+ const removedMatch = shortstat.match(/(\d+) deletion/);
3152
+ if (addedMatch) {
3153
+ added = parseInt(addedMatch[1], 10);
3154
+ }
3155
+ if (removedMatch) {
3156
+ removed = parseInt(removedMatch[1], 10);
3157
+ }
3158
+ added += await this.countUntrackedAddedLines(repoPath);
3159
+ return {
3160
+ added,
3161
+ removed
3162
+ };
3163
+ } catch (error) {
3164
+ console.error("Error getting git diff:", error);
3165
+ return null;
3166
+ }
3167
+ });
3119
3168
  }
3120
- listUntrackedPaths(repoPath) {
3121
- const listing = execFileSync2("git", ["ls-files", "--others", "--exclude-standard", "-z"], {
3169
+ async listUntrackedPaths(repoPath) {
3170
+ const { stdout } = await execFileAsync("git", ["ls-files", "--others", "--exclude-standard", "-z"], {
3122
3171
  cwd: repoPath,
3123
3172
  encoding: "utf-8",
3124
- stdio: ["pipe", "pipe", "pipe"]
3173
+ maxBuffer: SUBPROCESS_MAX_BUFFER
3125
3174
  });
3126
- return listing.split("\0").filter(Boolean);
3175
+ return stdout.split("\0").filter(Boolean);
3127
3176
  }
3128
- getUntrackedDiff(repoPath) {
3129
- const paths = this.listUntrackedPaths(repoPath);
3177
+ async getUntrackedDiff(repoPath) {
3178
+ const paths = await this.listUntrackedPaths(repoPath);
3130
3179
  if (paths.length === 0) return "";
3131
3180
  try {
3132
- execFileSync2("git", ["add", "--intent-to-add", "--", ...paths], { cwd: repoPath, stdio: ["pipe", "pipe", "pipe"] });
3133
- const result = spawnSync("git", ["diff", "--", ...paths], {
3181
+ await execFileAsync("git", ["add", "--intent-to-add", "--", ...paths], {
3134
3182
  cwd: repoPath,
3135
- encoding: "utf-8",
3136
- maxBuffer: 50 * 1024 * 1024
3183
+ maxBuffer: SUBPROCESS_MAX_BUFFER
3137
3184
  });
3138
- return result.status === 0 || result.status === 1 ? result.stdout : "";
3185
+ return await this.readUntrackedDiff(repoPath, paths);
3139
3186
  } finally {
3140
3187
  try {
3141
- execFileSync2("git", ["reset", "--", ...paths], { cwd: repoPath, stdio: ["pipe", "pipe", "pipe"] });
3188
+ await execFileAsync("git", ["reset", "--", ...paths], {
3189
+ cwd: repoPath,
3190
+ maxBuffer: SUBPROCESS_MAX_BUFFER
3191
+ });
3142
3192
  } catch {
3143
3193
  }
3144
3194
  }
3145
3195
  }
3146
- countUntrackedAddedLines(repoPath) {
3196
+ readUntrackedDiff(repoPath, paths) {
3197
+ return new Promise((resolve3) => {
3198
+ const child = spawn("git", ["diff", "--", ...paths], {
3199
+ cwd: repoPath,
3200
+ stdio: ["ignore", "pipe", "pipe"]
3201
+ });
3202
+ const chunks = [];
3203
+ let total = 0;
3204
+ let truncated = false;
3205
+ child.stdout.on("data", (chunk) => {
3206
+ if (truncated) return;
3207
+ total += chunk.length;
3208
+ if (total > FULL_DIFF_MAX_BUFFER) {
3209
+ truncated = true;
3210
+ chunks.push(chunk.subarray(0, Math.max(0, FULL_DIFF_MAX_BUFFER - (total - chunk.length))));
3211
+ child.kill("SIGTERM");
3212
+ return;
3213
+ }
3214
+ chunks.push(chunk);
3215
+ });
3216
+ child.stderr.on("data", () => {
3217
+ });
3218
+ child.on("error", () => resolve3(""));
3219
+ child.on("close", (code) => {
3220
+ if (code === 0 || code === 1 || truncated) {
3221
+ resolve3(Buffer.concat(chunks).toString("utf-8"));
3222
+ } else {
3223
+ resolve3("");
3224
+ }
3225
+ });
3226
+ });
3227
+ }
3228
+ async countUntrackedAddedLines(repoPath) {
3147
3229
  try {
3148
- return this.listUntrackedPaths(repoPath).reduce((total, path4) => {
3230
+ const paths = await this.listUntrackedPaths(repoPath);
3231
+ let total = 0;
3232
+ for (const path4 of paths) {
3149
3233
  try {
3150
- const contents = readFileSync3(join5(repoPath, path4));
3234
+ const contents = await readFile2(join5(repoPath, path4));
3151
3235
  if (contents.length === 0 || contents.includes(0)) {
3152
- return total;
3236
+ continue;
3153
3237
  }
3154
3238
  let lines = 0;
3155
3239
  for (const byte of contents) {
3156
3240
  if (byte === 10) lines += 1;
3157
3241
  }
3158
- return total + lines + (contents[contents.length - 1] === 10 ? 0 : 1);
3242
+ total += lines + (contents[contents.length - 1] === 10 ? 0 : 1);
3159
3243
  } catch {
3160
- return total;
3161
3244
  }
3162
- }, 0);
3245
+ }
3246
+ return total;
3163
3247
  } catch (error) {
3164
3248
  console.error("Error counting untracked added lines:", error);
3165
3249
  return 0;
3166
3250
  }
3167
3251
  }
3168
3252
  getFullGitDiff(repoPath, defaultBranch) {
3169
- try {
3170
- const diffBase = this.getDiffBase(repoPath, defaultBranch);
3171
- const trackedDiff = execFileSync2("git", ["diff", diffBase, "-M", "-C"], {
3172
- cwd: repoPath,
3173
- encoding: "utf-8",
3174
- stdio: ["pipe", "pipe", "pipe"]
3175
- });
3176
- return trackedDiff + this.getUntrackedAsDiff(repoPath);
3177
- } catch {
3178
- return "";
3179
- }
3253
+ return this.fullDiffInFlight.run(repoPath, async () => {
3254
+ try {
3255
+ const diffBase = await this.getDiffBase(repoPath, defaultBranch);
3256
+ const { stdout: trackedDiff } = await execFileAsync("git", ["diff", diffBase, "-M", "-C"], {
3257
+ cwd: repoPath,
3258
+ encoding: "utf-8",
3259
+ maxBuffer: FULL_DIFF_MAX_BUFFER
3260
+ });
3261
+ const untrackedDiff = await this.getUntrackedAsDiff(repoPath);
3262
+ return trackedDiff + untrackedDiff;
3263
+ } catch {
3264
+ return "";
3265
+ }
3266
+ });
3180
3267
  }
3181
- getUntrackedAsDiff(repoPath) {
3268
+ async getUntrackedAsDiff(repoPath) {
3182
3269
  try {
3183
- return this.getUntrackedDiff(repoPath);
3270
+ return await this.getUntrackedDiff(repoPath);
3184
3271
  } catch (error) {
3185
3272
  console.error("Error building untracked diff:", error);
3186
3273
  return "";
3187
3274
  }
3188
3275
  }
3189
- getDiffBase(repoPath, defaultBranch) {
3276
+ async getDiffBase(repoPath, defaultBranch) {
3190
3277
  const baseBranch = `origin/${defaultBranch}`;
3191
3278
  try {
3192
- return runGitCommand(["merge-base", baseBranch, "HEAD"], repoPath);
3279
+ return await runGitCommand(["merge-base", baseBranch, "HEAD"], repoPath);
3193
3280
  } catch {
3194
3281
  return baseBranch;
3195
3282
  }
3196
3283
  }
3197
3284
  async getPullRequestUrl(repoName, repoPath, currentBranchArg, persistedRepoStateArg) {
3198
3285
  try {
3199
- const currentBranch = currentBranchArg ?? getCurrentBranch(repoPath);
3286
+ const currentBranch = currentBranchArg ?? await getCurrentBranch(repoPath);
3200
3287
  if (!currentBranch) {
3201
3288
  return { status: "not_found" };
3202
3289
  }
@@ -3206,7 +3293,7 @@ var GitService = class {
3206
3293
  }
3207
3294
  const persistedRepoState = persistedRepoStateArg ?? await loadRepoState(repoName);
3208
3295
  this.cachedPrByRepo.delete(repoName);
3209
- const result = this.lookupPrOnRemote(repoName, repoPath, currentBranch);
3296
+ const result = await this.lookupPrOnRemote(repoName, repoPath, currentBranch);
3210
3297
  if (result.status === "found") {
3211
3298
  this.cachedPrByRepo.set(repoName, { prUrl: result.url, currentBranch });
3212
3299
  if (persistedRepoState && !persistedRepoState.prUrls.includes(result.url)) {
@@ -3223,25 +3310,29 @@ var GitService = class {
3223
3310
  return { status: "error" };
3224
3311
  }
3225
3312
  }
3226
- lookupPrOnRemote(repoName, repoPath, branch) {
3313
+ async lookupPrOnRemote(repoName, repoPath, branch) {
3314
+ if (!await this.originIsGitHub(repoPath)) {
3315
+ return { status: "not_found" };
3316
+ }
3227
3317
  try {
3228
- const remoteRef = execFileSync2("git", ["ls-remote", "--heads", "origin", branch], {
3318
+ const { stdout } = await execFileAsync("git", ["ls-remote", "--heads", "origin", branch], {
3229
3319
  cwd: repoPath,
3230
3320
  encoding: "utf-8",
3231
- stdio: ["pipe", "pipe", "pipe"]
3232
- }).trim();
3233
- if (!remoteRef) {
3321
+ maxBuffer: SUBPROCESS_MAX_BUFFER
3322
+ });
3323
+ if (!stdout.trim()) {
3234
3324
  return { status: "not_found" };
3235
3325
  }
3236
3326
  } catch {
3237
3327
  return { status: "error" };
3238
3328
  }
3239
3329
  try {
3240
- const prInfo = execFileSync2("gh", ["pr", "view", branch, "--json", "url", "--jq", ".url"], {
3330
+ const { stdout } = await execFileAsync("gh", ["pr", "view", branch, "--json", "url", "--jq", ".url"], {
3241
3331
  cwd: repoPath,
3242
3332
  encoding: "utf-8",
3243
- stdio: ["pipe", "pipe", "pipe"]
3244
- }).trim();
3333
+ maxBuffer: SUBPROCESS_MAX_BUFFER
3334
+ });
3335
+ const prInfo = stdout.trim();
3245
3336
  return prInfo ? { status: "found", url: prInfo } : { status: "not_found" };
3246
3337
  } catch (error) {
3247
3338
  const message = error instanceof Error ? error.message : String(error);
@@ -3249,12 +3340,31 @@ var GitService = class {
3249
3340
  return { status: "error" };
3250
3341
  }
3251
3342
  }
3252
- resolveDefaultBranch(repoPath) {
3343
+ async originIsGitHub(repoPath) {
3344
+ const cached = this.originIsGitHubCache.get(repoPath);
3345
+ if (cached !== void 0) {
3346
+ return cached;
3347
+ }
3348
+ let isGitHub = false;
3349
+ try {
3350
+ const { stdout } = await execFileAsync("git", ["remote", "get-url", "origin"], {
3351
+ cwd: repoPath,
3352
+ encoding: "utf-8",
3353
+ maxBuffer: SUBPROCESS_MAX_BUFFER
3354
+ });
3355
+ isGitHub = isGitHubUrl(stdout.trim());
3356
+ } catch {
3357
+ isGitHub = false;
3358
+ }
3359
+ this.originIsGitHubCache.set(repoPath, isGitHub);
3360
+ return isGitHub;
3361
+ }
3362
+ async resolveDefaultBranch(repoPath) {
3253
3363
  const cached = this.defaultBranchCache.get(repoPath);
3254
3364
  if (cached) {
3255
3365
  return cached;
3256
3366
  }
3257
- const fromSymbolicRef = this.resolveDefaultBranchFromSymbolicRef(repoPath);
3367
+ const fromSymbolicRef = await this.resolveDefaultBranchFromSymbolicRef(repoPath);
3258
3368
  if (fromSymbolicRef) {
3259
3369
  this.defaultBranchCache.set(repoPath, fromSymbolicRef);
3260
3370
  return fromSymbolicRef;
@@ -3263,18 +3373,18 @@ var GitService = class {
3263
3373
  this.defaultBranchCache.set(repoPath, fallback);
3264
3374
  return fallback;
3265
3375
  }
3266
- resolveDefaultBranchFromSymbolicRef(repoPath) {
3376
+ async resolveDefaultBranchFromSymbolicRef(repoPath) {
3267
3377
  try {
3268
- const output = runGitCommand(["symbolic-ref", "--short", "refs/remotes/origin/HEAD"], repoPath);
3378
+ const output = await runGitCommand(["symbolic-ref", "--short", "refs/remotes/origin/HEAD"], repoPath);
3269
3379
  const match = output.match(/^origin\/(.+)$/);
3270
3380
  return match ? match[1] : null;
3271
3381
  } catch {
3272
3382
  return null;
3273
3383
  }
3274
3384
  }
3275
- findAvailableBranchName(baseName, cwd) {
3385
+ async findAvailableBranchName(baseName, cwd) {
3276
3386
  const sanitizedBaseName = this.sanitizeBranchName(baseName);
3277
- if (!branchExists(sanitizedBaseName, cwd)) {
3387
+ if (!await branchExists(sanitizedBaseName, cwd)) {
3278
3388
  return sanitizedBaseName;
3279
3389
  }
3280
3390
  return `${sanitizedBaseName}-${Date.now()}`;
@@ -3292,7 +3402,7 @@ var GitService = class {
3292
3402
  if (observedBranches) {
3293
3403
  for (const branch of observedBranches) {
3294
3404
  if (branch === currentBranch) continue;
3295
- const branchResult = this.lookupPrOnRemote(repo.name, repo.path, branch);
3405
+ const branchResult = await this.lookupPrOnRemote(repo.name, repo.path, branch);
3296
3406
  if (branchResult.status === "found") {
3297
3407
  prUrls = appendUniqueUrl(prUrls, branchResult.url);
3298
3408
  }
@@ -3304,7 +3414,7 @@ var GitService = class {
3304
3414
  defaultBranch: repo.defaultBranch,
3305
3415
  currentBranch,
3306
3416
  prUrls,
3307
- gitDiff: this.getGitDiffStats(repo.path, repo.defaultBranch),
3417
+ gitDiff: await this.getGitDiffStats(repo.path, repo.defaultBranch),
3308
3418
  startHooksCompleted
3309
3419
  };
3310
3420
  await saveRepoState(repo.name, state, state);
@@ -3392,20 +3502,17 @@ var EngineLogger = class {
3392
3502
  var engineLogger = new EngineLogger();
3393
3503
 
3394
3504
  // src/services/replicas-config-service.ts
3395
- import { readFile as readFile4, appendFile as appendFile2, writeFile as writeFile4, mkdir as mkdir5 } from "fs/promises";
3505
+ import { readFile as readFile5, appendFile as appendFile2, writeFile as writeFile4, mkdir as mkdir5 } from "fs/promises";
3396
3506
  import { existsSync as existsSync4 } from "fs";
3397
3507
  import { join as join9 } from "path";
3398
3508
  import { homedir as homedir7 } from "os";
3399
- import { spawn } from "child_process";
3509
+ import { spawn as spawn2 } from "child_process";
3400
3510
 
3401
3511
  // src/services/environment-details-service.ts
3402
- import { mkdir as mkdir3, readFile as readFile2 } from "fs/promises";
3512
+ import { mkdir as mkdir3, readFile as readFile3 } from "fs/promises";
3403
3513
  import { existsSync as existsSync3 } from "fs";
3404
3514
  import { homedir as homedir5 } from "os";
3405
3515
  import { join as join7 } from "path";
3406
- import { execFile } from "child_process";
3407
- import { promisify } from "util";
3408
- var execFileAsync = promisify(execFile);
3409
3516
  var REPLICAS_DIR = join7(homedir5(), ".replicas");
3410
3517
  var DETAILS_FILE = join7(REPLICAS_DIR, "environment-details.json");
3411
3518
  var CLAUDE_CREDENTIALS_PATH = join7(homedir5(), ".claude", ".credentials.json");
@@ -3478,7 +3585,7 @@ async function readDetails() {
3478
3585
  if (!existsSync3(DETAILS_FILE)) {
3479
3586
  return createDefaultDetails();
3480
3587
  }
3481
- const raw = await readFile2(DETAILS_FILE, "utf-8");
3588
+ const raw = await readFile3(DETAILS_FILE, "utf-8");
3482
3589
  const parsed = JSON.parse(raw);
3483
3590
  return { ...createDefaultDetails(), ...parsed };
3484
3591
  } catch {
@@ -3572,7 +3679,7 @@ var EnvironmentDetailsService = class {
3572
3679
  var environmentDetailsService = new EnvironmentDetailsService();
3573
3680
 
3574
3681
  // src/services/start-hook-logs-service.ts
3575
- import { mkdir as mkdir4, readFile as readFile3, writeFile as writeFile3, readdir as readdir2 } from "fs/promises";
3682
+ import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile3, readdir as readdir2 } from "fs/promises";
3576
3683
  import { homedir as homedir6 } from "os";
3577
3684
  import { join as join8 } from "path";
3578
3685
 
@@ -3649,7 +3756,7 @@ var StartHookLogsService = class {
3649
3756
  continue;
3650
3757
  }
3651
3758
  try {
3652
- const raw = await readFile3(join8(LOGS_DIR, file), "utf-8");
3759
+ const raw = await readFile4(join8(LOGS_DIR, file), "utf-8");
3653
3760
  const stored = normalizeStored(JSON.parse(raw));
3654
3761
  if (stored) {
3655
3762
  logs.push(withPreview(stored));
@@ -3668,7 +3775,7 @@ var StartHookLogsService = class {
3668
3775
  async getFullOutput(hookType, hookName) {
3669
3776
  const filename = hookType === "environment" ? ENVIRONMENT_HOOK_LOG_FILENAME : repoHookLogFilename(hookName);
3670
3777
  try {
3671
- const raw = await readFile3(join8(LOGS_DIR, filename), "utf-8");
3778
+ const raw = await readFile4(join8(LOGS_DIR, filename), "utf-8");
3672
3779
  const stored = normalizeStored(JSON.parse(raw));
3673
3780
  if (!stored || stored.hookType !== hookType || stored.hookName !== hookName) {
3674
3781
  return null;
@@ -3700,7 +3807,7 @@ async function readReplicasConfigFromDir(dirPath) {
3700
3807
  if (!existsSync4(configPath)) {
3701
3808
  continue;
3702
3809
  }
3703
- const data = await readFile4(configPath, "utf-8");
3810
+ const data = await readFile5(configPath, "utf-8");
3704
3811
  const config = parseReplicasConfigString(data, filename);
3705
3812
  return { config, filename };
3706
3813
  }
@@ -3802,7 +3909,7 @@ var ReplicasConfigService = class {
3802
3909
  emit(`$ ${params.label}
3803
3910
  `);
3804
3911
  return new Promise((resolve3) => {
3805
- const proc = spawn("bash", ["-lc", params.command], {
3912
+ const proc = spawn2("bash", ["-lc", params.command], {
3806
3913
  cwd: params.cwd,
3807
3914
  env: process.env,
3808
3915
  stdio: ["pipe", "pipe", "pipe"]
@@ -4135,7 +4242,7 @@ var EventService = class {
4135
4242
  var eventService = new EventService();
4136
4243
 
4137
4244
  // src/services/preview-service.ts
4138
- import { mkdir as mkdir7, readFile as readFile5 } from "fs/promises";
4245
+ import { mkdir as mkdir7, readFile as readFile6 } from "fs/promises";
4139
4246
  import { existsSync as existsSync5 } from "fs";
4140
4247
  import { randomUUID as randomUUID2 } from "crypto";
4141
4248
  import { homedir as homedir9 } from "os";
@@ -4146,7 +4253,7 @@ async function readPreviewsFile() {
4146
4253
  if (!existsSync5(PREVIEW_PORTS_FILE)) {
4147
4254
  return { previews: [] };
4148
4255
  }
4149
- const raw = await readFile5(PREVIEW_PORTS_FILE, "utf-8");
4256
+ const raw = await readFile6(PREVIEW_PORTS_FILE, "utf-8");
4150
4257
  return JSON.parse(raw);
4151
4258
  } catch {
4152
4259
  return { previews: [] };
@@ -4251,7 +4358,7 @@ async function registerDesktopPreview() {
4251
4358
 
4252
4359
  // src/services/chat/chat-service.ts
4253
4360
  import { existsSync as existsSync8 } from "fs";
4254
- import { appendFile as appendFile5, copyFile, mkdir as mkdir11, readFile as readFile8, rename as rename2, rm } from "fs/promises";
4361
+ import { appendFile as appendFile5, copyFile, mkdir as mkdir11, readFile as readFile9, rename as rename2, rm } from "fs/promises";
4255
4362
  import { homedir as homedir13 } from "os";
4256
4363
  import { join as join15 } from "path";
4257
4364
  import { randomUUID as randomUUID5 } from "crypto";
@@ -4266,7 +4373,7 @@ import { mkdir as mkdir9, appendFile as appendFile4 } from "fs/promises";
4266
4373
  import { homedir as homedir11 } from "os";
4267
4374
 
4268
4375
  // src/utils/jsonl-reader.ts
4269
- import { readFile as readFile6 } from "fs/promises";
4376
+ import { readFile as readFile7 } from "fs/promises";
4270
4377
  function isJsonlEvent(value) {
4271
4378
  if (!isRecord4(value)) {
4272
4379
  return false;
@@ -4288,7 +4395,7 @@ function parseJsonlEvents(lines) {
4288
4395
  }
4289
4396
  async function readJSONL(filePath) {
4290
4397
  try {
4291
- const content = await readFile6(filePath, "utf-8");
4398
+ const content = await readFile7(filePath, "utf-8");
4292
4399
  const lines = content.split("\n").filter((line) => line.trim());
4293
4400
  return parseJsonlEvents(lines);
4294
4401
  } catch (error) {
@@ -6039,7 +6146,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
6039
6146
  };
6040
6147
 
6041
6148
  // src/managers/codex-asp/app-server-process.ts
6042
- import { spawn as spawn2 } from "child_process";
6149
+ import { spawn as spawn3 } from "child_process";
6043
6150
  import { EventEmitter as EventEmitter2 } from "events";
6044
6151
 
6045
6152
  // src/managers/codex-asp/asp-client.ts
@@ -6234,7 +6341,7 @@ var AspClient = class {
6234
6341
  // src/managers/codex-asp/app-server-process.ts
6235
6342
  var DEFAULT_CODEX_BINARY = "codex";
6236
6343
  var DEFAULT_CODEX_ARGS = ["app-server", "--listen", "stdio://"];
6237
- var ENGINE_PACKAGE_VERSION = "0.1.256";
6344
+ var ENGINE_PACKAGE_VERSION = "0.1.258";
6238
6345
  var INITIALIZE_METHOD = "initialize";
6239
6346
  var INITIALIZED_NOTIFICATION = "initialized";
6240
6347
  var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
@@ -6261,7 +6368,7 @@ var AppServerProcess = class {
6261
6368
  return { client: this.client };
6262
6369
  }
6263
6370
  this.shuttingDown = false;
6264
- const child = spawn2(this.binary, this.args, {
6371
+ const child = spawn3(this.binary, this.args, {
6265
6372
  cwd: this.cwd,
6266
6373
  env: this.env,
6267
6374
  stdio: ["pipe", "pipe", "pipe"]
@@ -6501,7 +6608,7 @@ function isCodexAuthError(error) {
6501
6608
  }
6502
6609
 
6503
6610
  // src/managers/codex-asp/mappers.ts
6504
- import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
6611
+ import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
6505
6612
  var localImageCache = /* @__PURE__ */ new Map();
6506
6613
  var DEFAULT_MODEL = DEFAULT_CODEX_MODEL;
6507
6614
  var THREAD_START_METHOD = "thread/start";
@@ -6582,7 +6689,7 @@ function userImageForLocalPath(path4) {
6582
6689
  const image = {
6583
6690
  type: "image",
6584
6691
  mediaType: inferMediaType(path4),
6585
- data: readFileSync4(path4).toString("base64")
6692
+ data: readFileSync3(path4).toString("base64")
6586
6693
  };
6587
6694
  if (image.data.length > 0) localImageCache.set(path4, image);
6588
6695
  return image;
@@ -7854,7 +7961,7 @@ var CodexAspManager = class extends CodingAgentManager {
7854
7961
 
7855
7962
  // src/managers/codex-manager.ts
7856
7963
  import { Codex } from "@openai/codex-sdk";
7857
- import { readdir as readdir3, stat as stat2, writeFile as writeFile6, mkdir as mkdir10, readFile as readFile7 } from "fs/promises";
7964
+ import { readdir as readdir3, stat as stat2, writeFile as writeFile6, mkdir as mkdir10, readFile as readFile8 } from "fs/promises";
7858
7965
  import { existsSync as existsSync7 } from "fs";
7859
7966
  import { join as join14 } from "path";
7860
7967
  import { homedir as homedir12 } from "os";
@@ -7902,7 +8009,7 @@ var CodexManager = class extends CodingAgentManager {
7902
8009
  try {
7903
8010
  const sessionFile = await this.findSessionFile(this.currentThreadId);
7904
8011
  if (!sessionFile) return;
7905
- const content = await readFile7(sessionFile, "utf-8");
8012
+ const content = await readFile8(sessionFile, "utf-8");
7906
8013
  const lines = content.split("\n").map((line) => line.trim()).filter(Boolean);
7907
8014
  let latest = null;
7908
8015
  for (const line of lines) {
@@ -7942,7 +8049,7 @@ var CodexManager = class extends CodingAgentManager {
7942
8049
  let config = {};
7943
8050
  if (existsSync7(CODEX_CONFIG_PATH)) {
7944
8051
  try {
7945
- const existingContent = await readFile7(CODEX_CONFIG_PATH, "utf-8");
8052
+ const existingContent = await readFile8(CODEX_CONFIG_PATH, "utf-8");
7946
8053
  const parsed = parseToml(existingContent);
7947
8054
  if (isRecord4(parsed)) {
7948
8055
  config = parsed;
@@ -8185,7 +8292,7 @@ var CodexManager = class extends CodingAgentManager {
8185
8292
  const seenLines = /* @__PURE__ */ new Set();
8186
8293
  const seedSeenLines = async () => {
8187
8294
  try {
8188
- const content = await readFile7(sessionFile, "utf-8");
8295
+ const content = await readFile8(sessionFile, "utf-8");
8189
8296
  const lines = content.split("\n").map((line) => line.trim()).filter(Boolean);
8190
8297
  let latest = null;
8191
8298
  for (const line of lines) {
@@ -8207,7 +8314,7 @@ var CodexManager = class extends CodingAgentManager {
8207
8314
  const pump = async () => {
8208
8315
  let emitted = 0;
8209
8316
  try {
8210
- const content = await readFile7(sessionFile, "utf-8");
8317
+ const content = await readFile8(sessionFile, "utf-8");
8211
8318
  const lines = content.split("\n");
8212
8319
  const completeLines = content.endsWith("\n") ? lines : lines.slice(0, -1);
8213
8320
  for (const line of completeLines) {
@@ -9070,7 +9177,7 @@ var ChatService = class {
9070
9177
  }
9071
9178
  async readSenders(chatId) {
9072
9179
  try {
9073
- const content = await readFile8(this.senderFilePath(chatId), "utf-8");
9180
+ const content = await readFile9(this.senderFilePath(chatId), "utf-8");
9074
9181
  const lines = content.split("\n").filter((line) => line.trim().length > 0);
9075
9182
  const senders = [];
9076
9183
  for (const line of lines) {
@@ -9427,7 +9534,7 @@ var ChatService = class {
9427
9534
  }
9428
9535
  async loadChats() {
9429
9536
  try {
9430
- const content = await readFile8(CHATS_FILE, "utf-8");
9537
+ const content = await readFile9(CHATS_FILE, "utf-8");
9431
9538
  return parsePersistedChatsContent(content);
9432
9539
  } catch (error) {
9433
9540
  if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
@@ -9442,7 +9549,7 @@ var ChatService = class {
9442
9549
  console.error("[ChatService] Failed to quarantine corrupt chats file:", renameError);
9443
9550
  }
9444
9551
  try {
9445
- const backupContent = await readFile8(CHATS_BACKUP_FILE, "utf-8");
9552
+ const backupContent = await readFile9(CHATS_BACKUP_FILE, "utf-8");
9446
9553
  return parsePersistedChatsContent(backupContent);
9447
9554
  } catch (backupError) {
9448
9555
  if (backupError && typeof backupError === "object" && "code" in backupError && backupError.code === "ENOENT") {
@@ -9517,7 +9624,7 @@ var ChatService = class {
9517
9624
 
9518
9625
  // src/services/repo-file-service.ts
9519
9626
  import { execFile as execFile2 } from "child_process";
9520
- import { readFile as readFile9, realpath, stat as stat3 } from "fs/promises";
9627
+ import { readFile as readFile10, realpath, stat as stat3 } from "fs/promises";
9521
9628
  import { join as join16, resolve, extname } from "path";
9522
9629
  var CACHE_TTL_MS = 3e4;
9523
9630
  var SEARCH_TIMEOUT_MS = 15e3;
@@ -9707,7 +9814,7 @@ var RepoFileService = class {
9707
9814
  tooLarge: true
9708
9815
  };
9709
9816
  }
9710
- const content = await readFile9(fullPath, "utf-8");
9817
+ const content = await readFile10(fullPath, "utf-8");
9711
9818
  return {
9712
9819
  repoName,
9713
9820
  path: filePath,
@@ -9789,11 +9896,11 @@ var RepoFileService = class {
9789
9896
  // src/v1-routes.ts
9790
9897
  import { Hono } from "hono";
9791
9898
  import { z as z2 } from "zod";
9792
- import { readdir as readdir6, stat as stat4, readFile as readFile13 } from "fs/promises";
9899
+ import { readdir as readdir6, stat as stat4, readFile as readFile14 } from "fs/promises";
9793
9900
  import { join as join20, resolve as resolve2 } from "path";
9794
9901
 
9795
9902
  // src/services/plan-service.ts
9796
- import { readdir as readdir4, readFile as readFile10 } from "fs/promises";
9903
+ import { readdir as readdir4, readFile as readFile11 } from "fs/promises";
9797
9904
  import { homedir as homedir14 } from "os";
9798
9905
  import { basename, join as join17 } from "path";
9799
9906
  var PLAN_DIRECTORIES = [
@@ -9834,7 +9941,7 @@ var PlanService = class {
9834
9941
  for (const directory of PLAN_DIRECTORIES) {
9835
9942
  const filePath = join17(directory, safeFilename);
9836
9943
  try {
9837
- const content = await readFile10(filePath, "utf-8");
9944
+ const content = await readFile11(filePath, "utf-8");
9838
9945
  return { filename: safeFilename, content };
9839
9946
  } catch {
9840
9947
  }
@@ -9845,13 +9952,13 @@ var PlanService = class {
9845
9952
  var planService = new PlanService();
9846
9953
 
9847
9954
  // src/services/warm-hooks-service.ts
9848
- import { spawn as spawn3 } from "child_process";
9849
- import { readFile as readFile12 } from "fs/promises";
9955
+ import { spawn as spawn4 } from "child_process";
9956
+ import { readFile as readFile13 } from "fs/promises";
9850
9957
  import { existsSync as existsSync9 } from "fs";
9851
9958
  import { join as join19 } from "path";
9852
9959
 
9853
9960
  // src/services/warm-hook-logs-service.ts
9854
- import { mkdir as mkdir12, readFile as readFile11, writeFile as writeFile7, readdir as readdir5, appendFile as appendFile6, unlink as unlink3 } from "fs/promises";
9961
+ import { mkdir as mkdir12, readFile as readFile12, writeFile as writeFile7, readdir as readdir5, appendFile as appendFile6, unlink as unlink3 } from "fs/promises";
9855
9962
  import { homedir as homedir15 } from "os";
9856
9963
  import { join as join18 } from "path";
9857
9964
  var LOGS_DIR2 = join18(homedir15(), ".replicas", "warm-hook-logs");
@@ -9911,7 +10018,7 @@ var WarmHookLogsService = class {
9911
10018
  continue;
9912
10019
  }
9913
10020
  try {
9914
- const raw = await readFile11(join18(LOGS_DIR2, file), "utf-8");
10021
+ const raw = await readFile12(join18(LOGS_DIR2, file), "utf-8");
9915
10022
  const stored = JSON.parse(raw);
9916
10023
  logs.push(withPreview2(stored));
9917
10024
  } catch {
@@ -9940,7 +10047,7 @@ var WarmHookLogsService = class {
9940
10047
  }
9941
10048
  async getCurrentRunLog() {
9942
10049
  try {
9943
- return await readFile11(CURRENT_RUN_LOG, "utf-8");
10050
+ return await readFile12(CURRENT_RUN_LOG, "utf-8");
9944
10051
  } catch (err) {
9945
10052
  if (err.code === "ENOENT") return null;
9946
10053
  throw err;
@@ -9949,7 +10056,7 @@ var WarmHookLogsService = class {
9949
10056
  async getFullOutput(hookType, hookName) {
9950
10057
  const filename = hookType === "global" ? GLOBAL_FILENAME : hookType === "environment" ? ENVIRONMENT_HOOK_LOG_FILENAME : repoHookLogFilename(hookName);
9951
10058
  try {
9952
- const raw = await readFile11(join18(LOGS_DIR2, filename), "utf-8");
10059
+ const raw = await readFile12(join18(LOGS_DIR2, filename), "utf-8");
9953
10060
  const stored = JSON.parse(raw);
9954
10061
  if (stored.hookType !== hookType || stored.hookName !== hookName) {
9955
10062
  return null;
@@ -9973,7 +10080,7 @@ async function readRepoWarmHook(repoPath) {
9973
10080
  continue;
9974
10081
  }
9975
10082
  try {
9976
- const raw = await readFile12(configPath, "utf-8");
10083
+ const raw = await readFile13(configPath, "utf-8");
9977
10084
  const config = parseReplicasConfigString(raw, filename);
9978
10085
  if (!config.warmHook) {
9979
10086
  return null;
@@ -10020,7 +10127,7 @@ async function executeHookScriptStreaming(params) {
10020
10127
  params.onChunk(`$ ${params.label}
10021
10128
  `);
10022
10129
  return new Promise((resolve3) => {
10023
- const proc = spawn3("bash", ["-lc", params.content], {
10130
+ const proc = spawn4("bash", ["-lc", params.content], {
10024
10131
  cwd: params.cwd,
10025
10132
  env: process.env,
10026
10133
  stdio: ["pipe", "pipe", "pipe"]
@@ -10903,7 +11010,7 @@ function createV1Routes(deps) {
10903
11010
  const limit = Math.min(parseInt(c.req.query("limit") || "500", 10), 5e3);
10904
11011
  let content;
10905
11012
  try {
10906
- content = await readFile13(filePath, "utf-8");
11013
+ content = await readFile14(filePath, "utf-8");
10907
11014
  } catch {
10908
11015
  return c.json(jsonError("Log session not found"), 404);
10909
11016
  }
@@ -10943,12 +11050,10 @@ process.on("unhandledRejection", (reason) => {
10943
11050
  engineLogger.flush().finally(() => process.exit(1));
10944
11051
  });
10945
11052
  await eventService.initialize();
10946
- var READY_MESSAGE = "========= REPLICAS WORKSPACE READY ==========";
10947
- var COMPLETION_MESSAGE = "========= REPLICAS WORKSPACE INITIALIZATION COMPLETE ==========";
10948
- function checkActiveSSHSessions() {
11053
+ async function checkActiveSSHSessions() {
10949
11054
  try {
10950
- const output = execSync2('who | grep -v "^$" | wc -l', { encoding: "utf-8" });
10951
- const sessionCount = parseInt(output.trim(), 10);
11055
+ const { stdout } = await execAsync('who | grep -v "^$" | wc -l', { encoding: "utf-8" });
11056
+ const sessionCount = parseInt(stdout.trim(), 10);
10952
11057
  return sessionCount > 0;
10953
11058
  } catch {
10954
11059
  return false;
@@ -10972,32 +11077,22 @@ var authMiddleware = async (c, next) => {
10972
11077
  await next();
10973
11078
  };
10974
11079
  var chatService = new ChatService(gitService.getWorkspaceRoot());
10975
- app.get("/health", async (c) => {
11080
+ app.get("/health", (c) => {
10976
11081
  if (!engineReady) {
10977
11082
  return c.json({ status: "initializing", timestamp: (/* @__PURE__ */ new Date()).toISOString() }, 503);
10978
11083
  }
10979
- try {
10980
- const logContent = await readFile14("/var/log/cloud-init-output.log", "utf-8");
10981
- let status;
10982
- if (logContent.includes(COMPLETION_MESSAGE)) {
10983
- status = "active";
10984
- } else if (logContent.includes(READY_MESSAGE)) {
10985
- status = "ready";
10986
- } else {
10987
- status = "initializing";
10988
- }
10989
- return c.json({ status, timestamp: (/* @__PURE__ */ new Date()).toISOString() });
10990
- } catch {
10991
- return c.json({ status: "initializing", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
10992
- }
11084
+ return c.json({ status: "active", timestamp: (/* @__PURE__ */ new Date()).toISOString() });
10993
11085
  });
10994
11086
  app.use("*", authMiddleware);
10995
11087
  app.get("/status", async (c) => {
10996
11088
  try {
10997
- const repos = await gitService.listRepos();
11089
+ const [repos, hasActiveSSHSessions] = await Promise.all([
11090
+ gitService.listRepos(),
11091
+ checkActiveSSHSessions()
11092
+ ]);
10998
11093
  const status = {
10999
11094
  uptimeSeconds: Math.floor((Date.now() - bootTimeMs) / 1e3),
11000
- hasActiveSSHSessions: checkActiveSSHSessions(),
11095
+ hasActiveSSHSessions,
11001
11096
  activeSseClients: eventService.getSubscriberCount(),
11002
11097
  chatsTotal: chatService.listChats().length,
11003
11098
  chatsProcessing: chatService.getProcessingCount(),
@@ -11027,8 +11122,16 @@ function startStatusBroadcaster() {
11027
11122
  let previousHookStatus = "";
11028
11123
  let previousEngineStatus = "";
11029
11124
  let lastHooksRunning = replicasConfigService.areHooksRunning();
11030
- setInterval(() => {
11031
- gitService.listRepos().then((repos) => {
11125
+ const BROADCAST_INTERVAL_MS = 2e3;
11126
+ const scheduleNext = () => {
11127
+ setTimeout(tick, BROADCAST_INTERVAL_MS);
11128
+ };
11129
+ const tick = async () => {
11130
+ try {
11131
+ const [repos, hasActiveSSHSessions] = await Promise.all([
11132
+ gitService.listRepos(),
11133
+ checkActiveSSHSessions()
11134
+ ]);
11032
11135
  const serialized = JSON.stringify(repos);
11033
11136
  if (serialized !== previousRepoStatus) {
11034
11137
  previousRepoStatus = serialized;
@@ -11042,7 +11145,7 @@ function startStatusBroadcaster() {
11042
11145
  }
11043
11146
  const engineStatus = {
11044
11147
  uptimeSeconds: Math.floor((Date.now() - bootTimeMs) / 1e3),
11045
- hasActiveSSHSessions: checkActiveSSHSessions(),
11148
+ hasActiveSSHSessions,
11046
11149
  activeSseClients: eventService.getSubscriberCount(),
11047
11150
  chatsTotal: chatService.listChats().length,
11048
11151
  chatsProcessing: chatService.getProcessingCount(),
@@ -11060,67 +11163,70 @@ function startStatusBroadcaster() {
11060
11163
  }).catch(() => {
11061
11164
  });
11062
11165
  }
11063
- }).catch(() => {
11064
- });
11065
- const hooksRunning = replicasConfigService.areHooksRunning();
11066
- const hooksCompleted = replicasConfigService.areHooksCompleted();
11067
- const hooksFailed = replicasConfigService.didHooksFail();
11068
- const hookSnapshot = JSON.stringify({
11069
- running: hooksRunning,
11070
- completed: hooksCompleted,
11071
- failed: hooksFailed
11072
- });
11073
- if (hookSnapshot !== previousHookStatus) {
11074
- previousHookStatus = hookSnapshot;
11075
- if (!lastHooksRunning && hooksRunning) {
11076
- eventService.publish({
11077
- id: randomUUID6(),
11078
- ts: (/* @__PURE__ */ new Date()).toISOString(),
11079
- type: "hooks.started",
11080
- payload: { running: true, completed: false }
11081
- }).catch(() => {
11082
- });
11083
- }
11084
- if (hooksRunning) {
11085
- eventService.publish({
11086
- id: randomUUID6(),
11087
- ts: (/* @__PURE__ */ new Date()).toISOString(),
11088
- type: "hooks.progress",
11089
- payload: { running: true, completed: false }
11090
- }).catch(() => {
11091
- });
11092
- }
11093
- if (lastHooksRunning && !hooksRunning && hooksCompleted && !hooksFailed) {
11094
- eventService.publish({
11095
- id: randomUUID6(),
11096
- ts: (/* @__PURE__ */ new Date()).toISOString(),
11097
- type: "hooks.completed",
11098
- payload: { running: false, completed: true }
11099
- }).catch(() => {
11100
- });
11101
- }
11102
- if (lastHooksRunning && !hooksRunning && hooksFailed) {
11166
+ const hooksRunning = replicasConfigService.areHooksRunning();
11167
+ const hooksCompleted = replicasConfigService.areHooksCompleted();
11168
+ const hooksFailed = replicasConfigService.didHooksFail();
11169
+ const hookSnapshot = JSON.stringify({
11170
+ running: hooksRunning,
11171
+ completed: hooksCompleted,
11172
+ failed: hooksFailed
11173
+ });
11174
+ if (hookSnapshot !== previousHookStatus) {
11175
+ previousHookStatus = hookSnapshot;
11176
+ if (!lastHooksRunning && hooksRunning) {
11177
+ eventService.publish({
11178
+ id: randomUUID6(),
11179
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
11180
+ type: "hooks.started",
11181
+ payload: { running: true, completed: false }
11182
+ }).catch(() => {
11183
+ });
11184
+ }
11185
+ if (hooksRunning) {
11186
+ eventService.publish({
11187
+ id: randomUUID6(),
11188
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
11189
+ type: "hooks.progress",
11190
+ payload: { running: true, completed: false }
11191
+ }).catch(() => {
11192
+ });
11193
+ }
11194
+ if (lastHooksRunning && !hooksRunning && hooksCompleted && !hooksFailed) {
11195
+ eventService.publish({
11196
+ id: randomUUID6(),
11197
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
11198
+ type: "hooks.completed",
11199
+ payload: { running: false, completed: true }
11200
+ }).catch(() => {
11201
+ });
11202
+ }
11203
+ if (lastHooksRunning && !hooksRunning && hooksFailed) {
11204
+ eventService.publish({
11205
+ id: randomUUID6(),
11206
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
11207
+ type: "hooks.failed",
11208
+ payload: { running: false, completed: hooksCompleted }
11209
+ }).catch(() => {
11210
+ });
11211
+ }
11103
11212
  eventService.publish({
11104
11213
  id: randomUUID6(),
11105
11214
  ts: (/* @__PURE__ */ new Date()).toISOString(),
11106
- type: "hooks.failed",
11107
- payload: { running: false, completed: hooksCompleted }
11215
+ type: "hooks.status",
11216
+ payload: {
11217
+ running: hooksRunning,
11218
+ completed: hooksCompleted
11219
+ }
11108
11220
  }).catch(() => {
11109
11221
  });
11222
+ lastHooksRunning = hooksRunning;
11110
11223
  }
11111
- eventService.publish({
11112
- id: randomUUID6(),
11113
- ts: (/* @__PURE__ */ new Date()).toISOString(),
11114
- type: "hooks.status",
11115
- payload: {
11116
- running: hooksRunning,
11117
- completed: hooksCompleted
11118
- }
11119
- }).catch(() => {
11120
- });
11121
- lastHooksRunning = hooksRunning;
11224
+ } catch {
11225
+ } finally {
11226
+ scheduleNext();
11122
11227
  }
11123
- }, 2e3);
11228
+ };
11229
+ scheduleNext();
11124
11230
  }
11125
11231
  serve(
11126
11232
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.256",
3
+ "version": "0.1.258",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",