replicas-engine 0.1.257 → 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 +316 -226
  2. package/package.json +1 -1
package/dist/src/index.js CHANGED
@@ -3,21 +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 { execSync as execSync2 } from "child_process";
7
6
  import { randomUUID as randomUUID6 } from "crypto";
8
7
 
9
- // src/managers/github-token-manager.ts
10
- import { promises as fs } from "fs";
11
- import path from "path";
12
-
13
- // src/engine-env.ts
14
- import { homedir as homedir2 } from "os";
15
- import { join as join2 } from "path";
16
-
17
- // src/runtime-env-loader.ts
18
- import { readFileSync } from "fs";
19
- import { homedir } from "os";
20
- import { join } from "path";
8
+ // src/utils/exec.ts
9
+ import { exec, execFile } from "child_process";
10
+ import { promisify } from "util";
21
11
 
22
12
  // ../shared/src/type-guards.ts
23
13
  function isRecord(value) {
@@ -296,7 +286,7 @@ var WORKSPACE_SIZES = ["small", "large"];
296
286
  var INVALID_WORKSPACE_SIZE_ERROR = `Invalid size: must be one of ${WORKSPACE_SIZES.join(", ")}`;
297
287
 
298
288
  // ../shared/src/e2b.ts
299
- var E2B_TEMPLATE_NAME = "replicas-sandbox-2026-06-03-v2";
289
+ var E2B_TEMPLATE_NAME = "replicas-sandbox-2026-06-03-v3";
300
290
 
301
291
  // ../shared/src/runtime-env.ts
302
292
  function parsePosixEnvFile(content) {
@@ -357,6 +347,25 @@ function parsePosixEnvFile(content) {
357
347
  // ../shared/src/git.ts
358
348
  var GIT_PARTIAL_CLONE_FILTER = "blob:none";
359
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
+
360
369
  // ../shared/src/default-skills/replicas-agent/abilities/computer.ts
361
370
  var SECTION = `### Computer use (Linux desktop control)
362
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).
@@ -2240,7 +2249,23 @@ function isGitHubUrl(url) {
2240
2249
  return GITHUB_HOST_RE.test(url);
2241
2250
  }
2242
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
+
2243
2265
  // src/runtime-env-loader.ts
2266
+ import { readFileSync } from "fs";
2267
+ import { homedir } from "os";
2268
+ import { join } from "path";
2244
2269
  function loadRuntimeEnvFile() {
2245
2270
  let content;
2246
2271
  try {
@@ -2736,9 +2761,9 @@ var CodexTokenManager = class extends BaseRefreshManager {
2736
2761
  var codexTokenManager = new CodexTokenManager();
2737
2762
 
2738
2763
  // src/git/service.ts
2739
- import { readdir, stat } from "fs/promises";
2740
- import { existsSync as existsSync2, readFileSync as readFileSync3, unlinkSync } from "fs";
2741
- 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";
2742
2767
  import { join as join5 } from "path";
2743
2768
 
2744
2769
  // src/utils/state.ts
@@ -2869,15 +2894,15 @@ async function saveRepoState(repoName, state, fallbackState) {
2869
2894
  }
2870
2895
 
2871
2896
  // src/git/commands.ts
2872
- import { execFileSync } from "child_process";
2873
2897
  import { readFileSync as readFileSync2 } from "fs";
2874
2898
  import { join as join4 } from "path";
2875
- function runGitCommand(args, cwd) {
2876
- return execFileSync("git", args, {
2899
+ async function runGitCommand(args, cwd, options = {}) {
2900
+ const { stdout } = await execFileAsync("git", args, {
2877
2901
  cwd,
2878
2902
  encoding: "utf-8",
2879
- stdio: ["pipe", "pipe", "pipe"]
2880
- }).trim();
2903
+ maxBuffer: options.maxBuffer ?? SUBPROCESS_MAX_BUFFER
2904
+ });
2905
+ return stdout.trim();
2881
2906
  }
2882
2907
  function readRepoHeadBranch(repoPath) {
2883
2908
  try {
@@ -2888,17 +2913,17 @@ function readRepoHeadBranch(repoPath) {
2888
2913
  return null;
2889
2914
  }
2890
2915
  }
2891
- function branchExists(branchName, cwd) {
2916
+ async function branchExists(branchName, cwd) {
2892
2917
  try {
2893
- runGitCommand(["rev-parse", "--verify", branchName], cwd);
2918
+ await runGitCommand(["rev-parse", "--verify", branchName], cwd);
2894
2919
  return true;
2895
2920
  } catch {
2896
2921
  return false;
2897
2922
  }
2898
2923
  }
2899
- function getCurrentBranch(cwd) {
2924
+ async function getCurrentBranch(cwd) {
2900
2925
  try {
2901
- return runGitCommand(["rev-parse", "--abbrev-ref", "HEAD"], cwd);
2926
+ return await runGitCommand(["rev-parse", "--abbrev-ref", "HEAD"], cwd);
2902
2927
  } catch (error) {
2903
2928
  console.error("Error getting current branch:", error);
2904
2929
  return null;
@@ -2906,6 +2931,7 @@ function getCurrentBranch(cwd) {
2906
2931
  }
2907
2932
 
2908
2933
  // src/git/service.ts
2934
+ var FULL_DIFF_MAX_BUFFER = 50 * 1024 * 1024;
2909
2935
  function appendUniqueUrl(urls, url) {
2910
2936
  return urls.includes(url) ? urls : [...urls, url];
2911
2937
  }
@@ -2915,6 +2941,10 @@ var GitService = class {
2915
2941
  // No invalidation on purpose — `git remote set-url` mid-session is rare and
2916
2942
  // the worst case is `gh pr view` stays skipped until the next engine restart.
2917
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();
2918
2948
  getWorkspaceRoot() {
2919
2949
  return ENGINE_ENV.WORKSPACE_ROOT;
2920
2950
  }
@@ -2940,7 +2970,7 @@ var GitService = class {
2940
2970
  repos.push({
2941
2971
  name: entry,
2942
2972
  path: fullPath,
2943
- defaultBranch: this.resolveDefaultBranch(fullPath)
2973
+ defaultBranch: await this.resolveDefaultBranch(fullPath)
2944
2974
  });
2945
2975
  } catch {
2946
2976
  }
@@ -2949,13 +2979,21 @@ var GitService = class {
2949
2979
  }
2950
2980
  async listRepos(options) {
2951
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) {
2952
2986
  const repos = await this.listRepositories();
2953
2987
  const states = [];
2954
2988
  for (const repo of repos) {
2955
2989
  try {
2956
- const persistedState = await loadRepoState(repo.name);
2957
- const currentBranch = getCurrentBranch(repo.path) ?? repo.defaultBranch;
2958
- 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;
2959
2997
  states.push({
2960
2998
  name: repo.name,
2961
2999
  path: repo.path,
@@ -2963,7 +3001,7 @@ var GitService = class {
2963
3001
  currentBranch,
2964
3002
  prUrls: persistedState?.prUrls ?? [],
2965
3003
  // fullDiff may be empty if the diff subprocess fails.
2966
- gitDiff: includeDiffs && gitDiff ? { ...gitDiff, fullDiff: this.getFullGitDiff(repo.path, repo.defaultBranch) } : gitDiff,
3004
+ gitDiff: includeDiffs && gitDiff ? { ...gitDiff, fullDiff: fullDiff ?? "" } : gitDiff,
2967
3005
  startHooksCompleted: persistedState?.startHooksCompleted ?? false
2968
3006
  });
2969
3007
  } catch {
@@ -2977,7 +3015,7 @@ var GitService = class {
2977
3015
  for (const repo of repos) {
2978
3016
  try {
2979
3017
  const persistedState = await loadRepoState(repo.name);
2980
- const currentBranch = getCurrentBranch(repo.path) ?? repo.defaultBranch;
3018
+ const currentBranch = await getCurrentBranch(repo.path) ?? repo.defaultBranch;
2981
3019
  const startHooksCompleted = persistedState?.startHooksCompleted ?? false;
2982
3020
  const observed = observedBranchesByRepo?.get(repo.name);
2983
3021
  states.push(
@@ -3042,12 +3080,12 @@ var GitService = class {
3042
3080
  const persistedState = await loadRepoState(repo.name);
3043
3081
  const persistedBranch = persistedState?.currentBranch;
3044
3082
  if (!skipNetworkRefresh) {
3045
- runGitCommand(["fetch", "--all", "--prune", `--filter=${GIT_PARTIAL_CLONE_FILTER}`], repo.path);
3083
+ await runGitCommand(["fetch", "--all", "--prune", `--filter=${GIT_PARTIAL_CLONE_FILTER}`], repo.path);
3046
3084
  }
3047
- if (persistedBranch && branchExists(persistedBranch, repo.path)) {
3048
- const currentBranch = getCurrentBranch(repo.path);
3085
+ if (persistedBranch && await branchExists(persistedBranch, repo.path)) {
3086
+ const currentBranch = await getCurrentBranch(repo.path);
3049
3087
  if (currentBranch !== persistedBranch) {
3050
- runGitCommand(["checkout", persistedBranch], repo.path);
3088
+ await runGitCommand(["checkout", persistedBranch], repo.path);
3051
3089
  }
3052
3090
  results.push({
3053
3091
  name: repo.name,
@@ -3057,15 +3095,15 @@ var GitService = class {
3057
3095
  });
3058
3096
  continue;
3059
3097
  }
3060
- runGitCommand(["checkout", repo.defaultBranch], repo.path);
3098
+ await runGitCommand(["checkout", repo.defaultBranch], repo.path);
3061
3099
  if (!skipNetworkRefresh) {
3062
3100
  try {
3063
- runGitCommand(["pull", "--rebase", "--autostash"], repo.path);
3101
+ await runGitCommand(["pull", "--rebase", "--autostash"], repo.path);
3064
3102
  } catch {
3065
3103
  }
3066
3104
  }
3067
- const branchName = this.findAvailableBranchName(workspaceName, repo.path);
3068
- runGitCommand(["checkout", "-b", branchName], repo.path);
3105
+ const branchName = await this.findAvailableBranchName(workspaceName, repo.path);
3106
+ await runGitCommand(["checkout", "-b", branchName], repo.path);
3069
3107
  await saveRepoState(repo.name, { currentBranch: branchName }, baselineState);
3070
3108
  results.push({
3071
3109
  name: repo.name,
@@ -3103,109 +3141,149 @@ var GitService = class {
3103
3141
  return true;
3104
3142
  }
3105
3143
  getGitDiffStats(repoPath, defaultBranch) {
3106
- try {
3107
- const diffBase = this.getDiffBase(repoPath, defaultBranch);
3108
- const shortstat = runGitCommand(["diff", diffBase, "--shortstat", "-M"], repoPath);
3109
- let added = 0;
3110
- let removed = 0;
3111
- const addedMatch = shortstat.match(/(\d+) insertion/);
3112
- const removedMatch = shortstat.match(/(\d+) deletion/);
3113
- if (addedMatch) {
3114
- added = parseInt(addedMatch[1], 10);
3115
- }
3116
- if (removedMatch) {
3117
- removed = parseInt(removedMatch[1], 10);
3118
- }
3119
- added += this.countUntrackedAddedLines(repoPath);
3120
- return {
3121
- added,
3122
- removed
3123
- };
3124
- } catch (error) {
3125
- console.error("Error getting git diff:", error);
3126
- return null;
3127
- }
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
+ });
3128
3168
  }
3129
- listUntrackedPaths(repoPath) {
3130
- 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"], {
3131
3171
  cwd: repoPath,
3132
3172
  encoding: "utf-8",
3133
- stdio: ["pipe", "pipe", "pipe"]
3173
+ maxBuffer: SUBPROCESS_MAX_BUFFER
3134
3174
  });
3135
- return listing.split("\0").filter(Boolean);
3175
+ return stdout.split("\0").filter(Boolean);
3136
3176
  }
3137
- getUntrackedDiff(repoPath) {
3138
- const paths = this.listUntrackedPaths(repoPath);
3177
+ async getUntrackedDiff(repoPath) {
3178
+ const paths = await this.listUntrackedPaths(repoPath);
3139
3179
  if (paths.length === 0) return "";
3140
3180
  try {
3141
- execFileSync2("git", ["add", "--intent-to-add", "--", ...paths], { cwd: repoPath, stdio: ["pipe", "pipe", "pipe"] });
3142
- const result = spawnSync("git", ["diff", "--", ...paths], {
3181
+ await execFileAsync("git", ["add", "--intent-to-add", "--", ...paths], {
3143
3182
  cwd: repoPath,
3144
- encoding: "utf-8",
3145
- maxBuffer: 50 * 1024 * 1024
3183
+ maxBuffer: SUBPROCESS_MAX_BUFFER
3146
3184
  });
3147
- return result.status === 0 || result.status === 1 ? result.stdout : "";
3185
+ return await this.readUntrackedDiff(repoPath, paths);
3148
3186
  } finally {
3149
3187
  try {
3150
- 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
+ });
3151
3192
  } catch {
3152
3193
  }
3153
3194
  }
3154
3195
  }
3155
- 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) {
3156
3229
  try {
3157
- return this.listUntrackedPaths(repoPath).reduce((total, path4) => {
3230
+ const paths = await this.listUntrackedPaths(repoPath);
3231
+ let total = 0;
3232
+ for (const path4 of paths) {
3158
3233
  try {
3159
- const contents = readFileSync3(join5(repoPath, path4));
3234
+ const contents = await readFile2(join5(repoPath, path4));
3160
3235
  if (contents.length === 0 || contents.includes(0)) {
3161
- return total;
3236
+ continue;
3162
3237
  }
3163
3238
  let lines = 0;
3164
3239
  for (const byte of contents) {
3165
3240
  if (byte === 10) lines += 1;
3166
3241
  }
3167
- return total + lines + (contents[contents.length - 1] === 10 ? 0 : 1);
3242
+ total += lines + (contents[contents.length - 1] === 10 ? 0 : 1);
3168
3243
  } catch {
3169
- return total;
3170
3244
  }
3171
- }, 0);
3245
+ }
3246
+ return total;
3172
3247
  } catch (error) {
3173
3248
  console.error("Error counting untracked added lines:", error);
3174
3249
  return 0;
3175
3250
  }
3176
3251
  }
3177
3252
  getFullGitDiff(repoPath, defaultBranch) {
3178
- try {
3179
- const diffBase = this.getDiffBase(repoPath, defaultBranch);
3180
- const trackedDiff = execFileSync2("git", ["diff", diffBase, "-M", "-C"], {
3181
- cwd: repoPath,
3182
- encoding: "utf-8",
3183
- stdio: ["pipe", "pipe", "pipe"]
3184
- });
3185
- return trackedDiff + this.getUntrackedAsDiff(repoPath);
3186
- } catch {
3187
- return "";
3188
- }
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
+ });
3189
3267
  }
3190
- getUntrackedAsDiff(repoPath) {
3268
+ async getUntrackedAsDiff(repoPath) {
3191
3269
  try {
3192
- return this.getUntrackedDiff(repoPath);
3270
+ return await this.getUntrackedDiff(repoPath);
3193
3271
  } catch (error) {
3194
3272
  console.error("Error building untracked diff:", error);
3195
3273
  return "";
3196
3274
  }
3197
3275
  }
3198
- getDiffBase(repoPath, defaultBranch) {
3276
+ async getDiffBase(repoPath, defaultBranch) {
3199
3277
  const baseBranch = `origin/${defaultBranch}`;
3200
3278
  try {
3201
- return runGitCommand(["merge-base", baseBranch, "HEAD"], repoPath);
3279
+ return await runGitCommand(["merge-base", baseBranch, "HEAD"], repoPath);
3202
3280
  } catch {
3203
3281
  return baseBranch;
3204
3282
  }
3205
3283
  }
3206
3284
  async getPullRequestUrl(repoName, repoPath, currentBranchArg, persistedRepoStateArg) {
3207
3285
  try {
3208
- const currentBranch = currentBranchArg ?? getCurrentBranch(repoPath);
3286
+ const currentBranch = currentBranchArg ?? await getCurrentBranch(repoPath);
3209
3287
  if (!currentBranch) {
3210
3288
  return { status: "not_found" };
3211
3289
  }
@@ -3215,7 +3293,7 @@ var GitService = class {
3215
3293
  }
3216
3294
  const persistedRepoState = persistedRepoStateArg ?? await loadRepoState(repoName);
3217
3295
  this.cachedPrByRepo.delete(repoName);
3218
- const result = this.lookupPrOnRemote(repoName, repoPath, currentBranch);
3296
+ const result = await this.lookupPrOnRemote(repoName, repoPath, currentBranch);
3219
3297
  if (result.status === "found") {
3220
3298
  this.cachedPrByRepo.set(repoName, { prUrl: result.url, currentBranch });
3221
3299
  if (persistedRepoState && !persistedRepoState.prUrls.includes(result.url)) {
@@ -3232,28 +3310,29 @@ var GitService = class {
3232
3310
  return { status: "error" };
3233
3311
  }
3234
3312
  }
3235
- lookupPrOnRemote(repoName, repoPath, branch) {
3236
- if (!this.originIsGitHub(repoPath)) {
3313
+ async lookupPrOnRemote(repoName, repoPath, branch) {
3314
+ if (!await this.originIsGitHub(repoPath)) {
3237
3315
  return { status: "not_found" };
3238
3316
  }
3239
3317
  try {
3240
- const remoteRef = execFileSync2("git", ["ls-remote", "--heads", "origin", branch], {
3318
+ const { stdout } = await execFileAsync("git", ["ls-remote", "--heads", "origin", branch], {
3241
3319
  cwd: repoPath,
3242
3320
  encoding: "utf-8",
3243
- stdio: ["pipe", "pipe", "pipe"]
3244
- }).trim();
3245
- if (!remoteRef) {
3321
+ maxBuffer: SUBPROCESS_MAX_BUFFER
3322
+ });
3323
+ if (!stdout.trim()) {
3246
3324
  return { status: "not_found" };
3247
3325
  }
3248
3326
  } catch {
3249
3327
  return { status: "error" };
3250
3328
  }
3251
3329
  try {
3252
- const prInfo = execFileSync2("gh", ["pr", "view", branch, "--json", "url", "--jq", ".url"], {
3330
+ const { stdout } = await execFileAsync("gh", ["pr", "view", branch, "--json", "url", "--jq", ".url"], {
3253
3331
  cwd: repoPath,
3254
3332
  encoding: "utf-8",
3255
- stdio: ["pipe", "pipe", "pipe"]
3256
- }).trim();
3333
+ maxBuffer: SUBPROCESS_MAX_BUFFER
3334
+ });
3335
+ const prInfo = stdout.trim();
3257
3336
  return prInfo ? { status: "found", url: prInfo } : { status: "not_found" };
3258
3337
  } catch (error) {
3259
3338
  const message = error instanceof Error ? error.message : String(error);
@@ -3261,31 +3340,31 @@ var GitService = class {
3261
3340
  return { status: "error" };
3262
3341
  }
3263
3342
  }
3264
- originIsGitHub(repoPath) {
3343
+ async originIsGitHub(repoPath) {
3265
3344
  const cached = this.originIsGitHubCache.get(repoPath);
3266
3345
  if (cached !== void 0) {
3267
3346
  return cached;
3268
3347
  }
3269
3348
  let isGitHub = false;
3270
3349
  try {
3271
- const url = execFileSync2("git", ["remote", "get-url", "origin"], {
3350
+ const { stdout } = await execFileAsync("git", ["remote", "get-url", "origin"], {
3272
3351
  cwd: repoPath,
3273
3352
  encoding: "utf-8",
3274
- stdio: ["pipe", "pipe", "pipe"]
3275
- }).trim();
3276
- isGitHub = isGitHubUrl(url);
3353
+ maxBuffer: SUBPROCESS_MAX_BUFFER
3354
+ });
3355
+ isGitHub = isGitHubUrl(stdout.trim());
3277
3356
  } catch {
3278
3357
  isGitHub = false;
3279
3358
  }
3280
3359
  this.originIsGitHubCache.set(repoPath, isGitHub);
3281
3360
  return isGitHub;
3282
3361
  }
3283
- resolveDefaultBranch(repoPath) {
3362
+ async resolveDefaultBranch(repoPath) {
3284
3363
  const cached = this.defaultBranchCache.get(repoPath);
3285
3364
  if (cached) {
3286
3365
  return cached;
3287
3366
  }
3288
- const fromSymbolicRef = this.resolveDefaultBranchFromSymbolicRef(repoPath);
3367
+ const fromSymbolicRef = await this.resolveDefaultBranchFromSymbolicRef(repoPath);
3289
3368
  if (fromSymbolicRef) {
3290
3369
  this.defaultBranchCache.set(repoPath, fromSymbolicRef);
3291
3370
  return fromSymbolicRef;
@@ -3294,18 +3373,18 @@ var GitService = class {
3294
3373
  this.defaultBranchCache.set(repoPath, fallback);
3295
3374
  return fallback;
3296
3375
  }
3297
- resolveDefaultBranchFromSymbolicRef(repoPath) {
3376
+ async resolveDefaultBranchFromSymbolicRef(repoPath) {
3298
3377
  try {
3299
- const output = runGitCommand(["symbolic-ref", "--short", "refs/remotes/origin/HEAD"], repoPath);
3378
+ const output = await runGitCommand(["symbolic-ref", "--short", "refs/remotes/origin/HEAD"], repoPath);
3300
3379
  const match = output.match(/^origin\/(.+)$/);
3301
3380
  return match ? match[1] : null;
3302
3381
  } catch {
3303
3382
  return null;
3304
3383
  }
3305
3384
  }
3306
- findAvailableBranchName(baseName, cwd) {
3385
+ async findAvailableBranchName(baseName, cwd) {
3307
3386
  const sanitizedBaseName = this.sanitizeBranchName(baseName);
3308
- if (!branchExists(sanitizedBaseName, cwd)) {
3387
+ if (!await branchExists(sanitizedBaseName, cwd)) {
3309
3388
  return sanitizedBaseName;
3310
3389
  }
3311
3390
  return `${sanitizedBaseName}-${Date.now()}`;
@@ -3323,7 +3402,7 @@ var GitService = class {
3323
3402
  if (observedBranches) {
3324
3403
  for (const branch of observedBranches) {
3325
3404
  if (branch === currentBranch) continue;
3326
- const branchResult = this.lookupPrOnRemote(repo.name, repo.path, branch);
3405
+ const branchResult = await this.lookupPrOnRemote(repo.name, repo.path, branch);
3327
3406
  if (branchResult.status === "found") {
3328
3407
  prUrls = appendUniqueUrl(prUrls, branchResult.url);
3329
3408
  }
@@ -3335,7 +3414,7 @@ var GitService = class {
3335
3414
  defaultBranch: repo.defaultBranch,
3336
3415
  currentBranch,
3337
3416
  prUrls,
3338
- gitDiff: this.getGitDiffStats(repo.path, repo.defaultBranch),
3417
+ gitDiff: await this.getGitDiffStats(repo.path, repo.defaultBranch),
3339
3418
  startHooksCompleted
3340
3419
  };
3341
3420
  await saveRepoState(repo.name, state, state);
@@ -3423,20 +3502,17 @@ var EngineLogger = class {
3423
3502
  var engineLogger = new EngineLogger();
3424
3503
 
3425
3504
  // src/services/replicas-config-service.ts
3426
- 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";
3427
3506
  import { existsSync as existsSync4 } from "fs";
3428
3507
  import { join as join9 } from "path";
3429
3508
  import { homedir as homedir7 } from "os";
3430
- import { spawn } from "child_process";
3509
+ import { spawn as spawn2 } from "child_process";
3431
3510
 
3432
3511
  // src/services/environment-details-service.ts
3433
- import { mkdir as mkdir3, readFile as readFile2 } from "fs/promises";
3512
+ import { mkdir as mkdir3, readFile as readFile3 } from "fs/promises";
3434
3513
  import { existsSync as existsSync3 } from "fs";
3435
3514
  import { homedir as homedir5 } from "os";
3436
3515
  import { join as join7 } from "path";
3437
- import { execFile } from "child_process";
3438
- import { promisify } from "util";
3439
- var execFileAsync = promisify(execFile);
3440
3516
  var REPLICAS_DIR = join7(homedir5(), ".replicas");
3441
3517
  var DETAILS_FILE = join7(REPLICAS_DIR, "environment-details.json");
3442
3518
  var CLAUDE_CREDENTIALS_PATH = join7(homedir5(), ".claude", ".credentials.json");
@@ -3509,7 +3585,7 @@ async function readDetails() {
3509
3585
  if (!existsSync3(DETAILS_FILE)) {
3510
3586
  return createDefaultDetails();
3511
3587
  }
3512
- const raw = await readFile2(DETAILS_FILE, "utf-8");
3588
+ const raw = await readFile3(DETAILS_FILE, "utf-8");
3513
3589
  const parsed = JSON.parse(raw);
3514
3590
  return { ...createDefaultDetails(), ...parsed };
3515
3591
  } catch {
@@ -3603,7 +3679,7 @@ var EnvironmentDetailsService = class {
3603
3679
  var environmentDetailsService = new EnvironmentDetailsService();
3604
3680
 
3605
3681
  // src/services/start-hook-logs-service.ts
3606
- 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";
3607
3683
  import { homedir as homedir6 } from "os";
3608
3684
  import { join as join8 } from "path";
3609
3685
 
@@ -3680,7 +3756,7 @@ var StartHookLogsService = class {
3680
3756
  continue;
3681
3757
  }
3682
3758
  try {
3683
- const raw = await readFile3(join8(LOGS_DIR, file), "utf-8");
3759
+ const raw = await readFile4(join8(LOGS_DIR, file), "utf-8");
3684
3760
  const stored = normalizeStored(JSON.parse(raw));
3685
3761
  if (stored) {
3686
3762
  logs.push(withPreview(stored));
@@ -3699,7 +3775,7 @@ var StartHookLogsService = class {
3699
3775
  async getFullOutput(hookType, hookName) {
3700
3776
  const filename = hookType === "environment" ? ENVIRONMENT_HOOK_LOG_FILENAME : repoHookLogFilename(hookName);
3701
3777
  try {
3702
- const raw = await readFile3(join8(LOGS_DIR, filename), "utf-8");
3778
+ const raw = await readFile4(join8(LOGS_DIR, filename), "utf-8");
3703
3779
  const stored = normalizeStored(JSON.parse(raw));
3704
3780
  if (!stored || stored.hookType !== hookType || stored.hookName !== hookName) {
3705
3781
  return null;
@@ -3731,7 +3807,7 @@ async function readReplicasConfigFromDir(dirPath) {
3731
3807
  if (!existsSync4(configPath)) {
3732
3808
  continue;
3733
3809
  }
3734
- const data = await readFile4(configPath, "utf-8");
3810
+ const data = await readFile5(configPath, "utf-8");
3735
3811
  const config = parseReplicasConfigString(data, filename);
3736
3812
  return { config, filename };
3737
3813
  }
@@ -3833,7 +3909,7 @@ var ReplicasConfigService = class {
3833
3909
  emit(`$ ${params.label}
3834
3910
  `);
3835
3911
  return new Promise((resolve3) => {
3836
- const proc = spawn("bash", ["-lc", params.command], {
3912
+ const proc = spawn2("bash", ["-lc", params.command], {
3837
3913
  cwd: params.cwd,
3838
3914
  env: process.env,
3839
3915
  stdio: ["pipe", "pipe", "pipe"]
@@ -4166,7 +4242,7 @@ var EventService = class {
4166
4242
  var eventService = new EventService();
4167
4243
 
4168
4244
  // src/services/preview-service.ts
4169
- import { mkdir as mkdir7, readFile as readFile5 } from "fs/promises";
4245
+ import { mkdir as mkdir7, readFile as readFile6 } from "fs/promises";
4170
4246
  import { existsSync as existsSync5 } from "fs";
4171
4247
  import { randomUUID as randomUUID2 } from "crypto";
4172
4248
  import { homedir as homedir9 } from "os";
@@ -4177,7 +4253,7 @@ async function readPreviewsFile() {
4177
4253
  if (!existsSync5(PREVIEW_PORTS_FILE)) {
4178
4254
  return { previews: [] };
4179
4255
  }
4180
- const raw = await readFile5(PREVIEW_PORTS_FILE, "utf-8");
4256
+ const raw = await readFile6(PREVIEW_PORTS_FILE, "utf-8");
4181
4257
  return JSON.parse(raw);
4182
4258
  } catch {
4183
4259
  return { previews: [] };
@@ -4282,7 +4358,7 @@ async function registerDesktopPreview() {
4282
4358
 
4283
4359
  // src/services/chat/chat-service.ts
4284
4360
  import { existsSync as existsSync8 } from "fs";
4285
- 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";
4286
4362
  import { homedir as homedir13 } from "os";
4287
4363
  import { join as join15 } from "path";
4288
4364
  import { randomUUID as randomUUID5 } from "crypto";
@@ -4297,7 +4373,7 @@ import { mkdir as mkdir9, appendFile as appendFile4 } from "fs/promises";
4297
4373
  import { homedir as homedir11 } from "os";
4298
4374
 
4299
4375
  // src/utils/jsonl-reader.ts
4300
- import { readFile as readFile6 } from "fs/promises";
4376
+ import { readFile as readFile7 } from "fs/promises";
4301
4377
  function isJsonlEvent(value) {
4302
4378
  if (!isRecord4(value)) {
4303
4379
  return false;
@@ -4319,7 +4395,7 @@ function parseJsonlEvents(lines) {
4319
4395
  }
4320
4396
  async function readJSONL(filePath) {
4321
4397
  try {
4322
- const content = await readFile6(filePath, "utf-8");
4398
+ const content = await readFile7(filePath, "utf-8");
4323
4399
  const lines = content.split("\n").filter((line) => line.trim());
4324
4400
  return parseJsonlEvents(lines);
4325
4401
  } catch (error) {
@@ -6070,7 +6146,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
6070
6146
  };
6071
6147
 
6072
6148
  // src/managers/codex-asp/app-server-process.ts
6073
- import { spawn as spawn2 } from "child_process";
6149
+ import { spawn as spawn3 } from "child_process";
6074
6150
  import { EventEmitter as EventEmitter2 } from "events";
6075
6151
 
6076
6152
  // src/managers/codex-asp/asp-client.ts
@@ -6265,7 +6341,7 @@ var AspClient = class {
6265
6341
  // src/managers/codex-asp/app-server-process.ts
6266
6342
  var DEFAULT_CODEX_BINARY = "codex";
6267
6343
  var DEFAULT_CODEX_ARGS = ["app-server", "--listen", "stdio://"];
6268
- var ENGINE_PACKAGE_VERSION = "0.1.257";
6344
+ var ENGINE_PACKAGE_VERSION = "0.1.258";
6269
6345
  var INITIALIZE_METHOD = "initialize";
6270
6346
  var INITIALIZED_NOTIFICATION = "initialized";
6271
6347
  var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
@@ -6292,7 +6368,7 @@ var AppServerProcess = class {
6292
6368
  return { client: this.client };
6293
6369
  }
6294
6370
  this.shuttingDown = false;
6295
- const child = spawn2(this.binary, this.args, {
6371
+ const child = spawn3(this.binary, this.args, {
6296
6372
  cwd: this.cwd,
6297
6373
  env: this.env,
6298
6374
  stdio: ["pipe", "pipe", "pipe"]
@@ -6532,7 +6608,7 @@ function isCodexAuthError(error) {
6532
6608
  }
6533
6609
 
6534
6610
  // src/managers/codex-asp/mappers.ts
6535
- import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
6611
+ import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
6536
6612
  var localImageCache = /* @__PURE__ */ new Map();
6537
6613
  var DEFAULT_MODEL = DEFAULT_CODEX_MODEL;
6538
6614
  var THREAD_START_METHOD = "thread/start";
@@ -6613,7 +6689,7 @@ function userImageForLocalPath(path4) {
6613
6689
  const image = {
6614
6690
  type: "image",
6615
6691
  mediaType: inferMediaType(path4),
6616
- data: readFileSync4(path4).toString("base64")
6692
+ data: readFileSync3(path4).toString("base64")
6617
6693
  };
6618
6694
  if (image.data.length > 0) localImageCache.set(path4, image);
6619
6695
  return image;
@@ -7885,7 +7961,7 @@ var CodexAspManager = class extends CodingAgentManager {
7885
7961
 
7886
7962
  // src/managers/codex-manager.ts
7887
7963
  import { Codex } from "@openai/codex-sdk";
7888
- 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";
7889
7965
  import { existsSync as existsSync7 } from "fs";
7890
7966
  import { join as join14 } from "path";
7891
7967
  import { homedir as homedir12 } from "os";
@@ -7933,7 +8009,7 @@ var CodexManager = class extends CodingAgentManager {
7933
8009
  try {
7934
8010
  const sessionFile = await this.findSessionFile(this.currentThreadId);
7935
8011
  if (!sessionFile) return;
7936
- const content = await readFile7(sessionFile, "utf-8");
8012
+ const content = await readFile8(sessionFile, "utf-8");
7937
8013
  const lines = content.split("\n").map((line) => line.trim()).filter(Boolean);
7938
8014
  let latest = null;
7939
8015
  for (const line of lines) {
@@ -7973,7 +8049,7 @@ var CodexManager = class extends CodingAgentManager {
7973
8049
  let config = {};
7974
8050
  if (existsSync7(CODEX_CONFIG_PATH)) {
7975
8051
  try {
7976
- const existingContent = await readFile7(CODEX_CONFIG_PATH, "utf-8");
8052
+ const existingContent = await readFile8(CODEX_CONFIG_PATH, "utf-8");
7977
8053
  const parsed = parseToml(existingContent);
7978
8054
  if (isRecord4(parsed)) {
7979
8055
  config = parsed;
@@ -8216,7 +8292,7 @@ var CodexManager = class extends CodingAgentManager {
8216
8292
  const seenLines = /* @__PURE__ */ new Set();
8217
8293
  const seedSeenLines = async () => {
8218
8294
  try {
8219
- const content = await readFile7(sessionFile, "utf-8");
8295
+ const content = await readFile8(sessionFile, "utf-8");
8220
8296
  const lines = content.split("\n").map((line) => line.trim()).filter(Boolean);
8221
8297
  let latest = null;
8222
8298
  for (const line of lines) {
@@ -8238,7 +8314,7 @@ var CodexManager = class extends CodingAgentManager {
8238
8314
  const pump = async () => {
8239
8315
  let emitted = 0;
8240
8316
  try {
8241
- const content = await readFile7(sessionFile, "utf-8");
8317
+ const content = await readFile8(sessionFile, "utf-8");
8242
8318
  const lines = content.split("\n");
8243
8319
  const completeLines = content.endsWith("\n") ? lines : lines.slice(0, -1);
8244
8320
  for (const line of completeLines) {
@@ -9101,7 +9177,7 @@ var ChatService = class {
9101
9177
  }
9102
9178
  async readSenders(chatId) {
9103
9179
  try {
9104
- const content = await readFile8(this.senderFilePath(chatId), "utf-8");
9180
+ const content = await readFile9(this.senderFilePath(chatId), "utf-8");
9105
9181
  const lines = content.split("\n").filter((line) => line.trim().length > 0);
9106
9182
  const senders = [];
9107
9183
  for (const line of lines) {
@@ -9458,7 +9534,7 @@ var ChatService = class {
9458
9534
  }
9459
9535
  async loadChats() {
9460
9536
  try {
9461
- const content = await readFile8(CHATS_FILE, "utf-8");
9537
+ const content = await readFile9(CHATS_FILE, "utf-8");
9462
9538
  return parsePersistedChatsContent(content);
9463
9539
  } catch (error) {
9464
9540
  if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
@@ -9473,7 +9549,7 @@ var ChatService = class {
9473
9549
  console.error("[ChatService] Failed to quarantine corrupt chats file:", renameError);
9474
9550
  }
9475
9551
  try {
9476
- const backupContent = await readFile8(CHATS_BACKUP_FILE, "utf-8");
9552
+ const backupContent = await readFile9(CHATS_BACKUP_FILE, "utf-8");
9477
9553
  return parsePersistedChatsContent(backupContent);
9478
9554
  } catch (backupError) {
9479
9555
  if (backupError && typeof backupError === "object" && "code" in backupError && backupError.code === "ENOENT") {
@@ -9548,7 +9624,7 @@ var ChatService = class {
9548
9624
 
9549
9625
  // src/services/repo-file-service.ts
9550
9626
  import { execFile as execFile2 } from "child_process";
9551
- import { readFile as readFile9, realpath, stat as stat3 } from "fs/promises";
9627
+ import { readFile as readFile10, realpath, stat as stat3 } from "fs/promises";
9552
9628
  import { join as join16, resolve, extname } from "path";
9553
9629
  var CACHE_TTL_MS = 3e4;
9554
9630
  var SEARCH_TIMEOUT_MS = 15e3;
@@ -9738,7 +9814,7 @@ var RepoFileService = class {
9738
9814
  tooLarge: true
9739
9815
  };
9740
9816
  }
9741
- const content = await readFile9(fullPath, "utf-8");
9817
+ const content = await readFile10(fullPath, "utf-8");
9742
9818
  return {
9743
9819
  repoName,
9744
9820
  path: filePath,
@@ -9820,11 +9896,11 @@ var RepoFileService = class {
9820
9896
  // src/v1-routes.ts
9821
9897
  import { Hono } from "hono";
9822
9898
  import { z as z2 } from "zod";
9823
- 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";
9824
9900
  import { join as join20, resolve as resolve2 } from "path";
9825
9901
 
9826
9902
  // src/services/plan-service.ts
9827
- import { readdir as readdir4, readFile as readFile10 } from "fs/promises";
9903
+ import { readdir as readdir4, readFile as readFile11 } from "fs/promises";
9828
9904
  import { homedir as homedir14 } from "os";
9829
9905
  import { basename, join as join17 } from "path";
9830
9906
  var PLAN_DIRECTORIES = [
@@ -9865,7 +9941,7 @@ var PlanService = class {
9865
9941
  for (const directory of PLAN_DIRECTORIES) {
9866
9942
  const filePath = join17(directory, safeFilename);
9867
9943
  try {
9868
- const content = await readFile10(filePath, "utf-8");
9944
+ const content = await readFile11(filePath, "utf-8");
9869
9945
  return { filename: safeFilename, content };
9870
9946
  } catch {
9871
9947
  }
@@ -9876,13 +9952,13 @@ var PlanService = class {
9876
9952
  var planService = new PlanService();
9877
9953
 
9878
9954
  // src/services/warm-hooks-service.ts
9879
- import { spawn as spawn3 } from "child_process";
9880
- import { readFile as readFile12 } from "fs/promises";
9955
+ import { spawn as spawn4 } from "child_process";
9956
+ import { readFile as readFile13 } from "fs/promises";
9881
9957
  import { existsSync as existsSync9 } from "fs";
9882
9958
  import { join as join19 } from "path";
9883
9959
 
9884
9960
  // src/services/warm-hook-logs-service.ts
9885
- 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";
9886
9962
  import { homedir as homedir15 } from "os";
9887
9963
  import { join as join18 } from "path";
9888
9964
  var LOGS_DIR2 = join18(homedir15(), ".replicas", "warm-hook-logs");
@@ -9942,7 +10018,7 @@ var WarmHookLogsService = class {
9942
10018
  continue;
9943
10019
  }
9944
10020
  try {
9945
- const raw = await readFile11(join18(LOGS_DIR2, file), "utf-8");
10021
+ const raw = await readFile12(join18(LOGS_DIR2, file), "utf-8");
9946
10022
  const stored = JSON.parse(raw);
9947
10023
  logs.push(withPreview2(stored));
9948
10024
  } catch {
@@ -9971,7 +10047,7 @@ var WarmHookLogsService = class {
9971
10047
  }
9972
10048
  async getCurrentRunLog() {
9973
10049
  try {
9974
- return await readFile11(CURRENT_RUN_LOG, "utf-8");
10050
+ return await readFile12(CURRENT_RUN_LOG, "utf-8");
9975
10051
  } catch (err) {
9976
10052
  if (err.code === "ENOENT") return null;
9977
10053
  throw err;
@@ -9980,7 +10056,7 @@ var WarmHookLogsService = class {
9980
10056
  async getFullOutput(hookType, hookName) {
9981
10057
  const filename = hookType === "global" ? GLOBAL_FILENAME : hookType === "environment" ? ENVIRONMENT_HOOK_LOG_FILENAME : repoHookLogFilename(hookName);
9982
10058
  try {
9983
- const raw = await readFile11(join18(LOGS_DIR2, filename), "utf-8");
10059
+ const raw = await readFile12(join18(LOGS_DIR2, filename), "utf-8");
9984
10060
  const stored = JSON.parse(raw);
9985
10061
  if (stored.hookType !== hookType || stored.hookName !== hookName) {
9986
10062
  return null;
@@ -10004,7 +10080,7 @@ async function readRepoWarmHook(repoPath) {
10004
10080
  continue;
10005
10081
  }
10006
10082
  try {
10007
- const raw = await readFile12(configPath, "utf-8");
10083
+ const raw = await readFile13(configPath, "utf-8");
10008
10084
  const config = parseReplicasConfigString(raw, filename);
10009
10085
  if (!config.warmHook) {
10010
10086
  return null;
@@ -10051,7 +10127,7 @@ async function executeHookScriptStreaming(params) {
10051
10127
  params.onChunk(`$ ${params.label}
10052
10128
  `);
10053
10129
  return new Promise((resolve3) => {
10054
- const proc = spawn3("bash", ["-lc", params.content], {
10130
+ const proc = spawn4("bash", ["-lc", params.content], {
10055
10131
  cwd: params.cwd,
10056
10132
  env: process.env,
10057
10133
  stdio: ["pipe", "pipe", "pipe"]
@@ -10934,7 +11010,7 @@ function createV1Routes(deps) {
10934
11010
  const limit = Math.min(parseInt(c.req.query("limit") || "500", 10), 5e3);
10935
11011
  let content;
10936
11012
  try {
10937
- content = await readFile13(filePath, "utf-8");
11013
+ content = await readFile14(filePath, "utf-8");
10938
11014
  } catch {
10939
11015
  return c.json(jsonError("Log session not found"), 404);
10940
11016
  }
@@ -10974,10 +11050,10 @@ process.on("unhandledRejection", (reason) => {
10974
11050
  engineLogger.flush().finally(() => process.exit(1));
10975
11051
  });
10976
11052
  await eventService.initialize();
10977
- function checkActiveSSHSessions() {
11053
+ async function checkActiveSSHSessions() {
10978
11054
  try {
10979
- const output = execSync2('who | grep -v "^$" | wc -l', { encoding: "utf-8" });
10980
- 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);
10981
11057
  return sessionCount > 0;
10982
11058
  } catch {
10983
11059
  return false;
@@ -11010,10 +11086,13 @@ app.get("/health", (c) => {
11010
11086
  app.use("*", authMiddleware);
11011
11087
  app.get("/status", async (c) => {
11012
11088
  try {
11013
- const repos = await gitService.listRepos();
11089
+ const [repos, hasActiveSSHSessions] = await Promise.all([
11090
+ gitService.listRepos(),
11091
+ checkActiveSSHSessions()
11092
+ ]);
11014
11093
  const status = {
11015
11094
  uptimeSeconds: Math.floor((Date.now() - bootTimeMs) / 1e3),
11016
- hasActiveSSHSessions: checkActiveSSHSessions(),
11095
+ hasActiveSSHSessions,
11017
11096
  activeSseClients: eventService.getSubscriberCount(),
11018
11097
  chatsTotal: chatService.listChats().length,
11019
11098
  chatsProcessing: chatService.getProcessingCount(),
@@ -11043,8 +11122,16 @@ function startStatusBroadcaster() {
11043
11122
  let previousHookStatus = "";
11044
11123
  let previousEngineStatus = "";
11045
11124
  let lastHooksRunning = replicasConfigService.areHooksRunning();
11046
- setInterval(() => {
11047
- 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
+ ]);
11048
11135
  const serialized = JSON.stringify(repos);
11049
11136
  if (serialized !== previousRepoStatus) {
11050
11137
  previousRepoStatus = serialized;
@@ -11058,7 +11145,7 @@ function startStatusBroadcaster() {
11058
11145
  }
11059
11146
  const engineStatus = {
11060
11147
  uptimeSeconds: Math.floor((Date.now() - bootTimeMs) / 1e3),
11061
- hasActiveSSHSessions: checkActiveSSHSessions(),
11148
+ hasActiveSSHSessions,
11062
11149
  activeSseClients: eventService.getSubscriberCount(),
11063
11150
  chatsTotal: chatService.listChats().length,
11064
11151
  chatsProcessing: chatService.getProcessingCount(),
@@ -11076,67 +11163,70 @@ function startStatusBroadcaster() {
11076
11163
  }).catch(() => {
11077
11164
  });
11078
11165
  }
11079
- }).catch(() => {
11080
- });
11081
- const hooksRunning = replicasConfigService.areHooksRunning();
11082
- const hooksCompleted = replicasConfigService.areHooksCompleted();
11083
- const hooksFailed = replicasConfigService.didHooksFail();
11084
- const hookSnapshot = JSON.stringify({
11085
- running: hooksRunning,
11086
- completed: hooksCompleted,
11087
- failed: hooksFailed
11088
- });
11089
- if (hookSnapshot !== previousHookStatus) {
11090
- previousHookStatus = hookSnapshot;
11091
- if (!lastHooksRunning && hooksRunning) {
11092
- eventService.publish({
11093
- id: randomUUID6(),
11094
- ts: (/* @__PURE__ */ new Date()).toISOString(),
11095
- type: "hooks.started",
11096
- payload: { running: true, completed: false }
11097
- }).catch(() => {
11098
- });
11099
- }
11100
- if (hooksRunning) {
11101
- eventService.publish({
11102
- id: randomUUID6(),
11103
- ts: (/* @__PURE__ */ new Date()).toISOString(),
11104
- type: "hooks.progress",
11105
- payload: { running: true, completed: false }
11106
- }).catch(() => {
11107
- });
11108
- }
11109
- if (lastHooksRunning && !hooksRunning && hooksCompleted && !hooksFailed) {
11110
- eventService.publish({
11111
- id: randomUUID6(),
11112
- ts: (/* @__PURE__ */ new Date()).toISOString(),
11113
- type: "hooks.completed",
11114
- payload: { running: false, completed: true }
11115
- }).catch(() => {
11116
- });
11117
- }
11118
- 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
+ }
11119
11212
  eventService.publish({
11120
11213
  id: randomUUID6(),
11121
11214
  ts: (/* @__PURE__ */ new Date()).toISOString(),
11122
- type: "hooks.failed",
11123
- payload: { running: false, completed: hooksCompleted }
11215
+ type: "hooks.status",
11216
+ payload: {
11217
+ running: hooksRunning,
11218
+ completed: hooksCompleted
11219
+ }
11124
11220
  }).catch(() => {
11125
11221
  });
11222
+ lastHooksRunning = hooksRunning;
11126
11223
  }
11127
- eventService.publish({
11128
- id: randomUUID6(),
11129
- ts: (/* @__PURE__ */ new Date()).toISOString(),
11130
- type: "hooks.status",
11131
- payload: {
11132
- running: hooksRunning,
11133
- completed: hooksCompleted
11134
- }
11135
- }).catch(() => {
11136
- });
11137
- lastHooksRunning = hooksRunning;
11224
+ } catch {
11225
+ } finally {
11226
+ scheduleNext();
11138
11227
  }
11139
- }, 2e3);
11228
+ };
11229
+ scheduleNext();
11140
11230
  }
11141
11231
  serve(
11142
11232
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.257",
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",