replicas-engine 0.1.118 → 0.1.119
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 +176 -122
- package/package.json +1 -1
package/dist/src/index.js
CHANGED
|
@@ -362,7 +362,7 @@ var codexTokenManager = new CodexTokenManager();
|
|
|
362
362
|
import { readdir, stat } from "fs/promises";
|
|
363
363
|
import { existsSync as existsSync2 } from "fs";
|
|
364
364
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
365
|
-
import { join as
|
|
365
|
+
import { join as join4 } from "path";
|
|
366
366
|
|
|
367
367
|
// src/utils/state.ts
|
|
368
368
|
import { readFile, writeFile, mkdir, rename, unlink } from "fs/promises";
|
|
@@ -413,7 +413,12 @@ function coerceRepoState(value) {
|
|
|
413
413
|
if (typeof value.path !== "string") return null;
|
|
414
414
|
if (typeof value.defaultBranch !== "string") return null;
|
|
415
415
|
if (typeof value.currentBranch !== "string") return null;
|
|
416
|
-
if (!(value.
|
|
416
|
+
if (!Array.isArray(value.prUrls)) return null;
|
|
417
|
+
const prUrls = [];
|
|
418
|
+
for (const entry of value.prUrls) {
|
|
419
|
+
if (typeof entry !== "string") return null;
|
|
420
|
+
if (!prUrls.includes(entry)) prUrls.push(entry);
|
|
421
|
+
}
|
|
417
422
|
if (!(value.gitDiff === null || isEngineRepoDiff(value.gitDiff))) return null;
|
|
418
423
|
if (typeof value.startHooksCompleted !== "boolean") return null;
|
|
419
424
|
return {
|
|
@@ -421,7 +426,7 @@ function coerceRepoState(value) {
|
|
|
421
426
|
path: value.path,
|
|
422
427
|
defaultBranch: value.defaultBranch,
|
|
423
428
|
currentBranch: value.currentBranch,
|
|
424
|
-
|
|
429
|
+
prUrls,
|
|
425
430
|
gitDiff: value.gitDiff,
|
|
426
431
|
startHooksCompleted: value.startHooksCompleted
|
|
427
432
|
};
|
|
@@ -482,6 +487,8 @@ async function saveRepoState(repoName, state, fallbackState) {
|
|
|
482
487
|
|
|
483
488
|
// src/git/commands.ts
|
|
484
489
|
import { execFileSync } from "child_process";
|
|
490
|
+
import { readFileSync } from "fs";
|
|
491
|
+
import { join as join3 } from "path";
|
|
485
492
|
function runGitCommand(args, cwd) {
|
|
486
493
|
return execFileSync("git", args, {
|
|
487
494
|
cwd,
|
|
@@ -489,6 +496,15 @@ function runGitCommand(args, cwd) {
|
|
|
489
496
|
stdio: ["pipe", "pipe", "pipe"]
|
|
490
497
|
}).trim();
|
|
491
498
|
}
|
|
499
|
+
function readRepoHeadBranch(repoPath) {
|
|
500
|
+
try {
|
|
501
|
+
const contents = readFileSync(join3(repoPath, ".git", "HEAD"), "utf-8").trim();
|
|
502
|
+
const match = contents.match(/^ref:\s+refs\/heads\/(.+)$/);
|
|
503
|
+
return match ? match[1] : null;
|
|
504
|
+
} catch {
|
|
505
|
+
return null;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
492
508
|
function branchExists(branchName, cwd) {
|
|
493
509
|
try {
|
|
494
510
|
runGitCommand(["rev-parse", "--verify", branchName], cwd);
|
|
@@ -507,6 +523,9 @@ function getCurrentBranch(cwd) {
|
|
|
507
523
|
}
|
|
508
524
|
|
|
509
525
|
// src/git/service.ts
|
|
526
|
+
function appendUniqueUrl(urls, url) {
|
|
527
|
+
return urls.includes(url) ? urls : [...urls, url];
|
|
528
|
+
}
|
|
510
529
|
var GitService = class {
|
|
511
530
|
defaultBranchCache = /* @__PURE__ */ new Map();
|
|
512
531
|
cachedPrByRepo = /* @__PURE__ */ new Map();
|
|
@@ -522,13 +541,13 @@ var GitService = class {
|
|
|
522
541
|
const entries = await readdir(root);
|
|
523
542
|
const repos = [];
|
|
524
543
|
for (const entry of entries) {
|
|
525
|
-
const fullPath =
|
|
544
|
+
const fullPath = join4(root, entry);
|
|
526
545
|
try {
|
|
527
546
|
const entryStat = await stat(fullPath);
|
|
528
547
|
if (!entryStat.isDirectory()) {
|
|
529
548
|
continue;
|
|
530
549
|
}
|
|
531
|
-
const hasGit = Boolean(await this.safeStat(
|
|
550
|
+
const hasGit = Boolean(await this.safeStat(join4(fullPath, ".git")));
|
|
532
551
|
if (!hasGit) {
|
|
533
552
|
continue;
|
|
534
553
|
}
|
|
@@ -563,7 +582,7 @@ var GitService = class {
|
|
|
563
582
|
path: repo.path,
|
|
564
583
|
defaultBranch: repo.defaultBranch,
|
|
565
584
|
currentBranch,
|
|
566
|
-
|
|
585
|
+
prUrls: persistedState?.prUrls ?? [],
|
|
567
586
|
gitDiff: persistedMatchesCurrentBranch ? persistedState.gitDiff : null,
|
|
568
587
|
startHooksCompleted: persistedState?.startHooksCompleted ?? false
|
|
569
588
|
});
|
|
@@ -572,7 +591,7 @@ var GitService = class {
|
|
|
572
591
|
}
|
|
573
592
|
return states;
|
|
574
593
|
}
|
|
575
|
-
async refreshRepos() {
|
|
594
|
+
async refreshRepos(observedBranchesByRepo) {
|
|
576
595
|
const repos = await this.listRepositories();
|
|
577
596
|
const states = [];
|
|
578
597
|
for (const repo of repos) {
|
|
@@ -580,12 +599,29 @@ var GitService = class {
|
|
|
580
599
|
const persistedState = await loadRepoState(repo.name);
|
|
581
600
|
const currentBranch = getCurrentBranch(repo.path) ?? repo.defaultBranch;
|
|
582
601
|
const startHooksCompleted = persistedState?.startHooksCompleted ?? false;
|
|
583
|
-
|
|
602
|
+
const observed = observedBranchesByRepo?.get(repo.name);
|
|
603
|
+
states.push(
|
|
604
|
+
await this.refreshRepoMetadata(repo, currentBranch, startHooksCompleted, persistedState, observed)
|
|
605
|
+
);
|
|
584
606
|
} catch {
|
|
585
607
|
}
|
|
586
608
|
}
|
|
587
609
|
return states;
|
|
588
610
|
}
|
|
611
|
+
// Fast branch snapshot for per-event observation during a turn. Reads
|
|
612
|
+
// .git/HEAD directly for each repo (no subprocess) so callers can safely
|
|
613
|
+
// invoke this on every agent event without measurable overhead.
|
|
614
|
+
async snapshotCurrentBranches() {
|
|
615
|
+
const repos = await this.listRepositories();
|
|
616
|
+
const branches = /* @__PURE__ */ new Map();
|
|
617
|
+
for (const repo of repos) {
|
|
618
|
+
const branch = readRepoHeadBranch(repo.path);
|
|
619
|
+
if (branch) {
|
|
620
|
+
branches.set(repo.name, branch);
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
return branches;
|
|
624
|
+
}
|
|
589
625
|
async setBranchNameAndCheckout(name) {
|
|
590
626
|
ENGINE_ENV.WORKSPACE_BRANCH_NAME = name;
|
|
591
627
|
return this.initializeGitRepository();
|
|
@@ -618,7 +654,7 @@ var GitService = class {
|
|
|
618
654
|
path: repo.path,
|
|
619
655
|
defaultBranch: repo.defaultBranch,
|
|
620
656
|
currentBranch: repo.defaultBranch,
|
|
621
|
-
|
|
657
|
+
prUrls: [],
|
|
622
658
|
gitDiff: null,
|
|
623
659
|
startHooksCompleted: false
|
|
624
660
|
};
|
|
@@ -645,7 +681,7 @@ var GitService = class {
|
|
|
645
681
|
}
|
|
646
682
|
const branchName = this.findAvailableBranchName(workspaceName, repo.path);
|
|
647
683
|
runGitCommand(["checkout", "-b", branchName], repo.path);
|
|
648
|
-
await saveRepoState(repo.name, { currentBranch: branchName
|
|
684
|
+
await saveRepoState(repo.name, { currentBranch: branchName }, baselineState);
|
|
649
685
|
results.push({
|
|
650
686
|
name: repo.name,
|
|
651
687
|
success: true,
|
|
@@ -723,56 +759,50 @@ var GitService = class {
|
|
|
723
759
|
return { status: "found", url: cachedPr.prUrl };
|
|
724
760
|
}
|
|
725
761
|
const persistedRepoState = persistedRepoStateArg ?? await loadRepoState(repoName);
|
|
726
|
-
if (persistedRepoState?.prUrl && persistedRepoState.currentBranch === currentBranch) {
|
|
727
|
-
this.cachedPrByRepo.set(repoName, {
|
|
728
|
-
prUrl: persistedRepoState.prUrl,
|
|
729
|
-
currentBranch
|
|
730
|
-
});
|
|
731
|
-
return { status: "found", url: persistedRepoState.prUrl };
|
|
732
|
-
}
|
|
733
762
|
this.cachedPrByRepo.delete(repoName);
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
if (!remoteRef) {
|
|
744
|
-
return { status: "not_found" };
|
|
763
|
+
const result = this.lookupPrOnRemote(repoName, repoPath, currentBranch);
|
|
764
|
+
if (result.status === "found") {
|
|
765
|
+
this.cachedPrByRepo.set(repoName, { prUrl: result.url, currentBranch });
|
|
766
|
+
if (persistedRepoState && !persistedRepoState.prUrls.includes(result.url)) {
|
|
767
|
+
await saveRepoState(
|
|
768
|
+
repoName,
|
|
769
|
+
{ prUrls: appendUniqueUrl(persistedRepoState.prUrls, result.url) },
|
|
770
|
+
persistedRepoState
|
|
771
|
+
);
|
|
745
772
|
}
|
|
746
|
-
} catch {
|
|
747
|
-
return { status: "error" };
|
|
748
|
-
}
|
|
749
|
-
try {
|
|
750
|
-
const prInfo = execFileSync2("gh", ["pr", "view", "--json", "url", "--jq", ".url"], {
|
|
751
|
-
cwd: repoPath,
|
|
752
|
-
encoding: "utf-8",
|
|
753
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
754
|
-
}).trim();
|
|
755
|
-
if (prInfo) {
|
|
756
|
-
this.cachedPrByRepo.set(repoName, {
|
|
757
|
-
prUrl: prInfo,
|
|
758
|
-
currentBranch
|
|
759
|
-
});
|
|
760
|
-
if (persistedRepoState) {
|
|
761
|
-
await saveRepoState(repoName, { prUrl: prInfo }, persistedRepoState);
|
|
762
|
-
}
|
|
763
|
-
return { status: "found", url: prInfo };
|
|
764
|
-
}
|
|
765
|
-
} catch (error) {
|
|
766
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
767
|
-
console.warn(`[GitService] gh pr view failed for ${repoName}: ${message}`);
|
|
768
|
-
return { status: "error" };
|
|
769
773
|
}
|
|
770
|
-
return
|
|
774
|
+
return result;
|
|
771
775
|
} catch (error) {
|
|
772
776
|
console.error("Error checking for pull request:", error);
|
|
773
777
|
return { status: "error" };
|
|
774
778
|
}
|
|
775
779
|
}
|
|
780
|
+
lookupPrOnRemote(repoName, repoPath, branch) {
|
|
781
|
+
try {
|
|
782
|
+
const remoteRef = execFileSync2("git", ["ls-remote", "--heads", "origin", branch], {
|
|
783
|
+
cwd: repoPath,
|
|
784
|
+
encoding: "utf-8",
|
|
785
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
786
|
+
}).trim();
|
|
787
|
+
if (!remoteRef) {
|
|
788
|
+
return { status: "not_found" };
|
|
789
|
+
}
|
|
790
|
+
} catch {
|
|
791
|
+
return { status: "error" };
|
|
792
|
+
}
|
|
793
|
+
try {
|
|
794
|
+
const prInfo = execFileSync2("gh", ["pr", "view", branch, "--json", "url", "--jq", ".url"], {
|
|
795
|
+
cwd: repoPath,
|
|
796
|
+
encoding: "utf-8",
|
|
797
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
798
|
+
}).trim();
|
|
799
|
+
return prInfo ? { status: "found", url: prInfo } : { status: "not_found" };
|
|
800
|
+
} catch (error) {
|
|
801
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
802
|
+
console.warn(`[GitService] gh pr view ${branch} failed for ${repoName}: ${message}`);
|
|
803
|
+
return { status: "error" };
|
|
804
|
+
}
|
|
805
|
+
}
|
|
776
806
|
resolveDefaultBranch(repoPath) {
|
|
777
807
|
const cached = this.defaultBranchCache.get(repoPath);
|
|
778
808
|
if (cached) {
|
|
@@ -807,22 +837,27 @@ var GitService = class {
|
|
|
807
837
|
const normalized = name.toLowerCase().replace(/[^a-z0-9._/-]+/g, "-").replace(/\/{2,}/g, "/").replace(/^-+|-+$/g, "");
|
|
808
838
|
return normalized || "replicas";
|
|
809
839
|
}
|
|
810
|
-
async refreshRepoMetadata(repo, currentBranch, startHooksCompleted, persistedState) {
|
|
840
|
+
async refreshRepoMetadata(repo, currentBranch, startHooksCompleted, persistedState, observedBranches) {
|
|
811
841
|
const prResult = await this.getPullRequestUrl(repo.name, repo.path, currentBranch, persistedState);
|
|
812
|
-
let
|
|
842
|
+
let prUrls = persistedState?.prUrls ?? [];
|
|
813
843
|
if (prResult.status === "found") {
|
|
814
|
-
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
844
|
+
prUrls = appendUniqueUrl(prUrls, prResult.url);
|
|
845
|
+
}
|
|
846
|
+
if (observedBranches) {
|
|
847
|
+
for (const branch of observedBranches) {
|
|
848
|
+
if (branch === currentBranch) continue;
|
|
849
|
+
const branchResult = this.lookupPrOnRemote(repo.name, repo.path, branch);
|
|
850
|
+
if (branchResult.status === "found") {
|
|
851
|
+
prUrls = appendUniqueUrl(prUrls, branchResult.url);
|
|
852
|
+
}
|
|
853
|
+
}
|
|
819
854
|
}
|
|
820
855
|
const state = {
|
|
821
856
|
name: repo.name,
|
|
822
857
|
path: repo.path,
|
|
823
858
|
defaultBranch: repo.defaultBranch,
|
|
824
859
|
currentBranch,
|
|
825
|
-
|
|
860
|
+
prUrls,
|
|
826
861
|
gitDiff: this.getGitDiffStats(repo.path, repo.defaultBranch),
|
|
827
862
|
startHooksCompleted
|
|
828
863
|
};
|
|
@@ -845,10 +880,10 @@ var gitService = new GitService();
|
|
|
845
880
|
// src/utils/logger.ts
|
|
846
881
|
import { appendFile, mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
847
882
|
import { homedir as homedir3 } from "os";
|
|
848
|
-
import { join as
|
|
883
|
+
import { join as join5 } from "path";
|
|
849
884
|
import { format } from "util";
|
|
850
885
|
import { randomBytes } from "crypto";
|
|
851
|
-
var LOG_DIR =
|
|
886
|
+
var LOG_DIR = join5(homedir3(), ".replicas", "logs");
|
|
852
887
|
var EngineLogger = class {
|
|
853
888
|
_sessionId = null;
|
|
854
889
|
filePath = null;
|
|
@@ -860,7 +895,7 @@ var EngineLogger = class {
|
|
|
860
895
|
async initialize() {
|
|
861
896
|
await mkdir2(LOG_DIR, { recursive: true });
|
|
862
897
|
this._sessionId = this.createSessionId();
|
|
863
|
-
this.filePath =
|
|
898
|
+
this.filePath = join5(LOG_DIR, `${this._sessionId}.log`);
|
|
864
899
|
await writeFile2(this.filePath, `=== Replicas Engine Session ${this._sessionId} ===
|
|
865
900
|
`, "utf-8");
|
|
866
901
|
this.patchConsole();
|
|
@@ -913,7 +948,7 @@ var engineLogger = new EngineLogger();
|
|
|
913
948
|
// src/services/replicas-config-service.ts
|
|
914
949
|
import { readFile as readFile4, appendFile as appendFile2, writeFile as writeFile5, mkdir as mkdir5 } from "fs/promises";
|
|
915
950
|
import { existsSync as existsSync4 } from "fs";
|
|
916
|
-
import { join as
|
|
951
|
+
import { join as join8 } from "path";
|
|
917
952
|
import { homedir as homedir5 } from "os";
|
|
918
953
|
import { exec } from "child_process";
|
|
919
954
|
import { promisify as promisify2 } from "util";
|
|
@@ -1102,7 +1137,7 @@ function parseReplicasConfigString(content, filename) {
|
|
|
1102
1137
|
}
|
|
1103
1138
|
|
|
1104
1139
|
// ../shared/src/engine/environment.ts
|
|
1105
|
-
var DAYTONA_SNAPSHOT_ID = "22-04-2026-islington-
|
|
1140
|
+
var DAYTONA_SNAPSHOT_ID = "22-04-2026-islington-v2";
|
|
1106
1141
|
|
|
1107
1142
|
// ../shared/src/engine/types.ts
|
|
1108
1143
|
var DEFAULT_CHAT_TITLES = {
|
|
@@ -1120,14 +1155,14 @@ var WORKSPACE_FILE_CONTENT_MAX_SIZE_BYTES = 1 * 1024 * 1024;
|
|
|
1120
1155
|
import { mkdir as mkdir3, readFile as readFile2, writeFile as writeFile3 } from "fs/promises";
|
|
1121
1156
|
import { existsSync as existsSync3 } from "fs";
|
|
1122
1157
|
import { homedir as homedir4 } from "os";
|
|
1123
|
-
import { join as
|
|
1158
|
+
import { join as join6 } from "path";
|
|
1124
1159
|
import { execFile } from "child_process";
|
|
1125
1160
|
import { promisify } from "util";
|
|
1126
1161
|
var execFileAsync = promisify(execFile);
|
|
1127
1162
|
var REPLICAS_DIR = SANDBOX_PATHS.REPLICAS_DIR;
|
|
1128
|
-
var DETAILS_FILE =
|
|
1129
|
-
var CLAUDE_CREDENTIALS_PATH =
|
|
1130
|
-
var CODEX_AUTH_PATH =
|
|
1163
|
+
var DETAILS_FILE = join6(REPLICAS_DIR, "environment-details.json");
|
|
1164
|
+
var CLAUDE_CREDENTIALS_PATH = join6(homedir4(), ".claude", ".credentials.json");
|
|
1165
|
+
var CODEX_AUTH_PATH = join6(homedir4(), ".codex", "auth.json");
|
|
1131
1166
|
function detectClaudeAuthMethod() {
|
|
1132
1167
|
if (existsSync3(CLAUDE_CREDENTIALS_PATH)) {
|
|
1133
1168
|
return "oauth";
|
|
@@ -1273,8 +1308,8 @@ var environmentDetailsService = new EnvironmentDetailsService();
|
|
|
1273
1308
|
// src/services/start-hook-logs-service.ts
|
|
1274
1309
|
import { createHash } from "crypto";
|
|
1275
1310
|
import { mkdir as mkdir4, readFile as readFile3, writeFile as writeFile4, readdir as readdir2 } from "fs/promises";
|
|
1276
|
-
import { join as
|
|
1277
|
-
var LOGS_DIR =
|
|
1311
|
+
import { join as join7 } from "path";
|
|
1312
|
+
var LOGS_DIR = join7(SANDBOX_PATHS.REPLICAS_DIR, "start-hook-logs");
|
|
1278
1313
|
function sanitizeFilename(name) {
|
|
1279
1314
|
const safe = name.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
1280
1315
|
const hash = createHash("sha256").update(name).digest("hex").slice(0, 8);
|
|
@@ -1294,7 +1329,7 @@ var StartHookLogsService = class {
|
|
|
1294
1329
|
async saveRepoLog(repoName, entry) {
|
|
1295
1330
|
await this.ensureDir();
|
|
1296
1331
|
const log = { repoName, ...entry };
|
|
1297
|
-
await writeFile4(
|
|
1332
|
+
await writeFile4(join7(LOGS_DIR, repoFilename(repoName)), `${JSON.stringify(log, null, 2)}
|
|
1298
1333
|
`, "utf-8");
|
|
1299
1334
|
}
|
|
1300
1335
|
async getAllLogs() {
|
|
@@ -1313,7 +1348,7 @@ var StartHookLogsService = class {
|
|
|
1313
1348
|
continue;
|
|
1314
1349
|
}
|
|
1315
1350
|
try {
|
|
1316
|
-
const raw = await readFile3(
|
|
1351
|
+
const raw = await readFile3(join7(LOGS_DIR, file), "utf-8");
|
|
1317
1352
|
const stored = JSON.parse(raw);
|
|
1318
1353
|
logs.push(withPreview(stored));
|
|
1319
1354
|
} catch {
|
|
@@ -1324,7 +1359,7 @@ var StartHookLogsService = class {
|
|
|
1324
1359
|
}
|
|
1325
1360
|
async getFullOutput(repoName) {
|
|
1326
1361
|
try {
|
|
1327
|
-
const raw = await readFile3(
|
|
1362
|
+
const raw = await readFile3(join7(LOGS_DIR, repoFilename(repoName)), "utf-8");
|
|
1328
1363
|
const stored = JSON.parse(raw);
|
|
1329
1364
|
if (stored.repoName !== repoName) {
|
|
1330
1365
|
return null;
|
|
@@ -1342,7 +1377,7 @@ var startHookLogsService = new StartHookLogsService();
|
|
|
1342
1377
|
|
|
1343
1378
|
// src/services/replicas-config-service.ts
|
|
1344
1379
|
var execAsync = promisify2(exec);
|
|
1345
|
-
var START_HOOKS_LOG =
|
|
1380
|
+
var START_HOOKS_LOG = join8(homedir5(), ".replicas", "startHooks.log");
|
|
1346
1381
|
var START_HOOKS_RUNNING_PROMPT = `IMPORTANT - Start Hooks Running:
|
|
1347
1382
|
Start hooks are shell commands/scripts set by repository owners that run on workspace startup.
|
|
1348
1383
|
These hooks are currently executing in the background. You can:
|
|
@@ -1353,7 +1388,7 @@ The start hooks may install dependencies, build projects, or perform other setup
|
|
|
1353
1388
|
If your task depends on setup being complete, check the log file before proceeding.`;
|
|
1354
1389
|
async function readReplicasConfigFromDir(dirPath) {
|
|
1355
1390
|
for (const filename of REPLICAS_CONFIG_FILENAMES) {
|
|
1356
|
-
const configPath =
|
|
1391
|
+
const configPath = join8(dirPath, filename);
|
|
1357
1392
|
if (!existsSync4(configPath)) {
|
|
1358
1393
|
continue;
|
|
1359
1394
|
}
|
|
@@ -1417,7 +1452,7 @@ var ReplicasConfigService = class {
|
|
|
1417
1452
|
const logLine = `[${timestamp}] ${message}
|
|
1418
1453
|
`;
|
|
1419
1454
|
try {
|
|
1420
|
-
await mkdir5(
|
|
1455
|
+
await mkdir5(join8(homedir5(), ".replicas"), { recursive: true });
|
|
1421
1456
|
await appendFile2(START_HOOKS_LOG, logLine, "utf-8");
|
|
1422
1457
|
} catch (error) {
|
|
1423
1458
|
console.error("Failed to write to start hooks log:", error);
|
|
@@ -1441,7 +1476,7 @@ var ReplicasConfigService = class {
|
|
|
1441
1476
|
this.hooksRunning = true;
|
|
1442
1477
|
this.hooksCompleted = false;
|
|
1443
1478
|
try {
|
|
1444
|
-
await mkdir5(
|
|
1479
|
+
await mkdir5(join8(homedir5(), ".replicas"), { recursive: true });
|
|
1445
1480
|
await writeFile5(
|
|
1446
1481
|
START_HOOKS_LOG,
|
|
1447
1482
|
`=== Start Hooks Execution Log ===
|
|
@@ -1528,7 +1563,7 @@ Repositories: ${hookEntries.length}
|
|
|
1528
1563
|
path: entry.workingDirectory,
|
|
1529
1564
|
defaultBranch: entry.defaultBranch,
|
|
1530
1565
|
currentBranch: entry.defaultBranch,
|
|
1531
|
-
|
|
1566
|
+
prUrls: [],
|
|
1532
1567
|
gitDiff: null,
|
|
1533
1568
|
startHooksCompleted: false
|
|
1534
1569
|
};
|
|
@@ -1593,10 +1628,10 @@ var replicasConfigService = new ReplicasConfigService();
|
|
|
1593
1628
|
// src/services/event-service.ts
|
|
1594
1629
|
import { appendFile as appendFile3, mkdir as mkdir6 } from "fs/promises";
|
|
1595
1630
|
import { homedir as homedir6 } from "os";
|
|
1596
|
-
import { join as
|
|
1631
|
+
import { join as join9 } from "path";
|
|
1597
1632
|
import { randomUUID } from "crypto";
|
|
1598
|
-
var ENGINE_DIR =
|
|
1599
|
-
var EVENTS_FILE =
|
|
1633
|
+
var ENGINE_DIR = join9(homedir6(), ".replicas", "engine");
|
|
1634
|
+
var EVENTS_FILE = join9(ENGINE_DIR, "events.jsonl");
|
|
1600
1635
|
var EventService = class {
|
|
1601
1636
|
subscribers = /* @__PURE__ */ new Map();
|
|
1602
1637
|
writeChain = Promise.resolve();
|
|
@@ -1689,14 +1724,14 @@ var previewService = new PreviewService();
|
|
|
1689
1724
|
import { existsSync as existsSync7 } from "fs";
|
|
1690
1725
|
import { mkdir as mkdir10, readFile as readFile8, rm, writeFile as writeFile8 } from "fs/promises";
|
|
1691
1726
|
import { homedir as homedir9 } from "os";
|
|
1692
|
-
import { join as
|
|
1727
|
+
import { join as join12 } from "path";
|
|
1693
1728
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
1694
1729
|
|
|
1695
1730
|
// src/managers/claude-manager.ts
|
|
1696
1731
|
import {
|
|
1697
1732
|
query
|
|
1698
1733
|
} from "@anthropic-ai/claude-agent-sdk";
|
|
1699
|
-
import { join as
|
|
1734
|
+
import { join as join10 } from "path";
|
|
1700
1735
|
import { mkdir as mkdir8, appendFile as appendFile4 } from "fs/promises";
|
|
1701
1736
|
import { homedir as homedir7 } from "os";
|
|
1702
1737
|
|
|
@@ -2467,7 +2502,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
|
|
|
2467
2502
|
disallowedToolsOverride;
|
|
2468
2503
|
constructor(options) {
|
|
2469
2504
|
super(options);
|
|
2470
|
-
this.historyFile = options.historyFilePath ??
|
|
2505
|
+
this.historyFile = options.historyFilePath ?? join10(homedir7(), ".replicas", "claude", "history.jsonl");
|
|
2471
2506
|
this.systemPromptOverride = options.systemPromptOverride;
|
|
2472
2507
|
this.toolsOverride = options.tools;
|
|
2473
2508
|
this.mcpServersConfig = options.mcpServers;
|
|
@@ -2650,7 +2685,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
|
|
|
2650
2685
|
};
|
|
2651
2686
|
}
|
|
2652
2687
|
async initialize() {
|
|
2653
|
-
const historyDir =
|
|
2688
|
+
const historyDir = join10(homedir7(), ".replicas", "claude");
|
|
2654
2689
|
await mkdir8(historyDir, { recursive: true });
|
|
2655
2690
|
if (this.initialSessionId) {
|
|
2656
2691
|
this.sessionId = this.initialSessionId;
|
|
@@ -2704,11 +2739,11 @@ import { Codex } from "@openai/codex-sdk";
|
|
|
2704
2739
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
2705
2740
|
import { readdir as readdir3, stat as stat2, writeFile as writeFile7, mkdir as mkdir9, readFile as readFile7 } from "fs/promises";
|
|
2706
2741
|
import { existsSync as existsSync6 } from "fs";
|
|
2707
|
-
import { join as
|
|
2742
|
+
import { join as join11 } from "path";
|
|
2708
2743
|
import { homedir as homedir8 } from "os";
|
|
2709
2744
|
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
2710
2745
|
var DEFAULT_MODEL = "gpt-5.4";
|
|
2711
|
-
var CODEX_CONFIG_PATH =
|
|
2746
|
+
var CODEX_CONFIG_PATH = join11(homedir8(), ".codex", "config.toml");
|
|
2712
2747
|
function isLinearThoughtEvent2(event) {
|
|
2713
2748
|
return event.content.type === "thought";
|
|
2714
2749
|
}
|
|
@@ -2732,7 +2767,7 @@ var CodexManager = class extends CodingAgentManager {
|
|
|
2732
2767
|
this.codex = new Codex(
|
|
2733
2768
|
ENGINE_ENV.OPENAI_API_KEY ? { apiKey: ENGINE_ENV.OPENAI_API_KEY } : void 0
|
|
2734
2769
|
);
|
|
2735
|
-
this.tempImageDir =
|
|
2770
|
+
this.tempImageDir = join11(homedir8(), ".replicas", "codex", "temp-images");
|
|
2736
2771
|
this.initializeManager(this.processMessageInternal.bind(this));
|
|
2737
2772
|
}
|
|
2738
2773
|
async initialize() {
|
|
@@ -2752,7 +2787,7 @@ var CodexManager = class extends CodingAgentManager {
|
|
|
2752
2787
|
*/
|
|
2753
2788
|
async updateCodexConfig(developerInstructions) {
|
|
2754
2789
|
try {
|
|
2755
|
-
const codexDir =
|
|
2790
|
+
const codexDir = join11(homedir8(), ".codex");
|
|
2756
2791
|
await mkdir9(codexDir, { recursive: true });
|
|
2757
2792
|
let config = {};
|
|
2758
2793
|
if (existsSync6(CODEX_CONFIG_PATH)) {
|
|
@@ -2788,7 +2823,7 @@ var CodexManager = class extends CodingAgentManager {
|
|
|
2788
2823
|
for (const image of images) {
|
|
2789
2824
|
const ext = image.source.media_type.split("/")[1] || "png";
|
|
2790
2825
|
const filename = `img_${randomUUID3()}.${ext}`;
|
|
2791
|
-
const filepath =
|
|
2826
|
+
const filepath = join11(this.tempImageDir, filename);
|
|
2792
2827
|
const buffer = Buffer.from(image.source.data, "base64");
|
|
2793
2828
|
await writeFile7(filepath, buffer);
|
|
2794
2829
|
tempPaths.push(filepath);
|
|
@@ -2927,13 +2962,13 @@ var CodexManager = class extends CodingAgentManager {
|
|
|
2927
2962
|
}
|
|
2928
2963
|
// Helper methods for finding session files
|
|
2929
2964
|
async findSessionFile(threadId) {
|
|
2930
|
-
const sessionsDir =
|
|
2965
|
+
const sessionsDir = join11(homedir8(), ".codex", "sessions");
|
|
2931
2966
|
try {
|
|
2932
2967
|
const now = /* @__PURE__ */ new Date();
|
|
2933
2968
|
const year = now.getFullYear();
|
|
2934
2969
|
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
2935
2970
|
const day = String(now.getDate()).padStart(2, "0");
|
|
2936
|
-
const todayDir =
|
|
2971
|
+
const todayDir = join11(sessionsDir, String(year), month, day);
|
|
2937
2972
|
const file = await this.findFileInDirectory(todayDir, threadId);
|
|
2938
2973
|
if (file) return file;
|
|
2939
2974
|
for (let daysAgo = 1; daysAgo <= 7; daysAgo++) {
|
|
@@ -2942,7 +2977,7 @@ var CodexManager = class extends CodingAgentManager {
|
|
|
2942
2977
|
const searchYear = date.getFullYear();
|
|
2943
2978
|
const searchMonth = String(date.getMonth() + 1).padStart(2, "0");
|
|
2944
2979
|
const searchDay = String(date.getDate()).padStart(2, "0");
|
|
2945
|
-
const searchDir =
|
|
2980
|
+
const searchDir = join11(sessionsDir, String(searchYear), searchMonth, searchDay);
|
|
2946
2981
|
const file2 = await this.findFileInDirectory(searchDir, threadId);
|
|
2947
2982
|
if (file2) return file2;
|
|
2948
2983
|
}
|
|
@@ -2956,7 +2991,7 @@ var CodexManager = class extends CodingAgentManager {
|
|
|
2956
2991
|
const files = await readdir3(directory);
|
|
2957
2992
|
for (const file of files) {
|
|
2958
2993
|
if (file.endsWith(".jsonl") && file.includes(threadId)) {
|
|
2959
|
-
const fullPath =
|
|
2994
|
+
const fullPath = join11(directory, file);
|
|
2960
2995
|
const stats = await stat2(fullPath);
|
|
2961
2996
|
if (stats.isFile()) {
|
|
2962
2997
|
return fullPath;
|
|
@@ -3639,11 +3674,11 @@ var DuplicateDefaultChatError = class extends Error {
|
|
|
3639
3674
|
};
|
|
3640
3675
|
|
|
3641
3676
|
// src/services/chat/chat-service.ts
|
|
3642
|
-
var ENGINE_DIR2 =
|
|
3643
|
-
var CHATS_FILE =
|
|
3644
|
-
var CLAUDE_HISTORY_DIR =
|
|
3645
|
-
var RELAY_HISTORY_DIR =
|
|
3646
|
-
var CODEX_AUTH_PATH2 =
|
|
3677
|
+
var ENGINE_DIR2 = join12(homedir9(), ".replicas", "engine");
|
|
3678
|
+
var CHATS_FILE = join12(ENGINE_DIR2, "chats.json");
|
|
3679
|
+
var CLAUDE_HISTORY_DIR = join12(ENGINE_DIR2, "claude-histories");
|
|
3680
|
+
var RELAY_HISTORY_DIR = join12(ENGINE_DIR2, "relay-histories");
|
|
3681
|
+
var CODEX_AUTH_PATH2 = join12(homedir9(), ".codex", "auth.json");
|
|
3647
3682
|
function isCodexAvailable() {
|
|
3648
3683
|
return existsSync7(CODEX_AUTH_PATH2) || Boolean(ENGINE_ENV.OPENAI_API_KEY);
|
|
3649
3684
|
}
|
|
@@ -3801,7 +3836,7 @@ var ChatService = class {
|
|
|
3801
3836
|
async deleteHistoryFile(persisted) {
|
|
3802
3837
|
if (persisted.provider === "claude" || persisted.provider === "relay") {
|
|
3803
3838
|
const dir = persisted.provider === "claude" ? CLAUDE_HISTORY_DIR : RELAY_HISTORY_DIR;
|
|
3804
|
-
await rm(
|
|
3839
|
+
await rm(join12(dir, `${persisted.id}.jsonl`), { force: true });
|
|
3805
3840
|
}
|
|
3806
3841
|
}
|
|
3807
3842
|
async getChatHistory(chatId) {
|
|
@@ -3844,7 +3879,7 @@ var ChatService = class {
|
|
|
3844
3879
|
if (persisted.provider === "claude") {
|
|
3845
3880
|
provider = new ClaudeManager({
|
|
3846
3881
|
workingDirectory: this.workingDirectory,
|
|
3847
|
-
historyFilePath:
|
|
3882
|
+
historyFilePath: join12(CLAUDE_HISTORY_DIR, `${persisted.id}.jsonl`),
|
|
3848
3883
|
initialSessionId: persisted.providerSessionId,
|
|
3849
3884
|
onSaveSessionId: saveSession,
|
|
3850
3885
|
onTurnComplete: onProviderTurnComplete,
|
|
@@ -3853,7 +3888,7 @@ var ChatService = class {
|
|
|
3853
3888
|
} else if (persisted.provider === "relay") {
|
|
3854
3889
|
provider = new RelayManager({
|
|
3855
3890
|
workingDirectory: this.workingDirectory,
|
|
3856
|
-
historyFilePath:
|
|
3891
|
+
historyFilePath: join12(RELAY_HISTORY_DIR, `${persisted.id}.jsonl`),
|
|
3857
3892
|
initialSessionId: persisted.providerSessionId,
|
|
3858
3893
|
onSaveSessionId: saveSession,
|
|
3859
3894
|
onTurnComplete: onProviderTurnComplete,
|
|
@@ -3874,7 +3909,8 @@ var ChatService = class {
|
|
|
3874
3909
|
persisted,
|
|
3875
3910
|
provider,
|
|
3876
3911
|
pendingMessageIds: [],
|
|
3877
|
-
hasActiveTurn: false
|
|
3912
|
+
hasActiveTurn: false,
|
|
3913
|
+
observedBranchesByRepo: /* @__PURE__ */ new Map()
|
|
3878
3914
|
};
|
|
3879
3915
|
}
|
|
3880
3916
|
touch(chat) {
|
|
@@ -3922,6 +3958,8 @@ var ChatService = class {
|
|
|
3922
3958
|
});
|
|
3923
3959
|
}
|
|
3924
3960
|
this.touch(chat);
|
|
3961
|
+
this.observeCurrentBranches(chat).catch(() => {
|
|
3962
|
+
});
|
|
3925
3963
|
this.publish({
|
|
3926
3964
|
type: "chat.turn.delta",
|
|
3927
3965
|
payload: {
|
|
@@ -3937,7 +3975,7 @@ var ChatService = class {
|
|
|
3937
3975
|
return;
|
|
3938
3976
|
}
|
|
3939
3977
|
if (!chat.hasActiveTurn) {
|
|
3940
|
-
await this.publishAgentTurnCompleteWebhook();
|
|
3978
|
+
await this.publishAgentTurnCompleteWebhook(chat);
|
|
3941
3979
|
return;
|
|
3942
3980
|
}
|
|
3943
3981
|
chat.hasActiveTurn = false;
|
|
@@ -3950,7 +3988,7 @@ var ChatService = class {
|
|
|
3950
3988
|
}
|
|
3951
3989
|
}).catch(() => {
|
|
3952
3990
|
});
|
|
3953
|
-
await this.publishAgentTurnCompleteWebhook();
|
|
3991
|
+
await this.publishAgentTurnCompleteWebhook(chat);
|
|
3954
3992
|
}
|
|
3955
3993
|
getRuntimeChat(chatId) {
|
|
3956
3994
|
return this.chats.get(chatId) ?? null;
|
|
@@ -3986,15 +4024,31 @@ var ChatService = class {
|
|
|
3986
4024
|
};
|
|
3987
4025
|
await eventService.publish(event);
|
|
3988
4026
|
}
|
|
3989
|
-
async
|
|
4027
|
+
async observeCurrentBranches(chat) {
|
|
4028
|
+
try {
|
|
4029
|
+
const snapshot = await gitService.snapshotCurrentBranches();
|
|
4030
|
+
for (const [repoName, branch] of snapshot) {
|
|
4031
|
+
let observed = chat.observedBranchesByRepo.get(repoName);
|
|
4032
|
+
if (!observed) {
|
|
4033
|
+
observed = /* @__PURE__ */ new Set();
|
|
4034
|
+
chat.observedBranchesByRepo.set(repoName, observed);
|
|
4035
|
+
}
|
|
4036
|
+
observed.add(branch);
|
|
4037
|
+
}
|
|
4038
|
+
} catch {
|
|
4039
|
+
}
|
|
4040
|
+
}
|
|
4041
|
+
async publishAgentTurnCompleteWebhook(chat) {
|
|
3990
4042
|
try {
|
|
3991
4043
|
await githubTokenManager.refreshOnce();
|
|
3992
4044
|
} catch {
|
|
3993
4045
|
}
|
|
3994
4046
|
const linearSessionId = ENGINE_ENV.LINEAR_SESSION_ID;
|
|
4047
|
+
const observedBranches = chat.observedBranchesByRepo;
|
|
4048
|
+
chat.observedBranchesByRepo = /* @__PURE__ */ new Map();
|
|
3995
4049
|
let repoStatuses;
|
|
3996
4050
|
try {
|
|
3997
|
-
repoStatuses = await gitService.refreshRepos();
|
|
4051
|
+
repoStatuses = await gitService.refreshRepos(observedBranches);
|
|
3998
4052
|
console.log(`Repository Statuses Refreshed: `, repoStatuses);
|
|
3999
4053
|
} catch (error) {
|
|
4000
4054
|
console.error("[ChatService] Failed to refresh repo statuses:", error);
|
|
@@ -4012,15 +4066,15 @@ var ChatService = class {
|
|
|
4012
4066
|
import { Hono } from "hono";
|
|
4013
4067
|
import { z as z2 } from "zod";
|
|
4014
4068
|
import { readdir as readdir6, stat as stat3, readFile as readFile12 } from "fs/promises";
|
|
4015
|
-
import { join as
|
|
4069
|
+
import { join as join16, resolve } from "path";
|
|
4016
4070
|
|
|
4017
4071
|
// src/services/plan-service.ts
|
|
4018
4072
|
import { readdir as readdir4, readFile as readFile9 } from "fs/promises";
|
|
4019
4073
|
import { homedir as homedir10 } from "os";
|
|
4020
|
-
import { basename, join as
|
|
4074
|
+
import { basename, join as join13 } from "path";
|
|
4021
4075
|
var PLAN_DIRECTORIES = [
|
|
4022
|
-
|
|
4023
|
-
|
|
4076
|
+
join13(homedir10(), ".claude", "plans"),
|
|
4077
|
+
join13(homedir10(), ".replicas", "plans")
|
|
4024
4078
|
];
|
|
4025
4079
|
function isMarkdownFile(filename) {
|
|
4026
4080
|
return filename.toLowerCase().endsWith(".md");
|
|
@@ -4054,7 +4108,7 @@ var PlanService = class {
|
|
|
4054
4108
|
return null;
|
|
4055
4109
|
}
|
|
4056
4110
|
for (const directory of PLAN_DIRECTORIES) {
|
|
4057
|
-
const filePath =
|
|
4111
|
+
const filePath = join13(directory, safeFilename);
|
|
4058
4112
|
try {
|
|
4059
4113
|
const content = await readFile9(filePath, "utf-8");
|
|
4060
4114
|
return { filename: safeFilename, content };
|
|
@@ -4071,13 +4125,13 @@ import { execFile as execFile2 } from "child_process";
|
|
|
4071
4125
|
import { promisify as promisify3 } from "util";
|
|
4072
4126
|
import { readFile as readFile11 } from "fs/promises";
|
|
4073
4127
|
import { existsSync as existsSync8 } from "fs";
|
|
4074
|
-
import { join as
|
|
4128
|
+
import { join as join15 } from "path";
|
|
4075
4129
|
|
|
4076
4130
|
// src/services/warm-hook-logs-service.ts
|
|
4077
4131
|
import { createHash as createHash2 } from "crypto";
|
|
4078
4132
|
import { mkdir as mkdir11, readFile as readFile10, writeFile as writeFile9, readdir as readdir5 } from "fs/promises";
|
|
4079
|
-
import { join as
|
|
4080
|
-
var LOGS_DIR2 =
|
|
4133
|
+
import { join as join14 } from "path";
|
|
4134
|
+
var LOGS_DIR2 = join14(SANDBOX_PATHS.REPLICAS_DIR, "warm-hook-logs");
|
|
4081
4135
|
function sanitizeFilename2(name) {
|
|
4082
4136
|
const safe = name.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
4083
4137
|
const hash = createHash2("sha256").update(name).digest("hex").slice(0, 8);
|
|
@@ -4104,7 +4158,7 @@ var WarmHookLogsService = class {
|
|
|
4104
4158
|
hookName: "organization",
|
|
4105
4159
|
...entry
|
|
4106
4160
|
};
|
|
4107
|
-
await writeFile9(
|
|
4161
|
+
await writeFile9(join14(LOGS_DIR2, globalFilename()), `${JSON.stringify(log, null, 2)}
|
|
4108
4162
|
`, "utf-8");
|
|
4109
4163
|
}
|
|
4110
4164
|
async saveRepoHookLog(repoName, entry) {
|
|
@@ -4114,7 +4168,7 @@ var WarmHookLogsService = class {
|
|
|
4114
4168
|
hookName: repoName,
|
|
4115
4169
|
...entry
|
|
4116
4170
|
};
|
|
4117
|
-
await writeFile9(
|
|
4171
|
+
await writeFile9(join14(LOGS_DIR2, repoFilename2(repoName)), `${JSON.stringify(log, null, 2)}
|
|
4118
4172
|
`, "utf-8");
|
|
4119
4173
|
}
|
|
4120
4174
|
async getAllLogs() {
|
|
@@ -4133,7 +4187,7 @@ var WarmHookLogsService = class {
|
|
|
4133
4187
|
continue;
|
|
4134
4188
|
}
|
|
4135
4189
|
try {
|
|
4136
|
-
const raw = await readFile10(
|
|
4190
|
+
const raw = await readFile10(join14(LOGS_DIR2, file), "utf-8");
|
|
4137
4191
|
const stored = JSON.parse(raw);
|
|
4138
4192
|
logs.push(withPreview2(stored));
|
|
4139
4193
|
} catch {
|
|
@@ -4150,7 +4204,7 @@ var WarmHookLogsService = class {
|
|
|
4150
4204
|
async getFullOutput(hookType, hookName) {
|
|
4151
4205
|
const filename = hookType === "global" ? globalFilename() : repoFilename2(hookName);
|
|
4152
4206
|
try {
|
|
4153
|
-
const raw = await readFile10(
|
|
4207
|
+
const raw = await readFile10(join14(LOGS_DIR2, filename), "utf-8");
|
|
4154
4208
|
const stored = JSON.parse(raw);
|
|
4155
4209
|
if (stored.hookType !== hookType || stored.hookName !== hookName) {
|
|
4156
4210
|
return null;
|
|
@@ -4197,7 +4251,7 @@ async function executeHookScript(params) {
|
|
|
4197
4251
|
}
|
|
4198
4252
|
async function readRepoWarmHook(repoPath) {
|
|
4199
4253
|
for (const filename of REPLICAS_CONFIG_FILENAMES) {
|
|
4200
|
-
const configPath =
|
|
4254
|
+
const configPath = join15(repoPath, filename);
|
|
4201
4255
|
if (!existsSync8(configPath)) {
|
|
4202
4256
|
continue;
|
|
4203
4257
|
}
|
|
@@ -4724,7 +4778,7 @@ function createV1Routes(deps) {
|
|
|
4724
4778
|
const logFiles = files.filter((f) => f.endsWith(".log"));
|
|
4725
4779
|
const sessions = await Promise.all(
|
|
4726
4780
|
logFiles.map(async (filename) => {
|
|
4727
|
-
const filePath =
|
|
4781
|
+
const filePath = join16(LOG_DIR, filename);
|
|
4728
4782
|
const fileStat = await stat3(filePath);
|
|
4729
4783
|
const sessionId = filename.replace(/\.log$/, "");
|
|
4730
4784
|
return {
|