replicas-engine 0.1.27 → 0.1.29

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 +119 -28
  2. package/package.json +1 -1
package/dist/src/index.js CHANGED
@@ -3,8 +3,8 @@
3
3
  // src/index.ts
4
4
  import "dotenv/config";
5
5
  import { serve } from "@hono/node-server";
6
- import { Hono as Hono3 } from "hono";
7
- import { readFile as readFile4 } from "fs/promises";
6
+ import { Hono as Hono4 } from "hono";
7
+ import { readFile as readFile5 } from "fs/promises";
8
8
  import { execSync as execSync2 } from "child_process";
9
9
 
10
10
  // src/middleware/auth.ts
@@ -630,7 +630,7 @@ var MessageQueue = class {
630
630
  * Add a message to the queue or start processing immediately if not busy
631
631
  * @returns Object indicating whether the message was queued or started processing
632
632
  */
633
- async enqueue(message, model, customInstructions, images) {
633
+ async enqueue(message, model, customInstructions, images, permissionMode) {
634
634
  const messageId = this.generateMessageId();
635
635
  const queuedMessage = {
636
636
  id: messageId,
@@ -638,6 +638,7 @@ var MessageQueue = class {
638
638
  model,
639
639
  customInstructions,
640
640
  images,
641
+ permissionMode,
641
642
  queuedAt: (/* @__PURE__ */ new Date()).toISOString()
642
643
  };
643
644
  if (this.processing) {
@@ -663,7 +664,8 @@ var MessageQueue = class {
663
664
  queuedMessage.message,
664
665
  queuedMessage.model,
665
666
  queuedMessage.customInstructions,
666
- queuedMessage.images
667
+ queuedMessage.images,
668
+ queuedMessage.permissionMode
667
669
  );
668
670
  } catch (error) {
669
671
  console.error("[MessageQueue] Error processing message:", error);
@@ -983,7 +985,7 @@ Commands: ${hooks.length}
983
985
  var replicasConfigService = new ReplicasConfigService();
984
986
 
985
987
  // src/services/codex-manager.ts
986
- var DEFAULT_MODEL = "gpt-5.1-codex";
988
+ var DEFAULT_MODEL = "gpt-5.2-codex";
987
989
  var CodexManager = class {
988
990
  codex;
989
991
  currentThreadId = null;
@@ -1025,9 +1027,9 @@ var CodexManager = class {
1025
1027
  * If already processing, adds to queue.
1026
1028
  * @returns Object with queued status, messageId, and position in queue
1027
1029
  */
1028
- async enqueueMessage(message, model, customInstructions, images) {
1030
+ async enqueueMessage(message, model, customInstructions, images, permissionMode) {
1029
1031
  await this.initialized;
1030
- return this.messageQueue.enqueue(message, model, customInstructions, images);
1032
+ return this.messageQueue.enqueue(message, model, customInstructions, images, permissionMode);
1031
1033
  }
1032
1034
  /**
1033
1035
  * Get the current queue status
@@ -1066,8 +1068,8 @@ var CodexManager = class {
1066
1068
  * Legacy sendMessage method - now uses the queue internally
1067
1069
  * @deprecated Use enqueueMessage for better control over queue status
1068
1070
  */
1069
- async sendMessage(message, model, customInstructions, images) {
1070
- await this.enqueueMessage(message, model, customInstructions, images);
1071
+ async sendMessage(message, model, customInstructions, images, permissionMode) {
1072
+ await this.enqueueMessage(message, model, customInstructions, images, permissionMode);
1071
1073
  }
1072
1074
  /**
1073
1075
  * Helper method to save normalized images to temp files for Codex SDK
@@ -1089,7 +1091,7 @@ var CodexManager = class {
1089
1091
  /**
1090
1092
  * Internal method that actually processes the message
1091
1093
  */
1092
- async processMessageInternal(message, model, customInstructions, images) {
1094
+ async processMessageInternal(message, model, customInstructions, images, permissionMode) {
1093
1095
  const linearSessionId = process.env.LINEAR_SESSION_ID;
1094
1096
  let tempImagePaths = [];
1095
1097
  try {
@@ -1097,19 +1099,20 @@ var CodexManager = class {
1097
1099
  const normalizedImages = await normalizeImages(images);
1098
1100
  tempImagePaths = await this.saveImagesToTempFiles(normalizedImages);
1099
1101
  }
1102
+ const sandboxMode = permissionMode === "read" ? "read-only" : "danger-full-access";
1100
1103
  if (!this.currentThread) {
1101
1104
  if (this.currentThreadId) {
1102
1105
  this.currentThread = this.codex.resumeThread(this.currentThreadId, {
1103
1106
  workingDirectory: this.workingDirectory,
1104
1107
  skipGitRepoCheck: true,
1105
- sandboxMode: "danger-full-access",
1108
+ sandboxMode,
1106
1109
  model: model || DEFAULT_MODEL
1107
1110
  });
1108
1111
  } else {
1109
1112
  this.currentThread = this.codex.startThread({
1110
1113
  workingDirectory: this.workingDirectory,
1111
1114
  skipGitRepoCheck: true,
1112
- sandboxMode: "danger-full-access",
1115
+ sandboxMode,
1113
1116
  model: model || DEFAULT_MODEL
1114
1117
  });
1115
1118
  const startHooksInstruction = this.getStartHooksInstruction();
@@ -1330,11 +1333,11 @@ var codexManager = new CodexManager();
1330
1333
  codex.post("/send", async (c) => {
1331
1334
  try {
1332
1335
  const body = await c.req.json();
1333
- const { message, model, customInstructions, images } = body;
1336
+ const { message, model, customInstructions, images, permissionMode } = body;
1334
1337
  if (!message || typeof message !== "string") {
1335
1338
  return c.json({ error: "Message is required and must be a string" }, 400);
1336
1339
  }
1337
- const result = await codexManager.enqueueMessage(message, model, customInstructions, images);
1340
+ const result = await codexManager.enqueueMessage(message, model, customInstructions, images, permissionMode);
1338
1341
  const response = {
1339
1342
  success: true,
1340
1343
  message: result.queued ? `Message queued at position ${result.position}` : "Message sent successfully",
@@ -1509,9 +1512,9 @@ var ClaudeManager = class {
1509
1512
  * If already processing, adds to queue.
1510
1513
  * @returns Object with queued status, messageId, and position in queue
1511
1514
  */
1512
- async enqueueMessage(message, model, customInstructions, images) {
1515
+ async enqueueMessage(message, model, customInstructions, images, permissionMode) {
1513
1516
  await this.initialized;
1514
- return this.messageQueue.enqueue(message, model, customInstructions, images);
1517
+ return this.messageQueue.enqueue(message, model, customInstructions, images, permissionMode);
1515
1518
  }
1516
1519
  /**
1517
1520
  * Get the current queue status
@@ -1550,13 +1553,13 @@ var ClaudeManager = class {
1550
1553
  * Legacy sendMessage method - now uses the queue internally
1551
1554
  * @deprecated Use enqueueMessage for better control over queue status
1552
1555
  */
1553
- async sendMessage(message, model, customInstructions, images) {
1554
- await this.enqueueMessage(message, model, customInstructions, images);
1556
+ async sendMessage(message, model, customInstructions, images, permissionMode) {
1557
+ await this.enqueueMessage(message, model, customInstructions, images, permissionMode);
1555
1558
  }
1556
1559
  /**
1557
1560
  * Internal method that actually processes the message
1558
1561
  */
1559
- async processMessageInternal(message, model, customInstructions, images) {
1562
+ async processMessageInternal(message, model, customInstructions, images, permissionMode) {
1560
1563
  const linearSessionId = process.env.LINEAR_SESSION_ID;
1561
1564
  if (!message || !message.trim()) {
1562
1565
  throw new Error("Message cannot be empty");
@@ -1612,8 +1615,8 @@ var ClaudeManager = class {
1612
1615
  options: {
1613
1616
  resume: this.sessionId || void 0,
1614
1617
  cwd: this.workingDirectory,
1615
- permissionMode: "bypassPermissions",
1616
- allowDangerouslySkipPermissions: true,
1618
+ permissionMode: permissionMode === "read" ? "plan" : "bypassPermissions",
1619
+ allowDangerouslySkipPermissions: permissionMode !== "read",
1617
1620
  settingSources: ["user", "project", "local"],
1618
1621
  systemPrompt: {
1619
1622
  type: "preset",
@@ -1740,11 +1743,11 @@ var claudeManager = new ClaudeManager();
1740
1743
  claude.post("/send", async (c) => {
1741
1744
  try {
1742
1745
  const body = await c.req.json();
1743
- const { message, model, customInstructions, images } = body;
1746
+ const { message, model, customInstructions, images, permissionMode } = body;
1744
1747
  if (!message || typeof message !== "string") {
1745
1748
  return c.json({ error: "Message is required and must be a string" }, 400);
1746
1749
  }
1747
- const result = await claudeManager.enqueueMessage(message, model, customInstructions, images);
1750
+ const result = await claudeManager.enqueueMessage(message, model, customInstructions, images, permissionMode);
1748
1751
  const response = {
1749
1752
  success: true,
1750
1753
  message: result.queued ? `Message queued at position ${result.position}` : "Message sent successfully",
@@ -1878,6 +1881,93 @@ claude.post("/reset", async (c) => {
1878
1881
  });
1879
1882
  var claude_default = claude;
1880
1883
 
1884
+ // src/routes/plans.ts
1885
+ import { Hono as Hono3 } from "hono";
1886
+
1887
+ // src/services/plans-service.ts
1888
+ import { readFile as readFile4, readdir as readdir2, mkdir as mkdir5 } from "fs/promises";
1889
+ import { existsSync as existsSync3 } from "fs";
1890
+ import { join as join5, basename } from "path";
1891
+ import { homedir as homedir5 } from "os";
1892
+ var PLANS_DIR = join5(homedir5(), ".replicas", "plans");
1893
+ function isValidFilename(filename) {
1894
+ if (!filename.endsWith(".md")) {
1895
+ return false;
1896
+ }
1897
+ const safePattern = /^[a-zA-Z0-9_-]+\.md$/;
1898
+ return safePattern.test(filename);
1899
+ }
1900
+ async function ensurePlansDir() {
1901
+ if (!existsSync3(PLANS_DIR)) {
1902
+ await mkdir5(PLANS_DIR, { recursive: true });
1903
+ }
1904
+ }
1905
+ async function listPlans() {
1906
+ await ensurePlansDir();
1907
+ try {
1908
+ const files = await readdir2(PLANS_DIR);
1909
+ return files.filter((file) => file.endsWith(".md")).sort((a, b) => a.localeCompare(b));
1910
+ } catch {
1911
+ return [];
1912
+ }
1913
+ }
1914
+ async function getPlanContent(filename) {
1915
+ if (!isValidFilename(filename)) {
1916
+ throw new Error("Invalid filename");
1917
+ }
1918
+ await ensurePlansDir();
1919
+ const filePath = join5(PLANS_DIR, basename(filename));
1920
+ if (!existsSync3(filePath)) {
1921
+ throw new Error("Plan not found");
1922
+ }
1923
+ const content = await readFile4(filePath, "utf-8");
1924
+ return content;
1925
+ }
1926
+
1927
+ // src/routes/plans.ts
1928
+ var plans = new Hono3();
1929
+ plans.get("/", async (c) => {
1930
+ try {
1931
+ const planFiles = await listPlans();
1932
+ return c.json({ plans: planFiles });
1933
+ } catch (error) {
1934
+ return c.json(
1935
+ {
1936
+ error: "Failed to list plans",
1937
+ details: error instanceof Error ? error.message : "Unknown error"
1938
+ },
1939
+ 500
1940
+ );
1941
+ }
1942
+ });
1943
+ plans.get("/:filename", async (c) => {
1944
+ const filename = c.req.param("filename");
1945
+ if (!filename) {
1946
+ return c.json({ error: "Filename is required" }, 400);
1947
+ }
1948
+ try {
1949
+ const content = await getPlanContent(filename);
1950
+ return c.json({ content, filename });
1951
+ } catch (error) {
1952
+ if (error instanceof Error) {
1953
+ if (error.message === "Invalid filename") {
1954
+ return c.json({ error: "Invalid filename" }, 400);
1955
+ }
1956
+ if (error.message === "Plan not found") {
1957
+ return c.json({ error: "Plan not found" }, 404);
1958
+ }
1959
+ }
1960
+ return c.json(
1961
+ {
1962
+ error: "Failed to read plan",
1963
+ details: error instanceof Error ? error.message : "Unknown error"
1964
+ },
1965
+ 500
1966
+ );
1967
+ }
1968
+ });
1969
+ var plans_default = plans;
1970
+
1881
1971
  // src/services/github-token-manager.ts
1882
1972
  import { promises as fs } from "fs";
1883
1973
  import path from "path";
@@ -2136,7 +2226,7 @@ var CodexTokenManager = class {
2136
2226
  var codexTokenManager = new CodexTokenManager();
2137
2227
 
2138
2228
  // src/services/git-init.ts
2139
- import { existsSync as existsSync3 } from "fs";
2229
+ import { existsSync as existsSync4 } from "fs";
2140
2230
  import path4 from "path";
2141
2231
  var initializedBranch = null;
2142
2232
  function findAvailableBranchName(baseName, cwd) {
@@ -2172,7 +2262,7 @@ async function initializeGitRepository() {
2172
2262
  };
2173
2263
  }
2174
2264
  const repoPath = path4.join(workspaceHome, "workspaces", repoName);
2175
- if (!existsSync3(repoPath)) {
2265
+ if (!existsSync4(repoPath)) {
2176
2266
  console.log(`[GitInit] Repository directory does not exist: ${repoPath}`);
2177
2267
  console.log("[GitInit] Waiting for initializer to clone the repository...");
2178
2268
  return {
@@ -2180,7 +2270,7 @@ async function initializeGitRepository() {
2180
2270
  branch: null
2181
2271
  };
2182
2272
  }
2183
- if (!existsSync3(path4.join(repoPath, ".git"))) {
2273
+ if (!existsSync4(path4.join(repoPath, ".git"))) {
2184
2274
  return {
2185
2275
  success: false,
2186
2276
  branch: null,
@@ -2260,10 +2350,10 @@ function checkActiveSSHSessions() {
2260
2350
  return false;
2261
2351
  }
2262
2352
  }
2263
- var app = new Hono3();
2353
+ var app = new Hono4();
2264
2354
  app.get("/health", async (c) => {
2265
2355
  try {
2266
- const logContent = await readFile4("/var/log/cloud-init-output.log", "utf-8");
2356
+ const logContent = await readFile5("/var/log/cloud-init-output.log", "utf-8");
2267
2357
  let status;
2268
2358
  if (logContent.includes(COMPLETION_MESSAGE)) {
2269
2359
  status = "active";
@@ -2311,6 +2401,7 @@ app.get("/status", async (c) => {
2311
2401
  app.use("*", authMiddleware);
2312
2402
  app.route("/codex", codex_default);
2313
2403
  app.route("/claude", claude_default);
2404
+ app.route("/plans", plans_default);
2314
2405
  var port = Number(process.env.PORT) || 3737;
2315
2406
  serve(
2316
2407
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.27",
3
+ "version": "0.1.29",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",