replicas-engine 0.1.258 → 0.1.260

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/README.md CHANGED
@@ -31,10 +31,10 @@ Chats:
31
31
  - `POST /chats/:chatId/messages`
32
32
  - `POST /chats/:chatId/interrupt`
33
33
 
34
- Plans:
34
+ Canvas:
35
35
 
36
- - `GET /plans`
37
- - `GET /plans/:filename`
36
+ - `GET /canvas`
37
+ - `GET /canvas/:filename`
38
38
 
39
39
  Repos and hooks:
40
40
 
@@ -93,9 +93,9 @@ Engine persistence locations:
93
93
  - `~/.replicas/engine/events.jsonl`
94
94
  - `~/.replicas/engine-state.json`
95
95
  - `~/.replicas/startHooks.log`
96
- - Plan read locations (for `/plans` endpoints):
97
- - `~/.claude/plans`
98
- - `~/.replicas/plans`
96
+ - Canvas read locations (for `/canvas` endpoints):
97
+ - `~/.claude/plans` (where Claude Code writes its plans)
98
+ - `~/.replicas/canvas`
99
99
  - Health endpoint readiness signal file:
100
100
  - `/var/log/cloud-init-output.log` (if missing, `/health` reports `initializing`)
101
101
 
package/dist/src/index.js CHANGED
@@ -127,7 +127,7 @@ function detectLanguageByPath(filePath) {
127
127
 
128
128
  // ../shared/src/context-usage.ts
129
129
  var CODEX_CATEGORY_COLORS = {
130
- input: "#66bb6a",
130
+ input: "#3eeba3",
131
131
  output: "#56b6c2"
132
132
  };
133
133
  function clampPercentage(value) {
@@ -286,7 +286,7 @@ var WORKSPACE_SIZES = ["small", "large"];
286
286
  var INVALID_WORKSPACE_SIZE_ERROR = `Invalid size: must be one of ${WORKSPACE_SIZES.join(", ")}`;
287
287
 
288
288
  // ../shared/src/e2b.ts
289
- var E2B_TEMPLATE_NAME = "replicas-sandbox-2026-06-03-v3";
289
+ var E2B_TEMPLATE_NAME = "replicas-sandbox-2026-06-04-v2";
290
290
 
291
291
  // ../shared/src/runtime-env.ts
292
292
  function parsePosixEnvFile(content) {
@@ -1643,6 +1643,7 @@ var DEFAULT_DEFAULT_SKILLS = {
1643
1643
  };
1644
1644
 
1645
1645
  // ../shared/src/prompts.ts
1646
+ var MAX_CODEX_GOAL_OBJECTIVE_CHARS = 4e3;
1646
1647
  function parseGoalCommand(message) {
1647
1648
  const match = message.trim().match(/^\/goal(?:\s+([\s\S]*))?$/i);
1648
1649
  const value = match?.[1]?.trim();
@@ -1650,6 +1651,13 @@ function parseGoalCommand(message) {
1650
1651
  if (/^(clear|reset|unset)$/i.test(value)) return { type: "clear" };
1651
1652
  return { type: "set", objective: value };
1652
1653
  }
1654
+ function getGoalCommandObjectiveValidationError(message) {
1655
+ const command = parseGoalCommand(message);
1656
+ if (command?.type !== "set") return null;
1657
+ const length = command.objective.length;
1658
+ if (length <= MAX_CODEX_GOAL_OBJECTIVE_CHARS) return null;
1659
+ return `Goal objective must be at most ${MAX_CODEX_GOAL_OBJECTIVE_CHARS} characters (${length} provided). Shorten the /goal objective, or send the extra details as a regular message after setting the goal.`;
1660
+ }
1653
1661
 
1654
1662
  // ../shared/src/replicas-config.ts
1655
1663
  import { parse as parseYaml } from "yaml";
@@ -1831,6 +1839,29 @@ var MODEL_LABELS = {
1831
1839
  "gpt-5": "GPT-5"
1832
1840
  };
1833
1841
  var IMAGE_MEDIA_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"];
1842
+ var CANVAS_KIND_BY_EXTENSION = {
1843
+ ".md": { kind: "markdown", mimeType: "text/markdown; charset=utf-8" },
1844
+ ".markdown": { kind: "markdown", mimeType: "text/markdown; charset=utf-8" },
1845
+ ".html": { kind: "html", mimeType: "text/html; charset=utf-8" },
1846
+ ".htm": { kind: "html", mimeType: "text/html; charset=utf-8" },
1847
+ ".png": { kind: "image", mimeType: "image/png" },
1848
+ ".jpg": { kind: "image", mimeType: "image/jpeg" },
1849
+ ".jpeg": { kind: "image", mimeType: "image/jpeg" },
1850
+ ".gif": { kind: "image", mimeType: "image/gif" },
1851
+ ".webp": { kind: "image", mimeType: "image/webp" },
1852
+ ".svg": { kind: "image", mimeType: "image/svg+xml" },
1853
+ ".mp4": { kind: "video", mimeType: "video/mp4" },
1854
+ ".webm": { kind: "video", mimeType: "video/webm" },
1855
+ ".mov": { kind: "video", mimeType: "video/quicktime" },
1856
+ ".mp3": { kind: "audio", mimeType: "audio/mpeg" },
1857
+ ".wav": { kind: "audio", mimeType: "audio/wav" },
1858
+ ".ogg": { kind: "audio", mimeType: "audio/ogg" }
1859
+ };
1860
+ function classifyCanvasFilename(filename) {
1861
+ const idx = filename.lastIndexOf(".");
1862
+ const ext = idx === -1 ? "" : filename.slice(idx).toLowerCase();
1863
+ return CANVAS_KIND_BY_EXTENSION[ext] ?? { kind: "other", mimeType: "application/octet-stream" };
1864
+ }
1834
1865
 
1835
1866
  // ../shared/src/engine/v1.ts
1836
1867
  var MERGED_MESSAGE_SEPARATOR = "\n\n<!-- replicas:merged -->\n\n";
@@ -6341,7 +6372,7 @@ var AspClient = class {
6341
6372
  // src/managers/codex-asp/app-server-process.ts
6342
6373
  var DEFAULT_CODEX_BINARY = "codex";
6343
6374
  var DEFAULT_CODEX_ARGS = ["app-server", "--listen", "stdio://"];
6344
- var ENGINE_PACKAGE_VERSION = "0.1.258";
6375
+ var ENGINE_PACKAGE_VERSION = "0.1.260";
6345
6376
  var INITIALIZE_METHOD = "initialize";
6346
6377
  var INITIALIZED_NOTIFICATION = "initialized";
6347
6378
  var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
@@ -9896,60 +9927,88 @@ var RepoFileService = class {
9896
9927
  // src/v1-routes.ts
9897
9928
  import { Hono } from "hono";
9898
9929
  import { z as z2 } from "zod";
9899
- import { readdir as readdir6, stat as stat4, readFile as readFile14 } from "fs/promises";
9930
+ import { readdir as readdir6, stat as stat5, readFile as readFile14 } from "fs/promises";
9900
9931
  import { join as join20, resolve as resolve2 } from "path";
9901
9932
 
9902
- // src/services/plan-service.ts
9903
- import { readdir as readdir4, readFile as readFile11 } from "fs/promises";
9933
+ // src/services/canvas-service.ts
9934
+ import { readdir as readdir4, readFile as readFile11, stat as stat4 } from "fs/promises";
9904
9935
  import { homedir as homedir14 } from "os";
9905
9936
  import { basename, join as join17 } from "path";
9906
- var PLAN_DIRECTORIES = [
9937
+ var CANVAS_DIRECTORIES = [
9907
9938
  join17(homedir14(), ".claude", "plans"),
9908
- join17(homedir14(), ".replicas", "plans")
9939
+ join17(homedir14(), ".replicas", "canvas")
9909
9940
  ];
9910
- function isMarkdownFile(filename) {
9911
- return filename.toLowerCase().endsWith(".md");
9941
+ var MAX_CANVAS_FILE_BYTES = 5 * 1024 * 1024;
9942
+ function isTextKind(kind) {
9943
+ return kind === "markdown" || kind === "html";
9912
9944
  }
9913
- function sanitizePlanFilename(filename) {
9945
+ function sanitizeFilename(filename) {
9914
9946
  return basename(filename);
9915
9947
  }
9916
- var PlanService = class {
9917
- async listPlans() {
9918
- const planNames = /* @__PURE__ */ new Set();
9919
- for (const directory of PLAN_DIRECTORIES) {
9948
+ var CanvasService = class {
9949
+ async listItems() {
9950
+ const items = /* @__PURE__ */ new Map();
9951
+ for (const directory of CANVAS_DIRECTORIES) {
9952
+ let entries;
9920
9953
  try {
9921
- const entries = await readdir4(directory, { withFileTypes: true });
9922
- for (const entry of entries) {
9923
- if (!entry.isFile()) {
9924
- continue;
9925
- }
9926
- if (!isMarkdownFile(entry.name)) {
9927
- continue;
9928
- }
9929
- planNames.add(entry.name);
9930
- }
9954
+ entries = await readdir4(directory, { withFileTypes: true });
9931
9955
  } catch {
9956
+ continue;
9957
+ }
9958
+ for (const entry of entries) {
9959
+ if (!entry.isFile()) continue;
9960
+ if (entry.name.startsWith(".")) continue;
9961
+ if (items.has(entry.name)) continue;
9962
+ const { kind } = classifyCanvasFilename(entry.name);
9963
+ let sizeBytes = 0;
9964
+ try {
9965
+ const s = await stat4(join17(directory, entry.name));
9966
+ sizeBytes = s.size;
9967
+ } catch {
9968
+ continue;
9969
+ }
9970
+ items.set(entry.name, { filename: entry.name, kind, sizeBytes });
9932
9971
  }
9933
9972
  }
9934
- return Array.from(planNames).sort((a, b) => a.localeCompare(b));
9973
+ return Array.from(items.values()).sort((a, b) => a.filename.localeCompare(b.filename));
9935
9974
  }
9936
- async getPlan(filename) {
9937
- const safeFilename = sanitizePlanFilename(filename);
9938
- if (!safeFilename || safeFilename !== filename || !isMarkdownFile(safeFilename)) {
9939
- return null;
9940
- }
9941
- for (const directory of PLAN_DIRECTORIES) {
9942
- const filePath = join17(directory, safeFilename);
9975
+ async getItem(filename) {
9976
+ const safe = sanitizeFilename(filename);
9977
+ if (!safe || safe !== filename || safe.startsWith(".")) return null;
9978
+ const { kind, mimeType } = classifyCanvasFilename(safe);
9979
+ for (const directory of CANVAS_DIRECTORIES) {
9980
+ const filePath = join17(directory, safe);
9981
+ let sizeBytes = 0;
9982
+ try {
9983
+ const s = await stat4(filePath);
9984
+ sizeBytes = s.size;
9985
+ } catch {
9986
+ continue;
9987
+ }
9988
+ if (sizeBytes > MAX_CANVAS_FILE_BYTES) {
9989
+ return {
9990
+ filename: safe,
9991
+ kind,
9992
+ sizeBytes,
9993
+ mimeType,
9994
+ tooLarge: true
9995
+ };
9996
+ }
9943
9997
  try {
9944
- const content = await readFile11(filePath, "utf-8");
9945
- return { filename: safeFilename, content };
9998
+ if (isTextKind(kind)) {
9999
+ const content = await readFile11(filePath, "utf-8");
10000
+ return { filename: safe, kind, sizeBytes, mimeType, content };
10001
+ }
10002
+ const buf = await readFile11(filePath);
10003
+ return { filename: safe, kind, sizeBytes, mimeType, base64: buf.toString("base64") };
9946
10004
  } catch {
10005
+ continue;
9947
10006
  }
9948
10007
  }
9949
10008
  return null;
9950
10009
  }
9951
10010
  };
9952
- var planService = new PlanService();
10011
+ var canvasService = new CanvasService();
9953
10012
 
9954
10013
  // src/services/warm-hooks-service.ts
9955
10014
  import { spawn as spawn4 } from "child_process";
@@ -10516,6 +10575,10 @@ function createV1Routes(deps) {
10516
10575
  app2.post("/chats/:chatId/messages", async (c) => {
10517
10576
  try {
10518
10577
  const body = sendMessageSchema.parse(await c.req.json());
10578
+ const goalValidationError = getGoalCommandObjectiveValidationError(body.message);
10579
+ if (goalValidationError) {
10580
+ return c.json(jsonError(goalValidationError), 400);
10581
+ }
10519
10582
  const result = await deps.chatService.sendMessage(c.req.param("chatId"), body);
10520
10583
  return c.json(result);
10521
10584
  } catch (error) {
@@ -10636,31 +10699,31 @@ function createV1Routes(deps) {
10636
10699
  }
10637
10700
  return c.json(result);
10638
10701
  });
10639
- app2.get("/plans", async (c) => {
10702
+ app2.get("/canvas", async (c) => {
10640
10703
  try {
10641
- const plans = await planService.listPlans();
10642
- return c.json({ plans });
10704
+ const items = await canvasService.listItems();
10705
+ return c.json({ items });
10643
10706
  } catch (error) {
10644
10707
  return c.json(
10645
- jsonError("Failed to list plans", error instanceof Error ? error.message : "Unknown error"),
10708
+ jsonError("Failed to list canvas items", error instanceof Error ? error.message : "Unknown error"),
10646
10709
  500
10647
10710
  );
10648
10711
  }
10649
10712
  });
10650
- app2.get("/plans/:filename", async (c) => {
10713
+ app2.get("/canvas/:filename", async (c) => {
10651
10714
  try {
10652
10715
  const filename = c.req.param("filename");
10653
10716
  if (!filename) {
10654
10717
  return c.json(jsonError("Filename required"), 400);
10655
10718
  }
10656
- const plan = await planService.getPlan(filename);
10657
- if (!plan) {
10658
- return c.json(jsonError("Plan not found"), 404);
10719
+ const item = await canvasService.getItem(filename);
10720
+ if (!item) {
10721
+ return c.json(jsonError("Canvas item not found"), 404);
10659
10722
  }
10660
- return c.json(plan);
10723
+ return c.json(item);
10661
10724
  } catch (error) {
10662
10725
  return c.json(
10663
- jsonError("Failed to read plan", error instanceof Error ? error.message : "Unknown error"),
10726
+ jsonError("Failed to read canvas item", error instanceof Error ? error.message : "Unknown error"),
10664
10727
  500
10665
10728
  );
10666
10729
  }
@@ -10974,7 +11037,7 @@ function createV1Routes(deps) {
10974
11037
  const sessions = await Promise.all(
10975
11038
  logFiles.map(async (filename) => {
10976
11039
  const filePath = join20(LOG_DIR, filename);
10977
- const fileStat = await stat4(filePath);
11040
+ const fileStat = await stat5(filePath);
10978
11041
  const sessionId = filename.replace(/\.log$/, "");
10979
11042
  return {
10980
11043
  sessionId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.258",
3
+ "version": "0.1.260",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",