replicas-engine 0.1.25 → 0.1.27

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 +157 -13
  2. package/package.json +2 -2
package/dist/src/index.js CHANGED
@@ -54,6 +54,35 @@ async function readJSONL(filePath) {
54
54
  return [];
55
55
  }
56
56
  }
57
+ async function readJSONLPaginated(filePath, limit, offset = 0) {
58
+ try {
59
+ const content = await readFile(filePath, "utf-8");
60
+ const lines = content.split("\n").filter((line) => line.trim());
61
+ const allEvents = lines.map((line) => {
62
+ try {
63
+ return JSON.parse(line);
64
+ } catch (e) {
65
+ return null;
66
+ }
67
+ }).filter((event) => event !== null);
68
+ const total = allEvents.length;
69
+ const startIndex = Math.max(0, total - offset - (limit ?? total));
70
+ const endIndex = Math.max(0, total - offset);
71
+ const events = allEvents.slice(startIndex, endIndex);
72
+ const hasMore = startIndex > 0;
73
+ return {
74
+ events,
75
+ total,
76
+ hasMore
77
+ };
78
+ } catch (error) {
79
+ return {
80
+ events: [],
81
+ total: 0,
82
+ hasMore: false
83
+ };
84
+ }
85
+ }
57
86
 
58
87
  // src/services/codex-manager.ts
59
88
  import { readdir, stat, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
@@ -110,6 +139,14 @@ function summarizeInput(input) {
110
139
  if (obj.pattern) return `pattern: ${String(obj.pattern)}`;
111
140
  if (obj.query) return String(obj.query);
112
141
  if (obj.url) return String(obj.url);
142
+ if (obj.todos && Array.isArray(obj.todos)) {
143
+ const todos = obj.todos;
144
+ const completed = todos.filter((t) => t.status === "completed").length;
145
+ const total = todos.length;
146
+ const currentTask = todos.find((t) => t.status === "in_progress");
147
+ const summary = `${completed}/${total}`;
148
+ return currentTask?.content ? `${summary}: ${currentTask.content}` : summary;
149
+ }
113
150
  const keys = Object.keys(obj);
114
151
  if (keys.length > 0) {
115
152
  const firstKey = keys[0];
@@ -298,12 +335,18 @@ function convertCodexEvent(event, linearSessionId) {
298
335
  };
299
336
  }
300
337
  if (item.type === "todo_list") {
338
+ const items = "items" in item && Array.isArray(item.items) ? item.items : [];
339
+ const completed = items.filter((t) => t.completed).length;
340
+ const total = items.length;
341
+ const currentTask = items.find((t) => !t.completed);
342
+ const summary = `${completed}/${total}`;
343
+ const parameter = currentTask?.text ? `${summary}: ${currentTask.text}` : summary;
301
344
  return {
302
345
  linearSessionId,
303
346
  content: {
304
347
  type: "action",
305
348
  action: "Updating plan",
306
- parameter: ""
349
+ parameter
307
350
  }
308
351
  };
309
352
  }
@@ -366,12 +409,18 @@ function convertCodexEvent(event, linearSessionId) {
366
409
  };
367
410
  }
368
411
  if (item.type === "todo_list") {
412
+ const items = "items" in item && Array.isArray(item.items) ? item.items : [];
413
+ const completed = items.filter((t) => t.completed).length;
414
+ const total = items.length;
415
+ const currentTask = items.find((t) => !t.completed);
416
+ const summary = `${completed}/${total}`;
417
+ const parameter = currentTask?.text ? `${summary}: ${currentTask.text}` : summary;
369
418
  return {
370
419
  linearSessionId,
371
420
  content: {
372
421
  type: "action",
373
422
  action: "Updating plan",
374
- parameter: "",
423
+ parameter,
375
424
  result: "Done"
376
425
  }
377
426
  };
@@ -518,6 +567,9 @@ async function getPullRequestUrl(cwd) {
518
567
  return cachedPr.prUrl;
519
568
  }
520
569
  cachedPr = null;
570
+ if (persistedState.prUrl && persistedState.branch !== currentBranch) {
571
+ await saveEngineState({ prUrl: null });
572
+ }
521
573
  try {
522
574
  const remoteRef = execSync(`git ls-remote --heads origin ${currentBranch}`, {
523
575
  cwd,
@@ -1126,11 +1178,10 @@ var CodexManager = class {
1126
1178
  });
1127
1179
  }
1128
1180
  } finally {
1129
- if (linearSessionId) {
1130
- const status = await getGitStatus(this.workingDirectory);
1131
- monolithService.sendEvent({ type: "agent_turn_complete", payload: { linearSessionId, status } }).catch(() => {
1132
- });
1133
- }
1181
+ const status = await getGitStatus(this.workingDirectory);
1182
+ const payload = linearSessionId ? { linearSessionId, status } : { status };
1183
+ monolithService.sendEvent({ type: "agent_turn_complete", payload }).catch(() => {
1184
+ });
1134
1185
  }
1135
1186
  }
1136
1187
  async getHistory() {
@@ -1153,6 +1204,38 @@ var CodexManager = class {
1153
1204
  events
1154
1205
  };
1155
1206
  }
1207
+ /**
1208
+ * Get paginated history from the end (bottom-up pagination).
1209
+ * @param limit - Maximum number of events to return
1210
+ * @param offset - Number of events to skip from the end
1211
+ * @returns Paginated history result
1212
+ */
1213
+ async getHistoryPaginated(limit, offset = 0) {
1214
+ if (!this.currentThreadId) {
1215
+ return {
1216
+ thread_id: null,
1217
+ events: [],
1218
+ total: 0,
1219
+ hasMore: false
1220
+ };
1221
+ }
1222
+ const sessionFile = await this.findSessionFile(this.currentThreadId);
1223
+ if (!sessionFile) {
1224
+ return {
1225
+ thread_id: this.currentThreadId,
1226
+ events: [],
1227
+ total: 0,
1228
+ hasMore: false
1229
+ };
1230
+ }
1231
+ const result = await readJSONLPaginated(sessionFile, limit, offset);
1232
+ return {
1233
+ thread_id: this.currentThreadId,
1234
+ events: result.events,
1235
+ total: result.total,
1236
+ hasMore: result.hasMore
1237
+ };
1238
+ }
1156
1239
  async getStatus() {
1157
1240
  let sessionFile = null;
1158
1241
  if (this.currentThreadId) {
@@ -1273,6 +1356,29 @@ codex.post("/send", async (c) => {
1273
1356
  });
1274
1357
  codex.get("/history", async (c) => {
1275
1358
  try {
1359
+ const limitStr = c.req.query("limit");
1360
+ const offsetStr = c.req.query("offset");
1361
+ if (limitStr !== void 0 || offsetStr !== void 0) {
1362
+ const limit = limitStr ? parseInt(limitStr, 10) : void 0;
1363
+ const offset = offsetStr ? parseInt(offsetStr, 10) : 0;
1364
+ if (limitStr && (isNaN(limit) || limit < 1)) {
1365
+ return c.json({ error: "limit must be a positive integer" }, 400);
1366
+ }
1367
+ if (isNaN(offset) || offset < 0) {
1368
+ return c.json({ error: "offset must be a non-negative integer" }, 400);
1369
+ }
1370
+ const history2 = await codexManager.getHistoryPaginated(limit, offset);
1371
+ if (!history2.thread_id) {
1372
+ return c.json({
1373
+ message: "No active thread",
1374
+ thread_id: null,
1375
+ events: [],
1376
+ total: 0,
1377
+ hasMore: false
1378
+ });
1379
+ }
1380
+ return c.json(history2);
1381
+ }
1276
1382
  const history = await codexManager.getHistory();
1277
1383
  if (!history.thread_id) {
1278
1384
  return c.json({
@@ -1544,11 +1650,10 @@ var ClaudeManager = class {
1544
1650
  });
1545
1651
  }
1546
1652
  } finally {
1547
- if (linearSessionId) {
1548
- const status = await getGitStatus(this.workingDirectory);
1549
- monolithService.sendEvent({ type: "agent_turn_complete", payload: { linearSessionId, status } }).catch(() => {
1550
- });
1551
- }
1653
+ const status = await getGitStatus(this.workingDirectory);
1654
+ const payload = linearSessionId ? { linearSessionId, status } : { status };
1655
+ monolithService.sendEvent({ type: "agent_turn_complete", payload }).catch(() => {
1656
+ });
1552
1657
  }
1553
1658
  }
1554
1659
  async getHistory() {
@@ -1559,6 +1664,22 @@ var ClaudeManager = class {
1559
1664
  events
1560
1665
  };
1561
1666
  }
1667
+ /**
1668
+ * Get paginated history from the end (bottom-up pagination).
1669
+ * @param limit - Maximum number of events to return
1670
+ * @param offset - Number of events to skip from the end
1671
+ * @returns Paginated history result
1672
+ */
1673
+ async getHistoryPaginated(limit, offset = 0) {
1674
+ await this.initialized;
1675
+ const result = await readJSONLPaginated(this.historyFile, limit, offset);
1676
+ return {
1677
+ thread_id: this.sessionId,
1678
+ events: result.events,
1679
+ total: result.total,
1680
+ hasMore: result.hasMore
1681
+ };
1682
+ }
1562
1683
  async getStatus() {
1563
1684
  await this.initialized;
1564
1685
  const status = {
@@ -1645,6 +1766,29 @@ claude.post("/send", async (c) => {
1645
1766
  });
1646
1767
  claude.get("/history", async (c) => {
1647
1768
  try {
1769
+ const limitStr = c.req.query("limit");
1770
+ const offsetStr = c.req.query("offset");
1771
+ if (limitStr !== void 0 || offsetStr !== void 0) {
1772
+ const limit = limitStr ? parseInt(limitStr, 10) : void 0;
1773
+ const offset = offsetStr ? parseInt(offsetStr, 10) : 0;
1774
+ if (limitStr && (isNaN(limit) || limit < 1)) {
1775
+ return c.json({ error: "limit must be a positive integer" }, 400);
1776
+ }
1777
+ if (isNaN(offset) || offset < 0) {
1778
+ return c.json({ error: "offset must be a non-negative integer" }, 400);
1779
+ }
1780
+ const history2 = await claudeManager.getHistoryPaginated(limit, offset);
1781
+ if (!history2.thread_id) {
1782
+ return c.json({
1783
+ message: "No active session",
1784
+ thread_id: null,
1785
+ events: [],
1786
+ total: 0,
1787
+ hasMore: false
1788
+ });
1789
+ }
1790
+ return c.json(history2);
1791
+ }
1648
1792
  const history = await claudeManager.getHistory();
1649
1793
  if (!history.thread_id) {
1650
1794
  return c.json({
@@ -2086,7 +2230,7 @@ async function initializeGitRepository() {
2086
2230
  console.log(`[GitInit] Creating workspace branch: ${branchName}`);
2087
2231
  runGitCommand(`git checkout -b ${branchName}`, repoPath);
2088
2232
  initializedBranch = branchName;
2089
- await saveEngineState({ branch: branchName });
2233
+ await saveEngineState({ branch: branchName, prUrl: null });
2090
2234
  console.log(`[GitInit] Successfully initialized on branch: ${branchName}`);
2091
2235
  return {
2092
2236
  success: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.25",
3
+ "version": "0.1.27",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",
@@ -31,7 +31,7 @@
31
31
  "@openai/codex-sdk": "^0.50.0",
32
32
  "dotenv": "^17.2.3",
33
33
  "hono": "^4.10.3",
34
- "zod": "3.24.1"
34
+ "zod": "^3.25.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@types/node": "^20.11.17",