replicas-engine 0.1.22 → 0.1.24
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 +281 -188
- package/package.json +1 -1
package/dist/src/index.js
CHANGED
|
@@ -56,9 +56,9 @@ async function readJSONL(filePath) {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
// src/services/codex-manager.ts
|
|
59
|
-
import { readdir, stat, writeFile as
|
|
60
|
-
import { join as
|
|
61
|
-
import { homedir as
|
|
59
|
+
import { readdir, stat, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
|
|
60
|
+
import { join as join3 } from "path";
|
|
61
|
+
import { homedir as homedir3 } from "os";
|
|
62
62
|
|
|
63
63
|
// src/services/monolith-service.ts
|
|
64
64
|
var MonolithService = class {
|
|
@@ -397,7 +397,8 @@ var DEFAULT_STATE = {
|
|
|
397
397
|
branch: null,
|
|
398
398
|
prUrl: null,
|
|
399
399
|
claudeSessionId: null,
|
|
400
|
-
codexThreadId: null
|
|
400
|
+
codexThreadId: null,
|
|
401
|
+
startHooksCompleted: false
|
|
401
402
|
};
|
|
402
403
|
async function loadEngineState() {
|
|
403
404
|
try {
|
|
@@ -720,7 +721,208 @@ async function normalizeImages(images) {
|
|
|
720
721
|
return normalized;
|
|
721
722
|
}
|
|
722
723
|
|
|
724
|
+
// src/services/replicas-config.ts
|
|
725
|
+
import { readFile as readFile3, appendFile, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
726
|
+
import { existsSync as existsSync2 } from "fs";
|
|
727
|
+
import { join as join2 } from "path";
|
|
728
|
+
import { homedir as homedir2 } from "os";
|
|
729
|
+
import { exec } from "child_process";
|
|
730
|
+
import { promisify } from "util";
|
|
731
|
+
var execAsync = promisify(exec);
|
|
732
|
+
var START_HOOKS_LOG = join2(homedir2(), ".replicas", "startHooks.log");
|
|
733
|
+
var START_HOOKS_RUNNING_PROMPT = `IMPORTANT - Start Hooks Running:
|
|
734
|
+
Start hooks are shell commands/scripts set by the repository owners that run on workspace startup.
|
|
735
|
+
These hooks are currently executing in the background. You can:
|
|
736
|
+
- Check the status and output by reading ~/.replicas/startHooks.log
|
|
737
|
+
- View the hook commands in replicas.json in the repository root (under the "startHook" field)
|
|
738
|
+
|
|
739
|
+
The start hooks may install dependencies, build projects, or perform other setup tasks.
|
|
740
|
+
If your task depends on setup being complete, check the log file before proceeding.`;
|
|
741
|
+
var ReplicasConfigService = class {
|
|
742
|
+
config = null;
|
|
743
|
+
workingDirectory;
|
|
744
|
+
hooksRunning = false;
|
|
745
|
+
hooksCompleted = false;
|
|
746
|
+
startHooksPromise = null;
|
|
747
|
+
constructor() {
|
|
748
|
+
const repoName = process.env.REPLICAS_REPO_NAME;
|
|
749
|
+
const workspaceHome = process.env.WORKSPACE_HOME || process.env.HOME || homedir2();
|
|
750
|
+
if (repoName) {
|
|
751
|
+
this.workingDirectory = join2(workspaceHome, "workspaces", repoName);
|
|
752
|
+
} else {
|
|
753
|
+
this.workingDirectory = workspaceHome;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Initialize the service by reading replicas.json and starting start hooks asynchronously
|
|
758
|
+
* Start hooks run in the background and don't block engine startup
|
|
759
|
+
*/
|
|
760
|
+
async initialize() {
|
|
761
|
+
await this.loadConfig();
|
|
762
|
+
this.startHooksPromise = this.executeStartHooks();
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* Load and parse the replicas.json config file
|
|
766
|
+
*/
|
|
767
|
+
async loadConfig() {
|
|
768
|
+
const configPath = join2(this.workingDirectory, "replicas.json");
|
|
769
|
+
if (!existsSync2(configPath)) {
|
|
770
|
+
console.log("No replicas.json found in workspace directory");
|
|
771
|
+
this.config = null;
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
try {
|
|
775
|
+
const data = await readFile3(configPath, "utf-8");
|
|
776
|
+
const config = JSON.parse(data);
|
|
777
|
+
if (config.copy && !Array.isArray(config.copy)) {
|
|
778
|
+
throw new Error('Invalid replicas.json: "copy" must be an array of file paths');
|
|
779
|
+
}
|
|
780
|
+
if (config.ports && !Array.isArray(config.ports)) {
|
|
781
|
+
throw new Error('Invalid replicas.json: "ports" must be an array of port numbers');
|
|
782
|
+
}
|
|
783
|
+
if (config.ports && !config.ports.every((p) => typeof p === "number")) {
|
|
784
|
+
throw new Error("Invalid replicas.json: all ports must be numbers");
|
|
785
|
+
}
|
|
786
|
+
if (config.organizationId && typeof config.organizationId !== "string") {
|
|
787
|
+
throw new Error('Invalid replicas.json: "organizationId" must be a string');
|
|
788
|
+
}
|
|
789
|
+
if (config.systemPrompt && typeof config.systemPrompt !== "string") {
|
|
790
|
+
throw new Error('Invalid replicas.json: "systemPrompt" must be a string');
|
|
791
|
+
}
|
|
792
|
+
if (config.startHook) {
|
|
793
|
+
if (typeof config.startHook !== "object" || Array.isArray(config.startHook)) {
|
|
794
|
+
throw new Error('Invalid replicas.json: "startHook" must be an object with "commands" array');
|
|
795
|
+
}
|
|
796
|
+
if (!Array.isArray(config.startHook.commands)) {
|
|
797
|
+
throw new Error('Invalid replicas.json: "startHook.commands" must be an array of shell commands');
|
|
798
|
+
}
|
|
799
|
+
if (!config.startHook.commands.every((cmd) => typeof cmd === "string")) {
|
|
800
|
+
throw new Error("Invalid replicas.json: all startHook.commands entries must be strings");
|
|
801
|
+
}
|
|
802
|
+
if (config.startHook.timeout !== void 0 && (typeof config.startHook.timeout !== "number" || config.startHook.timeout <= 0)) {
|
|
803
|
+
throw new Error('Invalid replicas.json: "startHook.timeout" must be a positive number');
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
this.config = config;
|
|
807
|
+
console.log("Loaded replicas.json config:", {
|
|
808
|
+
hasSystemPrompt: !!config.systemPrompt,
|
|
809
|
+
startHookCount: config.startHook?.commands.length ?? 0
|
|
810
|
+
});
|
|
811
|
+
} catch (error) {
|
|
812
|
+
if (error instanceof SyntaxError) {
|
|
813
|
+
console.error("Failed to parse replicas.json:", error.message);
|
|
814
|
+
} else if (error instanceof Error) {
|
|
815
|
+
console.error("Error loading replicas.json:", error.message);
|
|
816
|
+
}
|
|
817
|
+
this.config = null;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Write a message to the start hooks log file
|
|
822
|
+
*/
|
|
823
|
+
async logToFile(message) {
|
|
824
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
825
|
+
const logLine = `[${timestamp}] ${message}
|
|
826
|
+
`;
|
|
827
|
+
try {
|
|
828
|
+
await mkdir2(join2(homedir2(), ".replicas"), { recursive: true });
|
|
829
|
+
await appendFile(START_HOOKS_LOG, logLine, "utf-8");
|
|
830
|
+
} catch (error) {
|
|
831
|
+
console.error("Failed to write to start hooks log:", error);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Execute all start hooks defined in replicas.json
|
|
836
|
+
* Writes output to ~/.replicas/startHooks.log
|
|
837
|
+
* Only runs once per workspace lifecycle (persisted across sleep/wake cycles)
|
|
838
|
+
*/
|
|
839
|
+
async executeStartHooks() {
|
|
840
|
+
const persistedState = await loadEngineState();
|
|
841
|
+
if (persistedState.startHooksCompleted) {
|
|
842
|
+
console.log("Start hooks already completed in previous session, skipping");
|
|
843
|
+
this.hooksCompleted = true;
|
|
844
|
+
await this.logToFile("Start hooks already completed in previous session, skipping");
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
847
|
+
const startHookConfig = this.config?.startHook;
|
|
848
|
+
if (!startHookConfig || startHookConfig.commands.length === 0) {
|
|
849
|
+
this.hooksCompleted = true;
|
|
850
|
+
await saveEngineState({ startHooksCompleted: true });
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
const timeout = startHookConfig.timeout ?? 3e5;
|
|
854
|
+
const hooks = startHookConfig.commands;
|
|
855
|
+
this.hooksRunning = true;
|
|
856
|
+
await mkdir2(join2(homedir2(), ".replicas"), { recursive: true });
|
|
857
|
+
await writeFile2(START_HOOKS_LOG, `=== Start Hooks Execution Log ===
|
|
858
|
+
Started: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
859
|
+
Commands: ${hooks.length}
|
|
860
|
+
|
|
861
|
+
`, "utf-8");
|
|
862
|
+
console.log(`Executing ${hooks.length} start hook(s) with timeout ${timeout}ms...`);
|
|
863
|
+
await this.logToFile(`Executing ${hooks.length} start hook(s) with timeout ${timeout}ms...`);
|
|
864
|
+
for (const hook of hooks) {
|
|
865
|
+
try {
|
|
866
|
+
console.log(`Running start hook: ${hook}`);
|
|
867
|
+
await this.logToFile(`
|
|
868
|
+
--- Running: ${hook} ---`);
|
|
869
|
+
const { stdout, stderr } = await execAsync(hook, {
|
|
870
|
+
cwd: this.workingDirectory,
|
|
871
|
+
timeout,
|
|
872
|
+
env: process.env
|
|
873
|
+
});
|
|
874
|
+
if (stdout) {
|
|
875
|
+
console.log(`[${hook}] stdout:`, stdout);
|
|
876
|
+
await this.logToFile(`[stdout] ${stdout}`);
|
|
877
|
+
}
|
|
878
|
+
if (stderr) {
|
|
879
|
+
console.warn(`[${hook}] stderr:`, stderr);
|
|
880
|
+
await this.logToFile(`[stderr] ${stderr}`);
|
|
881
|
+
}
|
|
882
|
+
console.log(`Start hook completed: ${hook}`);
|
|
883
|
+
await this.logToFile(`--- Completed: ${hook} ---`);
|
|
884
|
+
} catch (error) {
|
|
885
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
886
|
+
console.error(`Start hook failed: ${hook}`, errorMessage);
|
|
887
|
+
await this.logToFile(`[ERROR] ${hook} failed: ${errorMessage}`);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
this.hooksRunning = false;
|
|
891
|
+
this.hooksCompleted = true;
|
|
892
|
+
await saveEngineState({ startHooksCompleted: true });
|
|
893
|
+
console.log("All start hooks completed");
|
|
894
|
+
await this.logToFile(`
|
|
895
|
+
=== All start hooks completed at ${(/* @__PURE__ */ new Date()).toISOString()} ===`);
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* Get the system prompt from replicas.json
|
|
899
|
+
*/
|
|
900
|
+
getSystemPrompt() {
|
|
901
|
+
return this.config?.systemPrompt;
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* Get the full config object
|
|
905
|
+
*/
|
|
906
|
+
getConfig() {
|
|
907
|
+
return this.config;
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Check if start hooks are currently running
|
|
911
|
+
*/
|
|
912
|
+
areHooksRunning() {
|
|
913
|
+
return this.hooksRunning;
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* Get start hook configuration (for system prompt)
|
|
917
|
+
*/
|
|
918
|
+
getStartHookConfig() {
|
|
919
|
+
return this.config?.startHook;
|
|
920
|
+
}
|
|
921
|
+
};
|
|
922
|
+
var replicasConfigService = new ReplicasConfigService();
|
|
923
|
+
|
|
723
924
|
// src/services/codex-manager.ts
|
|
925
|
+
var DEFAULT_MODEL = "gpt-5.1-codex";
|
|
724
926
|
var CodexManager = class {
|
|
725
927
|
codex;
|
|
726
928
|
currentThreadId = null;
|
|
@@ -736,14 +938,14 @@ var CodexManager = class {
|
|
|
736
938
|
this.workingDirectory = workingDirectory;
|
|
737
939
|
} else {
|
|
738
940
|
const repoName = process.env.REPLICAS_REPO_NAME;
|
|
739
|
-
const workspaceHome = process.env.WORKSPACE_HOME || process.env.HOME ||
|
|
941
|
+
const workspaceHome = process.env.WORKSPACE_HOME || process.env.HOME || homedir3();
|
|
740
942
|
if (repoName) {
|
|
741
|
-
this.workingDirectory =
|
|
943
|
+
this.workingDirectory = join3(workspaceHome, "workspaces", repoName);
|
|
742
944
|
} else {
|
|
743
945
|
this.workingDirectory = workspaceHome;
|
|
744
946
|
}
|
|
745
947
|
}
|
|
746
|
-
this.tempImageDir =
|
|
948
|
+
this.tempImageDir = join3(homedir3(), ".replicas", "codex", "temp-images");
|
|
747
949
|
this.messageQueue = new MessageQueue(this.processMessageInternal.bind(this));
|
|
748
950
|
this.initialized = this.initialize();
|
|
749
951
|
}
|
|
@@ -785,6 +987,20 @@ var CodexManager = class {
|
|
|
785
987
|
getBaseSystemPrompt() {
|
|
786
988
|
return this.baseSystemPrompt;
|
|
787
989
|
}
|
|
990
|
+
/**
|
|
991
|
+
* Generate start hooks instruction for the system prompt
|
|
992
|
+
* Returns a message explaining start hooks status if they are running
|
|
993
|
+
*/
|
|
994
|
+
getStartHooksInstruction() {
|
|
995
|
+
if (!replicasConfigService.areHooksRunning()) {
|
|
996
|
+
return void 0;
|
|
997
|
+
}
|
|
998
|
+
const startHookConfig = replicasConfigService.getStartHookConfig();
|
|
999
|
+
if (!startHookConfig || startHookConfig.commands.length === 0) {
|
|
1000
|
+
return void 0;
|
|
1001
|
+
}
|
|
1002
|
+
return START_HOOKS_RUNNING_PROMPT;
|
|
1003
|
+
}
|
|
788
1004
|
/**
|
|
789
1005
|
* Legacy sendMessage method - now uses the queue internally
|
|
790
1006
|
* @deprecated Use enqueueMessage for better control over queue status
|
|
@@ -797,14 +1013,14 @@ var CodexManager = class {
|
|
|
797
1013
|
* @returns Array of temp file paths
|
|
798
1014
|
*/
|
|
799
1015
|
async saveImagesToTempFiles(images) {
|
|
800
|
-
await
|
|
1016
|
+
await mkdir3(this.tempImageDir, { recursive: true });
|
|
801
1017
|
const tempPaths = [];
|
|
802
1018
|
for (const image of images) {
|
|
803
1019
|
const ext = image.source.media_type.split("/")[1] || "png";
|
|
804
1020
|
const filename = `img_${randomUUID()}.${ext}`;
|
|
805
|
-
const filepath =
|
|
1021
|
+
const filepath = join3(this.tempImageDir, filename);
|
|
806
1022
|
const buffer = Buffer.from(image.source.data, "base64");
|
|
807
|
-
await
|
|
1023
|
+
await writeFile3(filepath, buffer);
|
|
808
1024
|
tempPaths.push(filepath);
|
|
809
1025
|
}
|
|
810
1026
|
return tempPaths;
|
|
@@ -826,23 +1042,27 @@ var CodexManager = class {
|
|
|
826
1042
|
workingDirectory: this.workingDirectory,
|
|
827
1043
|
skipGitRepoCheck: true,
|
|
828
1044
|
sandboxMode: "danger-full-access",
|
|
829
|
-
model: model ||
|
|
1045
|
+
model: model || DEFAULT_MODEL
|
|
830
1046
|
});
|
|
831
1047
|
} else {
|
|
832
1048
|
this.currentThread = this.codex.startThread({
|
|
833
1049
|
workingDirectory: this.workingDirectory,
|
|
834
1050
|
skipGitRepoCheck: true,
|
|
835
1051
|
sandboxMode: "danger-full-access",
|
|
836
|
-
model: model ||
|
|
1052
|
+
model: model || DEFAULT_MODEL
|
|
837
1053
|
});
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
1054
|
+
const startHooksInstruction = this.getStartHooksInstruction();
|
|
1055
|
+
const parts = [];
|
|
1056
|
+
if (this.baseSystemPrompt) {
|
|
1057
|
+
parts.push(this.baseSystemPrompt);
|
|
1058
|
+
}
|
|
1059
|
+
if (startHooksInstruction) {
|
|
1060
|
+
parts.push(startHooksInstruction);
|
|
1061
|
+
}
|
|
1062
|
+
if (customInstructions) {
|
|
1063
|
+
parts.push(customInstructions);
|
|
845
1064
|
}
|
|
1065
|
+
const combinedInstructions = parts.length > 0 ? parts.join("\n\n") : void 0;
|
|
846
1066
|
if (combinedInstructions) {
|
|
847
1067
|
message = combinedInstructions + "\n" + message;
|
|
848
1068
|
}
|
|
@@ -852,7 +1072,6 @@ ${customInstructions}`;
|
|
|
852
1072
|
this.currentThreadId = event.thread_id;
|
|
853
1073
|
await saveEngineState({ codexThreadId: this.currentThreadId });
|
|
854
1074
|
console.log(`[CodexManager] Captured and persisted thread ID: ${this.currentThreadId}`);
|
|
855
|
-
break;
|
|
856
1075
|
}
|
|
857
1076
|
}
|
|
858
1077
|
if (!this.currentThreadId && this.currentThread.id) {
|
|
@@ -955,13 +1174,13 @@ ${customInstructions}`;
|
|
|
955
1174
|
}
|
|
956
1175
|
// Helper methods for finding session files
|
|
957
1176
|
async findSessionFile(threadId) {
|
|
958
|
-
const sessionsDir =
|
|
1177
|
+
const sessionsDir = join3(homedir3(), ".codex", "sessions");
|
|
959
1178
|
try {
|
|
960
1179
|
const now = /* @__PURE__ */ new Date();
|
|
961
1180
|
const year = now.getFullYear();
|
|
962
1181
|
const month = String(now.getMonth() + 1).padStart(2, "0");
|
|
963
1182
|
const day = String(now.getDate()).padStart(2, "0");
|
|
964
|
-
const todayDir =
|
|
1183
|
+
const todayDir = join3(sessionsDir, String(year), month, day);
|
|
965
1184
|
const file = await this.findFileInDirectory(todayDir, threadId);
|
|
966
1185
|
if (file) return file;
|
|
967
1186
|
for (let daysAgo = 1; daysAgo <= 7; daysAgo++) {
|
|
@@ -970,7 +1189,7 @@ ${customInstructions}`;
|
|
|
970
1189
|
const searchYear = date.getFullYear();
|
|
971
1190
|
const searchMonth = String(date.getMonth() + 1).padStart(2, "0");
|
|
972
1191
|
const searchDay = String(date.getDate()).padStart(2, "0");
|
|
973
|
-
const searchDir =
|
|
1192
|
+
const searchDir = join3(sessionsDir, String(searchYear), searchMonth, searchDay);
|
|
974
1193
|
const file2 = await this.findFileInDirectory(searchDir, threadId);
|
|
975
1194
|
if (file2) return file2;
|
|
976
1195
|
}
|
|
@@ -984,7 +1203,7 @@ ${customInstructions}`;
|
|
|
984
1203
|
const files = await readdir(directory);
|
|
985
1204
|
for (const file of files) {
|
|
986
1205
|
if (file.endsWith(".jsonl") && file.includes(threadId)) {
|
|
987
|
-
const fullPath =
|
|
1206
|
+
const fullPath = join3(directory, file);
|
|
988
1207
|
const stats = await stat(fullPath);
|
|
989
1208
|
if (stats.isFile()) {
|
|
990
1209
|
return fullPath;
|
|
@@ -1126,9 +1345,9 @@ import { Hono as Hono2 } from "hono";
|
|
|
1126
1345
|
import {
|
|
1127
1346
|
query
|
|
1128
1347
|
} from "@anthropic-ai/claude-agent-sdk";
|
|
1129
|
-
import { join as
|
|
1130
|
-
import { mkdir as
|
|
1131
|
-
import { homedir as
|
|
1348
|
+
import { join as join4 } from "path";
|
|
1349
|
+
import { mkdir as mkdir4, appendFile as appendFile2, rm } from "fs/promises";
|
|
1350
|
+
import { homedir as homedir4 } from "os";
|
|
1132
1351
|
var ClaudeManager = class {
|
|
1133
1352
|
workingDirectory;
|
|
1134
1353
|
historyFile;
|
|
@@ -1141,14 +1360,14 @@ var ClaudeManager = class {
|
|
|
1141
1360
|
this.workingDirectory = workingDirectory;
|
|
1142
1361
|
} else {
|
|
1143
1362
|
const repoName = process.env.REPLICAS_REPO_NAME;
|
|
1144
|
-
const workspaceHome = process.env.WORKSPACE_HOME || process.env.HOME ||
|
|
1363
|
+
const workspaceHome = process.env.WORKSPACE_HOME || process.env.HOME || homedir4();
|
|
1145
1364
|
if (repoName) {
|
|
1146
|
-
this.workingDirectory =
|
|
1365
|
+
this.workingDirectory = join4(workspaceHome, "workspaces", repoName);
|
|
1147
1366
|
} else {
|
|
1148
1367
|
this.workingDirectory = workspaceHome;
|
|
1149
1368
|
}
|
|
1150
1369
|
}
|
|
1151
|
-
this.historyFile =
|
|
1370
|
+
this.historyFile = join4(homedir4(), ".replicas", "claude", "history.jsonl");
|
|
1152
1371
|
this.initialized = this.initialize();
|
|
1153
1372
|
this.messageQueue = new MessageQueue(this.processMessageInternal.bind(this));
|
|
1154
1373
|
}
|
|
@@ -1183,6 +1402,20 @@ var ClaudeManager = class {
|
|
|
1183
1402
|
getBaseSystemPrompt() {
|
|
1184
1403
|
return this.baseSystemPrompt;
|
|
1185
1404
|
}
|
|
1405
|
+
/**
|
|
1406
|
+
* Generate start hooks instruction for the system prompt
|
|
1407
|
+
* Returns a message explaining start hooks status if they are running
|
|
1408
|
+
*/
|
|
1409
|
+
getStartHooksInstruction() {
|
|
1410
|
+
if (!replicasConfigService.areHooksRunning()) {
|
|
1411
|
+
return void 0;
|
|
1412
|
+
}
|
|
1413
|
+
const startHookConfig = replicasConfigService.getStartHookConfig();
|
|
1414
|
+
if (!startHookConfig || startHookConfig.commands.length === 0) {
|
|
1415
|
+
return void 0;
|
|
1416
|
+
}
|
|
1417
|
+
return START_HOOKS_RUNNING_PROMPT;
|
|
1418
|
+
}
|
|
1186
1419
|
/**
|
|
1187
1420
|
* Legacy sendMessage method - now uses the queue internally
|
|
1188
1421
|
* @deprecated Use enqueueMessage for better control over queue status
|
|
@@ -1232,14 +1465,18 @@ var ClaudeManager = class {
|
|
|
1232
1465
|
const promptIterable = (async function* () {
|
|
1233
1466
|
yield userMessage;
|
|
1234
1467
|
})();
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
${customInstructions}`;
|
|
1240
|
-
} else {
|
|
1241
|
-
combinedInstructions = this.baseSystemPrompt || customInstructions;
|
|
1468
|
+
const startHooksInstruction = this.getStartHooksInstruction();
|
|
1469
|
+
const parts = [];
|
|
1470
|
+
if (this.baseSystemPrompt) {
|
|
1471
|
+
parts.push(this.baseSystemPrompt);
|
|
1242
1472
|
}
|
|
1473
|
+
if (startHooksInstruction) {
|
|
1474
|
+
parts.push(startHooksInstruction);
|
|
1475
|
+
}
|
|
1476
|
+
if (customInstructions) {
|
|
1477
|
+
parts.push(customInstructions);
|
|
1478
|
+
}
|
|
1479
|
+
const combinedInstructions = parts.length > 0 ? parts.join("\n\n") : void 0;
|
|
1243
1480
|
const response = query({
|
|
1244
1481
|
prompt: promptIterable,
|
|
1245
1482
|
options: {
|
|
@@ -1310,8 +1547,8 @@ ${customInstructions}`;
|
|
|
1310
1547
|
}
|
|
1311
1548
|
}
|
|
1312
1549
|
async initialize() {
|
|
1313
|
-
const historyDir =
|
|
1314
|
-
await
|
|
1550
|
+
const historyDir = join4(homedir4(), ".replicas", "claude");
|
|
1551
|
+
await mkdir4(historyDir, { recursive: true });
|
|
1315
1552
|
const persistedState = await loadEngineState();
|
|
1316
1553
|
if (persistedState.claudeSessionId) {
|
|
1317
1554
|
this.sessionId = persistedState.claudeSessionId;
|
|
@@ -1333,7 +1570,7 @@ ${customInstructions}`;
|
|
|
1333
1570
|
type: `claude-${event.type}`,
|
|
1334
1571
|
payload: event
|
|
1335
1572
|
};
|
|
1336
|
-
await
|
|
1573
|
+
await appendFile2(this.historyFile, JSON.stringify(jsonEvent) + "\n", "utf-8");
|
|
1337
1574
|
}
|
|
1338
1575
|
};
|
|
1339
1576
|
|
|
@@ -1716,7 +1953,7 @@ var CodexTokenManager = class {
|
|
|
1716
1953
|
var codexTokenManager = new CodexTokenManager();
|
|
1717
1954
|
|
|
1718
1955
|
// src/services/git-init.ts
|
|
1719
|
-
import { existsSync as
|
|
1956
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1720
1957
|
import path4 from "path";
|
|
1721
1958
|
var initializedBranch = null;
|
|
1722
1959
|
function findAvailableBranchName(baseName, cwd) {
|
|
@@ -1752,7 +1989,7 @@ async function initializeGitRepository() {
|
|
|
1752
1989
|
};
|
|
1753
1990
|
}
|
|
1754
1991
|
const repoPath = path4.join(workspaceHome, "workspaces", repoName);
|
|
1755
|
-
if (!
|
|
1992
|
+
if (!existsSync3(repoPath)) {
|
|
1756
1993
|
console.log(`[GitInit] Repository directory does not exist: ${repoPath}`);
|
|
1757
1994
|
console.log("[GitInit] Waiting for initializer to clone the repository...");
|
|
1758
1995
|
return {
|
|
@@ -1760,7 +1997,7 @@ async function initializeGitRepository() {
|
|
|
1760
1997
|
branch: null
|
|
1761
1998
|
};
|
|
1762
1999
|
}
|
|
1763
|
-
if (!
|
|
2000
|
+
if (!existsSync3(path4.join(repoPath, ".git"))) {
|
|
1764
2001
|
return {
|
|
1765
2002
|
success: false,
|
|
1766
2003
|
branch: null,
|
|
@@ -1828,148 +2065,6 @@ async function initializeGitRepository() {
|
|
|
1828
2065
|
}
|
|
1829
2066
|
}
|
|
1830
2067
|
|
|
1831
|
-
// src/services/replicas-config.ts
|
|
1832
|
-
import { readFile as readFile3 } from "fs/promises";
|
|
1833
|
-
import { existsSync as existsSync3 } from "fs";
|
|
1834
|
-
import { join as join4 } from "path";
|
|
1835
|
-
import { homedir as homedir4 } from "os";
|
|
1836
|
-
import { exec } from "child_process";
|
|
1837
|
-
import { promisify } from "util";
|
|
1838
|
-
var execAsync = promisify(exec);
|
|
1839
|
-
var ReplicasConfigService = class {
|
|
1840
|
-
config = null;
|
|
1841
|
-
workingDirectory;
|
|
1842
|
-
hooksExecuted = false;
|
|
1843
|
-
constructor() {
|
|
1844
|
-
const repoName = process.env.REPLICAS_REPO_NAME;
|
|
1845
|
-
const workspaceHome = process.env.WORKSPACE_HOME || process.env.HOME || homedir4();
|
|
1846
|
-
if (repoName) {
|
|
1847
|
-
this.workingDirectory = join4(workspaceHome, "workspaces", repoName);
|
|
1848
|
-
} else {
|
|
1849
|
-
this.workingDirectory = workspaceHome;
|
|
1850
|
-
}
|
|
1851
|
-
}
|
|
1852
|
-
/**
|
|
1853
|
-
* Initialize the service by reading replicas.json and executing start hooks
|
|
1854
|
-
*/
|
|
1855
|
-
async initialize() {
|
|
1856
|
-
await this.loadConfig();
|
|
1857
|
-
await this.executeStartHooks();
|
|
1858
|
-
}
|
|
1859
|
-
/**
|
|
1860
|
-
* Load and parse the replicas.json config file
|
|
1861
|
-
*/
|
|
1862
|
-
async loadConfig() {
|
|
1863
|
-
const configPath = join4(this.workingDirectory, "replicas.json");
|
|
1864
|
-
if (!existsSync3(configPath)) {
|
|
1865
|
-
console.log("No replicas.json found in workspace directory");
|
|
1866
|
-
this.config = null;
|
|
1867
|
-
return;
|
|
1868
|
-
}
|
|
1869
|
-
try {
|
|
1870
|
-
const data = await readFile3(configPath, "utf-8");
|
|
1871
|
-
const config = JSON.parse(data);
|
|
1872
|
-
if (config.copy && !Array.isArray(config.copy)) {
|
|
1873
|
-
throw new Error('Invalid replicas.json: "copy" must be an array of file paths');
|
|
1874
|
-
}
|
|
1875
|
-
if (config.ports && !Array.isArray(config.ports)) {
|
|
1876
|
-
throw new Error('Invalid replicas.json: "ports" must be an array of port numbers');
|
|
1877
|
-
}
|
|
1878
|
-
if (config.ports && !config.ports.every((p) => typeof p === "number")) {
|
|
1879
|
-
throw new Error("Invalid replicas.json: all ports must be numbers");
|
|
1880
|
-
}
|
|
1881
|
-
if (config.systemPrompt && typeof config.systemPrompt !== "string") {
|
|
1882
|
-
throw new Error('Invalid replicas.json: "systemPrompt" must be a string');
|
|
1883
|
-
}
|
|
1884
|
-
if (config.startHook) {
|
|
1885
|
-
if (typeof config.startHook !== "object" || Array.isArray(config.startHook)) {
|
|
1886
|
-
throw new Error('Invalid replicas.json: "startHook" must be an object with "commands" array');
|
|
1887
|
-
}
|
|
1888
|
-
if (!Array.isArray(config.startHook.commands)) {
|
|
1889
|
-
throw new Error('Invalid replicas.json: "startHook.commands" must be an array of shell commands');
|
|
1890
|
-
}
|
|
1891
|
-
if (!config.startHook.commands.every((cmd) => typeof cmd === "string")) {
|
|
1892
|
-
throw new Error("Invalid replicas.json: all startHook.commands entries must be strings");
|
|
1893
|
-
}
|
|
1894
|
-
if (config.startHook.timeout !== void 0 && (typeof config.startHook.timeout !== "number" || config.startHook.timeout <= 0)) {
|
|
1895
|
-
throw new Error('Invalid replicas.json: "startHook.timeout" must be a positive number');
|
|
1896
|
-
}
|
|
1897
|
-
}
|
|
1898
|
-
this.config = config;
|
|
1899
|
-
console.log("Loaded replicas.json config:", {
|
|
1900
|
-
hasSystemPrompt: !!config.systemPrompt,
|
|
1901
|
-
startHookCount: config.startHook?.commands.length ?? 0
|
|
1902
|
-
});
|
|
1903
|
-
} catch (error) {
|
|
1904
|
-
if (error instanceof SyntaxError) {
|
|
1905
|
-
console.error("Failed to parse replicas.json:", error.message);
|
|
1906
|
-
} else if (error instanceof Error) {
|
|
1907
|
-
console.error("Error loading replicas.json:", error.message);
|
|
1908
|
-
}
|
|
1909
|
-
this.config = null;
|
|
1910
|
-
}
|
|
1911
|
-
}
|
|
1912
|
-
/**
|
|
1913
|
-
* Execute all start hooks defined in replicas.json
|
|
1914
|
-
*/
|
|
1915
|
-
async executeStartHooks() {
|
|
1916
|
-
if (this.hooksExecuted) {
|
|
1917
|
-
console.log("Start hooks already executed, skipping");
|
|
1918
|
-
return;
|
|
1919
|
-
}
|
|
1920
|
-
const startHookConfig = this.config?.startHook;
|
|
1921
|
-
if (!startHookConfig || startHookConfig.commands.length === 0) {
|
|
1922
|
-
this.hooksExecuted = true;
|
|
1923
|
-
return;
|
|
1924
|
-
}
|
|
1925
|
-
const timeout = startHookConfig.timeout ?? 3e5;
|
|
1926
|
-
const hooks = startHookConfig.commands;
|
|
1927
|
-
console.log(`Executing ${hooks.length} start hook(s) with timeout ${timeout}ms...`);
|
|
1928
|
-
for (const hook of hooks) {
|
|
1929
|
-
try {
|
|
1930
|
-
console.log(`Running start hook: ${hook}`);
|
|
1931
|
-
const { stdout, stderr } = await execAsync(hook, {
|
|
1932
|
-
cwd: this.workingDirectory,
|
|
1933
|
-
timeout,
|
|
1934
|
-
env: process.env
|
|
1935
|
-
});
|
|
1936
|
-
if (stdout) {
|
|
1937
|
-
console.log(`[${hook}] stdout:`, stdout);
|
|
1938
|
-
}
|
|
1939
|
-
if (stderr) {
|
|
1940
|
-
console.warn(`[${hook}] stderr:`, stderr);
|
|
1941
|
-
}
|
|
1942
|
-
console.log(`Start hook completed: ${hook}`);
|
|
1943
|
-
} catch (error) {
|
|
1944
|
-
if (error instanceof Error) {
|
|
1945
|
-
console.error(`Start hook failed: ${hook}`, error.message);
|
|
1946
|
-
}
|
|
1947
|
-
}
|
|
1948
|
-
}
|
|
1949
|
-
this.hooksExecuted = true;
|
|
1950
|
-
console.log("All start hooks completed");
|
|
1951
|
-
}
|
|
1952
|
-
/**
|
|
1953
|
-
* Get the system prompt from replicas.json
|
|
1954
|
-
*/
|
|
1955
|
-
getSystemPrompt() {
|
|
1956
|
-
return this.config?.systemPrompt;
|
|
1957
|
-
}
|
|
1958
|
-
/**
|
|
1959
|
-
* Get the full config object
|
|
1960
|
-
*/
|
|
1961
|
-
getConfig() {
|
|
1962
|
-
return this.config;
|
|
1963
|
-
}
|
|
1964
|
-
/**
|
|
1965
|
-
* Check if start hooks have been executed
|
|
1966
|
-
*/
|
|
1967
|
-
hasExecutedHooks() {
|
|
1968
|
-
return this.hooksExecuted;
|
|
1969
|
-
}
|
|
1970
|
-
};
|
|
1971
|
-
var replicasConfigService = new ReplicasConfigService();
|
|
1972
|
-
|
|
1973
2068
|
// src/index.ts
|
|
1974
2069
|
var READY_MESSAGE = "========= REPLICAS WORKSPACE READY ==========";
|
|
1975
2070
|
var COMPLETION_MESSAGE = "========= REPLICAS WORKSPACE INITIALIZATION COMPLETE ==========";
|
|
@@ -2017,9 +2112,7 @@ app.get("/status", async (c) => {
|
|
|
2017
2112
|
isCodexUsed,
|
|
2018
2113
|
isClaudeUsed,
|
|
2019
2114
|
hasActiveSSHSessions,
|
|
2020
|
-
...gitStatus
|
|
2021
|
-
linearBetaEnabled: true
|
|
2022
|
-
// TODO: delete
|
|
2115
|
+
...gitStatus
|
|
2023
2116
|
});
|
|
2024
2117
|
} catch (error) {
|
|
2025
2118
|
console.error("Error getting workspace status:", error);
|