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.
- package/dist/src/index.js +464 -38
- 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
|
|
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 = "
|
|
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((
|
|
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
|
-
|
|
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((
|
|
2775
|
-
this.waiters.push(
|
|
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((
|
|
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
|
|
4624
|
-
import { join as
|
|
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
|
|
5030
|
+
import { readdir as readdir4, readFile as readFile10 } from "fs/promises";
|
|
4628
5031
|
import { homedir as homedir10 } from "os";
|
|
4629
|
-
import { basename, join as
|
|
5032
|
+
import { basename, join as join14 } from "path";
|
|
4630
5033
|
var PLAN_DIRECTORIES = [
|
|
4631
|
-
|
|
4632
|
-
|
|
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 =
|
|
5069
|
+
const filePath = join14(directory, safeFilename);
|
|
4667
5070
|
try {
|
|
4668
|
-
const content = await
|
|
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
|
|
5083
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
4681
5084
|
import { existsSync as existsSync8 } from "fs";
|
|
4682
|
-
import { join as
|
|
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
|
|
4687
|
-
import { join as
|
|
4688
|
-
var LOGS_DIR2 =
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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 =
|
|
5197
|
+
const configPath = join16(repoPath, filename);
|
|
4795
5198
|
if (!existsSync8(configPath)) {
|
|
4796
5199
|
continue;
|
|
4797
5200
|
}
|
|
4798
5201
|
try {
|
|
4799
|
-
const raw = await
|
|
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((
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
5554
|
-
const fileStat = await
|
|
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 =
|
|
5583
|
-
if (!filePath.startsWith(
|
|
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
|
|
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
|
|
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
|
-
|
|
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 = "";
|