replicas-engine 0.1.166 → 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 +514 -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
|
|
|
@@ -20,6 +20,96 @@ import { readFileSync } from "fs";
|
|
|
20
20
|
|
|
21
21
|
// ../shared/src/event.ts
|
|
22
22
|
var CODEX_QUOTA_STATUS_EVENT_TYPE = "codex-quota-status";
|
|
23
|
+
var CONTEXT_USAGE_EVENT_TYPE = "context-usage";
|
|
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
|
+
}
|
|
23
113
|
|
|
24
114
|
// ../shared/src/pricing.ts
|
|
25
115
|
var PLANS = {
|
|
@@ -285,7 +375,7 @@ function parseReplicasConfigString(content, filename) {
|
|
|
285
375
|
}
|
|
286
376
|
|
|
287
377
|
// ../shared/src/engine/environment.ts
|
|
288
|
-
var DAYTONA_SNAPSHOT_ID = "
|
|
378
|
+
var DAYTONA_SNAPSHOT_ID = "14-05-2026-royal-york-v13";
|
|
289
379
|
|
|
290
380
|
// ../shared/src/engine/types.ts
|
|
291
381
|
var DEFAULT_CHAT_TITLES = {
|
|
@@ -453,7 +543,7 @@ var BaseRefreshManager = class {
|
|
|
453
543
|
this.health.lastErrorMessage = message;
|
|
454
544
|
if (attempt < 3) {
|
|
455
545
|
console.warn(`[${this.managerName}] Initial refresh attempt ${attempt} failed, retrying in 2s...`);
|
|
456
|
-
await new Promise((
|
|
546
|
+
await new Promise((resolve3) => setTimeout(resolve3, 2e3));
|
|
457
547
|
} else {
|
|
458
548
|
console.error(`[${this.managerName}] Initial refresh failed after 3 attempts:`, error);
|
|
459
549
|
}
|
|
@@ -694,7 +784,7 @@ var codexTokenManager = new CodexTokenManager();
|
|
|
694
784
|
// src/git/service.ts
|
|
695
785
|
import { readdir, stat } from "fs/promises";
|
|
696
786
|
import { existsSync as existsSync2 } from "fs";
|
|
697
|
-
import { execFileSync as execFileSync2 } from "child_process";
|
|
787
|
+
import { execFileSync as execFileSync2, spawnSync } from "child_process";
|
|
698
788
|
import { join as join4 } from "path";
|
|
699
789
|
|
|
700
790
|
// src/utils/state.ts
|
|
@@ -1052,6 +1142,7 @@ var GitService = class {
|
|
|
1052
1142
|
if (removedMatch) {
|
|
1053
1143
|
removed = parseInt(removedMatch[1], 10);
|
|
1054
1144
|
}
|
|
1145
|
+
added += this.countUntrackedAddedLines(repoPath);
|
|
1055
1146
|
return {
|
|
1056
1147
|
added,
|
|
1057
1148
|
removed
|
|
@@ -1061,18 +1152,60 @@ var GitService = class {
|
|
|
1061
1152
|
return null;
|
|
1062
1153
|
}
|
|
1063
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
|
+
}
|
|
1064
1188
|
getFullGitDiff(repoPath, defaultBranch) {
|
|
1065
1189
|
try {
|
|
1066
1190
|
const diffBase = this.getDiffBase(repoPath, defaultBranch);
|
|
1067
|
-
|
|
1191
|
+
const trackedDiff = execFileSync2("git", ["diff", diffBase, "-M", "-C"], {
|
|
1068
1192
|
cwd: repoPath,
|
|
1069
1193
|
encoding: "utf-8",
|
|
1070
1194
|
stdio: ["pipe", "pipe", "pipe"]
|
|
1071
1195
|
});
|
|
1196
|
+
return trackedDiff + this.getUntrackedAsDiff(repoPath);
|
|
1072
1197
|
} catch {
|
|
1073
1198
|
return "";
|
|
1074
1199
|
}
|
|
1075
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
|
+
}
|
|
1076
1209
|
getDiffBase(repoPath, defaultBranch) {
|
|
1077
1210
|
const baseBranch = `origin/${defaultBranch}`;
|
|
1078
1211
|
try {
|
|
@@ -2616,6 +2749,15 @@ var CodingAgentManager = class {
|
|
|
2616
2749
|
await this.initialized;
|
|
2617
2750
|
return this.messageQueue.enqueue(request);
|
|
2618
2751
|
}
|
|
2752
|
+
emitContextUsage(payload) {
|
|
2753
|
+
const event = {
|
|
2754
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2755
|
+
type: CONTEXT_USAGE_EVENT_TYPE,
|
|
2756
|
+
payload: { ...payload }
|
|
2757
|
+
};
|
|
2758
|
+
this.onEvent(event);
|
|
2759
|
+
return event;
|
|
2760
|
+
}
|
|
2619
2761
|
buildCombinedInstructions(customInstructions) {
|
|
2620
2762
|
const startHooksInstruction = this.getStartHooksInstruction();
|
|
2621
2763
|
const repositorySystemPromptInstruction = this.getRepositorySystemPromptInstruction();
|
|
@@ -2761,8 +2903,8 @@ var PromptStream = class {
|
|
|
2761
2903
|
if (this.closed) {
|
|
2762
2904
|
return Promise.resolve({ value: void 0, done: true });
|
|
2763
2905
|
}
|
|
2764
|
-
return new Promise((
|
|
2765
|
-
this.waiters.push(
|
|
2906
|
+
return new Promise((resolve3) => {
|
|
2907
|
+
this.waiters.push(resolve3);
|
|
2766
2908
|
});
|
|
2767
2909
|
}
|
|
2768
2910
|
};
|
|
@@ -2926,6 +3068,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
|
|
|
2926
3068
|
}
|
|
2927
3069
|
}
|
|
2928
3070
|
if (msg.type === "result") {
|
|
3071
|
+
await this.recordContextUsage(response);
|
|
2929
3072
|
this.activePromptStream?.close();
|
|
2930
3073
|
break;
|
|
2931
3074
|
}
|
|
@@ -2947,6 +3090,45 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
|
|
|
2947
3090
|
}
|
|
2948
3091
|
}
|
|
2949
3092
|
}
|
|
3093
|
+
getContextUsageProvider() {
|
|
3094
|
+
return this.systemPromptOverride ? "relay" : "claude";
|
|
3095
|
+
}
|
|
3096
|
+
buildContextUsagePayload(usage) {
|
|
3097
|
+
const maxTokens = Number.isFinite(usage.maxTokens) ? usage.maxTokens : null;
|
|
3098
|
+
const percentage = Number.isFinite(usage.percentage) ? usage.percentage : maxTokens && maxTokens > 0 ? usage.totalTokens / maxTokens * 100 : 0;
|
|
3099
|
+
return {
|
|
3100
|
+
provider: this.getContextUsageProvider(),
|
|
3101
|
+
source: "claude_context",
|
|
3102
|
+
model: usage.model,
|
|
3103
|
+
totalTokens: usage.totalTokens,
|
|
3104
|
+
maxTokens,
|
|
3105
|
+
rawMaxTokens: Number.isFinite(usage.rawMaxTokens) ? usage.rawMaxTokens : maxTokens,
|
|
3106
|
+
percentage,
|
|
3107
|
+
categories: usage.categories.map((category) => ({
|
|
3108
|
+
name: category.name,
|
|
3109
|
+
tokens: category.tokens,
|
|
3110
|
+
percentage: maxTokens && maxTokens > 0 ? category.tokens / maxTokens * 100 : 0,
|
|
3111
|
+
color: category.color,
|
|
3112
|
+
...category.isDeferred !== void 0 ? { isDeferred: category.isDeferred } : {}
|
|
3113
|
+
})),
|
|
3114
|
+
apiUsage: usage.apiUsage ? {
|
|
3115
|
+
inputTokens: usage.apiUsage.input_tokens,
|
|
3116
|
+
outputTokens: usage.apiUsage.output_tokens,
|
|
3117
|
+
cacheCreationInputTokens: usage.apiUsage.cache_creation_input_tokens,
|
|
3118
|
+
cacheReadInputTokens: usage.apiUsage.cache_read_input_tokens
|
|
3119
|
+
} : null,
|
|
3120
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3121
|
+
};
|
|
3122
|
+
}
|
|
3123
|
+
async recordContextUsage(response) {
|
|
3124
|
+
try {
|
|
3125
|
+
const usage = await response.getContextUsage();
|
|
3126
|
+
const event = this.emitContextUsage(this.buildContextUsagePayload(usage));
|
|
3127
|
+
await appendFile4(this.historyFile, JSON.stringify(event) + "\n", "utf-8");
|
|
3128
|
+
} catch (error) {
|
|
3129
|
+
console.warn("[ClaudeManager] Failed to record context usage:", error instanceof Error ? error.message : error);
|
|
3130
|
+
}
|
|
3131
|
+
}
|
|
2950
3132
|
/**
|
|
2951
3133
|
* Determines if an error is an OAuth authentication failure from the Claude SDK.
|
|
2952
3134
|
* Known patterns:
|
|
@@ -3037,7 +3219,7 @@ function isJsonlEvent2(value) {
|
|
|
3037
3219
|
return typeof value.timestamp === "string" && typeof value.type === "string" && isRecord3(value.payload);
|
|
3038
3220
|
}
|
|
3039
3221
|
function sleep(ms) {
|
|
3040
|
-
return new Promise((
|
|
3222
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
3041
3223
|
}
|
|
3042
3224
|
function extractRateLimitsSnapshot(parsed) {
|
|
3043
3225
|
if (!isRecord3(parsed)) return null;
|
|
@@ -4567,19 +4749,290 @@ var ChatService = class {
|
|
|
4567
4749
|
}
|
|
4568
4750
|
};
|
|
4569
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
|
+
|
|
4570
5023
|
// src/v1-routes.ts
|
|
4571
5024
|
import { Hono } from "hono";
|
|
4572
5025
|
import { z as z2 } from "zod";
|
|
4573
|
-
import { readdir as readdir6, stat as
|
|
4574
|
-
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";
|
|
4575
5028
|
|
|
4576
5029
|
// src/services/plan-service.ts
|
|
4577
|
-
import { readdir as readdir4, readFile as
|
|
5030
|
+
import { readdir as readdir4, readFile as readFile10 } from "fs/promises";
|
|
4578
5031
|
import { homedir as homedir10 } from "os";
|
|
4579
|
-
import { basename, join as
|
|
5032
|
+
import { basename, join as join14 } from "path";
|
|
4580
5033
|
var PLAN_DIRECTORIES = [
|
|
4581
|
-
|
|
4582
|
-
|
|
5034
|
+
join14(homedir10(), ".claude", "plans"),
|
|
5035
|
+
join14(homedir10(), ".replicas", "plans")
|
|
4583
5036
|
];
|
|
4584
5037
|
function isMarkdownFile(filename) {
|
|
4585
5038
|
return filename.toLowerCase().endsWith(".md");
|
|
@@ -4613,9 +5066,9 @@ var PlanService = class {
|
|
|
4613
5066
|
return null;
|
|
4614
5067
|
}
|
|
4615
5068
|
for (const directory of PLAN_DIRECTORIES) {
|
|
4616
|
-
const filePath =
|
|
5069
|
+
const filePath = join14(directory, safeFilename);
|
|
4617
5070
|
try {
|
|
4618
|
-
const content = await
|
|
5071
|
+
const content = await readFile10(filePath, "utf-8");
|
|
4619
5072
|
return { filename: safeFilename, content };
|
|
4620
5073
|
} catch {
|
|
4621
5074
|
}
|
|
@@ -4627,15 +5080,15 @@ var planService = new PlanService();
|
|
|
4627
5080
|
|
|
4628
5081
|
// src/services/warm-hooks-service.ts
|
|
4629
5082
|
import { spawn } from "child_process";
|
|
4630
|
-
import { readFile as
|
|
5083
|
+
import { readFile as readFile12 } from "fs/promises";
|
|
4631
5084
|
import { existsSync as existsSync8 } from "fs";
|
|
4632
|
-
import { join as
|
|
5085
|
+
import { join as join16 } from "path";
|
|
4633
5086
|
|
|
4634
5087
|
// src/services/warm-hook-logs-service.ts
|
|
4635
5088
|
import { createHash as createHash2 } from "crypto";
|
|
4636
|
-
import { mkdir as mkdir11, readFile as
|
|
4637
|
-
import { join as
|
|
4638
|
-
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");
|
|
4639
5092
|
function sanitizeFilename2(name) {
|
|
4640
5093
|
const safe = name.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
4641
5094
|
const hash = createHash2("sha256").update(name).digest("hex").slice(0, 8);
|
|
@@ -4665,7 +5118,7 @@ var WarmHookLogsService = class {
|
|
|
4665
5118
|
hookName: "organization",
|
|
4666
5119
|
...entry
|
|
4667
5120
|
};
|
|
4668
|
-
await writeFile9(
|
|
5121
|
+
await writeFile9(join15(LOGS_DIR2, globalFilename()), `${JSON.stringify(log, null, 2)}
|
|
4669
5122
|
`, "utf-8");
|
|
4670
5123
|
}
|
|
4671
5124
|
async saveEnvironmentHookLog(entry) {
|
|
@@ -4675,7 +5128,7 @@ var WarmHookLogsService = class {
|
|
|
4675
5128
|
hookName: "environment",
|
|
4676
5129
|
...entry
|
|
4677
5130
|
};
|
|
4678
|
-
await writeFile9(
|
|
5131
|
+
await writeFile9(join15(LOGS_DIR2, environmentFilename()), `${JSON.stringify(log, null, 2)}
|
|
4679
5132
|
`, "utf-8");
|
|
4680
5133
|
}
|
|
4681
5134
|
async saveRepoHookLog(repoName, entry) {
|
|
@@ -4685,7 +5138,7 @@ var WarmHookLogsService = class {
|
|
|
4685
5138
|
hookName: repoName,
|
|
4686
5139
|
...entry
|
|
4687
5140
|
};
|
|
4688
|
-
await writeFile9(
|
|
5141
|
+
await writeFile9(join15(LOGS_DIR2, repoFilename2(repoName)), `${JSON.stringify(log, null, 2)}
|
|
4689
5142
|
`, "utf-8");
|
|
4690
5143
|
}
|
|
4691
5144
|
async getAllLogs() {
|
|
@@ -4704,7 +5157,7 @@ var WarmHookLogsService = class {
|
|
|
4704
5157
|
continue;
|
|
4705
5158
|
}
|
|
4706
5159
|
try {
|
|
4707
|
-
const raw = await
|
|
5160
|
+
const raw = await readFile11(join15(LOGS_DIR2, file), "utf-8");
|
|
4708
5161
|
const stored = JSON.parse(raw);
|
|
4709
5162
|
logs.push(withPreview2(stored));
|
|
4710
5163
|
} catch {
|
|
@@ -4722,7 +5175,7 @@ var WarmHookLogsService = class {
|
|
|
4722
5175
|
async getFullOutput(hookType, hookName) {
|
|
4723
5176
|
const filename = hookType === "global" ? globalFilename() : hookType === "environment" ? environmentFilename() : repoFilename2(hookName);
|
|
4724
5177
|
try {
|
|
4725
|
-
const raw = await
|
|
5178
|
+
const raw = await readFile11(join15(LOGS_DIR2, filename), "utf-8");
|
|
4726
5179
|
const stored = JSON.parse(raw);
|
|
4727
5180
|
if (stored.hookType !== hookType || stored.hookName !== hookName) {
|
|
4728
5181
|
return null;
|
|
@@ -4741,12 +5194,12 @@ var warmHookLogsService = new WarmHookLogsService();
|
|
|
4741
5194
|
// src/services/warm-hooks-service.ts
|
|
4742
5195
|
async function readRepoWarmHook(repoPath) {
|
|
4743
5196
|
for (const filename of REPLICAS_CONFIG_FILENAMES) {
|
|
4744
|
-
const configPath =
|
|
5197
|
+
const configPath = join16(repoPath, filename);
|
|
4745
5198
|
if (!existsSync8(configPath)) {
|
|
4746
5199
|
continue;
|
|
4747
5200
|
}
|
|
4748
5201
|
try {
|
|
4749
|
-
const raw = await
|
|
5202
|
+
const raw = await readFile12(configPath, "utf-8");
|
|
4750
5203
|
const config = parseReplicasConfigString(raw, filename);
|
|
4751
5204
|
if (!config.warmHook) {
|
|
4752
5205
|
return null;
|
|
@@ -4792,7 +5245,7 @@ async function executeHookScriptStreaming(params) {
|
|
|
4792
5245
|
let bufferExceeded = false;
|
|
4793
5246
|
params.onChunk(`$ ${params.label}
|
|
4794
5247
|
`);
|
|
4795
|
-
return new Promise((
|
|
5248
|
+
return new Promise((resolve3) => {
|
|
4796
5249
|
const proc = spawn("bash", ["-lc", params.content], {
|
|
4797
5250
|
cwd: params.cwd,
|
|
4798
5251
|
env: process.env,
|
|
@@ -4825,7 +5278,7 @@ async function executeHookScriptStreaming(params) {
|
|
|
4825
5278
|
proc.on("close", (code) => {
|
|
4826
5279
|
clearTimeout(timer);
|
|
4827
5280
|
const output = [`$ ${params.label}`, ...outputParts].join("");
|
|
4828
|
-
|
|
5281
|
+
resolve3({
|
|
4829
5282
|
exitCode: bufferExceeded ? 1 : code ?? 1,
|
|
4830
5283
|
output,
|
|
4831
5284
|
timedOut: killed && !bufferExceeded
|
|
@@ -4835,7 +5288,7 @@ async function executeHookScriptStreaming(params) {
|
|
|
4835
5288
|
clearTimeout(timer);
|
|
4836
5289
|
const output = [`$ ${params.label}
|
|
4837
5290
|
`, ...outputParts, err.message].join("");
|
|
4838
|
-
|
|
5291
|
+
resolve3({
|
|
4839
5292
|
exitCode: 1,
|
|
4840
5293
|
output,
|
|
4841
5294
|
timedOut: false
|
|
@@ -5236,6 +5689,28 @@ function createV1Routes(deps) {
|
|
|
5236
5689
|
};
|
|
5237
5690
|
return c.json(response);
|
|
5238
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
|
+
});
|
|
5239
5714
|
app2.get("/plans", async (c) => {
|
|
5240
5715
|
try {
|
|
5241
5716
|
const plans = await planService.listPlans();
|
|
@@ -5500,8 +5975,8 @@ function createV1Routes(deps) {
|
|
|
5500
5975
|
const logFiles = files.filter((f) => f.endsWith(".log"));
|
|
5501
5976
|
const sessions = await Promise.all(
|
|
5502
5977
|
logFiles.map(async (filename) => {
|
|
5503
|
-
const filePath =
|
|
5504
|
-
const fileStat = await
|
|
5978
|
+
const filePath = join17(LOG_DIR, filename);
|
|
5979
|
+
const fileStat = await stat4(filePath);
|
|
5505
5980
|
const sessionId = filename.replace(/\.log$/, "");
|
|
5506
5981
|
return {
|
|
5507
5982
|
sessionId,
|
|
@@ -5529,15 +6004,15 @@ function createV1Routes(deps) {
|
|
|
5529
6004
|
if (!sessionId || /[/\\]/.test(sessionId) || sessionId.includes("..")) {
|
|
5530
6005
|
return c.json(jsonError("Invalid session ID"), 400);
|
|
5531
6006
|
}
|
|
5532
|
-
const filePath =
|
|
5533
|
-
if (!filePath.startsWith(
|
|
6007
|
+
const filePath = resolve2(LOG_DIR, `${sessionId}.log`);
|
|
6008
|
+
if (!filePath.startsWith(resolve2(LOG_DIR))) {
|
|
5534
6009
|
return c.json(jsonError("Invalid session ID"), 400);
|
|
5535
6010
|
}
|
|
5536
6011
|
const offset = parseInt(c.req.query("offset") || "0", 10);
|
|
5537
6012
|
const limit = Math.min(parseInt(c.req.query("limit") || "500", 10), 5e3);
|
|
5538
6013
|
let content;
|
|
5539
6014
|
try {
|
|
5540
|
-
content = await
|
|
6015
|
+
content = await readFile13(filePath, "utf-8");
|
|
5541
6016
|
} catch {
|
|
5542
6017
|
return c.json(jsonError("Log session not found"), 404);
|
|
5543
6018
|
}
|
|
@@ -5611,7 +6086,7 @@ app.get("/health", async (c) => {
|
|
|
5611
6086
|
return c.json({ status: "initializing", timestamp: (/* @__PURE__ */ new Date()).toISOString() }, 503);
|
|
5612
6087
|
}
|
|
5613
6088
|
try {
|
|
5614
|
-
const logContent = await
|
|
6089
|
+
const logContent = await readFile14("/var/log/cloud-init-output.log", "utf-8");
|
|
5615
6090
|
let status;
|
|
5616
6091
|
if (logContent.includes(COMPLETION_MESSAGE)) {
|
|
5617
6092
|
status = "active";
|
|
@@ -5653,7 +6128,8 @@ app.get("/token-refresh/health", async (c) => {
|
|
|
5653
6128
|
codex: codexTokenManager.getHealthStatus()
|
|
5654
6129
|
});
|
|
5655
6130
|
});
|
|
5656
|
-
|
|
6131
|
+
var repoFileService = new RepoFileService(gitService);
|
|
6132
|
+
app.route("/", createV1Routes({ chatService, repoFileService }));
|
|
5657
6133
|
var port = ENGINE_ENV.REPLICAS_ENGINE_PORT;
|
|
5658
6134
|
function startStatusBroadcaster() {
|
|
5659
6135
|
let previousRepoStatus = "";
|