replicas-engine 0.1.257 → 0.1.259

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 +329 -227
  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) {
@@ -137,7 +127,7 @@ function detectLanguageByPath(filePath) {
137
127
 
138
128
  // ../shared/src/context-usage.ts
139
129
  var CODEX_CATEGORY_COLORS = {
140
- input: "#66bb6a",
130
+ input: "#3eeba3",
141
131
  output: "#56b6c2"
142
132
  };
143
133
  function clampPercentage(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-04-v1";
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).
@@ -1634,6 +1643,7 @@ var DEFAULT_DEFAULT_SKILLS = {
1634
1643
  };
1635
1644
 
1636
1645
  // ../shared/src/prompts.ts
1646
+ var MAX_CODEX_GOAL_OBJECTIVE_CHARS = 4e3;
1637
1647
  function parseGoalCommand(message) {
1638
1648
  const match = message.trim().match(/^\/goal(?:\s+([\s\S]*))?$/i);
1639
1649
  const value = match?.[1]?.trim();
@@ -1641,6 +1651,13 @@ function parseGoalCommand(message) {
1641
1651
  if (/^(clear|reset|unset)$/i.test(value)) return { type: "clear" };
1642
1652
  return { type: "set", objective: value };
1643
1653
  }
1654
+ function getGoalCommandObjectiveValidationError(message) {
1655
+ const command = parseGoalCommand(message);
1656
+ if (command?.type !== "set") return null;
1657
+ const length = command.objective.length;
1658
+ if (length <= MAX_CODEX_GOAL_OBJECTIVE_CHARS) return null;
1659
+ return `Goal objective must be at most ${MAX_CODEX_GOAL_OBJECTIVE_CHARS} characters (${length} provided). Shorten the /goal objective, or send the extra details as a regular message after setting the goal.`;
1660
+ }
1644
1661
 
1645
1662
  // ../shared/src/replicas-config.ts
1646
1663
  import { parse as parseYaml } from "yaml";
@@ -2240,7 +2257,23 @@ function isGitHubUrl(url) {
2240
2257
  return GITHUB_HOST_RE.test(url);
2241
2258
  }
2242
2259
 
2260
+ // src/utils/exec.ts
2261
+ var execAsync = promisify(exec);
2262
+ var execFileAsync = promisify(execFile);
2263
+ var SUBPROCESS_MAX_BUFFER = HOOK_EXEC_MAX_BUFFER_BYTES;
2264
+
2265
+ // src/managers/github-token-manager.ts
2266
+ import { promises as fs } from "fs";
2267
+ import path from "path";
2268
+
2269
+ // src/engine-env.ts
2270
+ import { homedir as homedir2 } from "os";
2271
+ import { join as join2 } from "path";
2272
+
2243
2273
  // src/runtime-env-loader.ts
2274
+ import { readFileSync } from "fs";
2275
+ import { homedir } from "os";
2276
+ import { join } from "path";
2244
2277
  function loadRuntimeEnvFile() {
2245
2278
  let content;
2246
2279
  try {
@@ -2736,9 +2769,9 @@ var CodexTokenManager = class extends BaseRefreshManager {
2736
2769
  var codexTokenManager = new CodexTokenManager();
2737
2770
 
2738
2771
  // 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";
2772
+ import { readdir, readFile as readFile2, stat } from "fs/promises";
2773
+ import { existsSync as existsSync2, unlinkSync } from "fs";
2774
+ import { spawn } from "child_process";
2742
2775
  import { join as join5 } from "path";
2743
2776
 
2744
2777
  // src/utils/state.ts
@@ -2869,15 +2902,15 @@ async function saveRepoState(repoName, state, fallbackState) {
2869
2902
  }
2870
2903
 
2871
2904
  // src/git/commands.ts
2872
- import { execFileSync } from "child_process";
2873
2905
  import { readFileSync as readFileSync2 } from "fs";
2874
2906
  import { join as join4 } from "path";
2875
- function runGitCommand(args, cwd) {
2876
- return execFileSync("git", args, {
2907
+ async function runGitCommand(args, cwd, options = {}) {
2908
+ const { stdout } = await execFileAsync("git", args, {
2877
2909
  cwd,
2878
2910
  encoding: "utf-8",
2879
- stdio: ["pipe", "pipe", "pipe"]
2880
- }).trim();
2911
+ maxBuffer: options.maxBuffer ?? SUBPROCESS_MAX_BUFFER
2912
+ });
2913
+ return stdout.trim();
2881
2914
  }
2882
2915
  function readRepoHeadBranch(repoPath) {
2883
2916
  try {
@@ -2888,17 +2921,17 @@ function readRepoHeadBranch(repoPath) {
2888
2921
  return null;
2889
2922
  }
2890
2923
  }
2891
- function branchExists(branchName, cwd) {
2924
+ async function branchExists(branchName, cwd) {
2892
2925
  try {
2893
- runGitCommand(["rev-parse", "--verify", branchName], cwd);
2926
+ await runGitCommand(["rev-parse", "--verify", branchName], cwd);
2894
2927
  return true;
2895
2928
  } catch {
2896
2929
  return false;
2897
2930
  }
2898
2931
  }
2899
- function getCurrentBranch(cwd) {
2932
+ async function getCurrentBranch(cwd) {
2900
2933
  try {
2901
- return runGitCommand(["rev-parse", "--abbrev-ref", "HEAD"], cwd);
2934
+ return await runGitCommand(["rev-parse", "--abbrev-ref", "HEAD"], cwd);
2902
2935
  } catch (error) {
2903
2936
  console.error("Error getting current branch:", error);
2904
2937
  return null;
@@ -2906,6 +2939,7 @@ function getCurrentBranch(cwd) {
2906
2939
  }
2907
2940
 
2908
2941
  // src/git/service.ts
2942
+ var FULL_DIFF_MAX_BUFFER = 50 * 1024 * 1024;
2909
2943
  function appendUniqueUrl(urls, url) {
2910
2944
  return urls.includes(url) ? urls : [...urls, url];
2911
2945
  }
@@ -2915,6 +2949,10 @@ var GitService = class {
2915
2949
  // No invalidation on purpose — `git remote set-url` mid-session is rare and
2916
2950
  // the worst case is `gh pr view` stays skipped until the next engine restart.
2917
2951
  originIsGitHubCache = /* @__PURE__ */ new Map();
2952
+ // Broadcaster + UI /repos requests fan in here every ~2s; one shared run.
2953
+ listReposInFlight = new InFlightMap();
2954
+ diffStatsInFlight = new InFlightMap();
2955
+ fullDiffInFlight = new InFlightMap();
2918
2956
  getWorkspaceRoot() {
2919
2957
  return ENGINE_ENV.WORKSPACE_ROOT;
2920
2958
  }
@@ -2940,7 +2978,7 @@ var GitService = class {
2940
2978
  repos.push({
2941
2979
  name: entry,
2942
2980
  path: fullPath,
2943
- defaultBranch: this.resolveDefaultBranch(fullPath)
2981
+ defaultBranch: await this.resolveDefaultBranch(fullPath)
2944
2982
  });
2945
2983
  } catch {
2946
2984
  }
@@ -2949,13 +2987,21 @@ var GitService = class {
2949
2987
  }
2950
2988
  async listRepos(options) {
2951
2989
  const includeDiffs = options?.includeDiffs === true;
2990
+ const key = includeDiffs ? "diffs" : "base";
2991
+ return this.listReposInFlight.run(key, () => this.computeListRepos(includeDiffs));
2992
+ }
2993
+ async computeListRepos(includeDiffs) {
2952
2994
  const repos = await this.listRepositories();
2953
2995
  const states = [];
2954
2996
  for (const repo of repos) {
2955
2997
  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);
2998
+ const [persistedState, currentBranchRaw, gitDiff] = await Promise.all([
2999
+ loadRepoState(repo.name),
3000
+ getCurrentBranch(repo.path),
3001
+ this.getGitDiffStats(repo.path, repo.defaultBranch)
3002
+ ]);
3003
+ const currentBranch = currentBranchRaw ?? repo.defaultBranch;
3004
+ const fullDiff = includeDiffs && gitDiff ? await this.getFullGitDiff(repo.path, repo.defaultBranch) : void 0;
2959
3005
  states.push({
2960
3006
  name: repo.name,
2961
3007
  path: repo.path,
@@ -2963,7 +3009,7 @@ var GitService = class {
2963
3009
  currentBranch,
2964
3010
  prUrls: persistedState?.prUrls ?? [],
2965
3011
  // fullDiff may be empty if the diff subprocess fails.
2966
- gitDiff: includeDiffs && gitDiff ? { ...gitDiff, fullDiff: this.getFullGitDiff(repo.path, repo.defaultBranch) } : gitDiff,
3012
+ gitDiff: includeDiffs && gitDiff ? { ...gitDiff, fullDiff: fullDiff ?? "" } : gitDiff,
2967
3013
  startHooksCompleted: persistedState?.startHooksCompleted ?? false
2968
3014
  });
2969
3015
  } catch {
@@ -2977,7 +3023,7 @@ var GitService = class {
2977
3023
  for (const repo of repos) {
2978
3024
  try {
2979
3025
  const persistedState = await loadRepoState(repo.name);
2980
- const currentBranch = getCurrentBranch(repo.path) ?? repo.defaultBranch;
3026
+ const currentBranch = await getCurrentBranch(repo.path) ?? repo.defaultBranch;
2981
3027
  const startHooksCompleted = persistedState?.startHooksCompleted ?? false;
2982
3028
  const observed = observedBranchesByRepo?.get(repo.name);
2983
3029
  states.push(
@@ -3042,12 +3088,12 @@ var GitService = class {
3042
3088
  const persistedState = await loadRepoState(repo.name);
3043
3089
  const persistedBranch = persistedState?.currentBranch;
3044
3090
  if (!skipNetworkRefresh) {
3045
- runGitCommand(["fetch", "--all", "--prune", `--filter=${GIT_PARTIAL_CLONE_FILTER}`], repo.path);
3091
+ await runGitCommand(["fetch", "--all", "--prune", `--filter=${GIT_PARTIAL_CLONE_FILTER}`], repo.path);
3046
3092
  }
3047
- if (persistedBranch && branchExists(persistedBranch, repo.path)) {
3048
- const currentBranch = getCurrentBranch(repo.path);
3093
+ if (persistedBranch && await branchExists(persistedBranch, repo.path)) {
3094
+ const currentBranch = await getCurrentBranch(repo.path);
3049
3095
  if (currentBranch !== persistedBranch) {
3050
- runGitCommand(["checkout", persistedBranch], repo.path);
3096
+ await runGitCommand(["checkout", persistedBranch], repo.path);
3051
3097
  }
3052
3098
  results.push({
3053
3099
  name: repo.name,
@@ -3057,15 +3103,15 @@ var GitService = class {
3057
3103
  });
3058
3104
  continue;
3059
3105
  }
3060
- runGitCommand(["checkout", repo.defaultBranch], repo.path);
3106
+ await runGitCommand(["checkout", repo.defaultBranch], repo.path);
3061
3107
  if (!skipNetworkRefresh) {
3062
3108
  try {
3063
- runGitCommand(["pull", "--rebase", "--autostash"], repo.path);
3109
+ await runGitCommand(["pull", "--rebase", "--autostash"], repo.path);
3064
3110
  } catch {
3065
3111
  }
3066
3112
  }
3067
- const branchName = this.findAvailableBranchName(workspaceName, repo.path);
3068
- runGitCommand(["checkout", "-b", branchName], repo.path);
3113
+ const branchName = await this.findAvailableBranchName(workspaceName, repo.path);
3114
+ await runGitCommand(["checkout", "-b", branchName], repo.path);
3069
3115
  await saveRepoState(repo.name, { currentBranch: branchName }, baselineState);
3070
3116
  results.push({
3071
3117
  name: repo.name,
@@ -3103,109 +3149,149 @@ var GitService = class {
3103
3149
  return true;
3104
3150
  }
3105
3151
  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
- }
3152
+ return this.diffStatsInFlight.run(repoPath, async () => {
3153
+ try {
3154
+ const diffBase = await this.getDiffBase(repoPath, defaultBranch);
3155
+ const shortstat = await runGitCommand(["diff", diffBase, "--shortstat", "-M"], repoPath);
3156
+ let added = 0;
3157
+ let removed = 0;
3158
+ const addedMatch = shortstat.match(/(\d+) insertion/);
3159
+ const removedMatch = shortstat.match(/(\d+) deletion/);
3160
+ if (addedMatch) {
3161
+ added = parseInt(addedMatch[1], 10);
3162
+ }
3163
+ if (removedMatch) {
3164
+ removed = parseInt(removedMatch[1], 10);
3165
+ }
3166
+ added += await this.countUntrackedAddedLines(repoPath);
3167
+ return {
3168
+ added,
3169
+ removed
3170
+ };
3171
+ } catch (error) {
3172
+ console.error("Error getting git diff:", error);
3173
+ return null;
3174
+ }
3175
+ });
3128
3176
  }
3129
- listUntrackedPaths(repoPath) {
3130
- const listing = execFileSync2("git", ["ls-files", "--others", "--exclude-standard", "-z"], {
3177
+ async listUntrackedPaths(repoPath) {
3178
+ const { stdout } = await execFileAsync("git", ["ls-files", "--others", "--exclude-standard", "-z"], {
3131
3179
  cwd: repoPath,
3132
3180
  encoding: "utf-8",
3133
- stdio: ["pipe", "pipe", "pipe"]
3181
+ maxBuffer: SUBPROCESS_MAX_BUFFER
3134
3182
  });
3135
- return listing.split("\0").filter(Boolean);
3183
+ return stdout.split("\0").filter(Boolean);
3136
3184
  }
3137
- getUntrackedDiff(repoPath) {
3138
- const paths = this.listUntrackedPaths(repoPath);
3185
+ async getUntrackedDiff(repoPath) {
3186
+ const paths = await this.listUntrackedPaths(repoPath);
3139
3187
  if (paths.length === 0) return "";
3140
3188
  try {
3141
- execFileSync2("git", ["add", "--intent-to-add", "--", ...paths], { cwd: repoPath, stdio: ["pipe", "pipe", "pipe"] });
3142
- const result = spawnSync("git", ["diff", "--", ...paths], {
3189
+ await execFileAsync("git", ["add", "--intent-to-add", "--", ...paths], {
3143
3190
  cwd: repoPath,
3144
- encoding: "utf-8",
3145
- maxBuffer: 50 * 1024 * 1024
3191
+ maxBuffer: SUBPROCESS_MAX_BUFFER
3146
3192
  });
3147
- return result.status === 0 || result.status === 1 ? result.stdout : "";
3193
+ return await this.readUntrackedDiff(repoPath, paths);
3148
3194
  } finally {
3149
3195
  try {
3150
- execFileSync2("git", ["reset", "--", ...paths], { cwd: repoPath, stdio: ["pipe", "pipe", "pipe"] });
3196
+ await execFileAsync("git", ["reset", "--", ...paths], {
3197
+ cwd: repoPath,
3198
+ maxBuffer: SUBPROCESS_MAX_BUFFER
3199
+ });
3151
3200
  } catch {
3152
3201
  }
3153
3202
  }
3154
3203
  }
3155
- countUntrackedAddedLines(repoPath) {
3204
+ readUntrackedDiff(repoPath, paths) {
3205
+ return new Promise((resolve3) => {
3206
+ const child = spawn("git", ["diff", "--", ...paths], {
3207
+ cwd: repoPath,
3208
+ stdio: ["ignore", "pipe", "pipe"]
3209
+ });
3210
+ const chunks = [];
3211
+ let total = 0;
3212
+ let truncated = false;
3213
+ child.stdout.on("data", (chunk) => {
3214
+ if (truncated) return;
3215
+ total += chunk.length;
3216
+ if (total > FULL_DIFF_MAX_BUFFER) {
3217
+ truncated = true;
3218
+ chunks.push(chunk.subarray(0, Math.max(0, FULL_DIFF_MAX_BUFFER - (total - chunk.length))));
3219
+ child.kill("SIGTERM");
3220
+ return;
3221
+ }
3222
+ chunks.push(chunk);
3223
+ });
3224
+ child.stderr.on("data", () => {
3225
+ });
3226
+ child.on("error", () => resolve3(""));
3227
+ child.on("close", (code) => {
3228
+ if (code === 0 || code === 1 || truncated) {
3229
+ resolve3(Buffer.concat(chunks).toString("utf-8"));
3230
+ } else {
3231
+ resolve3("");
3232
+ }
3233
+ });
3234
+ });
3235
+ }
3236
+ async countUntrackedAddedLines(repoPath) {
3156
3237
  try {
3157
- return this.listUntrackedPaths(repoPath).reduce((total, path4) => {
3238
+ const paths = await this.listUntrackedPaths(repoPath);
3239
+ let total = 0;
3240
+ for (const path4 of paths) {
3158
3241
  try {
3159
- const contents = readFileSync3(join5(repoPath, path4));
3242
+ const contents = await readFile2(join5(repoPath, path4));
3160
3243
  if (contents.length === 0 || contents.includes(0)) {
3161
- return total;
3244
+ continue;
3162
3245
  }
3163
3246
  let lines = 0;
3164
3247
  for (const byte of contents) {
3165
3248
  if (byte === 10) lines += 1;
3166
3249
  }
3167
- return total + lines + (contents[contents.length - 1] === 10 ? 0 : 1);
3250
+ total += lines + (contents[contents.length - 1] === 10 ? 0 : 1);
3168
3251
  } catch {
3169
- return total;
3170
3252
  }
3171
- }, 0);
3253
+ }
3254
+ return total;
3172
3255
  } catch (error) {
3173
3256
  console.error("Error counting untracked added lines:", error);
3174
3257
  return 0;
3175
3258
  }
3176
3259
  }
3177
3260
  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
- }
3261
+ return this.fullDiffInFlight.run(repoPath, async () => {
3262
+ try {
3263
+ const diffBase = await this.getDiffBase(repoPath, defaultBranch);
3264
+ const { stdout: trackedDiff } = await execFileAsync("git", ["diff", diffBase, "-M", "-C"], {
3265
+ cwd: repoPath,
3266
+ encoding: "utf-8",
3267
+ maxBuffer: FULL_DIFF_MAX_BUFFER
3268
+ });
3269
+ const untrackedDiff = await this.getUntrackedAsDiff(repoPath);
3270
+ return trackedDiff + untrackedDiff;
3271
+ } catch {
3272
+ return "";
3273
+ }
3274
+ });
3189
3275
  }
3190
- getUntrackedAsDiff(repoPath) {
3276
+ async getUntrackedAsDiff(repoPath) {
3191
3277
  try {
3192
- return this.getUntrackedDiff(repoPath);
3278
+ return await this.getUntrackedDiff(repoPath);
3193
3279
  } catch (error) {
3194
3280
  console.error("Error building untracked diff:", error);
3195
3281
  return "";
3196
3282
  }
3197
3283
  }
3198
- getDiffBase(repoPath, defaultBranch) {
3284
+ async getDiffBase(repoPath, defaultBranch) {
3199
3285
  const baseBranch = `origin/${defaultBranch}`;
3200
3286
  try {
3201
- return runGitCommand(["merge-base", baseBranch, "HEAD"], repoPath);
3287
+ return await runGitCommand(["merge-base", baseBranch, "HEAD"], repoPath);
3202
3288
  } catch {
3203
3289
  return baseBranch;
3204
3290
  }
3205
3291
  }
3206
3292
  async getPullRequestUrl(repoName, repoPath, currentBranchArg, persistedRepoStateArg) {
3207
3293
  try {
3208
- const currentBranch = currentBranchArg ?? getCurrentBranch(repoPath);
3294
+ const currentBranch = currentBranchArg ?? await getCurrentBranch(repoPath);
3209
3295
  if (!currentBranch) {
3210
3296
  return { status: "not_found" };
3211
3297
  }
@@ -3215,7 +3301,7 @@ var GitService = class {
3215
3301
  }
3216
3302
  const persistedRepoState = persistedRepoStateArg ?? await loadRepoState(repoName);
3217
3303
  this.cachedPrByRepo.delete(repoName);
3218
- const result = this.lookupPrOnRemote(repoName, repoPath, currentBranch);
3304
+ const result = await this.lookupPrOnRemote(repoName, repoPath, currentBranch);
3219
3305
  if (result.status === "found") {
3220
3306
  this.cachedPrByRepo.set(repoName, { prUrl: result.url, currentBranch });
3221
3307
  if (persistedRepoState && !persistedRepoState.prUrls.includes(result.url)) {
@@ -3232,28 +3318,29 @@ var GitService = class {
3232
3318
  return { status: "error" };
3233
3319
  }
3234
3320
  }
3235
- lookupPrOnRemote(repoName, repoPath, branch) {
3236
- if (!this.originIsGitHub(repoPath)) {
3321
+ async lookupPrOnRemote(repoName, repoPath, branch) {
3322
+ if (!await this.originIsGitHub(repoPath)) {
3237
3323
  return { status: "not_found" };
3238
3324
  }
3239
3325
  try {
3240
- const remoteRef = execFileSync2("git", ["ls-remote", "--heads", "origin", branch], {
3326
+ const { stdout } = await execFileAsync("git", ["ls-remote", "--heads", "origin", branch], {
3241
3327
  cwd: repoPath,
3242
3328
  encoding: "utf-8",
3243
- stdio: ["pipe", "pipe", "pipe"]
3244
- }).trim();
3245
- if (!remoteRef) {
3329
+ maxBuffer: SUBPROCESS_MAX_BUFFER
3330
+ });
3331
+ if (!stdout.trim()) {
3246
3332
  return { status: "not_found" };
3247
3333
  }
3248
3334
  } catch {
3249
3335
  return { status: "error" };
3250
3336
  }
3251
3337
  try {
3252
- const prInfo = execFileSync2("gh", ["pr", "view", branch, "--json", "url", "--jq", ".url"], {
3338
+ const { stdout } = await execFileAsync("gh", ["pr", "view", branch, "--json", "url", "--jq", ".url"], {
3253
3339
  cwd: repoPath,
3254
3340
  encoding: "utf-8",
3255
- stdio: ["pipe", "pipe", "pipe"]
3256
- }).trim();
3341
+ maxBuffer: SUBPROCESS_MAX_BUFFER
3342
+ });
3343
+ const prInfo = stdout.trim();
3257
3344
  return prInfo ? { status: "found", url: prInfo } : { status: "not_found" };
3258
3345
  } catch (error) {
3259
3346
  const message = error instanceof Error ? error.message : String(error);
@@ -3261,31 +3348,31 @@ var GitService = class {
3261
3348
  return { status: "error" };
3262
3349
  }
3263
3350
  }
3264
- originIsGitHub(repoPath) {
3351
+ async originIsGitHub(repoPath) {
3265
3352
  const cached = this.originIsGitHubCache.get(repoPath);
3266
3353
  if (cached !== void 0) {
3267
3354
  return cached;
3268
3355
  }
3269
3356
  let isGitHub = false;
3270
3357
  try {
3271
- const url = execFileSync2("git", ["remote", "get-url", "origin"], {
3358
+ const { stdout } = await execFileAsync("git", ["remote", "get-url", "origin"], {
3272
3359
  cwd: repoPath,
3273
3360
  encoding: "utf-8",
3274
- stdio: ["pipe", "pipe", "pipe"]
3275
- }).trim();
3276
- isGitHub = isGitHubUrl(url);
3361
+ maxBuffer: SUBPROCESS_MAX_BUFFER
3362
+ });
3363
+ isGitHub = isGitHubUrl(stdout.trim());
3277
3364
  } catch {
3278
3365
  isGitHub = false;
3279
3366
  }
3280
3367
  this.originIsGitHubCache.set(repoPath, isGitHub);
3281
3368
  return isGitHub;
3282
3369
  }
3283
- resolveDefaultBranch(repoPath) {
3370
+ async resolveDefaultBranch(repoPath) {
3284
3371
  const cached = this.defaultBranchCache.get(repoPath);
3285
3372
  if (cached) {
3286
3373
  return cached;
3287
3374
  }
3288
- const fromSymbolicRef = this.resolveDefaultBranchFromSymbolicRef(repoPath);
3375
+ const fromSymbolicRef = await this.resolveDefaultBranchFromSymbolicRef(repoPath);
3289
3376
  if (fromSymbolicRef) {
3290
3377
  this.defaultBranchCache.set(repoPath, fromSymbolicRef);
3291
3378
  return fromSymbolicRef;
@@ -3294,18 +3381,18 @@ var GitService = class {
3294
3381
  this.defaultBranchCache.set(repoPath, fallback);
3295
3382
  return fallback;
3296
3383
  }
3297
- resolveDefaultBranchFromSymbolicRef(repoPath) {
3384
+ async resolveDefaultBranchFromSymbolicRef(repoPath) {
3298
3385
  try {
3299
- const output = runGitCommand(["symbolic-ref", "--short", "refs/remotes/origin/HEAD"], repoPath);
3386
+ const output = await runGitCommand(["symbolic-ref", "--short", "refs/remotes/origin/HEAD"], repoPath);
3300
3387
  const match = output.match(/^origin\/(.+)$/);
3301
3388
  return match ? match[1] : null;
3302
3389
  } catch {
3303
3390
  return null;
3304
3391
  }
3305
3392
  }
3306
- findAvailableBranchName(baseName, cwd) {
3393
+ async findAvailableBranchName(baseName, cwd) {
3307
3394
  const sanitizedBaseName = this.sanitizeBranchName(baseName);
3308
- if (!branchExists(sanitizedBaseName, cwd)) {
3395
+ if (!await branchExists(sanitizedBaseName, cwd)) {
3309
3396
  return sanitizedBaseName;
3310
3397
  }
3311
3398
  return `${sanitizedBaseName}-${Date.now()}`;
@@ -3323,7 +3410,7 @@ var GitService = class {
3323
3410
  if (observedBranches) {
3324
3411
  for (const branch of observedBranches) {
3325
3412
  if (branch === currentBranch) continue;
3326
- const branchResult = this.lookupPrOnRemote(repo.name, repo.path, branch);
3413
+ const branchResult = await this.lookupPrOnRemote(repo.name, repo.path, branch);
3327
3414
  if (branchResult.status === "found") {
3328
3415
  prUrls = appendUniqueUrl(prUrls, branchResult.url);
3329
3416
  }
@@ -3335,7 +3422,7 @@ var GitService = class {
3335
3422
  defaultBranch: repo.defaultBranch,
3336
3423
  currentBranch,
3337
3424
  prUrls,
3338
- gitDiff: this.getGitDiffStats(repo.path, repo.defaultBranch),
3425
+ gitDiff: await this.getGitDiffStats(repo.path, repo.defaultBranch),
3339
3426
  startHooksCompleted
3340
3427
  };
3341
3428
  await saveRepoState(repo.name, state, state);
@@ -3423,20 +3510,17 @@ var EngineLogger = class {
3423
3510
  var engineLogger = new EngineLogger();
3424
3511
 
3425
3512
  // src/services/replicas-config-service.ts
3426
- import { readFile as readFile4, appendFile as appendFile2, writeFile as writeFile4, mkdir as mkdir5 } from "fs/promises";
3513
+ import { readFile as readFile5, appendFile as appendFile2, writeFile as writeFile4, mkdir as mkdir5 } from "fs/promises";
3427
3514
  import { existsSync as existsSync4 } from "fs";
3428
3515
  import { join as join9 } from "path";
3429
3516
  import { homedir as homedir7 } from "os";
3430
- import { spawn } from "child_process";
3517
+ import { spawn as spawn2 } from "child_process";
3431
3518
 
3432
3519
  // src/services/environment-details-service.ts
3433
- import { mkdir as mkdir3, readFile as readFile2 } from "fs/promises";
3520
+ import { mkdir as mkdir3, readFile as readFile3 } from "fs/promises";
3434
3521
  import { existsSync as existsSync3 } from "fs";
3435
3522
  import { homedir as homedir5 } from "os";
3436
3523
  import { join as join7 } from "path";
3437
- import { execFile } from "child_process";
3438
- import { promisify } from "util";
3439
- var execFileAsync = promisify(execFile);
3440
3524
  var REPLICAS_DIR = join7(homedir5(), ".replicas");
3441
3525
  var DETAILS_FILE = join7(REPLICAS_DIR, "environment-details.json");
3442
3526
  var CLAUDE_CREDENTIALS_PATH = join7(homedir5(), ".claude", ".credentials.json");
@@ -3509,7 +3593,7 @@ async function readDetails() {
3509
3593
  if (!existsSync3(DETAILS_FILE)) {
3510
3594
  return createDefaultDetails();
3511
3595
  }
3512
- const raw = await readFile2(DETAILS_FILE, "utf-8");
3596
+ const raw = await readFile3(DETAILS_FILE, "utf-8");
3513
3597
  const parsed = JSON.parse(raw);
3514
3598
  return { ...createDefaultDetails(), ...parsed };
3515
3599
  } catch {
@@ -3603,7 +3687,7 @@ var EnvironmentDetailsService = class {
3603
3687
  var environmentDetailsService = new EnvironmentDetailsService();
3604
3688
 
3605
3689
  // src/services/start-hook-logs-service.ts
3606
- import { mkdir as mkdir4, readFile as readFile3, writeFile as writeFile3, readdir as readdir2 } from "fs/promises";
3690
+ import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile3, readdir as readdir2 } from "fs/promises";
3607
3691
  import { homedir as homedir6 } from "os";
3608
3692
  import { join as join8 } from "path";
3609
3693
 
@@ -3680,7 +3764,7 @@ var StartHookLogsService = class {
3680
3764
  continue;
3681
3765
  }
3682
3766
  try {
3683
- const raw = await readFile3(join8(LOGS_DIR, file), "utf-8");
3767
+ const raw = await readFile4(join8(LOGS_DIR, file), "utf-8");
3684
3768
  const stored = normalizeStored(JSON.parse(raw));
3685
3769
  if (stored) {
3686
3770
  logs.push(withPreview(stored));
@@ -3699,7 +3783,7 @@ var StartHookLogsService = class {
3699
3783
  async getFullOutput(hookType, hookName) {
3700
3784
  const filename = hookType === "environment" ? ENVIRONMENT_HOOK_LOG_FILENAME : repoHookLogFilename(hookName);
3701
3785
  try {
3702
- const raw = await readFile3(join8(LOGS_DIR, filename), "utf-8");
3786
+ const raw = await readFile4(join8(LOGS_DIR, filename), "utf-8");
3703
3787
  const stored = normalizeStored(JSON.parse(raw));
3704
3788
  if (!stored || stored.hookType !== hookType || stored.hookName !== hookName) {
3705
3789
  return null;
@@ -3731,7 +3815,7 @@ async function readReplicasConfigFromDir(dirPath) {
3731
3815
  if (!existsSync4(configPath)) {
3732
3816
  continue;
3733
3817
  }
3734
- const data = await readFile4(configPath, "utf-8");
3818
+ const data = await readFile5(configPath, "utf-8");
3735
3819
  const config = parseReplicasConfigString(data, filename);
3736
3820
  return { config, filename };
3737
3821
  }
@@ -3833,7 +3917,7 @@ var ReplicasConfigService = class {
3833
3917
  emit(`$ ${params.label}
3834
3918
  `);
3835
3919
  return new Promise((resolve3) => {
3836
- const proc = spawn("bash", ["-lc", params.command], {
3920
+ const proc = spawn2("bash", ["-lc", params.command], {
3837
3921
  cwd: params.cwd,
3838
3922
  env: process.env,
3839
3923
  stdio: ["pipe", "pipe", "pipe"]
@@ -4166,7 +4250,7 @@ var EventService = class {
4166
4250
  var eventService = new EventService();
4167
4251
 
4168
4252
  // src/services/preview-service.ts
4169
- import { mkdir as mkdir7, readFile as readFile5 } from "fs/promises";
4253
+ import { mkdir as mkdir7, readFile as readFile6 } from "fs/promises";
4170
4254
  import { existsSync as existsSync5 } from "fs";
4171
4255
  import { randomUUID as randomUUID2 } from "crypto";
4172
4256
  import { homedir as homedir9 } from "os";
@@ -4177,7 +4261,7 @@ async function readPreviewsFile() {
4177
4261
  if (!existsSync5(PREVIEW_PORTS_FILE)) {
4178
4262
  return { previews: [] };
4179
4263
  }
4180
- const raw = await readFile5(PREVIEW_PORTS_FILE, "utf-8");
4264
+ const raw = await readFile6(PREVIEW_PORTS_FILE, "utf-8");
4181
4265
  return JSON.parse(raw);
4182
4266
  } catch {
4183
4267
  return { previews: [] };
@@ -4282,7 +4366,7 @@ async function registerDesktopPreview() {
4282
4366
 
4283
4367
  // src/services/chat/chat-service.ts
4284
4368
  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";
4369
+ import { appendFile as appendFile5, copyFile, mkdir as mkdir11, readFile as readFile9, rename as rename2, rm } from "fs/promises";
4286
4370
  import { homedir as homedir13 } from "os";
4287
4371
  import { join as join15 } from "path";
4288
4372
  import { randomUUID as randomUUID5 } from "crypto";
@@ -4297,7 +4381,7 @@ import { mkdir as mkdir9, appendFile as appendFile4 } from "fs/promises";
4297
4381
  import { homedir as homedir11 } from "os";
4298
4382
 
4299
4383
  // src/utils/jsonl-reader.ts
4300
- import { readFile as readFile6 } from "fs/promises";
4384
+ import { readFile as readFile7 } from "fs/promises";
4301
4385
  function isJsonlEvent(value) {
4302
4386
  if (!isRecord4(value)) {
4303
4387
  return false;
@@ -4319,7 +4403,7 @@ function parseJsonlEvents(lines) {
4319
4403
  }
4320
4404
  async function readJSONL(filePath) {
4321
4405
  try {
4322
- const content = await readFile6(filePath, "utf-8");
4406
+ const content = await readFile7(filePath, "utf-8");
4323
4407
  const lines = content.split("\n").filter((line) => line.trim());
4324
4408
  return parseJsonlEvents(lines);
4325
4409
  } catch (error) {
@@ -6070,7 +6154,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
6070
6154
  };
6071
6155
 
6072
6156
  // src/managers/codex-asp/app-server-process.ts
6073
- import { spawn as spawn2 } from "child_process";
6157
+ import { spawn as spawn3 } from "child_process";
6074
6158
  import { EventEmitter as EventEmitter2 } from "events";
6075
6159
 
6076
6160
  // src/managers/codex-asp/asp-client.ts
@@ -6265,7 +6349,7 @@ var AspClient = class {
6265
6349
  // src/managers/codex-asp/app-server-process.ts
6266
6350
  var DEFAULT_CODEX_BINARY = "codex";
6267
6351
  var DEFAULT_CODEX_ARGS = ["app-server", "--listen", "stdio://"];
6268
- var ENGINE_PACKAGE_VERSION = "0.1.257";
6352
+ var ENGINE_PACKAGE_VERSION = "0.1.259";
6269
6353
  var INITIALIZE_METHOD = "initialize";
6270
6354
  var INITIALIZED_NOTIFICATION = "initialized";
6271
6355
  var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
@@ -6292,7 +6376,7 @@ var AppServerProcess = class {
6292
6376
  return { client: this.client };
6293
6377
  }
6294
6378
  this.shuttingDown = false;
6295
- const child = spawn2(this.binary, this.args, {
6379
+ const child = spawn3(this.binary, this.args, {
6296
6380
  cwd: this.cwd,
6297
6381
  env: this.env,
6298
6382
  stdio: ["pipe", "pipe", "pipe"]
@@ -6532,7 +6616,7 @@ function isCodexAuthError(error) {
6532
6616
  }
6533
6617
 
6534
6618
  // src/managers/codex-asp/mappers.ts
6535
- import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
6619
+ import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
6536
6620
  var localImageCache = /* @__PURE__ */ new Map();
6537
6621
  var DEFAULT_MODEL = DEFAULT_CODEX_MODEL;
6538
6622
  var THREAD_START_METHOD = "thread/start";
@@ -6613,7 +6697,7 @@ function userImageForLocalPath(path4) {
6613
6697
  const image = {
6614
6698
  type: "image",
6615
6699
  mediaType: inferMediaType(path4),
6616
- data: readFileSync4(path4).toString("base64")
6700
+ data: readFileSync3(path4).toString("base64")
6617
6701
  };
6618
6702
  if (image.data.length > 0) localImageCache.set(path4, image);
6619
6703
  return image;
@@ -7885,7 +7969,7 @@ var CodexAspManager = class extends CodingAgentManager {
7885
7969
 
7886
7970
  // src/managers/codex-manager.ts
7887
7971
  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";
7972
+ import { readdir as readdir3, stat as stat2, writeFile as writeFile6, mkdir as mkdir10, readFile as readFile8 } from "fs/promises";
7889
7973
  import { existsSync as existsSync7 } from "fs";
7890
7974
  import { join as join14 } from "path";
7891
7975
  import { homedir as homedir12 } from "os";
@@ -7933,7 +8017,7 @@ var CodexManager = class extends CodingAgentManager {
7933
8017
  try {
7934
8018
  const sessionFile = await this.findSessionFile(this.currentThreadId);
7935
8019
  if (!sessionFile) return;
7936
- const content = await readFile7(sessionFile, "utf-8");
8020
+ const content = await readFile8(sessionFile, "utf-8");
7937
8021
  const lines = content.split("\n").map((line) => line.trim()).filter(Boolean);
7938
8022
  let latest = null;
7939
8023
  for (const line of lines) {
@@ -7973,7 +8057,7 @@ var CodexManager = class extends CodingAgentManager {
7973
8057
  let config = {};
7974
8058
  if (existsSync7(CODEX_CONFIG_PATH)) {
7975
8059
  try {
7976
- const existingContent = await readFile7(CODEX_CONFIG_PATH, "utf-8");
8060
+ const existingContent = await readFile8(CODEX_CONFIG_PATH, "utf-8");
7977
8061
  const parsed = parseToml(existingContent);
7978
8062
  if (isRecord4(parsed)) {
7979
8063
  config = parsed;
@@ -8216,7 +8300,7 @@ var CodexManager = class extends CodingAgentManager {
8216
8300
  const seenLines = /* @__PURE__ */ new Set();
8217
8301
  const seedSeenLines = async () => {
8218
8302
  try {
8219
- const content = await readFile7(sessionFile, "utf-8");
8303
+ const content = await readFile8(sessionFile, "utf-8");
8220
8304
  const lines = content.split("\n").map((line) => line.trim()).filter(Boolean);
8221
8305
  let latest = null;
8222
8306
  for (const line of lines) {
@@ -8238,7 +8322,7 @@ var CodexManager = class extends CodingAgentManager {
8238
8322
  const pump = async () => {
8239
8323
  let emitted = 0;
8240
8324
  try {
8241
- const content = await readFile7(sessionFile, "utf-8");
8325
+ const content = await readFile8(sessionFile, "utf-8");
8242
8326
  const lines = content.split("\n");
8243
8327
  const completeLines = content.endsWith("\n") ? lines : lines.slice(0, -1);
8244
8328
  for (const line of completeLines) {
@@ -9101,7 +9185,7 @@ var ChatService = class {
9101
9185
  }
9102
9186
  async readSenders(chatId) {
9103
9187
  try {
9104
- const content = await readFile8(this.senderFilePath(chatId), "utf-8");
9188
+ const content = await readFile9(this.senderFilePath(chatId), "utf-8");
9105
9189
  const lines = content.split("\n").filter((line) => line.trim().length > 0);
9106
9190
  const senders = [];
9107
9191
  for (const line of lines) {
@@ -9458,7 +9542,7 @@ var ChatService = class {
9458
9542
  }
9459
9543
  async loadChats() {
9460
9544
  try {
9461
- const content = await readFile8(CHATS_FILE, "utf-8");
9545
+ const content = await readFile9(CHATS_FILE, "utf-8");
9462
9546
  return parsePersistedChatsContent(content);
9463
9547
  } catch (error) {
9464
9548
  if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
@@ -9473,7 +9557,7 @@ var ChatService = class {
9473
9557
  console.error("[ChatService] Failed to quarantine corrupt chats file:", renameError);
9474
9558
  }
9475
9559
  try {
9476
- const backupContent = await readFile8(CHATS_BACKUP_FILE, "utf-8");
9560
+ const backupContent = await readFile9(CHATS_BACKUP_FILE, "utf-8");
9477
9561
  return parsePersistedChatsContent(backupContent);
9478
9562
  } catch (backupError) {
9479
9563
  if (backupError && typeof backupError === "object" && "code" in backupError && backupError.code === "ENOENT") {
@@ -9548,7 +9632,7 @@ var ChatService = class {
9548
9632
 
9549
9633
  // src/services/repo-file-service.ts
9550
9634
  import { execFile as execFile2 } from "child_process";
9551
- import { readFile as readFile9, realpath, stat as stat3 } from "fs/promises";
9635
+ import { readFile as readFile10, realpath, stat as stat3 } from "fs/promises";
9552
9636
  import { join as join16, resolve, extname } from "path";
9553
9637
  var CACHE_TTL_MS = 3e4;
9554
9638
  var SEARCH_TIMEOUT_MS = 15e3;
@@ -9738,7 +9822,7 @@ var RepoFileService = class {
9738
9822
  tooLarge: true
9739
9823
  };
9740
9824
  }
9741
- const content = await readFile9(fullPath, "utf-8");
9825
+ const content = await readFile10(fullPath, "utf-8");
9742
9826
  return {
9743
9827
  repoName,
9744
9828
  path: filePath,
@@ -9820,11 +9904,11 @@ var RepoFileService = class {
9820
9904
  // src/v1-routes.ts
9821
9905
  import { Hono } from "hono";
9822
9906
  import { z as z2 } from "zod";
9823
- import { readdir as readdir6, stat as stat4, readFile as readFile13 } from "fs/promises";
9907
+ import { readdir as readdir6, stat as stat4, readFile as readFile14 } from "fs/promises";
9824
9908
  import { join as join20, resolve as resolve2 } from "path";
9825
9909
 
9826
9910
  // src/services/plan-service.ts
9827
- import { readdir as readdir4, readFile as readFile10 } from "fs/promises";
9911
+ import { readdir as readdir4, readFile as readFile11 } from "fs/promises";
9828
9912
  import { homedir as homedir14 } from "os";
9829
9913
  import { basename, join as join17 } from "path";
9830
9914
  var PLAN_DIRECTORIES = [
@@ -9865,7 +9949,7 @@ var PlanService = class {
9865
9949
  for (const directory of PLAN_DIRECTORIES) {
9866
9950
  const filePath = join17(directory, safeFilename);
9867
9951
  try {
9868
- const content = await readFile10(filePath, "utf-8");
9952
+ const content = await readFile11(filePath, "utf-8");
9869
9953
  return { filename: safeFilename, content };
9870
9954
  } catch {
9871
9955
  }
@@ -9876,13 +9960,13 @@ var PlanService = class {
9876
9960
  var planService = new PlanService();
9877
9961
 
9878
9962
  // src/services/warm-hooks-service.ts
9879
- import { spawn as spawn3 } from "child_process";
9880
- import { readFile as readFile12 } from "fs/promises";
9963
+ import { spawn as spawn4 } from "child_process";
9964
+ import { readFile as readFile13 } from "fs/promises";
9881
9965
  import { existsSync as existsSync9 } from "fs";
9882
9966
  import { join as join19 } from "path";
9883
9967
 
9884
9968
  // 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";
9969
+ import { mkdir as mkdir12, readFile as readFile12, writeFile as writeFile7, readdir as readdir5, appendFile as appendFile6, unlink as unlink3 } from "fs/promises";
9886
9970
  import { homedir as homedir15 } from "os";
9887
9971
  import { join as join18 } from "path";
9888
9972
  var LOGS_DIR2 = join18(homedir15(), ".replicas", "warm-hook-logs");
@@ -9942,7 +10026,7 @@ var WarmHookLogsService = class {
9942
10026
  continue;
9943
10027
  }
9944
10028
  try {
9945
- const raw = await readFile11(join18(LOGS_DIR2, file), "utf-8");
10029
+ const raw = await readFile12(join18(LOGS_DIR2, file), "utf-8");
9946
10030
  const stored = JSON.parse(raw);
9947
10031
  logs.push(withPreview2(stored));
9948
10032
  } catch {
@@ -9971,7 +10055,7 @@ var WarmHookLogsService = class {
9971
10055
  }
9972
10056
  async getCurrentRunLog() {
9973
10057
  try {
9974
- return await readFile11(CURRENT_RUN_LOG, "utf-8");
10058
+ return await readFile12(CURRENT_RUN_LOG, "utf-8");
9975
10059
  } catch (err) {
9976
10060
  if (err.code === "ENOENT") return null;
9977
10061
  throw err;
@@ -9980,7 +10064,7 @@ var WarmHookLogsService = class {
9980
10064
  async getFullOutput(hookType, hookName) {
9981
10065
  const filename = hookType === "global" ? GLOBAL_FILENAME : hookType === "environment" ? ENVIRONMENT_HOOK_LOG_FILENAME : repoHookLogFilename(hookName);
9982
10066
  try {
9983
- const raw = await readFile11(join18(LOGS_DIR2, filename), "utf-8");
10067
+ const raw = await readFile12(join18(LOGS_DIR2, filename), "utf-8");
9984
10068
  const stored = JSON.parse(raw);
9985
10069
  if (stored.hookType !== hookType || stored.hookName !== hookName) {
9986
10070
  return null;
@@ -10004,7 +10088,7 @@ async function readRepoWarmHook(repoPath) {
10004
10088
  continue;
10005
10089
  }
10006
10090
  try {
10007
- const raw = await readFile12(configPath, "utf-8");
10091
+ const raw = await readFile13(configPath, "utf-8");
10008
10092
  const config = parseReplicasConfigString(raw, filename);
10009
10093
  if (!config.warmHook) {
10010
10094
  return null;
@@ -10051,7 +10135,7 @@ async function executeHookScriptStreaming(params) {
10051
10135
  params.onChunk(`$ ${params.label}
10052
10136
  `);
10053
10137
  return new Promise((resolve3) => {
10054
- const proc = spawn3("bash", ["-lc", params.content], {
10138
+ const proc = spawn4("bash", ["-lc", params.content], {
10055
10139
  cwd: params.cwd,
10056
10140
  env: process.env,
10057
10141
  stdio: ["pipe", "pipe", "pipe"]
@@ -10440,6 +10524,10 @@ function createV1Routes(deps) {
10440
10524
  app2.post("/chats/:chatId/messages", async (c) => {
10441
10525
  try {
10442
10526
  const body = sendMessageSchema.parse(await c.req.json());
10527
+ const goalValidationError = getGoalCommandObjectiveValidationError(body.message);
10528
+ if (goalValidationError) {
10529
+ return c.json(jsonError(goalValidationError), 400);
10530
+ }
10443
10531
  const result = await deps.chatService.sendMessage(c.req.param("chatId"), body);
10444
10532
  return c.json(result);
10445
10533
  } catch (error) {
@@ -10934,7 +11022,7 @@ function createV1Routes(deps) {
10934
11022
  const limit = Math.min(parseInt(c.req.query("limit") || "500", 10), 5e3);
10935
11023
  let content;
10936
11024
  try {
10937
- content = await readFile13(filePath, "utf-8");
11025
+ content = await readFile14(filePath, "utf-8");
10938
11026
  } catch {
10939
11027
  return c.json(jsonError("Log session not found"), 404);
10940
11028
  }
@@ -10974,10 +11062,10 @@ process.on("unhandledRejection", (reason) => {
10974
11062
  engineLogger.flush().finally(() => process.exit(1));
10975
11063
  });
10976
11064
  await eventService.initialize();
10977
- function checkActiveSSHSessions() {
11065
+ async function checkActiveSSHSessions() {
10978
11066
  try {
10979
- const output = execSync2('who | grep -v "^$" | wc -l', { encoding: "utf-8" });
10980
- const sessionCount = parseInt(output.trim(), 10);
11067
+ const { stdout } = await execAsync('who | grep -v "^$" | wc -l', { encoding: "utf-8" });
11068
+ const sessionCount = parseInt(stdout.trim(), 10);
10981
11069
  return sessionCount > 0;
10982
11070
  } catch {
10983
11071
  return false;
@@ -11010,10 +11098,13 @@ app.get("/health", (c) => {
11010
11098
  app.use("*", authMiddleware);
11011
11099
  app.get("/status", async (c) => {
11012
11100
  try {
11013
- const repos = await gitService.listRepos();
11101
+ const [repos, hasActiveSSHSessions] = await Promise.all([
11102
+ gitService.listRepos(),
11103
+ checkActiveSSHSessions()
11104
+ ]);
11014
11105
  const status = {
11015
11106
  uptimeSeconds: Math.floor((Date.now() - bootTimeMs) / 1e3),
11016
- hasActiveSSHSessions: checkActiveSSHSessions(),
11107
+ hasActiveSSHSessions,
11017
11108
  activeSseClients: eventService.getSubscriberCount(),
11018
11109
  chatsTotal: chatService.listChats().length,
11019
11110
  chatsProcessing: chatService.getProcessingCount(),
@@ -11043,8 +11134,16 @@ function startStatusBroadcaster() {
11043
11134
  let previousHookStatus = "";
11044
11135
  let previousEngineStatus = "";
11045
11136
  let lastHooksRunning = replicasConfigService.areHooksRunning();
11046
- setInterval(() => {
11047
- gitService.listRepos().then((repos) => {
11137
+ const BROADCAST_INTERVAL_MS = 2e3;
11138
+ const scheduleNext = () => {
11139
+ setTimeout(tick, BROADCAST_INTERVAL_MS);
11140
+ };
11141
+ const tick = async () => {
11142
+ try {
11143
+ const [repos, hasActiveSSHSessions] = await Promise.all([
11144
+ gitService.listRepos(),
11145
+ checkActiveSSHSessions()
11146
+ ]);
11048
11147
  const serialized = JSON.stringify(repos);
11049
11148
  if (serialized !== previousRepoStatus) {
11050
11149
  previousRepoStatus = serialized;
@@ -11058,7 +11157,7 @@ function startStatusBroadcaster() {
11058
11157
  }
11059
11158
  const engineStatus = {
11060
11159
  uptimeSeconds: Math.floor((Date.now() - bootTimeMs) / 1e3),
11061
- hasActiveSSHSessions: checkActiveSSHSessions(),
11160
+ hasActiveSSHSessions,
11062
11161
  activeSseClients: eventService.getSubscriberCount(),
11063
11162
  chatsTotal: chatService.listChats().length,
11064
11163
  chatsProcessing: chatService.getProcessingCount(),
@@ -11076,67 +11175,70 @@ function startStatusBroadcaster() {
11076
11175
  }).catch(() => {
11077
11176
  });
11078
11177
  }
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) {
11178
+ const hooksRunning = replicasConfigService.areHooksRunning();
11179
+ const hooksCompleted = replicasConfigService.areHooksCompleted();
11180
+ const hooksFailed = replicasConfigService.didHooksFail();
11181
+ const hookSnapshot = JSON.stringify({
11182
+ running: hooksRunning,
11183
+ completed: hooksCompleted,
11184
+ failed: hooksFailed
11185
+ });
11186
+ if (hookSnapshot !== previousHookStatus) {
11187
+ previousHookStatus = hookSnapshot;
11188
+ if (!lastHooksRunning && hooksRunning) {
11189
+ eventService.publish({
11190
+ id: randomUUID6(),
11191
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
11192
+ type: "hooks.started",
11193
+ payload: { running: true, completed: false }
11194
+ }).catch(() => {
11195
+ });
11196
+ }
11197
+ if (hooksRunning) {
11198
+ eventService.publish({
11199
+ id: randomUUID6(),
11200
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
11201
+ type: "hooks.progress",
11202
+ payload: { running: true, completed: false }
11203
+ }).catch(() => {
11204
+ });
11205
+ }
11206
+ if (lastHooksRunning && !hooksRunning && hooksCompleted && !hooksFailed) {
11207
+ eventService.publish({
11208
+ id: randomUUID6(),
11209
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
11210
+ type: "hooks.completed",
11211
+ payload: { running: false, completed: true }
11212
+ }).catch(() => {
11213
+ });
11214
+ }
11215
+ if (lastHooksRunning && !hooksRunning && hooksFailed) {
11216
+ eventService.publish({
11217
+ id: randomUUID6(),
11218
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
11219
+ type: "hooks.failed",
11220
+ payload: { running: false, completed: hooksCompleted }
11221
+ }).catch(() => {
11222
+ });
11223
+ }
11119
11224
  eventService.publish({
11120
11225
  id: randomUUID6(),
11121
11226
  ts: (/* @__PURE__ */ new Date()).toISOString(),
11122
- type: "hooks.failed",
11123
- payload: { running: false, completed: hooksCompleted }
11227
+ type: "hooks.status",
11228
+ payload: {
11229
+ running: hooksRunning,
11230
+ completed: hooksCompleted
11231
+ }
11124
11232
  }).catch(() => {
11125
11233
  });
11234
+ lastHooksRunning = hooksRunning;
11126
11235
  }
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;
11236
+ } catch {
11237
+ } finally {
11238
+ scheduleNext();
11138
11239
  }
11139
- }, 2e3);
11240
+ };
11241
+ scheduleNext();
11140
11242
  }
11141
11243
  serve(
11142
11244
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.257",
3
+ "version": "0.1.259",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",