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.
- package/dist/src/index.js +192 -7
- 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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
34
|
+
"zod": "^3.25.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/node": "^20.11.17",
|