replicas-engine 0.1.19 → 0.1.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/src/index.js +198 -5
  2. package/package.json +1 -1
package/dist/src/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  import "dotenv/config";
5
5
  import { serve } from "@hono/node-server";
6
6
  import { Hono as Hono3 } from "hono";
7
- import { readFile as readFile2 } from "fs/promises";
7
+ import { readFile as readFile3 } from "fs/promises";
8
8
  import { execSync as execSync2 } from "child_process";
9
9
 
10
10
  // src/middleware/auth.ts
@@ -608,6 +608,7 @@ var CodexManager = class {
608
608
  currentThread = null;
609
609
  workingDirectory;
610
610
  messageQueue;
611
+ baseSystemPrompt;
611
612
  constructor(workingDirectory) {
612
613
  this.codex = new Codex();
613
614
  if (workingDirectory) {
@@ -640,6 +641,19 @@ var CodexManager = class {
640
641
  getQueueStatus() {
641
642
  return this.messageQueue.getStatus();
642
643
  }
644
+ /**
645
+ * Set the base system prompt from replicas.json
646
+ * This will be combined with any custom instructions passed to individual messages
647
+ */
648
+ setBaseSystemPrompt(prompt) {
649
+ this.baseSystemPrompt = prompt;
650
+ }
651
+ /**
652
+ * Get the current base system prompt
653
+ */
654
+ getBaseSystemPrompt() {
655
+ return this.baseSystemPrompt;
656
+ }
643
657
  /**
644
658
  * Legacy sendMessage method - now uses the queue internally
645
659
  * @deprecated Use enqueueMessage for better control over queue status
@@ -668,8 +682,16 @@ var CodexManager = class {
668
682
  sandboxMode: "danger-full-access",
669
683
  model: model || "gpt-5.1-codex"
670
684
  });
671
- if (customInstructions) {
672
- message = customInstructions + "\n" + message;
685
+ let combinedInstructions;
686
+ if (this.baseSystemPrompt && customInstructions) {
687
+ combinedInstructions = `${this.baseSystemPrompt}
688
+
689
+ ${customInstructions}`;
690
+ } else {
691
+ combinedInstructions = this.baseSystemPrompt || customInstructions;
692
+ }
693
+ if (combinedInstructions) {
694
+ message = combinedInstructions + "\n" + message;
673
695
  }
674
696
  const { events: events2 } = await this.currentThread.runStreamed("Hello");
675
697
  for await (const event of events2) {
@@ -945,6 +967,7 @@ var ClaudeManager = class {
945
967
  sessionId = null;
946
968
  initialized;
947
969
  messageQueue;
970
+ baseSystemPrompt;
948
971
  constructor(workingDirectory) {
949
972
  if (workingDirectory) {
950
973
  this.workingDirectory = workingDirectory;
@@ -979,6 +1002,19 @@ var ClaudeManager = class {
979
1002
  getQueueStatus() {
980
1003
  return this.messageQueue.getStatus();
981
1004
  }
1005
+ /**
1006
+ * Set the base system prompt from replicas.json
1007
+ * This will be combined with any custom instructions passed to individual messages
1008
+ */
1009
+ setBaseSystemPrompt(prompt) {
1010
+ this.baseSystemPrompt = prompt;
1011
+ }
1012
+ /**
1013
+ * Get the current base system prompt
1014
+ */
1015
+ getBaseSystemPrompt() {
1016
+ return this.baseSystemPrompt;
1017
+ }
982
1018
  /**
983
1019
  * Legacy sendMessage method - now uses the queue internally
984
1020
  * @deprecated Use enqueueMessage for better control over queue status
@@ -1014,6 +1050,14 @@ var ClaudeManager = class {
1014
1050
  const promptIterable = (async function* () {
1015
1051
  yield userMessage;
1016
1052
  })();
1053
+ let combinedInstructions;
1054
+ if (this.baseSystemPrompt && customInstructions) {
1055
+ combinedInstructions = `${this.baseSystemPrompt}
1056
+
1057
+ ${customInstructions}`;
1058
+ } else {
1059
+ combinedInstructions = this.baseSystemPrompt || customInstructions;
1060
+ }
1017
1061
  const response = query({
1018
1062
  prompt: promptIterable,
1019
1063
  options: {
@@ -1025,7 +1069,7 @@ var ClaudeManager = class {
1025
1069
  systemPrompt: {
1026
1070
  type: "preset",
1027
1071
  preset: "claude_code",
1028
- append: customInstructions
1072
+ append: combinedInstructions
1029
1073
  },
1030
1074
  env: process.env,
1031
1075
  model: model || "opus"
@@ -1568,6 +1612,148 @@ async function initializeGitRepository() {
1568
1612
  }
1569
1613
  }
1570
1614
 
1615
+ // src/services/replicas-config.ts
1616
+ import { readFile as readFile2 } from "fs/promises";
1617
+ import { existsSync as existsSync2 } from "fs";
1618
+ import { join as join3 } from "path";
1619
+ import { homedir as homedir3 } from "os";
1620
+ import { exec } from "child_process";
1621
+ import { promisify } from "util";
1622
+ var execAsync = promisify(exec);
1623
+ var ReplicasConfigService = class {
1624
+ config = null;
1625
+ workingDirectory;
1626
+ hooksExecuted = false;
1627
+ constructor() {
1628
+ const repoName = process.env.REPLICAS_REPO_NAME;
1629
+ const workspaceHome = process.env.WORKSPACE_HOME || process.env.HOME || homedir3();
1630
+ if (repoName) {
1631
+ this.workingDirectory = join3(workspaceHome, "workspaces", repoName);
1632
+ } else {
1633
+ this.workingDirectory = workspaceHome;
1634
+ }
1635
+ }
1636
+ /**
1637
+ * Initialize the service by reading replicas.json and executing start hooks
1638
+ */
1639
+ async initialize() {
1640
+ await this.loadConfig();
1641
+ await this.executeStartHooks();
1642
+ }
1643
+ /**
1644
+ * Load and parse the replicas.json config file
1645
+ */
1646
+ async loadConfig() {
1647
+ const configPath = join3(this.workingDirectory, "replicas.json");
1648
+ if (!existsSync2(configPath)) {
1649
+ console.log("No replicas.json found in workspace directory");
1650
+ this.config = null;
1651
+ return;
1652
+ }
1653
+ try {
1654
+ const data = await readFile2(configPath, "utf-8");
1655
+ const config = JSON.parse(data);
1656
+ if (config.copy && !Array.isArray(config.copy)) {
1657
+ throw new Error('Invalid replicas.json: "copy" must be an array of file paths');
1658
+ }
1659
+ if (config.ports && !Array.isArray(config.ports)) {
1660
+ throw new Error('Invalid replicas.json: "ports" must be an array of port numbers');
1661
+ }
1662
+ if (config.ports && !config.ports.every((p) => typeof p === "number")) {
1663
+ throw new Error("Invalid replicas.json: all ports must be numbers");
1664
+ }
1665
+ if (config.systemPrompt && typeof config.systemPrompt !== "string") {
1666
+ throw new Error('Invalid replicas.json: "systemPrompt" must be a string');
1667
+ }
1668
+ if (config.startHook) {
1669
+ if (typeof config.startHook !== "object" || Array.isArray(config.startHook)) {
1670
+ throw new Error('Invalid replicas.json: "startHook" must be an object with "commands" array');
1671
+ }
1672
+ if (!Array.isArray(config.startHook.commands)) {
1673
+ throw new Error('Invalid replicas.json: "startHook.commands" must be an array of shell commands');
1674
+ }
1675
+ if (!config.startHook.commands.every((cmd) => typeof cmd === "string")) {
1676
+ throw new Error("Invalid replicas.json: all startHook.commands entries must be strings");
1677
+ }
1678
+ if (config.startHook.timeout !== void 0 && (typeof config.startHook.timeout !== "number" || config.startHook.timeout <= 0)) {
1679
+ throw new Error('Invalid replicas.json: "startHook.timeout" must be a positive number');
1680
+ }
1681
+ }
1682
+ this.config = config;
1683
+ console.log("Loaded replicas.json config:", {
1684
+ hasSystemPrompt: !!config.systemPrompt,
1685
+ startHookCount: config.startHook?.commands.length ?? 0
1686
+ });
1687
+ } catch (error) {
1688
+ if (error instanceof SyntaxError) {
1689
+ console.error("Failed to parse replicas.json:", error.message);
1690
+ } else if (error instanceof Error) {
1691
+ console.error("Error loading replicas.json:", error.message);
1692
+ }
1693
+ this.config = null;
1694
+ }
1695
+ }
1696
+ /**
1697
+ * Execute all start hooks defined in replicas.json
1698
+ */
1699
+ async executeStartHooks() {
1700
+ if (this.hooksExecuted) {
1701
+ console.log("Start hooks already executed, skipping");
1702
+ return;
1703
+ }
1704
+ const startHookConfig = this.config?.startHook;
1705
+ if (!startHookConfig || startHookConfig.commands.length === 0) {
1706
+ this.hooksExecuted = true;
1707
+ return;
1708
+ }
1709
+ const timeout = startHookConfig.timeout ?? 3e5;
1710
+ const hooks = startHookConfig.commands;
1711
+ console.log(`Executing ${hooks.length} start hook(s) with timeout ${timeout}ms...`);
1712
+ for (const hook of hooks) {
1713
+ try {
1714
+ console.log(`Running start hook: ${hook}`);
1715
+ const { stdout, stderr } = await execAsync(hook, {
1716
+ cwd: this.workingDirectory,
1717
+ timeout,
1718
+ env: process.env
1719
+ });
1720
+ if (stdout) {
1721
+ console.log(`[${hook}] stdout:`, stdout);
1722
+ }
1723
+ if (stderr) {
1724
+ console.warn(`[${hook}] stderr:`, stderr);
1725
+ }
1726
+ console.log(`Start hook completed: ${hook}`);
1727
+ } catch (error) {
1728
+ if (error instanceof Error) {
1729
+ console.error(`Start hook failed: ${hook}`, error.message);
1730
+ }
1731
+ }
1732
+ }
1733
+ this.hooksExecuted = true;
1734
+ console.log("All start hooks completed");
1735
+ }
1736
+ /**
1737
+ * Get the system prompt from replicas.json
1738
+ */
1739
+ getSystemPrompt() {
1740
+ return this.config?.systemPrompt;
1741
+ }
1742
+ /**
1743
+ * Get the full config object
1744
+ */
1745
+ getConfig() {
1746
+ return this.config;
1747
+ }
1748
+ /**
1749
+ * Check if start hooks have been executed
1750
+ */
1751
+ hasExecutedHooks() {
1752
+ return this.hooksExecuted;
1753
+ }
1754
+ };
1755
+ var replicasConfigService = new ReplicasConfigService();
1756
+
1571
1757
  // src/index.ts
1572
1758
  var READY_MESSAGE = "========= REPLICAS WORKSPACE READY ==========";
1573
1759
  var COMPLETION_MESSAGE = "========= REPLICAS WORKSPACE INITIALIZATION COMPLETE ==========";
@@ -1583,7 +1769,7 @@ function checkActiveSSHSessions() {
1583
1769
  var app = new Hono3();
1584
1770
  app.get("/health", async (c) => {
1585
1771
  try {
1586
- const logContent = await readFile2("/var/log/cloud-init-output.log", "utf-8");
1772
+ const logContent = await readFile3("/var/log/cloud-init-output.log", "utf-8");
1587
1773
  let status;
1588
1774
  if (logContent.includes(COMPLETION_MESSAGE)) {
1589
1775
  status = "active";
@@ -1648,6 +1834,13 @@ serve(
1648
1834
  } else {
1649
1835
  console.warn(`Git initialization warning: ${gitResult.error}`);
1650
1836
  }
1837
+ await replicasConfigService.initialize();
1838
+ const systemPrompt = replicasConfigService.getSystemPrompt();
1839
+ if (systemPrompt) {
1840
+ claudeManager.setBaseSystemPrompt(systemPrompt);
1841
+ codexManager.setBaseSystemPrompt(systemPrompt);
1842
+ console.log("Applied system prompt from replicas.json to Claude and Codex managers");
1843
+ }
1651
1844
  await githubTokenManager.start();
1652
1845
  await claudeTokenManager.start();
1653
1846
  await codexTokenManager.start();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.19",
3
+ "version": "0.1.20",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",