replicas-engine 0.1.167 → 0.1.168

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 +464 -38
  2. package/package.json +1 -1
package/dist/src/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // src/index.ts
4
4
  import { serve } from "@hono/node-server";
5
5
  import { Hono as Hono2 } from "hono";
6
- import { readFile as readFile13 } from "fs/promises";
6
+ import { readFile as readFile14 } from "fs/promises";
7
7
  import { execSync as execSync2 } from "child_process";
8
8
  import { randomUUID as randomUUID5 } from "crypto";
9
9
 
@@ -22,6 +22,95 @@ import { readFileSync } from "fs";
22
22
  var CODEX_QUOTA_STATUS_EVENT_TYPE = "codex-quota-status";
23
23
  var CONTEXT_USAGE_EVENT_TYPE = "context-usage";
24
24
 
25
+ // ../shared/src/languages.ts
26
+ var EXT_TO_LANGUAGE = {
27
+ ".ts": "typescript",
28
+ ".tsx": "tsx",
29
+ ".js": "javascript",
30
+ ".jsx": "jsx",
31
+ ".mjs": "javascript",
32
+ ".cjs": "javascript",
33
+ ".py": "python",
34
+ ".pyw": "python",
35
+ ".pyi": "python",
36
+ ".rs": "rust",
37
+ ".go": "go",
38
+ ".rb": "ruby",
39
+ ".rake": "ruby",
40
+ ".java": "java",
41
+ ".kt": "kotlin",
42
+ ".kts": "kotlin",
43
+ ".scala": "scala",
44
+ ".swift": "swift",
45
+ ".m": "objectivec",
46
+ ".mm": "objectivec",
47
+ ".c": "c",
48
+ ".cpp": "cpp",
49
+ ".cc": "cpp",
50
+ ".cxx": "cpp",
51
+ ".h": "c",
52
+ ".hpp": "cpp",
53
+ ".hh": "cpp",
54
+ ".hxx": "cpp",
55
+ ".cs": "csharp",
56
+ ".vb": "vbnet",
57
+ ".fs": "fsharp",
58
+ ".php": "php",
59
+ ".phtml": "php",
60
+ ".css": "css",
61
+ ".scss": "scss",
62
+ ".sass": "sass",
63
+ ".less": "less",
64
+ ".html": "html",
65
+ ".htm": "html",
66
+ ".vue": "vue",
67
+ ".svelte": "svelte",
68
+ ".xml": "xml",
69
+ ".svg": "xml",
70
+ ".json": "json",
71
+ ".json5": "json5",
72
+ ".yaml": "yaml",
73
+ ".yml": "yaml",
74
+ ".toml": "toml",
75
+ ".ini": "ini",
76
+ ".md": "markdown",
77
+ ".markdown": "markdown",
78
+ ".sql": "sql",
79
+ ".sh": "bash",
80
+ ".bash": "bash",
81
+ ".zsh": "bash",
82
+ ".fish": "bash",
83
+ ".dockerfile": "dockerfile",
84
+ ".graphql": "graphql",
85
+ ".proto": "protobuf",
86
+ ".r": "r",
87
+ ".lua": "lua",
88
+ ".dart": "dart",
89
+ ".zig": "zig",
90
+ ".ex": "elixir",
91
+ ".exs": "elixir",
92
+ ".erl": "erlang",
93
+ ".hs": "haskell",
94
+ ".ml": "ocaml",
95
+ ".clj": "clojure",
96
+ ".cljs": "clojure",
97
+ ".pl": "perl",
98
+ ".perl": "perl",
99
+ ".elm": "elm",
100
+ ".lisp": "lisp"
101
+ };
102
+ function detectLanguageByPath(filePath) {
103
+ const dot = filePath.lastIndexOf(".");
104
+ if (dot === -1) {
105
+ const basename2 = filePath.split("/").pop() ?? "";
106
+ if (basename2 === "Dockerfile") return "dockerfile";
107
+ if (basename2 === "Makefile") return "makefile";
108
+ return null;
109
+ }
110
+ const ext = filePath.slice(dot).toLowerCase();
111
+ return EXT_TO_LANGUAGE[ext] ?? null;
112
+ }
113
+
25
114
  // ../shared/src/pricing.ts
26
115
  var PLANS = {
27
116
  hobby: {
@@ -286,7 +375,7 @@ function parseReplicasConfigString(content, filename) {
286
375
  }
287
376
 
288
377
  // ../shared/src/engine/environment.ts
289
- var DAYTONA_SNAPSHOT_ID = "13-05-2026-royal-york-v13";
378
+ var DAYTONA_SNAPSHOT_ID = "14-05-2026-royal-york-v13";
290
379
 
291
380
  // ../shared/src/engine/types.ts
292
381
  var DEFAULT_CHAT_TITLES = {
@@ -454,7 +543,7 @@ var BaseRefreshManager = class {
454
543
  this.health.lastErrorMessage = message;
455
544
  if (attempt < 3) {
456
545
  console.warn(`[${this.managerName}] Initial refresh attempt ${attempt} failed, retrying in 2s...`);
457
- await new Promise((resolve2) => setTimeout(resolve2, 2e3));
546
+ await new Promise((resolve3) => setTimeout(resolve3, 2e3));
458
547
  } else {
459
548
  console.error(`[${this.managerName}] Initial refresh failed after 3 attempts:`, error);
460
549
  }
@@ -695,7 +784,7 @@ var codexTokenManager = new CodexTokenManager();
695
784
  // src/git/service.ts
696
785
  import { readdir, stat } from "fs/promises";
697
786
  import { existsSync as existsSync2 } from "fs";
698
- import { execFileSync as execFileSync2 } from "child_process";
787
+ import { execFileSync as execFileSync2, spawnSync } from "child_process";
699
788
  import { join as join4 } from "path";
700
789
 
701
790
  // src/utils/state.ts
@@ -1053,6 +1142,7 @@ var GitService = class {
1053
1142
  if (removedMatch) {
1054
1143
  removed = parseInt(removedMatch[1], 10);
1055
1144
  }
1145
+ added += this.countUntrackedAddedLines(repoPath);
1056
1146
  return {
1057
1147
  added,
1058
1148
  removed
@@ -1062,18 +1152,60 @@ var GitService = class {
1062
1152
  return null;
1063
1153
  }
1064
1154
  }
1155
+ getUntrackedDiff(repoPath, extraFlags = []) {
1156
+ const listing = execFileSync2("git", ["ls-files", "--others", "--exclude-standard", "-z"], {
1157
+ cwd: repoPath,
1158
+ encoding: "utf-8",
1159
+ stdio: ["pipe", "pipe", "pipe"]
1160
+ });
1161
+ const paths = listing.split("\0").filter(Boolean);
1162
+ if (paths.length === 0) return "";
1163
+ try {
1164
+ execFileSync2("git", ["add", "--intent-to-add", "--", ...paths], { cwd: repoPath, stdio: ["pipe", "pipe", "pipe"] });
1165
+ const result = spawnSync("git", ["diff", ...extraFlags, "--", ...paths], {
1166
+ cwd: repoPath,
1167
+ encoding: "utf-8",
1168
+ maxBuffer: 50 * 1024 * 1024
1169
+ });
1170
+ return result.status === 0 || result.status === 1 ? result.stdout : "";
1171
+ } finally {
1172
+ try {
1173
+ execFileSync2("git", ["reset", "--", ...paths], { cwd: repoPath, stdio: ["pipe", "pipe", "pipe"] });
1174
+ } catch {
1175
+ }
1176
+ }
1177
+ }
1178
+ countUntrackedAddedLines(repoPath) {
1179
+ try {
1180
+ const output = this.getUntrackedDiff(repoPath, ["--shortstat"]);
1181
+ const match = output.match(/(\d+) insertion/);
1182
+ return match ? parseInt(match[1], 10) : 0;
1183
+ } catch (error) {
1184
+ console.error("Error counting untracked added lines:", error);
1185
+ return 0;
1186
+ }
1187
+ }
1065
1188
  getFullGitDiff(repoPath, defaultBranch) {
1066
1189
  try {
1067
1190
  const diffBase = this.getDiffBase(repoPath, defaultBranch);
1068
- return execFileSync2("git", ["diff", diffBase, "-M", "-C"], {
1191
+ const trackedDiff = execFileSync2("git", ["diff", diffBase, "-M", "-C"], {
1069
1192
  cwd: repoPath,
1070
1193
  encoding: "utf-8",
1071
1194
  stdio: ["pipe", "pipe", "pipe"]
1072
1195
  });
1196
+ return trackedDiff + this.getUntrackedAsDiff(repoPath);
1073
1197
  } catch {
1074
1198
  return "";
1075
1199
  }
1076
1200
  }
1201
+ getUntrackedAsDiff(repoPath) {
1202
+ try {
1203
+ return this.getUntrackedDiff(repoPath);
1204
+ } catch (error) {
1205
+ console.error("Error building untracked diff:", error);
1206
+ return "";
1207
+ }
1208
+ }
1077
1209
  getDiffBase(repoPath, defaultBranch) {
1078
1210
  const baseBranch = `origin/${defaultBranch}`;
1079
1211
  try {
@@ -2771,8 +2903,8 @@ var PromptStream = class {
2771
2903
  if (this.closed) {
2772
2904
  return Promise.resolve({ value: void 0, done: true });
2773
2905
  }
2774
- return new Promise((resolve2) => {
2775
- this.waiters.push(resolve2);
2906
+ return new Promise((resolve3) => {
2907
+ this.waiters.push(resolve3);
2776
2908
  });
2777
2909
  }
2778
2910
  };
@@ -3087,7 +3219,7 @@ function isJsonlEvent2(value) {
3087
3219
  return typeof value.timestamp === "string" && typeof value.type === "string" && isRecord3(value.payload);
3088
3220
  }
3089
3221
  function sleep(ms) {
3090
- return new Promise((resolve2) => setTimeout(resolve2, ms));
3222
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
3091
3223
  }
3092
3224
  function extractRateLimitsSnapshot(parsed) {
3093
3225
  if (!isRecord3(parsed)) return null;
@@ -4617,19 +4749,290 @@ var ChatService = class {
4617
4749
  }
4618
4750
  };
4619
4751
 
4752
+ // src/services/repo-file-service.ts
4753
+ import { execFile as execFile2 } from "child_process";
4754
+ import { readFile as readFile9, realpath, stat as stat3 } from "fs/promises";
4755
+ import { join as join13, resolve, extname } from "path";
4756
+ var CACHE_TTL_MS = 3e4;
4757
+ var SEARCH_TIMEOUT_MS = 15e3;
4758
+ var MAX_CONTENT_BYTES = 256 * 1024;
4759
+ var DEFAULT_LIMIT = 50;
4760
+ var MAX_LIMIT = 5e3;
4761
+ var EXCLUDED_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", ".next", "dist", "build", "out", "vendor", ".turbo", "__pycache__"]);
4762
+ function isBinaryExtension(filePath) {
4763
+ const ext = extname(filePath).toLowerCase();
4764
+ const binaryExts = /* @__PURE__ */ new Set([
4765
+ ".png",
4766
+ ".jpg",
4767
+ ".jpeg",
4768
+ ".gif",
4769
+ ".bmp",
4770
+ ".ico",
4771
+ ".webp",
4772
+ ".svg",
4773
+ ".woff",
4774
+ ".woff2",
4775
+ ".ttf",
4776
+ ".otf",
4777
+ ".eot",
4778
+ ".zip",
4779
+ ".tar",
4780
+ ".gz",
4781
+ ".bz2",
4782
+ ".7z",
4783
+ ".rar",
4784
+ ".pdf",
4785
+ ".doc",
4786
+ ".docx",
4787
+ ".xls",
4788
+ ".xlsx",
4789
+ ".exe",
4790
+ ".dll",
4791
+ ".so",
4792
+ ".dylib",
4793
+ ".o",
4794
+ ".a",
4795
+ ".mp3",
4796
+ ".mp4",
4797
+ ".avi",
4798
+ ".mov",
4799
+ ".wav",
4800
+ ".ogg",
4801
+ ".sqlite",
4802
+ ".db",
4803
+ ".lock"
4804
+ ]);
4805
+ return binaryExts.has(ext);
4806
+ }
4807
+ function isExcludedPath(filePath) {
4808
+ const segments = filePath.split("/");
4809
+ return segments.some((seg) => EXCLUDED_DIRS.has(seg));
4810
+ }
4811
+ function scoreMatch(query2, filePath) {
4812
+ const lowerQuery = query2.toLowerCase();
4813
+ const lowerPath = filePath.toLowerCase();
4814
+ const segments = lowerPath.split("/");
4815
+ const basename2 = segments[segments.length - 1] ?? "";
4816
+ if (basename2 === lowerQuery) return 100;
4817
+ if (basename2.startsWith(lowerQuery)) return 90;
4818
+ if (segments.some((seg) => seg === lowerQuery)) return 80;
4819
+ if (basename2.includes(lowerQuery)) return 70;
4820
+ if (segments.some((seg) => seg.includes(lowerQuery))) return 60;
4821
+ if (lowerPath.includes(lowerQuery)) return 50;
4822
+ return 0;
4823
+ }
4824
+ function execGitAsync(args, cwd, timeoutMs) {
4825
+ return new Promise((resolve3, reject) => {
4826
+ const child = execFile2("git", args, {
4827
+ cwd,
4828
+ encoding: "utf-8",
4829
+ timeout: timeoutMs,
4830
+ maxBuffer: 10 * 1024 * 1024
4831
+ }, (error, stdout) => {
4832
+ if (error) {
4833
+ reject(error);
4834
+ } else {
4835
+ resolve3(stdout);
4836
+ }
4837
+ });
4838
+ child.stdin?.end();
4839
+ });
4840
+ }
4841
+ var RepoFileService = class {
4842
+ cache = /* @__PURE__ */ new Map();
4843
+ gitService;
4844
+ indexInflight = null;
4845
+ constructor(gitService2) {
4846
+ this.gitService = gitService2;
4847
+ }
4848
+ async search(params) {
4849
+ const { q = "", prefix = "", refresh = false } = params;
4850
+ const limit = Math.min(Math.max(params.limit ?? DEFAULT_LIMIT, 1), MAX_LIMIT);
4851
+ const parsedOffset = params.cursor ? parseInt(Buffer.from(params.cursor, "base64").toString(), 10) : 0;
4852
+ const offset = Number.isNaN(parsedOffset) ? 0 : parsedOffset;
4853
+ let stale = false;
4854
+ let indexedAt = "";
4855
+ try {
4856
+ if (!this.indexInflight || refresh) {
4857
+ const inflight = this.indexAll(refresh);
4858
+ const wrapped = inflight.finally(() => {
4859
+ if (this.indexInflight === wrapped) this.indexInflight = null;
4860
+ });
4861
+ this.indexInflight = wrapped;
4862
+ }
4863
+ await this.indexInflight;
4864
+ } catch {
4865
+ stale = true;
4866
+ }
4867
+ const repos = await this.gitService.listRepositories();
4868
+ const multiRepo = repos.length > 1;
4869
+ const allResults = [];
4870
+ for (const repo of repos) {
4871
+ const cached = this.cache.get(repo.name);
4872
+ if (!cached) continue;
4873
+ if (!indexedAt || cached.timestamp > new Date(indexedAt).getTime()) {
4874
+ indexedAt = new Date(cached.timestamp).toISOString();
4875
+ }
4876
+ const isStale = Date.now() - cached.timestamp > CACHE_TTL_MS;
4877
+ if (isStale) stale = true;
4878
+ for (const filePath of cached.files) {
4879
+ if (prefix && !filePath.startsWith(prefix)) continue;
4880
+ let score = 0;
4881
+ if (q) {
4882
+ score = scoreMatch(q, filePath);
4883
+ if (score === 0) continue;
4884
+ }
4885
+ allResults.push({
4886
+ repoName: repo.name,
4887
+ path: filePath,
4888
+ displayPath: multiRepo ? `${repo.name}/${filePath}` : filePath,
4889
+ status: cached.changedFiles.has(filePath) ? "changed" : "untouched",
4890
+ score,
4891
+ indexedAt: new Date(cached.timestamp).toISOString(),
4892
+ stale: isStale
4893
+ });
4894
+ }
4895
+ }
4896
+ allResults.sort((a, b) => q ? b.score - a.score || a.displayPath.localeCompare(b.displayPath) : a.displayPath.localeCompare(b.displayPath));
4897
+ const paged = allResults.slice(offset, offset + limit);
4898
+ const nextOffset = offset + limit;
4899
+ const nextCursor = nextOffset < allResults.length ? Buffer.from(String(nextOffset)).toString("base64") : null;
4900
+ return {
4901
+ files: paged,
4902
+ nextCursor,
4903
+ indexedAt: indexedAt || (/* @__PURE__ */ new Date()).toISOString(),
4904
+ stale
4905
+ };
4906
+ }
4907
+ async readFile(repoName, filePath) {
4908
+ if (filePath.includes("..") || filePath.startsWith("/")) {
4909
+ return null;
4910
+ }
4911
+ const repos = await this.gitService.listRepositories();
4912
+ const repo = repos.find((r) => r.name === repoName);
4913
+ if (!repo) return null;
4914
+ try {
4915
+ const fullPath = await realpath(resolve(join13(repo.path, filePath)));
4916
+ const repoRoot = await realpath(repo.path);
4917
+ const repoPrefix = repoRoot.endsWith("/") ? repoRoot : repoRoot + "/";
4918
+ if (!fullPath.startsWith(repoPrefix) && fullPath !== repoRoot) return null;
4919
+ const fileStat = await stat3(fullPath);
4920
+ if (!fileStat.isFile()) return null;
4921
+ const sizeBytes = fileStat.size;
4922
+ if (isBinaryExtension(filePath)) {
4923
+ return {
4924
+ repoName,
4925
+ path: filePath,
4926
+ content: null,
4927
+ language: null,
4928
+ sizeBytes,
4929
+ binary: true,
4930
+ tooLarge: false
4931
+ };
4932
+ }
4933
+ if (sizeBytes > MAX_CONTENT_BYTES) {
4934
+ return {
4935
+ repoName,
4936
+ path: filePath,
4937
+ content: null,
4938
+ language: detectLanguageByPath(filePath),
4939
+ sizeBytes,
4940
+ binary: false,
4941
+ tooLarge: true
4942
+ };
4943
+ }
4944
+ const content = await readFile9(fullPath, "utf-8");
4945
+ return {
4946
+ repoName,
4947
+ path: filePath,
4948
+ content,
4949
+ language: detectLanguageByPath(filePath),
4950
+ sizeBytes,
4951
+ binary: false,
4952
+ tooLarge: false
4953
+ };
4954
+ } catch {
4955
+ return null;
4956
+ }
4957
+ }
4958
+ async indexAll(refresh) {
4959
+ const repos = await this.gitService.listRepositories();
4960
+ for (const repo of repos) {
4961
+ const cached = this.cache.get(repo.name);
4962
+ const isFresh = cached && Date.now() - cached.timestamp < CACHE_TTL_MS;
4963
+ if (isFresh && !refresh) continue;
4964
+ try {
4965
+ const [files, changedFiles] = await Promise.all([
4966
+ this.listRepoFiles(repo.path),
4967
+ this.listChangedFiles(repo.path, repo.defaultBranch)
4968
+ ]);
4969
+ this.cache.set(repo.name, {
4970
+ files,
4971
+ changedFiles,
4972
+ timestamp: Date.now()
4973
+ });
4974
+ } catch {
4975
+ }
4976
+ }
4977
+ }
4978
+ async listRepoFiles(repoPath) {
4979
+ const output = await execGitAsync(
4980
+ ["ls-files", "-z", "--cached", "--others", "--exclude-standard"],
4981
+ repoPath,
4982
+ SEARCH_TIMEOUT_MS
4983
+ );
4984
+ return output.split("\0").filter((f) => f.length > 0 && !isExcludedPath(f));
4985
+ }
4986
+ async listChangedFiles(repoPath, defaultBranch) {
4987
+ const changedFiles = /* @__PURE__ */ new Set();
4988
+ try {
4989
+ const mergeBase = await execGitAsync(
4990
+ ["merge-base", `origin/${defaultBranch}`, "HEAD"],
4991
+ repoPath,
4992
+ SEARCH_TIMEOUT_MS
4993
+ );
4994
+ const diffOutput = await execGitAsync(
4995
+ ["diff", "--name-only", "-M", mergeBase.trim()],
4996
+ repoPath,
4997
+ SEARCH_TIMEOUT_MS
4998
+ );
4999
+ for (const file of diffOutput.split("\n")) {
5000
+ if (file.length > 0) {
5001
+ changedFiles.add(file);
5002
+ }
5003
+ }
5004
+ } catch {
5005
+ }
5006
+ try {
5007
+ const untrackedOutput = await execGitAsync(
5008
+ ["ls-files", "--others", "--exclude-standard"],
5009
+ repoPath,
5010
+ SEARCH_TIMEOUT_MS
5011
+ );
5012
+ for (const file of untrackedOutput.split("\n")) {
5013
+ if (file.length > 0) {
5014
+ changedFiles.add(file);
5015
+ }
5016
+ }
5017
+ } catch {
5018
+ }
5019
+ return changedFiles;
5020
+ }
5021
+ };
5022
+
4620
5023
  // src/v1-routes.ts
4621
5024
  import { Hono } from "hono";
4622
5025
  import { z as z2 } from "zod";
4623
- import { readdir as readdir6, stat as stat3, readFile as readFile12 } from "fs/promises";
4624
- import { join as join16, resolve } from "path";
5026
+ import { readdir as readdir6, stat as stat4, readFile as readFile13 } from "fs/promises";
5027
+ import { join as join17, resolve as resolve2 } from "path";
4625
5028
 
4626
5029
  // src/services/plan-service.ts
4627
- import { readdir as readdir4, readFile as readFile9 } from "fs/promises";
5030
+ import { readdir as readdir4, readFile as readFile10 } from "fs/promises";
4628
5031
  import { homedir as homedir10 } from "os";
4629
- import { basename, join as join13 } from "path";
5032
+ import { basename, join as join14 } from "path";
4630
5033
  var PLAN_DIRECTORIES = [
4631
- join13(homedir10(), ".claude", "plans"),
4632
- join13(homedir10(), ".replicas", "plans")
5034
+ join14(homedir10(), ".claude", "plans"),
5035
+ join14(homedir10(), ".replicas", "plans")
4633
5036
  ];
4634
5037
  function isMarkdownFile(filename) {
4635
5038
  return filename.toLowerCase().endsWith(".md");
@@ -4663,9 +5066,9 @@ var PlanService = class {
4663
5066
  return null;
4664
5067
  }
4665
5068
  for (const directory of PLAN_DIRECTORIES) {
4666
- const filePath = join13(directory, safeFilename);
5069
+ const filePath = join14(directory, safeFilename);
4667
5070
  try {
4668
- const content = await readFile9(filePath, "utf-8");
5071
+ const content = await readFile10(filePath, "utf-8");
4669
5072
  return { filename: safeFilename, content };
4670
5073
  } catch {
4671
5074
  }
@@ -4677,15 +5080,15 @@ var planService = new PlanService();
4677
5080
 
4678
5081
  // src/services/warm-hooks-service.ts
4679
5082
  import { spawn } from "child_process";
4680
- import { readFile as readFile11 } from "fs/promises";
5083
+ import { readFile as readFile12 } from "fs/promises";
4681
5084
  import { existsSync as existsSync8 } from "fs";
4682
- import { join as join15 } from "path";
5085
+ import { join as join16 } from "path";
4683
5086
 
4684
5087
  // src/services/warm-hook-logs-service.ts
4685
5088
  import { createHash as createHash2 } from "crypto";
4686
- import { mkdir as mkdir11, readFile as readFile10, writeFile as writeFile9, readdir as readdir5 } from "fs/promises";
4687
- import { join as join14 } from "path";
4688
- var LOGS_DIR2 = join14(SANDBOX_PATHS.REPLICAS_DIR, "warm-hook-logs");
5089
+ import { mkdir as mkdir11, readFile as readFile11, writeFile as writeFile9, readdir as readdir5 } from "fs/promises";
5090
+ import { join as join15 } from "path";
5091
+ var LOGS_DIR2 = join15(SANDBOX_PATHS.REPLICAS_DIR, "warm-hook-logs");
4689
5092
  function sanitizeFilename2(name) {
4690
5093
  const safe = name.replace(/[^a-zA-Z0-9._-]/g, "_");
4691
5094
  const hash = createHash2("sha256").update(name).digest("hex").slice(0, 8);
@@ -4715,7 +5118,7 @@ var WarmHookLogsService = class {
4715
5118
  hookName: "organization",
4716
5119
  ...entry
4717
5120
  };
4718
- await writeFile9(join14(LOGS_DIR2, globalFilename()), `${JSON.stringify(log, null, 2)}
5121
+ await writeFile9(join15(LOGS_DIR2, globalFilename()), `${JSON.stringify(log, null, 2)}
4719
5122
  `, "utf-8");
4720
5123
  }
4721
5124
  async saveEnvironmentHookLog(entry) {
@@ -4725,7 +5128,7 @@ var WarmHookLogsService = class {
4725
5128
  hookName: "environment",
4726
5129
  ...entry
4727
5130
  };
4728
- await writeFile9(join14(LOGS_DIR2, environmentFilename()), `${JSON.stringify(log, null, 2)}
5131
+ await writeFile9(join15(LOGS_DIR2, environmentFilename()), `${JSON.stringify(log, null, 2)}
4729
5132
  `, "utf-8");
4730
5133
  }
4731
5134
  async saveRepoHookLog(repoName, entry) {
@@ -4735,7 +5138,7 @@ var WarmHookLogsService = class {
4735
5138
  hookName: repoName,
4736
5139
  ...entry
4737
5140
  };
4738
- await writeFile9(join14(LOGS_DIR2, repoFilename2(repoName)), `${JSON.stringify(log, null, 2)}
5141
+ await writeFile9(join15(LOGS_DIR2, repoFilename2(repoName)), `${JSON.stringify(log, null, 2)}
4739
5142
  `, "utf-8");
4740
5143
  }
4741
5144
  async getAllLogs() {
@@ -4754,7 +5157,7 @@ var WarmHookLogsService = class {
4754
5157
  continue;
4755
5158
  }
4756
5159
  try {
4757
- const raw = await readFile10(join14(LOGS_DIR2, file), "utf-8");
5160
+ const raw = await readFile11(join15(LOGS_DIR2, file), "utf-8");
4758
5161
  const stored = JSON.parse(raw);
4759
5162
  logs.push(withPreview2(stored));
4760
5163
  } catch {
@@ -4772,7 +5175,7 @@ var WarmHookLogsService = class {
4772
5175
  async getFullOutput(hookType, hookName) {
4773
5176
  const filename = hookType === "global" ? globalFilename() : hookType === "environment" ? environmentFilename() : repoFilename2(hookName);
4774
5177
  try {
4775
- const raw = await readFile10(join14(LOGS_DIR2, filename), "utf-8");
5178
+ const raw = await readFile11(join15(LOGS_DIR2, filename), "utf-8");
4776
5179
  const stored = JSON.parse(raw);
4777
5180
  if (stored.hookType !== hookType || stored.hookName !== hookName) {
4778
5181
  return null;
@@ -4791,12 +5194,12 @@ var warmHookLogsService = new WarmHookLogsService();
4791
5194
  // src/services/warm-hooks-service.ts
4792
5195
  async function readRepoWarmHook(repoPath) {
4793
5196
  for (const filename of REPLICAS_CONFIG_FILENAMES) {
4794
- const configPath = join15(repoPath, filename);
5197
+ const configPath = join16(repoPath, filename);
4795
5198
  if (!existsSync8(configPath)) {
4796
5199
  continue;
4797
5200
  }
4798
5201
  try {
4799
- const raw = await readFile11(configPath, "utf-8");
5202
+ const raw = await readFile12(configPath, "utf-8");
4800
5203
  const config = parseReplicasConfigString(raw, filename);
4801
5204
  if (!config.warmHook) {
4802
5205
  return null;
@@ -4842,7 +5245,7 @@ async function executeHookScriptStreaming(params) {
4842
5245
  let bufferExceeded = false;
4843
5246
  params.onChunk(`$ ${params.label}
4844
5247
  `);
4845
- return new Promise((resolve2) => {
5248
+ return new Promise((resolve3) => {
4846
5249
  const proc = spawn("bash", ["-lc", params.content], {
4847
5250
  cwd: params.cwd,
4848
5251
  env: process.env,
@@ -4875,7 +5278,7 @@ async function executeHookScriptStreaming(params) {
4875
5278
  proc.on("close", (code) => {
4876
5279
  clearTimeout(timer);
4877
5280
  const output = [`$ ${params.label}`, ...outputParts].join("");
4878
- resolve2({
5281
+ resolve3({
4879
5282
  exitCode: bufferExceeded ? 1 : code ?? 1,
4880
5283
  output,
4881
5284
  timedOut: killed && !bufferExceeded
@@ -4885,7 +5288,7 @@ async function executeHookScriptStreaming(params) {
4885
5288
  clearTimeout(timer);
4886
5289
  const output = [`$ ${params.label}
4887
5290
  `, ...outputParts, err.message].join("");
4888
- resolve2({
5291
+ resolve3({
4889
5292
  exitCode: 1,
4890
5293
  output,
4891
5294
  timedOut: false
@@ -5286,6 +5689,28 @@ function createV1Routes(deps) {
5286
5689
  };
5287
5690
  return c.json(response);
5288
5691
  });
5692
+ app2.get("/repo-files/search", async (c) => {
5693
+ const q = c.req.query("q") ?? "";
5694
+ const prefix = c.req.query("prefix") ?? "";
5695
+ const rawLimit = c.req.query("limit") ? parseInt(c.req.query("limit"), 10) : void 0;
5696
+ const limit = rawLimit !== void 0 && !Number.isNaN(rawLimit) ? rawLimit : void 0;
5697
+ const cursor = c.req.query("cursor") ?? void 0;
5698
+ const refresh = c.req.query("refresh") === "true";
5699
+ const result = await deps.repoFileService.search({ q, prefix, limit, cursor, refresh });
5700
+ return c.json(result);
5701
+ });
5702
+ app2.get("/repo-files/content", async (c) => {
5703
+ const repoName = c.req.query("repoName");
5704
+ const path4 = c.req.query("path");
5705
+ if (!repoName || !path4) {
5706
+ return c.json(jsonError("repoName and path are required"), 400);
5707
+ }
5708
+ const result = await deps.repoFileService.readFile(repoName, path4);
5709
+ if (!result) {
5710
+ return c.json(jsonError("File not found"), 404);
5711
+ }
5712
+ return c.json(result);
5713
+ });
5289
5714
  app2.get("/plans", async (c) => {
5290
5715
  try {
5291
5716
  const plans = await planService.listPlans();
@@ -5550,8 +5975,8 @@ function createV1Routes(deps) {
5550
5975
  const logFiles = files.filter((f) => f.endsWith(".log"));
5551
5976
  const sessions = await Promise.all(
5552
5977
  logFiles.map(async (filename) => {
5553
- const filePath = join16(LOG_DIR, filename);
5554
- const fileStat = await stat3(filePath);
5978
+ const filePath = join17(LOG_DIR, filename);
5979
+ const fileStat = await stat4(filePath);
5555
5980
  const sessionId = filename.replace(/\.log$/, "");
5556
5981
  return {
5557
5982
  sessionId,
@@ -5579,15 +6004,15 @@ function createV1Routes(deps) {
5579
6004
  if (!sessionId || /[/\\]/.test(sessionId) || sessionId.includes("..")) {
5580
6005
  return c.json(jsonError("Invalid session ID"), 400);
5581
6006
  }
5582
- const filePath = resolve(LOG_DIR, `${sessionId}.log`);
5583
- if (!filePath.startsWith(resolve(LOG_DIR))) {
6007
+ const filePath = resolve2(LOG_DIR, `${sessionId}.log`);
6008
+ if (!filePath.startsWith(resolve2(LOG_DIR))) {
5584
6009
  return c.json(jsonError("Invalid session ID"), 400);
5585
6010
  }
5586
6011
  const offset = parseInt(c.req.query("offset") || "0", 10);
5587
6012
  const limit = Math.min(parseInt(c.req.query("limit") || "500", 10), 5e3);
5588
6013
  let content;
5589
6014
  try {
5590
- content = await readFile12(filePath, "utf-8");
6015
+ content = await readFile13(filePath, "utf-8");
5591
6016
  } catch {
5592
6017
  return c.json(jsonError("Log session not found"), 404);
5593
6018
  }
@@ -5661,7 +6086,7 @@ app.get("/health", async (c) => {
5661
6086
  return c.json({ status: "initializing", timestamp: (/* @__PURE__ */ new Date()).toISOString() }, 503);
5662
6087
  }
5663
6088
  try {
5664
- const logContent = await readFile13("/var/log/cloud-init-output.log", "utf-8");
6089
+ const logContent = await readFile14("/var/log/cloud-init-output.log", "utf-8");
5665
6090
  let status;
5666
6091
  if (logContent.includes(COMPLETION_MESSAGE)) {
5667
6092
  status = "active";
@@ -5703,7 +6128,8 @@ app.get("/token-refresh/health", async (c) => {
5703
6128
  codex: codexTokenManager.getHealthStatus()
5704
6129
  });
5705
6130
  });
5706
- app.route("/", createV1Routes({ chatService }));
6131
+ var repoFileService = new RepoFileService(gitService);
6132
+ app.route("/", createV1Routes({ chatService, repoFileService }));
5707
6133
  var port = ENGINE_ENV.REPLICAS_ENGINE_PORT;
5708
6134
  function startStatusBroadcaster() {
5709
6135
  let previousRepoStatus = "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.167",
3
+ "version": "0.1.168",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",