replicas-engine 0.1.24 → 0.1.26

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 +192 -7
  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";
@@ -91,6 +120,15 @@ var MonolithService = class {
91
120
  var monolithService = new MonolithService();
92
121
 
93
122
  // src/services/linear-event-converter.ts
123
+ function linearThoughtToResponse(thought) {
124
+ return {
125
+ linearSessionId: thought.linearSessionId,
126
+ content: {
127
+ type: "response",
128
+ body: thought.content.body
129
+ }
130
+ };
131
+ }
94
132
  function summarizeInput(input) {
95
133
  if (!input) return "";
96
134
  if (typeof input === "string") return input;
@@ -101,6 +139,14 @@ function summarizeInput(input) {
101
139
  if (obj.pattern) return `pattern: ${String(obj.pattern)}`;
102
140
  if (obj.query) return String(obj.query);
103
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
+ }
104
150
  const keys = Object.keys(obj);
105
151
  if (keys.length > 0) {
106
152
  const firstKey = keys[0];
@@ -289,12 +335,18 @@ function convertCodexEvent(event, linearSessionId) {
289
335
  };
290
336
  }
291
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;
292
344
  return {
293
345
  linearSessionId,
294
346
  content: {
295
347
  type: "action",
296
348
  action: "Updating plan",
297
- parameter: ""
349
+ parameter
298
350
  }
299
351
  };
300
352
  }
@@ -357,12 +409,18 @@ function convertCodexEvent(event, linearSessionId) {
357
409
  };
358
410
  }
359
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;
360
418
  return {
361
419
  linearSessionId,
362
420
  content: {
363
421
  type: "action",
364
422
  action: "Updating plan",
365
- parameter: "",
423
+ parameter,
366
424
  result: "Done"
367
425
  }
368
426
  };
@@ -509,6 +567,9 @@ async function getPullRequestUrl(cwd) {
509
567
  return cachedPr.prUrl;
510
568
  }
511
569
  cachedPr = null;
570
+ if (persistedState.prUrl && persistedState.branch !== currentBranch) {
571
+ await saveEngineState({ prUrl: null });
572
+ }
512
573
  try {
513
574
  const remoteRef = execSync(`git ls-remote --heads origin ${currentBranch}`, {
514
575
  cwd,
@@ -1092,15 +1153,30 @@ var CodexManager = class {
1092
1153
  input = message;
1093
1154
  }
1094
1155
  const { events } = await this.currentThread.runStreamed(input);
1156
+ let latestThoughtEvent = null;
1095
1157
  for await (const event of events) {
1096
1158
  if (linearSessionId) {
1097
1159
  const linearEvent = convertCodexEvent(event, linearSessionId);
1098
1160
  if (linearEvent) {
1099
- monolithService.sendEvent({ type: "agent_update", payload: linearEvent }).catch(() => {
1100
- });
1161
+ if (latestThoughtEvent) {
1162
+ monolithService.sendEvent({ type: "agent_update", payload: latestThoughtEvent }).catch(() => {
1163
+ });
1164
+ }
1165
+ if (linearEvent.content.type === "thought") {
1166
+ latestThoughtEvent = linearEvent;
1167
+ } else {
1168
+ latestThoughtEvent = null;
1169
+ monolithService.sendEvent({ type: "agent_update", payload: linearEvent }).catch(() => {
1170
+ });
1171
+ }
1101
1172
  }
1102
1173
  }
1103
1174
  }
1175
+ if (linearSessionId && latestThoughtEvent) {
1176
+ const responseEvent = linearThoughtToResponse(latestThoughtEvent);
1177
+ monolithService.sendEvent({ type: "agent_update", payload: responseEvent }).catch(() => {
1178
+ });
1179
+ }
1104
1180
  } finally {
1105
1181
  if (linearSessionId) {
1106
1182
  const status = await getGitStatus(this.workingDirectory);
@@ -1129,6 +1205,38 @@ var CodexManager = class {
1129
1205
  events
1130
1206
  };
1131
1207
  }
1208
+ /**
1209
+ * Get paginated history from the end (bottom-up pagination).
1210
+ * @param limit - Maximum number of events to return
1211
+ * @param offset - Number of events to skip from the end
1212
+ * @returns Paginated history result
1213
+ */
1214
+ async getHistoryPaginated(limit, offset = 0) {
1215
+ if (!this.currentThreadId) {
1216
+ return {
1217
+ thread_id: null,
1218
+ events: [],
1219
+ total: 0,
1220
+ hasMore: false
1221
+ };
1222
+ }
1223
+ const sessionFile = await this.findSessionFile(this.currentThreadId);
1224
+ if (!sessionFile) {
1225
+ return {
1226
+ thread_id: this.currentThreadId,
1227
+ events: [],
1228
+ total: 0,
1229
+ hasMore: false
1230
+ };
1231
+ }
1232
+ const result = await readJSONLPaginated(sessionFile, limit, offset);
1233
+ return {
1234
+ thread_id: this.currentThreadId,
1235
+ events: result.events,
1236
+ total: result.total,
1237
+ hasMore: result.hasMore
1238
+ };
1239
+ }
1132
1240
  async getStatus() {
1133
1241
  let sessionFile = null;
1134
1242
  if (this.currentThreadId) {
@@ -1249,6 +1357,29 @@ codex.post("/send", async (c) => {
1249
1357
  });
1250
1358
  codex.get("/history", async (c) => {
1251
1359
  try {
1360
+ const limitStr = c.req.query("limit");
1361
+ const offsetStr = c.req.query("offset");
1362
+ if (limitStr !== void 0 || offsetStr !== void 0) {
1363
+ const limit = limitStr ? parseInt(limitStr, 10) : void 0;
1364
+ const offset = offsetStr ? parseInt(offsetStr, 10) : 0;
1365
+ if (limitStr && (isNaN(limit) || limit < 1)) {
1366
+ return c.json({ error: "limit must be a positive integer" }, 400);
1367
+ }
1368
+ if (isNaN(offset) || offset < 0) {
1369
+ return c.json({ error: "offset must be a non-negative integer" }, 400);
1370
+ }
1371
+ const history2 = await codexManager.getHistoryPaginated(limit, offset);
1372
+ if (!history2.thread_id) {
1373
+ return c.json({
1374
+ message: "No active thread",
1375
+ thread_id: null,
1376
+ events: [],
1377
+ total: 0,
1378
+ hasMore: false
1379
+ });
1380
+ }
1381
+ return c.json(history2);
1382
+ }
1252
1383
  const history = await codexManager.getHistory();
1253
1384
  if (!history.thread_id) {
1254
1385
  return c.json({
@@ -1494,16 +1625,31 @@ var ClaudeManager = class {
1494
1625
  model: model || "opus"
1495
1626
  }
1496
1627
  });
1628
+ let latestThoughtEvent = null;
1497
1629
  for await (const msg of response) {
1498
1630
  await this.handleMessage(msg);
1499
1631
  if (linearSessionId) {
1500
1632
  const linearEvent = convertClaudeEvent(msg, linearSessionId);
1501
1633
  if (linearEvent) {
1502
- monolithService.sendEvent({ type: "agent_update", payload: linearEvent }).catch(() => {
1503
- });
1634
+ if (latestThoughtEvent) {
1635
+ monolithService.sendEvent({ type: "agent_update", payload: latestThoughtEvent }).catch(() => {
1636
+ });
1637
+ }
1638
+ if (linearEvent.content.type === "thought") {
1639
+ latestThoughtEvent = linearEvent;
1640
+ } else {
1641
+ latestThoughtEvent = null;
1642
+ monolithService.sendEvent({ type: "agent_update", payload: linearEvent }).catch(() => {
1643
+ });
1644
+ }
1504
1645
  }
1505
1646
  }
1506
1647
  }
1648
+ if (linearSessionId && latestThoughtEvent) {
1649
+ const responseEvent = linearThoughtToResponse(latestThoughtEvent);
1650
+ monolithService.sendEvent({ type: "agent_update", payload: responseEvent }).catch(() => {
1651
+ });
1652
+ }
1507
1653
  } finally {
1508
1654
  if (linearSessionId) {
1509
1655
  const status = await getGitStatus(this.workingDirectory);
@@ -1520,6 +1666,22 @@ var ClaudeManager = class {
1520
1666
  events
1521
1667
  };
1522
1668
  }
1669
+ /**
1670
+ * Get paginated history from the end (bottom-up pagination).
1671
+ * @param limit - Maximum number of events to return
1672
+ * @param offset - Number of events to skip from the end
1673
+ * @returns Paginated history result
1674
+ */
1675
+ async getHistoryPaginated(limit, offset = 0) {
1676
+ await this.initialized;
1677
+ const result = await readJSONLPaginated(this.historyFile, limit, offset);
1678
+ return {
1679
+ thread_id: this.sessionId,
1680
+ events: result.events,
1681
+ total: result.total,
1682
+ hasMore: result.hasMore
1683
+ };
1684
+ }
1523
1685
  async getStatus() {
1524
1686
  await this.initialized;
1525
1687
  const status = {
@@ -1606,6 +1768,29 @@ claude.post("/send", async (c) => {
1606
1768
  });
1607
1769
  claude.get("/history", async (c) => {
1608
1770
  try {
1771
+ const limitStr = c.req.query("limit");
1772
+ const offsetStr = c.req.query("offset");
1773
+ if (limitStr !== void 0 || offsetStr !== void 0) {
1774
+ const limit = limitStr ? parseInt(limitStr, 10) : void 0;
1775
+ const offset = offsetStr ? parseInt(offsetStr, 10) : 0;
1776
+ if (limitStr && (isNaN(limit) || limit < 1)) {
1777
+ return c.json({ error: "limit must be a positive integer" }, 400);
1778
+ }
1779
+ if (isNaN(offset) || offset < 0) {
1780
+ return c.json({ error: "offset must be a non-negative integer" }, 400);
1781
+ }
1782
+ const history2 = await claudeManager.getHistoryPaginated(limit, offset);
1783
+ if (!history2.thread_id) {
1784
+ return c.json({
1785
+ message: "No active session",
1786
+ thread_id: null,
1787
+ events: [],
1788
+ total: 0,
1789
+ hasMore: false
1790
+ });
1791
+ }
1792
+ return c.json(history2);
1793
+ }
1609
1794
  const history = await claudeManager.getHistory();
1610
1795
  if (!history.thread_id) {
1611
1796
  return c.json({
@@ -2047,7 +2232,7 @@ async function initializeGitRepository() {
2047
2232
  console.log(`[GitInit] Creating workspace branch: ${branchName}`);
2048
2233
  runGitCommand(`git checkout -b ${branchName}`, repoPath);
2049
2234
  initializedBranch = branchName;
2050
- await saveEngineState({ branch: branchName });
2235
+ await saveEngineState({ branch: branchName, prUrl: null });
2051
2236
  console.log(`[GitInit] Successfully initialized on branch: ${branchName}`);
2052
2237
  return {
2053
2238
  success: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.24",
3
+ "version": "0.1.26",
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",