replicas-engine 0.1.711 → 0.1.713

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.
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  HOOK_EXEC_MAX_BUFFER_BYTES,
4
4
  isVersionBelow
5
- } from "./chunk-VTONH6X5.js";
5
+ } from "./chunk-GSMOYM7N.js";
6
6
 
7
7
  // src/utils/presigned-upload.ts
8
8
  import { createReadStream } from "fs";
@@ -267,7 +267,7 @@ var DEFAULT_CODEX_ARGS = [
267
267
  var MIN_CODEX_CLI_VERSION = "0.144.6";
268
268
  var CODEX_UPGRADE_TIMEOUT_MS = 12e4;
269
269
  var codexCliVersionEnsured = null;
270
- var ENGINE_PACKAGE_VERSION = "0.1.711";
270
+ var ENGINE_PACKAGE_VERSION = "0.1.713";
271
271
  var INITIALIZE_METHOD = "initialize";
272
272
  var INITIALIZED_NOTIFICATION = "initialized";
273
273
  var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
@@ -536,7 +536,8 @@ function serializeCanvasContentResponse(input) {
536
536
  filename: input.filename,
537
537
  kind: input.kind,
538
538
  sizeBytes: input.sizeBytes,
539
- mimeType: input.mimeType
539
+ mimeType: input.mimeType,
540
+ revision: input.revision
540
541
  };
541
542
  if (isCanvasTextKind(input.kind)) {
542
543
  return { ...base, content: new TextDecoder().decode(input.bytes) };
@@ -4066,6 +4067,9 @@ var AUDIT_LOG_ACTIONS = Object.values(AUDIT_LOG_ACTION);
4066
4067
  // ../shared/src/automations/types.ts
4067
4068
  var AUTOMATION_DEBOUNCE_MAX_SECONDS = 24 * 60 * 60;
4068
4069
 
4070
+ // ../shared/src/routes/repo-files.ts
4071
+ var MAX_REPO_FILE_CONTENT_BYTES = 256 * 1024;
4072
+
4069
4073
  // ../shared/src/routes/admin.ts
4070
4074
  var WEBHOOK_PROVIDERS = ["linear", "github", "gitlab", "slack", "e2b", "stripe", "sentry"];
4071
4075
  var WEBHOOK_PROVIDER_SET = new Set(WEBHOOK_PROVIDERS);
@@ -6685,6 +6689,7 @@ export {
6685
6689
  IMAGE_MEDIA_TYPES,
6686
6690
  MAX_CANVAS_FILE_BYTES,
6687
6691
  MAX_CANVAS_PREVIEW_BYTES,
6692
+ isCanvasTextKind,
6688
6693
  sanitizeCanvasFilename,
6689
6694
  serializeCanvasContentResponse,
6690
6695
  classifyCanvasFilename,
@@ -6768,6 +6773,7 @@ export {
6768
6773
  findPrMergeSignals,
6769
6774
  findGitCommitSignals,
6770
6775
  mayCreatePullRequest,
6776
+ MAX_REPO_FILE_CONTENT_BYTES,
6771
6777
  coerceBackgroundTaskPayload,
6772
6778
  isTerminalBackgroundTaskStatus,
6773
6779
  areUserMessagesWithinMatchWindow,
@@ -12,7 +12,7 @@ import {
12
12
  isValidAgentProvider,
13
13
  parsePosixEnvFile,
14
14
  readReplicasRuntimeEnv
15
- } from "./chunk-VTONH6X5.js";
15
+ } from "./chunk-GSMOYM7N.js";
16
16
 
17
17
  // src/engine-env.ts
18
18
  import { readFileSync as readFileSync2 } from "fs";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  mayCreatePullRequest
4
- } from "./chunk-VTONH6X5.js";
4
+ } from "./chunk-GSMOYM7N.js";
5
5
 
6
6
  // src/services/post-tool-pr-notifier.ts
7
7
  async function notifyPostToolUse(toolName, toolInput) {
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  evaluateCommandProtection
4
- } from "./chunk-Z7X6ZWKP.js";
4
+ } from "./chunk-JSCZVP7Q.js";
5
5
  import {
6
6
  isRecord
7
7
  } from "./chunk-2RB7SIP3.js";
8
- import "./chunk-VTONH6X5.js";
8
+ import "./chunk-GSMOYM7N.js";
9
9
 
10
10
  // src/command-protection-hook.ts
11
11
  var provider = process.argv[2];
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  evaluateCommandProtection
4
- } from "./chunk-Z7X6ZWKP.js";
4
+ } from "./chunk-JSCZVP7Q.js";
5
5
  import {
6
6
  notifyPostToolUse
7
- } from "./chunk-SI3I5LZI.js";
7
+ } from "./chunk-OF3EDBVV.js";
8
8
  import "./chunk-2RB7SIP3.js";
9
- import "./chunk-VTONH6X5.js";
9
+ import "./chunk-GSMOYM7N.js";
10
10
 
11
11
  // src/deepseek-command-protection-plugin.ts
12
12
  function replicasCommandProtection(context) {
@@ -4,12 +4,12 @@ import {
4
4
  buildCodexAgentEnv,
5
5
  putPresignedFile,
6
6
  recoverCompletedTurn
7
- } from "./chunk-F4HJ4HAT.js";
7
+ } from "./chunk-4TBCMRGQ.js";
8
8
  import {
9
9
  AGENT,
10
10
  getMemoryOutputSafetyViolation,
11
11
  headlessAgentRequestSchema
12
- } from "./chunk-VTONH6X5.js";
12
+ } from "./chunk-GSMOYM7N.js";
13
13
 
14
14
  // src/headless-agent.ts
15
15
  import { createHash } from "crypto";
package/dist/src/index.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  monolithService,
9
9
  reportCommandProtectionBlock,
10
10
  setAgentCredentialSnapshot
11
- } from "./chunk-Z7X6ZWKP.js";
11
+ } from "./chunk-JSCZVP7Q.js";
12
12
  import {
13
13
  ACCOUNT_RATE_LIMITS_UPDATED_METHOD,
14
14
  AGENT_MESSAGE_DELTA_METHOD,
@@ -35,7 +35,7 @@ import {
35
35
  execFileAsync,
36
36
  putPresignedFile,
37
37
  recoverCompletedTurn
38
- } from "./chunk-F4HJ4HAT.js";
38
+ } from "./chunk-4TBCMRGQ.js";
39
39
  import {
40
40
  isRecord as isRecord2
41
41
  } from "./chunk-2RB7SIP3.js";
@@ -97,6 +97,7 @@ import {
97
97
  MAX_CANVAS_FILE_BYTES,
98
98
  MAX_CANVAS_PREVIEW_BYTES,
99
99
  MAX_CODEX_GOAL_OBJECTIVE_CHARS,
100
+ MAX_REPO_FILE_CONTENT_BYTES,
100
101
  MEMORY_INDEX_FILENAME,
101
102
  MEMORY_ROOT,
102
103
  MERGED_MESSAGE_SEPARATOR,
@@ -165,6 +166,7 @@ import {
165
166
  isAgentChatMcpActivityRecord,
166
167
  isAgentChatSkillActivityRecord,
167
168
  isAgentChatTurnActivityRecord,
169
+ isCanvasTextKind,
168
170
  isChatMessageSender,
169
171
  isClaudeAuthErrorText,
170
172
  isClaudeResultError,
@@ -211,7 +213,7 @@ import {
211
213
  shellQuotePosix,
212
214
  stripAgentDiagnosticErrors,
213
215
  withTimeout
214
- } from "./chunk-VTONH6X5.js";
216
+ } from "./chunk-GSMOYM7N.js";
215
217
 
216
218
  // src/index.ts
217
219
  import { serve } from "@hono/node-server";
@@ -2627,7 +2629,7 @@ async function registerDesktopPreview() {
2627
2629
 
2628
2630
  // src/services/chat/chat-service.ts
2629
2631
  import { existsSync as existsSync11 } from "fs";
2630
- import { appendFile as appendFile4, copyFile as copyFile2, mkdir as mkdir18, readFile as readFile19, rename as rename3, rm as rm3 } from "fs/promises";
2632
+ import { appendFile as appendFile4, copyFile as copyFile2, mkdir as mkdir18, readFile as readFile20, rename as rename3, rm as rm3 } from "fs/promises";
2631
2633
  import { join as join28 } from "path";
2632
2634
  import { randomUUID as randomUUID9 } from "crypto";
2633
2635
 
@@ -11929,9 +11931,49 @@ var KeepAliveService = class _KeepAliveService {
11929
11931
  var keepAliveService = new KeepAliveService();
11930
11932
 
11931
11933
  // src/services/canvas-service.ts
11932
- import { readdir as readdir7, readFile as readFile17, stat as stat3 } from "fs/promises";
11934
+ import { readdir as readdir7, readFile as readFile18, stat as stat4 } from "fs/promises";
11933
11935
  import { homedir as homedir12 } from "os";
11934
11936
  import { join as join24 } from "path";
11937
+
11938
+ // src/utils/contained-file.ts
11939
+ import { realpath, stat as stat3 } from "fs/promises";
11940
+ import { resolve as resolve2 } from "path";
11941
+ async function resolveContainedFile(root, relativePath) {
11942
+ try {
11943
+ const [fullPath, resolvedRoot] = await Promise.all([
11944
+ realpath(resolve2(root, relativePath)),
11945
+ realpath(root)
11946
+ ]);
11947
+ const fileStat = await stat3(fullPath);
11948
+ const rootPrefix = resolvedRoot.endsWith("/") ? resolvedRoot : `${resolvedRoot}/`;
11949
+ return fullPath.startsWith(rootPrefix) && fileStat.isFile() ? { fullPath, sizeBytes: fileStat.size } : null;
11950
+ } catch {
11951
+ return null;
11952
+ }
11953
+ }
11954
+
11955
+ // src/utils/file-revision.ts
11956
+ import { createHash as createHash3 } from "crypto";
11957
+ function fileRevision(content) {
11958
+ return createHash3("sha256").update(content).digest("hex");
11959
+ }
11960
+
11961
+ // src/utils/revisioned-text-file.ts
11962
+ import { readFile as readFile17 } from "fs/promises";
11963
+ async function writeRevisionedTextFile(filePath, content, expectedRevision) {
11964
+ const current = await readFile17(filePath, "utf8");
11965
+ const revision = fileRevision(current);
11966
+ if (revision !== expectedRevision) {
11967
+ return {
11968
+ saved: false,
11969
+ current: { content: current, revision, sizeBytes: Buffer.byteLength(current) }
11970
+ };
11971
+ }
11972
+ await atomicWriteFile(filePath, content);
11973
+ return { saved: true, revision: fileRevision(content), sizeBytes: Buffer.byteLength(content) };
11974
+ }
11975
+
11976
+ // src/services/canvas-service.ts
11935
11977
  var GLOBAL_CANVAS_DIRECTORIES = [
11936
11978
  join24(homedir12(), ".claude", "plans"),
11937
11979
  join24(process.env.XDG_DATA_HOME ?? join24(homedir12(), ".local", "share"), "opencode", "plans"),
@@ -11949,6 +11991,7 @@ var CanvasService = class {
11949
11991
  this.directories = directories;
11950
11992
  }
11951
11993
  directories;
11994
+ writeLock = new AsyncLock();
11952
11995
  async listItems() {
11953
11996
  const items = /* @__PURE__ */ new Map();
11954
11997
  for (const directory of await this.directories()) {
@@ -11974,7 +12017,7 @@ var CanvasService = class {
11974
12017
  const { kind } = classifyCanvasFilename(filename);
11975
12018
  let sizeBytes = 0;
11976
12019
  try {
11977
- const s = await stat3(filePath);
12020
+ const s = await stat4(filePath);
11978
12021
  sizeBytes = s.size;
11979
12022
  } catch {
11980
12023
  continue;
@@ -11994,7 +12037,7 @@ var CanvasService = class {
11994
12037
  let sizeBytes = 0;
11995
12038
  let updatedAt = "";
11996
12039
  try {
11997
- const s = await stat3(filePath);
12040
+ const s = await stat4(filePath);
11998
12041
  sizeBytes = s.size;
11999
12042
  updatedAt = s.mtime.toISOString();
12000
12043
  } catch {
@@ -12011,7 +12054,7 @@ var CanvasService = class {
12011
12054
  };
12012
12055
  }
12013
12056
  try {
12014
- const bytes = await readFile17(filePath);
12057
+ const bytes = await readFile18(filePath);
12015
12058
  return { filename: safe, kind, sizeBytes, mimeType, updatedAt, bytes };
12016
12059
  } catch {
12017
12060
  continue;
@@ -12036,7 +12079,19 @@ var CanvasService = class {
12036
12079
  kind: item.kind,
12037
12080
  sizeBytes: item.sizeBytes,
12038
12081
  mimeType: item.mimeType,
12039
- bytes: item.bytes
12082
+ bytes: item.bytes,
12083
+ revision: fileRevision(item.bytes)
12084
+ });
12085
+ }
12086
+ async updateItem(filename, content, expectedRevision) {
12087
+ const safe = sanitizeCanvasFilename(filename);
12088
+ if (!safe || !isCanvasTextKind(classifyCanvasFilename(safe).kind) || Buffer.byteLength(content) > MAX_CANVAS_PREVIEW_BYTES) return null;
12089
+ return this.writeLock.run(async () => {
12090
+ for (const directory of await this.directories()) {
12091
+ const file = await resolveContainedFile(directory, safe);
12092
+ if (file) return await writeRevisionedTextFile(file.fullPath, content, expectedRevision);
12093
+ }
12094
+ return null;
12040
12095
  });
12041
12096
  }
12042
12097
  };
@@ -12160,8 +12215,8 @@ async function reconcileCanvasItems(filenames) {
12160
12215
 
12161
12216
  // src/services/upload-chat-transcripts.ts
12162
12217
  import { createReadStream } from "fs";
12163
- import { createHash as createHash3 } from "crypto";
12164
- import { readFile as readFile18, readdir as readdir8, stat as stat4 } from "fs/promises";
12218
+ import { createHash as createHash4 } from "crypto";
12219
+ import { readFile as readFile19, readdir as readdir8, stat as stat5 } from "fs/promises";
12165
12220
  import { basename as basename3, join as join26 } from "path";
12166
12221
 
12167
12222
  // src/services/chat/chat-senders.ts
@@ -12218,9 +12273,9 @@ async function flushAllChatTranscripts(chatsById = /* @__PURE__ */ new Map(), ca
12218
12273
  return capture ? { flushed, failed, captureSupported: true, revisions } : { flushed, failed };
12219
12274
  }
12220
12275
  async function uploadChatTranscript(chatId, filePath, chat, capture) {
12221
- const { size } = await stat4(filePath);
12276
+ const { size } = await stat5(filePath);
12222
12277
  if (size === 0) return null;
12223
- const historyPages = createChatTranscriptPages(await readFile18(filePath));
12278
+ const historyPages = createChatTranscriptPages(await readFile19(filePath));
12224
12279
  const metadata = chat ? {
12225
12280
  provider: chat.provider,
12226
12281
  credential: ENGINE_ENV.REPLICAS_AGENT_CREDENTIALS[chat.provider],
@@ -12231,7 +12286,7 @@ async function uploadChatTranscript(chatId, filePath, chat, capture) {
12231
12286
  deletedAt: chat.deletedAt ?? null
12232
12287
  } : {};
12233
12288
  if (capture) {
12234
- const hash = createHash3("sha256");
12289
+ const hash = createHash4("sha256");
12235
12290
  for await (const chunk of createReadStream(filePath)) hash.update(chunk);
12236
12291
  metadata.captureId = capture.captureId;
12237
12292
  metadata.captureReason = capture.reason;
@@ -12239,7 +12294,7 @@ async function uploadChatTranscript(chatId, filePath, chat, capture) {
12239
12294
  }
12240
12295
  try {
12241
12296
  metadata.senders = parseChatMessageSendersJsonl(
12242
- await readFile18(chatMessageSendersFilePath(chatId), "utf-8")
12297
+ await readFile19(chatMessageSendersFilePath(chatId), "utf-8")
12243
12298
  );
12244
12299
  } catch (error) {
12245
12300
  if (!(error && typeof error === "object" && "code" in error && error.code === "ENOENT")) throw error;
@@ -12288,7 +12343,7 @@ async function uploadChatTranscript(chatId, filePath, chat, capture) {
12288
12343
 
12289
12344
  // src/services/upload-engine-logs.ts
12290
12345
  import { createReadStream as createReadStream2 } from "fs";
12291
- import { readdir as readdir9, stat as stat5 } from "fs/promises";
12346
+ import { readdir as readdir9, stat as stat6 } from "fs/promises";
12292
12347
  import { join as join27 } from "path";
12293
12348
  var MAX_ENGINE_LOG_FLUSH_SESSIONS = 10;
12294
12349
  var MAX_ENGINE_LOG_FLUSH_BYTES = 5 * 1024 * 1024;
@@ -12319,7 +12374,7 @@ async function flushAllEngineLogs() {
12319
12374
  try {
12320
12375
  const sessionId = filename.slice(0, -".log".length);
12321
12376
  const filePath = join27(LOG_DIR, filename);
12322
- const fileStat = await runBeforeDeadline(() => stat5(filePath), deadline);
12377
+ const fileStat = await runBeforeDeadline(() => stat6(filePath), deadline);
12323
12378
  if (!fileStat.isFile()) {
12324
12379
  skipped++;
12325
12380
  return null;
@@ -12776,7 +12831,7 @@ var ChatService = class {
12776
12831
  }
12777
12832
  }
12778
12833
  async readSenders(chatId) {
12779
- return readFile19(chatMessageSendersFilePath(chatId), "utf-8").then(parseChatMessageSendersJsonl).catch((error) => {
12834
+ return readFile20(chatMessageSendersFilePath(chatId), "utf-8").then(parseChatMessageSendersJsonl).catch((error) => {
12780
12835
  if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") return [];
12781
12836
  console.error("[ChatService] Failed to read sender records:", error);
12782
12837
  return [];
@@ -13479,7 +13534,7 @@ var ChatService = class {
13479
13534
  }
13480
13535
  async loadChats() {
13481
13536
  try {
13482
- const content = await readFile19(CHATS_FILE, "utf-8");
13537
+ const content = await readFile20(CHATS_FILE, "utf-8");
13483
13538
  return parsePersistedChatsContent(content);
13484
13539
  } catch (error) {
13485
13540
  if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
@@ -13494,7 +13549,7 @@ var ChatService = class {
13494
13549
  console.error("[ChatService] Failed to quarantine corrupt chats file:", renameError);
13495
13550
  }
13496
13551
  try {
13497
- const backupContent = await readFile19(CHATS_BACKUP_FILE, "utf-8");
13552
+ const backupContent = await readFile20(CHATS_BACKUP_FILE, "utf-8");
13498
13553
  return parsePersistedChatsContent(backupContent);
13499
13554
  } catch (backupError) {
13500
13555
  if (backupError && typeof backupError === "object" && "code" in backupError && backupError.code === "ENOENT") {
@@ -13584,11 +13639,10 @@ var ChatService = class {
13584
13639
 
13585
13640
  // src/services/repo-file-service.ts
13586
13641
  import { execFile } from "child_process";
13587
- import { readFile as readFile20, realpath, stat as stat6 } from "fs/promises";
13588
- import { join as join29, resolve as resolve2, extname as extname2 } from "path";
13642
+ import { readFile as readFile21 } from "fs/promises";
13643
+ import { extname as extname2 } from "path";
13589
13644
  var CACHE_TTL_MS = 3e4;
13590
13645
  var SEARCH_TIMEOUT_MS = 15e3;
13591
- var MAX_CONTENT_BYTES = 256 * 1024;
13592
13646
  var DEFAULT_LIMIT = 50;
13593
13647
  var MAX_LIMIT = 5e3;
13594
13648
  var EXCLUDED_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", ".next", "dist", "build", "out", "vendor", ".turbo", "__pycache__"]);
@@ -13667,6 +13721,7 @@ var RepoFileService = class {
13667
13721
  cache = /* @__PURE__ */ new Map();
13668
13722
  gitService;
13669
13723
  indexInflight = null;
13724
+ writeLock = new AsyncLock();
13670
13725
  constructor(gitService2) {
13671
13726
  this.gitService = gitService2;
13672
13727
  }
@@ -13730,20 +13785,10 @@ var RepoFileService = class {
13730
13785
  };
13731
13786
  }
13732
13787
  async readFile(repoName, filePath) {
13733
- if (filePath.includes("..") || filePath.startsWith("/")) {
13734
- return null;
13735
- }
13736
- const repos = await this.gitService.listRepositories();
13737
- const repo = repos.find((r) => r.name === repoName);
13738
- if (!repo) return null;
13788
+ const resolved = await this.resolveFile(repoName, filePath);
13789
+ if (!resolved) return null;
13790
+ const { fullPath, sizeBytes } = resolved;
13739
13791
  try {
13740
- const fullPath = await realpath(resolve2(join29(repo.path, filePath)));
13741
- const repoRoot = await realpath(repo.path);
13742
- const repoPrefix = repoRoot.endsWith("/") ? repoRoot : repoRoot + "/";
13743
- if (!fullPath.startsWith(repoPrefix) && fullPath !== repoRoot) return null;
13744
- const fileStat = await stat6(fullPath);
13745
- if (!fileStat.isFile()) return null;
13746
- const sizeBytes = fileStat.size;
13747
13792
  if (isBinaryExtension(filePath)) {
13748
13793
  const { kind, mimeType } = classifyCanvasFilename(filePath);
13749
13794
  if (kind === "image" || kind === "pdf") {
@@ -13768,7 +13813,7 @@ var RepoFileService = class {
13768
13813
  sizeBytes,
13769
13814
  binary: true,
13770
13815
  tooLarge: false,
13771
- base64: (await readFile20(fullPath)).toString("base64"),
13816
+ base64: (await readFile21(fullPath)).toString("base64"),
13772
13817
  mimeType
13773
13818
  };
13774
13819
  }
@@ -13782,7 +13827,7 @@ var RepoFileService = class {
13782
13827
  tooLarge: false
13783
13828
  };
13784
13829
  }
13785
- if (sizeBytes > MAX_CONTENT_BYTES) {
13830
+ if (sizeBytes > MAX_REPO_FILE_CONTENT_BYTES) {
13786
13831
  return {
13787
13832
  repoName,
13788
13833
  path: filePath,
@@ -13793,7 +13838,7 @@ var RepoFileService = class {
13793
13838
  tooLarge: true
13794
13839
  };
13795
13840
  }
13796
- const content = await readFile20(fullPath, "utf-8");
13841
+ const content = await readFile21(fullPath, "utf-8");
13797
13842
  return {
13798
13843
  repoName,
13799
13844
  path: filePath,
@@ -13801,12 +13846,33 @@ var RepoFileService = class {
13801
13846
  language: detectLanguageByPath(filePath),
13802
13847
  sizeBytes,
13803
13848
  binary: false,
13804
- tooLarge: false
13849
+ tooLarge: false,
13850
+ revision: fileRevision(content)
13805
13851
  };
13806
13852
  } catch {
13807
13853
  return null;
13808
13854
  }
13809
13855
  }
13856
+ async updateFile(repoName, filePath, content, expectedRevision) {
13857
+ if (filePath.includes("..") || filePath.startsWith("/") || isBinaryExtension(filePath) || Buffer.byteLength(content) > MAX_REPO_FILE_CONTENT_BYTES) return null;
13858
+ return this.writeLock.run(async () => {
13859
+ const resolved = await this.resolveFile(repoName, filePath);
13860
+ if (!resolved) return null;
13861
+ try {
13862
+ const result = await writeRevisionedTextFile(resolved.fullPath, content, expectedRevision);
13863
+ if (result.saved) this.cache.delete(repoName);
13864
+ return result;
13865
+ } catch {
13866
+ return null;
13867
+ }
13868
+ });
13869
+ }
13870
+ async resolveFile(repoName, filePath) {
13871
+ if (filePath.includes("..") || filePath.startsWith("/")) return null;
13872
+ const repo = (await this.gitService.listRepositories()).find((repository) => repository.name === repoName);
13873
+ if (!repo) return null;
13874
+ return await resolveContainedFile(repo.path, filePath);
13875
+ }
13810
13876
  async indexAll(refresh) {
13811
13877
  const repos = await this.gitService.listRepositories();
13812
13878
  for (const repo of repos) {
@@ -13871,17 +13937,17 @@ var RepoFileService = class {
13871
13937
  // src/v1-routes.ts
13872
13938
  import { Hono } from "hono";
13873
13939
  import { z as z5 } from "zod";
13874
- import { readdir as readdir11, stat as stat7, readFile as readFile23 } from "fs/promises";
13940
+ import { readdir as readdir11, stat as stat7, readFile as readFile24 } from "fs/promises";
13875
13941
  import { join as join32, resolve as resolve3 } from "path";
13876
13942
 
13877
13943
  // src/services/warm-hooks-service.ts
13878
13944
  import { spawn as spawn6 } from "child_process";
13879
- import { readFile as readFile22 } from "fs/promises";
13945
+ import { readFile as readFile23 } from "fs/promises";
13880
13946
  import { existsSync as existsSync12 } from "fs";
13881
13947
  import { join as join31 } from "path";
13882
13948
 
13883
13949
  // src/services/warm-hook-logs-service.ts
13884
- import { mkdir as mkdir19, readFile as readFile21, writeFile as writeFile9, readdir as readdir10, appendFile as appendFile5, unlink as unlink4 } from "fs/promises";
13950
+ import { mkdir as mkdir19, readFile as readFile22, writeFile as writeFile9, readdir as readdir10, appendFile as appendFile5, unlink as unlink4 } from "fs/promises";
13885
13951
  import { homedir as homedir13 } from "os";
13886
13952
  import { join as join30 } from "path";
13887
13953
  var LOGS_DIR2 = join30(homedir13(), ".replicas", "warm-hook-logs");
@@ -13941,7 +14007,7 @@ var WarmHookLogsService = class {
13941
14007
  continue;
13942
14008
  }
13943
14009
  try {
13944
- const raw = await readFile21(join30(LOGS_DIR2, file), "utf-8");
14010
+ const raw = await readFile22(join30(LOGS_DIR2, file), "utf-8");
13945
14011
  const stored = JSON.parse(raw);
13946
14012
  logs.push(withPreview2(stored));
13947
14013
  } catch {
@@ -13970,7 +14036,7 @@ var WarmHookLogsService = class {
13970
14036
  }
13971
14037
  async getCurrentRunLog() {
13972
14038
  try {
13973
- return await readFile21(CURRENT_RUN_LOG, "utf-8");
14039
+ return await readFile22(CURRENT_RUN_LOG, "utf-8");
13974
14040
  } catch (err) {
13975
14041
  if (err.code === "ENOENT") return null;
13976
14042
  throw err;
@@ -13979,7 +14045,7 @@ var WarmHookLogsService = class {
13979
14045
  async getFullOutput(hookType, hookName) {
13980
14046
  const filename = hookType === "global" ? GLOBAL_FILENAME : hookType === "environment" ? ENVIRONMENT_HOOK_LOG_FILENAME : repoHookLogFilename(hookName);
13981
14047
  try {
13982
- const raw = await readFile21(join30(LOGS_DIR2, filename), "utf-8");
14048
+ const raw = await readFile22(join30(LOGS_DIR2, filename), "utf-8");
13983
14049
  const stored = JSON.parse(raw);
13984
14050
  if (stored.hookType !== hookType || stored.hookName !== hookName) {
13985
14051
  return null;
@@ -14003,7 +14069,7 @@ async function readRepoWarmHook(repoPath) {
14003
14069
  continue;
14004
14070
  }
14005
14071
  try {
14006
- const raw = await readFile22(configPath, "utf-8");
14072
+ const raw = await readFile23(configPath, "utf-8");
14007
14073
  const config = parseReplicasConfigString(raw, filename);
14008
14074
  if (!config.warmHook) {
14009
14075
  return null;
@@ -14401,6 +14467,10 @@ var updateGoalSchema = z5.object({
14401
14467
  }).refine((body) => body.objective !== void 0 || body.status !== void 0, {
14402
14468
  message: "Goal objective or status required"
14403
14469
  });
14470
+ var updateTextFileSchema = (maxLength) => z5.object({
14471
+ content: z5.string().max(maxLength),
14472
+ expectedRevision: z5.string().regex(/^[a-f0-9]{64}$/)
14473
+ });
14404
14474
  function jsonError(message, details) {
14405
14475
  return { error: message, details };
14406
14476
  }
@@ -14782,6 +14852,17 @@ function createV1Routes(deps) {
14782
14852
  }
14783
14853
  return c.json(result);
14784
14854
  });
14855
+ app2.put("/repo-files/content", async (c) => {
14856
+ const repoName = c.req.query("repoName");
14857
+ const path6 = c.req.query("path");
14858
+ if (!repoName || !path6) {
14859
+ return c.json(jsonError("repoName and path are required"), 400);
14860
+ }
14861
+ const body = updateTextFileSchema(MAX_REPO_FILE_CONTENT_BYTES).parse(await c.req.json());
14862
+ const result = await deps.repoFileService.updateFile(repoName, path6, body.content, body.expectedRevision);
14863
+ if (!result) return c.json(jsonError("File is not editable"), 400);
14864
+ return c.json(result);
14865
+ });
14785
14866
  app2.get("/terminal/sessions", (c) => {
14786
14867
  return c.json({ sessions: terminalService.list() });
14787
14868
  });
@@ -14883,6 +14964,14 @@ data: ${JSON.stringify("Terminal session not found")}
14883
14964
  );
14884
14965
  }
14885
14966
  });
14967
+ app2.put("/canvas/:filename{.+}", async (c) => {
14968
+ const filename = c.req.param("filename");
14969
+ if (!filename) return c.json(jsonError("Filename required"), 400);
14970
+ const body = updateTextFileSchema(MAX_CANVAS_PREVIEW_BYTES).parse(await c.req.json());
14971
+ const result = await deps.canvasService.updateItem(filename, body.content, body.expectedRevision);
14972
+ if (!result) return c.json(jsonError("Canvas item is not editable"), 400);
14973
+ return c.json(result);
14974
+ });
14886
14975
  app2.get("/hooks/status", (c) => {
14887
14976
  const response = {
14888
14977
  running: replicasConfigService.areHooksRunning(),
@@ -15250,7 +15339,7 @@ data: ${JSON.stringify("Terminal session not found")}
15250
15339
  }
15251
15340
  let content;
15252
15341
  try {
15253
- content = await readFile23(filePath, "utf-8");
15342
+ content = await readFile24(filePath, "utf-8");
15254
15343
  } catch {
15255
15344
  return c.json(jsonError("Log session not found"), 404);
15256
15345
  }
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  notifyPostToolUse
4
- } from "./chunk-SI3I5LZI.js";
4
+ } from "./chunk-OF3EDBVV.js";
5
5
  import {
6
6
  isRecord
7
7
  } from "./chunk-2RB7SIP3.js";
8
- import "./chunk-VTONH6X5.js";
8
+ import "./chunk-GSMOYM7N.js";
9
9
 
10
10
  // src/post-tool-pr-hook.ts
11
11
  async function main() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.711",
3
+ "version": "0.1.713",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",