replicas-engine 0.1.252 → 0.1.254

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 +206 -24
  2. package/package.json +1 -1
package/dist/src/index.js CHANGED
@@ -297,7 +297,7 @@ var WORKSPACE_SIZES = ["small", "large"];
297
297
  var INVALID_WORKSPACE_SIZE_ERROR = `Invalid size: must be one of ${WORKSPACE_SIZES.join(", ")}`;
298
298
 
299
299
  // ../shared/src/e2b.ts
300
- var E2B_TEMPLATE_NAME = "replicas-sandbox-2026-06-02-v2";
300
+ var E2B_TEMPLATE_NAME = "replicas-sandbox-2026-06-02-v4";
301
301
 
302
302
  // ../shared/src/runtime-env.ts
303
303
  function parsePosixEnvFile(content) {
@@ -1847,6 +1847,86 @@ function isCodexAspTranscript(value) {
1847
1847
  if (!isRecord(value)) return false;
1848
1848
  return typeof value.threadId === "string" && typeof value.updatedAt === "string" && Array.isArray(value.turns);
1849
1849
  }
1850
+ function isCodexAspTranscriptDelta(value) {
1851
+ if (!isRecord(value)) return false;
1852
+ if (typeof value.threadId !== "string" || typeof value.updatedAt !== "string" || !Array.isArray(value.turns) || value.reset !== void 0 && value.reset !== null && !isCodexAspTranscript(value.reset)) {
1853
+ return false;
1854
+ }
1855
+ return value.turns.every((turn) => isRecord(turn) && typeof turn.id === "string" && typeof turn.status === "string" && typeof turn.startedAt === "string" && (turn.completedAt === null || typeof turn.completedAt === "string") && Array.isArray(turn.items) && turn.items.every((item) => isRecord(item) && typeof item.id === "string" && (item.item === void 0 || isRecord(item.item) && typeof item.item.type === "string" && typeof item.item.id === "string" && typeof item.item.timestamp === "string") && (item.appendText === void 0 || typeof item.appendText === "string") && (item.appendOutput === void 0 || typeof item.appendOutput === "string")));
1856
+ }
1857
+ function appendCodexAspTranscriptField(current, append) {
1858
+ return append === void 0 ? current : `${current ?? ""}${append}`;
1859
+ }
1860
+ function patchCodexAspTranscriptItem(current, delta) {
1861
+ const base = delta.item ?? current;
1862
+ if (!base) {
1863
+ return null;
1864
+ }
1865
+ if (base.type === "agentMessage" && delta.appendText !== void 0) {
1866
+ return {
1867
+ ...base,
1868
+ text: appendCodexAspTranscriptField(base.text, delta.appendText) ?? ""
1869
+ };
1870
+ }
1871
+ if ((base.type === "commandExecution" || base.type === "fileChange") && delta.appendOutput !== void 0) {
1872
+ return {
1873
+ ...base,
1874
+ output: appendCodexAspTranscriptField(base.output, delta.appendOutput)
1875
+ };
1876
+ }
1877
+ return base;
1878
+ }
1879
+ function applyCodexAspTranscriptDelta(current, delta) {
1880
+ if (!delta) return current ?? null;
1881
+ if (delta.reset === null) {
1882
+ return null;
1883
+ }
1884
+ const base = delta.reset ?? (current?.threadId === delta.threadId ? current : {
1885
+ threadId: delta.threadId,
1886
+ updatedAt: delta.updatedAt,
1887
+ turns: []
1888
+ });
1889
+ const turns = base.turns.map((turn) => ({
1890
+ ...turn,
1891
+ items: [...turn.items]
1892
+ }));
1893
+ for (const turnDelta of delta.turns) {
1894
+ let turn = turns.find((candidate) => candidate.id === turnDelta.id);
1895
+ if (!turn) {
1896
+ turn = {
1897
+ id: turnDelta.id,
1898
+ status: turnDelta.status,
1899
+ startedAt: turnDelta.startedAt,
1900
+ completedAt: turnDelta.completedAt,
1901
+ items: []
1902
+ };
1903
+ turns.push(turn);
1904
+ } else {
1905
+ turn.status = turnDelta.status;
1906
+ turn.startedAt = turnDelta.startedAt;
1907
+ turn.completedAt = turnDelta.completedAt;
1908
+ }
1909
+ for (const itemDelta of turnDelta.items) {
1910
+ const itemIndex = turn.items.findIndex((item) => item.id === itemDelta.id);
1911
+ const patched = patchCodexAspTranscriptItem(turn.items[itemIndex], itemDelta);
1912
+ if (!patched) continue;
1913
+ if (itemIndex === -1) {
1914
+ turn.items.push(patched);
1915
+ } else {
1916
+ turn.items[itemIndex] = patched;
1917
+ }
1918
+ }
1919
+ }
1920
+ turns.sort((a, b) => Date.parse(a.startedAt) - Date.parse(b.startedAt));
1921
+ for (const turn of turns) {
1922
+ turn.items.sort((a, b) => (a.sequence ?? Number.MAX_SAFE_INTEGER) - (b.sequence ?? Number.MAX_SAFE_INTEGER) || Date.parse(a.timestamp) - Date.parse(b.timestamp));
1923
+ }
1924
+ return {
1925
+ threadId: delta.threadId,
1926
+ updatedAt: delta.updatedAt,
1927
+ turns
1928
+ };
1929
+ }
1850
1930
 
1851
1931
  // ../shared/src/routes/codex.ts
1852
1932
  var CODEX_AUTH_ENV_KEYS = [
@@ -2648,7 +2728,7 @@ var codexTokenManager = new CodexTokenManager();
2648
2728
 
2649
2729
  // src/git/service.ts
2650
2730
  import { readdir, stat } from "fs/promises";
2651
- import { existsSync as existsSync2, unlinkSync } from "fs";
2731
+ import { existsSync as existsSync2, readFileSync as readFileSync3, unlinkSync } from "fs";
2652
2732
  import { execFileSync as execFileSync2, spawnSync } from "child_process";
2653
2733
  import { join as join5 } from "path";
2654
2734
 
@@ -2857,27 +2937,21 @@ var GitService = class {
2857
2937
  }
2858
2938
  async listRepos(options) {
2859
2939
  const includeDiffs = options?.includeDiffs === true;
2860
- if (includeDiffs) {
2861
- const repos2 = await this.refreshRepos();
2862
- return repos2.map((repo) => ({
2863
- ...repo,
2864
- gitDiff: repo.gitDiff ? { ...repo.gitDiff, fullDiff: this.getFullGitDiff(repo.path, repo.defaultBranch) } : null
2865
- }));
2866
- }
2867
2940
  const repos = await this.listRepositories();
2868
2941
  const states = [];
2869
2942
  for (const repo of repos) {
2870
2943
  try {
2871
2944
  const persistedState = await loadRepoState(repo.name);
2872
2945
  const currentBranch = getCurrentBranch(repo.path) ?? repo.defaultBranch;
2873
- const persistedMatchesCurrentBranch = persistedState?.currentBranch === currentBranch;
2946
+ const gitDiff = this.getGitDiffStats(repo.path, repo.defaultBranch);
2874
2947
  states.push({
2875
2948
  name: repo.name,
2876
2949
  path: repo.path,
2877
2950
  defaultBranch: repo.defaultBranch,
2878
2951
  currentBranch,
2879
2952
  prUrls: persistedState?.prUrls ?? [],
2880
- gitDiff: persistedMatchesCurrentBranch ? persistedState.gitDiff : null,
2953
+ // fullDiff may be empty if the diff subprocess fails.
2954
+ gitDiff: includeDiffs && gitDiff ? { ...gitDiff, fullDiff: this.getFullGitDiff(repo.path, repo.defaultBranch) } : gitDiff,
2881
2955
  startHooksCompleted: persistedState?.startHooksCompleted ?? false
2882
2956
  });
2883
2957
  } catch {
@@ -3040,17 +3114,20 @@ var GitService = class {
3040
3114
  return null;
3041
3115
  }
3042
3116
  }
3043
- getUntrackedDiff(repoPath, extraFlags = []) {
3117
+ listUntrackedPaths(repoPath) {
3044
3118
  const listing = execFileSync2("git", ["ls-files", "--others", "--exclude-standard", "-z"], {
3045
3119
  cwd: repoPath,
3046
3120
  encoding: "utf-8",
3047
3121
  stdio: ["pipe", "pipe", "pipe"]
3048
3122
  });
3049
- const paths = listing.split("\0").filter(Boolean);
3123
+ return listing.split("\0").filter(Boolean);
3124
+ }
3125
+ getUntrackedDiff(repoPath) {
3126
+ const paths = this.listUntrackedPaths(repoPath);
3050
3127
  if (paths.length === 0) return "";
3051
3128
  try {
3052
3129
  execFileSync2("git", ["add", "--intent-to-add", "--", ...paths], { cwd: repoPath, stdio: ["pipe", "pipe", "pipe"] });
3053
- const result = spawnSync("git", ["diff", ...extraFlags, "--", ...paths], {
3130
+ const result = spawnSync("git", ["diff", "--", ...paths], {
3054
3131
  cwd: repoPath,
3055
3132
  encoding: "utf-8",
3056
3133
  maxBuffer: 50 * 1024 * 1024
@@ -3065,9 +3142,21 @@ var GitService = class {
3065
3142
  }
3066
3143
  countUntrackedAddedLines(repoPath) {
3067
3144
  try {
3068
- const output = this.getUntrackedDiff(repoPath, ["--shortstat"]);
3069
- const match = output.match(/(\d+) insertion/);
3070
- return match ? parseInt(match[1], 10) : 0;
3145
+ return this.listUntrackedPaths(repoPath).reduce((total, path4) => {
3146
+ try {
3147
+ const contents = readFileSync3(join5(repoPath, path4));
3148
+ if (contents.length === 0 || contents.includes(0)) {
3149
+ return total;
3150
+ }
3151
+ let lines = 0;
3152
+ for (const byte of contents) {
3153
+ if (byte === 10) lines += 1;
3154
+ }
3155
+ return total + lines + (contents[contents.length - 1] === 10 ? 0 : 1);
3156
+ } catch {
3157
+ return total;
3158
+ }
3159
+ }, 0);
3071
3160
  } catch (error) {
3072
3161
  console.error("Error counting untracked added lines:", error);
3073
3162
  return 0;
@@ -6142,7 +6231,7 @@ var AspClient = class {
6142
6231
  // src/managers/codex-asp/app-server-process.ts
6143
6232
  var DEFAULT_CODEX_BINARY = "codex";
6144
6233
  var DEFAULT_CODEX_ARGS = ["app-server", "--listen", "stdio://"];
6145
- var ENGINE_PACKAGE_VERSION = "0.1.252";
6234
+ var ENGINE_PACKAGE_VERSION = "0.1.254";
6146
6235
  var INITIALIZE_METHOD = "initialize";
6147
6236
  var INITIALIZED_NOTIFICATION = "initialized";
6148
6237
  var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
@@ -6409,7 +6498,7 @@ function isCodexAuthError(error) {
6409
6498
  }
6410
6499
 
6411
6500
  // src/managers/codex-asp/mappers.ts
6412
- import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
6501
+ import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
6413
6502
  var localImageCache = /* @__PURE__ */ new Map();
6414
6503
  var DEFAULT_MODEL = DEFAULT_CODEX_MODEL;
6415
6504
  var THREAD_START_METHOD = "thread/start";
@@ -6490,7 +6579,7 @@ function userImageForLocalPath(path4) {
6490
6579
  const image = {
6491
6580
  type: "image",
6492
6581
  mediaType: inferMediaType(path4),
6493
- data: readFileSync3(path4).toString("base64")
6582
+ data: readFileSync4(path4).toString("base64")
6494
6583
  };
6495
6584
  if (image.data.length > 0) localImageCache.set(path4, image);
6496
6585
  return image;
@@ -6965,6 +7054,7 @@ var CodexAspManager = class extends CodingAgentManager {
6965
7054
  threadAttached = false;
6966
7055
  historyEvents = [];
6967
7056
  codexAspTranscript = null;
7057
+ lastEmittedTranscripts = /* @__PURE__ */ new Map();
6968
7058
  codexAspSequence = 0;
6969
7059
  quotaStatus = new CodexQuotaStatusTracker();
6970
7060
  currentGoal = null;
@@ -7440,6 +7530,9 @@ var CodexAspManager = class extends CodingAgentManager {
7440
7530
  this.codexAspSequence = next;
7441
7531
  }
7442
7532
  mergeTranscriptSnapshot(snapshot) {
7533
+ if (snapshot?.threadId && this.codexAspTranscript?.threadId !== snapshot.threadId) {
7534
+ this.lastEmittedTranscripts.clear();
7535
+ }
7443
7536
  this.codexAspTranscript = mergeCodexAspTranscripts(this.codexAspTranscript, snapshot);
7444
7537
  this.syncTranscriptSequence(this.codexAspTranscript);
7445
7538
  return this.codexAspTranscript;
@@ -7448,6 +7541,7 @@ var CodexAspManager = class extends CodingAgentManager {
7448
7541
  if (this.codexAspTranscript?.threadId === threadId) {
7449
7542
  return this.codexAspTranscript;
7450
7543
  }
7544
+ this.lastEmittedTranscripts.clear();
7451
7545
  this.codexAspTranscript = {
7452
7546
  threadId,
7453
7547
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -7617,14 +7711,95 @@ var CodexAspManager = class extends CodingAgentManager {
7617
7711
  emitTranscriptUpdated(threadId, options = {}) {
7618
7712
  this.transcriptUpdateCoalescer.schedule(threadId, options);
7619
7713
  }
7714
+ buildTranscriptItemDelta(previous, current) {
7715
+ if (!previous || previous.type !== current.type) {
7716
+ return { id: current.id, item: structuredClone(current) };
7717
+ }
7718
+ if (current.type === "agentMessage" && previous.type === "agentMessage" && current.text.startsWith(previous.text) && current.text.length > previous.text.length) {
7719
+ return {
7720
+ id: current.id,
7721
+ appendText: current.text.slice(previous.text.length)
7722
+ };
7723
+ }
7724
+ if ((current.type === "commandExecution" || current.type === "fileChange") && previous.type === current.type && current.output && current.output.startsWith(previous.output ?? "") && current.output.length > (previous.output ?? "").length) {
7725
+ const { output: _output, ...itemWithoutOutput } = current;
7726
+ const item = {
7727
+ ...itemWithoutOutput,
7728
+ output: previous.output
7729
+ };
7730
+ return {
7731
+ id: current.id,
7732
+ item,
7733
+ appendOutput: current.output.slice((previous.output ?? "").length)
7734
+ };
7735
+ }
7736
+ if (JSON.stringify(previous) === JSON.stringify(current)) {
7737
+ return null;
7738
+ }
7739
+ return { id: current.id, item: structuredClone(current) };
7740
+ }
7741
+ buildTranscriptDelta(threadId, current) {
7742
+ const previous = this.lastEmittedTranscripts.get(threadId);
7743
+ const updatedAt = current?.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
7744
+ if (!current || previous === void 0 || previous?.threadId !== current.threadId) {
7745
+ return {
7746
+ threadId,
7747
+ updatedAt,
7748
+ reset: current ? structuredClone(current) : null,
7749
+ turns: []
7750
+ };
7751
+ }
7752
+ const turnDeltas = [];
7753
+ for (const currentTurn of current.turns) {
7754
+ const previousTurn = previous.turns.find((turn) => turn.id === currentTurn.id);
7755
+ if (!previousTurn) {
7756
+ turnDeltas.push({
7757
+ id: currentTurn.id,
7758
+ status: currentTurn.status,
7759
+ startedAt: currentTurn.startedAt,
7760
+ completedAt: currentTurn.completedAt,
7761
+ items: currentTurn.items.map((item) => ({ id: item.id, item: structuredClone(item) }))
7762
+ });
7763
+ continue;
7764
+ }
7765
+ const itemDeltas = currentTurn.items.map((item) => this.buildTranscriptItemDelta(
7766
+ previousTurn.items.find((candidate) => candidate.id === item.id),
7767
+ item
7768
+ )).filter((item) => item !== null);
7769
+ if (itemDeltas.length > 0 || currentTurn.status !== previousTurn.status || currentTurn.startedAt !== previousTurn.startedAt || currentTurn.completedAt !== previousTurn.completedAt) {
7770
+ turnDeltas.push({
7771
+ id: currentTurn.id,
7772
+ status: currentTurn.status,
7773
+ startedAt: currentTurn.startedAt,
7774
+ completedAt: currentTurn.completedAt,
7775
+ items: itemDeltas
7776
+ });
7777
+ }
7778
+ }
7779
+ return {
7780
+ threadId: current.threadId,
7781
+ updatedAt,
7782
+ turns: turnDeltas
7783
+ };
7784
+ }
7620
7785
  flushTranscriptUpdated(threadId) {
7621
7786
  const updatedAt = this.codexAspTranscript?.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString();
7622
- const transcript = this.codexAspTranscript ? structuredClone(this.codexAspTranscript) : null;
7787
+ const transcriptDelta = this.buildTranscriptDelta(threadId, this.codexAspTranscript);
7623
7788
  const updatePayload = {
7624
7789
  updatedAt,
7625
- transcript,
7626
- threadId
7790
+ threadId,
7791
+ transcript: transcriptDelta.reset ?? null,
7792
+ transcriptDelta
7627
7793
  };
7794
+ this.lastEmittedTranscripts.set(
7795
+ threadId,
7796
+ this.codexAspTranscript ? structuredClone(this.codexAspTranscript) : null
7797
+ );
7798
+ for (const emittedThreadId of this.lastEmittedTranscripts.keys()) {
7799
+ if (emittedThreadId !== threadId) {
7800
+ this.lastEmittedTranscripts.delete(emittedThreadId);
7801
+ }
7802
+ }
7628
7803
  this.onEvent({
7629
7804
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
7630
7805
  type: CODEX_ASP_TRANSCRIPT_UPDATED_EVENT_TYPE,
@@ -8714,7 +8889,14 @@ function getCodexTranscriptUserMessages(transcript) {
8714
8889
  function getCodexTranscriptFromEvent(event) {
8715
8890
  if (event.type !== CODEX_ASP_TRANSCRIPT_UPDATED_EVENT_TYPE) return null;
8716
8891
  const transcript = event.payload.transcript;
8717
- return isCodexAspTranscript(transcript) ? transcript : null;
8892
+ if (isCodexAspTranscript(transcript)) {
8893
+ return transcript;
8894
+ }
8895
+ const transcriptDelta = event.payload.transcriptDelta;
8896
+ if (!isCodexAspTranscriptDelta(transcriptDelta)) {
8897
+ return null;
8898
+ }
8899
+ return applyCodexAspTranscriptDelta(null, transcriptDelta);
8718
8900
  }
8719
8901
  function acceptedEventInCodexTranscript(acceptedEvent, transcript) {
8720
8902
  const message = getUserMessage(acceptedEvent);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.252",
3
+ "version": "0.1.254",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",