replicas-engine 0.1.35 → 0.1.36

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 +121 -2
  2. package/package.json +1 -1
package/dist/src/index.js CHANGED
@@ -716,6 +716,21 @@ var MessageQueue = class {
716
716
  clearQueue() {
717
717
  this.queue = [];
718
718
  }
719
+ drainQueue(options) {
720
+ const maxItems = options?.maxItems ?? this.queue.length;
721
+ const maxChars = options?.maxChars ?? Number.POSITIVE_INFINITY;
722
+ const drained = [];
723
+ let totalChars = 0;
724
+ for (const message of this.queue) {
725
+ if (drained.length >= maxItems) break;
726
+ const text = message.message;
727
+ if (totalChars + text.length > maxChars) break;
728
+ drained.push(text);
729
+ totalChars += text.length;
730
+ }
731
+ this.queue = [];
732
+ return drained;
733
+ }
719
734
  /**
720
735
  * Reset everything including clearing processing state
721
736
  */
@@ -1016,6 +1031,8 @@ var replicasConfigService = new ReplicasConfigService();
1016
1031
  import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
1017
1032
  var DEFAULT_MODEL = "gpt-5.2-codex";
1018
1033
  var CODEX_CONFIG_PATH = join3(homedir3(), ".codex", "config.toml");
1034
+ var MAX_INTERRUPT_QUEUE_ITEMS = 1e3;
1035
+ var MAX_INTERRUPT_QUEUE_CHARS = 2e5;
1019
1036
  var CodexManager = class {
1020
1037
  codex;
1021
1038
  currentThreadId = null;
@@ -1025,6 +1042,7 @@ var CodexManager = class {
1025
1042
  baseSystemPrompt;
1026
1043
  tempImageDir;
1027
1044
  initialized;
1045
+ activeAbortController = null;
1028
1046
  constructor(workingDirectory) {
1029
1047
  this.codex = new Codex();
1030
1048
  if (workingDirectory) {
@@ -1067,6 +1085,33 @@ var CodexManager = class {
1067
1085
  getQueueStatus() {
1068
1086
  return this.messageQueue.getStatus();
1069
1087
  }
1088
+ async interrupt() {
1089
+ const queue = this.messageQueue.drainQueue({
1090
+ maxItems: MAX_INTERRUPT_QUEUE_ITEMS,
1091
+ maxChars: MAX_INTERRUPT_QUEUE_CHARS
1092
+ });
1093
+ const isProcessing = this.messageQueue.isProcessing();
1094
+ if (isProcessing && this.activeAbortController) {
1095
+ try {
1096
+ this.activeAbortController.abort();
1097
+ } catch {
1098
+ }
1099
+ }
1100
+ const linearSessionId = process.env.LINEAR_SESSION_ID;
1101
+ if (linearSessionId && queue.length > 0) {
1102
+ const body = ["Interrupted. Cleared queue:", ...queue].join("\n");
1103
+ const thoughtEvent = {
1104
+ linearSessionId,
1105
+ content: {
1106
+ type: "thought",
1107
+ body
1108
+ }
1109
+ };
1110
+ monolithService.sendEvent({ type: "agent_update", payload: thoughtEvent }).catch(() => {
1111
+ });
1112
+ }
1113
+ return { interrupted: isProcessing || queue.length > 0, queue };
1114
+ }
1070
1115
  /**
1071
1116
  * Set the base system prompt from replicas.json
1072
1117
  * This will be combined with any custom instructions passed to individual messages
@@ -1172,11 +1217,13 @@ var CodexManager = class {
1172
1217
  model: model || DEFAULT_MODEL,
1173
1218
  webSearchMode: "live"
1174
1219
  };
1220
+ const abortController = new AbortController();
1221
+ this.activeAbortController = abortController;
1175
1222
  if (this.currentThreadId) {
1176
1223
  this.currentThread = this.codex.resumeThread(this.currentThreadId, threadOptions);
1177
1224
  } else {
1178
1225
  this.currentThread = this.codex.startThread(threadOptions);
1179
- const { events: events2 } = await this.currentThread.runStreamed("Hello");
1226
+ const { events: events2 } = await this.currentThread.runStreamed("Hello", { signal: abortController.signal });
1180
1227
  for await (const event of events2) {
1181
1228
  if (event.type === "thread.started") {
1182
1229
  this.currentThreadId = event.thread_id;
@@ -1200,7 +1247,7 @@ var CodexManager = class {
1200
1247
  } else {
1201
1248
  input = message;
1202
1249
  }
1203
- const { events } = await this.currentThread.runStreamed(input);
1250
+ const { events } = await this.currentThread.runStreamed(input, { signal: abortController.signal });
1204
1251
  let latestThoughtEvent = null;
1205
1252
  for await (const event of events) {
1206
1253
  if (linearSessionId) {
@@ -1226,6 +1273,7 @@ var CodexManager = class {
1226
1273
  });
1227
1274
  }
1228
1275
  } finally {
1276
+ this.activeAbortController = null;
1229
1277
  const status = await getGitStatus(this.workingDirectory);
1230
1278
  const payload = linearSessionId ? { linearSessionId, status } : { status };
1231
1279
  monolithService.sendEvent({ type: "agent_turn_complete", payload }).catch(() => {
@@ -1514,6 +1562,22 @@ codex.post("/reset", async (c) => {
1514
1562
  );
1515
1563
  }
1516
1564
  });
1565
+ codex.post("/interrupt", async (c) => {
1566
+ try {
1567
+ const result = await codexManager.interrupt();
1568
+ const response = result;
1569
+ return c.json(response);
1570
+ } catch (error) {
1571
+ console.error("Error in /codex/interrupt:", error);
1572
+ return c.json(
1573
+ {
1574
+ error: "Failed to interrupt thread",
1575
+ details: error instanceof Error ? error.message : "Unknown error"
1576
+ },
1577
+ 500
1578
+ );
1579
+ }
1580
+ });
1517
1581
  var codex_default = codex;
1518
1582
 
1519
1583
  // src/routes/claude.ts
@@ -1526,6 +1590,8 @@ import {
1526
1590
  import { join as join4 } from "path";
1527
1591
  import { mkdir as mkdir4, appendFile as appendFile2, rm } from "fs/promises";
1528
1592
  import { homedir as homedir4 } from "os";
1593
+ var MAX_INTERRUPT_QUEUE_ITEMS2 = 1e3;
1594
+ var MAX_INTERRUPT_QUEUE_CHARS2 = 2e5;
1529
1595
  var ClaudeManager = class {
1530
1596
  workingDirectory;
1531
1597
  historyFile;
@@ -1533,6 +1599,7 @@ var ClaudeManager = class {
1533
1599
  initialized;
1534
1600
  messageQueue;
1535
1601
  baseSystemPrompt;
1602
+ activeQuery = null;
1536
1603
  constructor(workingDirectory) {
1537
1604
  if (workingDirectory) {
1538
1605
  this.workingDirectory = workingDirectory;
@@ -1567,6 +1634,33 @@ var ClaudeManager = class {
1567
1634
  getQueueStatus() {
1568
1635
  return this.messageQueue.getStatus();
1569
1636
  }
1637
+ async interrupt() {
1638
+ const queue = this.messageQueue.drainQueue({
1639
+ maxItems: MAX_INTERRUPT_QUEUE_ITEMS2,
1640
+ maxChars: MAX_INTERRUPT_QUEUE_CHARS2
1641
+ });
1642
+ const isProcessing = this.messageQueue.isProcessing();
1643
+ if (isProcessing && this.activeQuery) {
1644
+ try {
1645
+ await this.activeQuery.interrupt();
1646
+ } catch {
1647
+ }
1648
+ }
1649
+ const linearSessionId = process.env.LINEAR_SESSION_ID;
1650
+ if (linearSessionId && queue.length > 0) {
1651
+ const body = ["Interrupted. Cleared queue:", ...queue].join("\n");
1652
+ const thoughtEvent = {
1653
+ linearSessionId,
1654
+ content: {
1655
+ type: "thought",
1656
+ body
1657
+ }
1658
+ };
1659
+ monolithService.sendEvent({ type: "agent_update", payload: thoughtEvent }).catch(() => {
1660
+ });
1661
+ }
1662
+ return { interrupted: isProcessing || queue.length > 0, queue };
1663
+ }
1570
1664
  /**
1571
1665
  * Set the base system prompt from replicas.json
1572
1666
  * This will be combined with any custom instructions passed to individual messages
@@ -1674,6 +1768,7 @@ var ClaudeManager = class {
1674
1768
  model: model || "opus"
1675
1769
  }
1676
1770
  });
1771
+ this.activeQuery = response;
1677
1772
  let latestThoughtEvent = null;
1678
1773
  for await (const msg of response) {
1679
1774
  await this.handleMessage(msg);
@@ -1700,6 +1795,7 @@ var ClaudeManager = class {
1700
1795
  });
1701
1796
  }
1702
1797
  } finally {
1798
+ this.activeQuery = null;
1703
1799
  const status = await getGitStatus(this.workingDirectory);
1704
1800
  const payload = linearSessionId ? { linearSessionId, status } : { status };
1705
1801
  monolithService.sendEvent({ type: "agent_turn_complete", payload }).catch(() => {
@@ -1926,6 +2022,22 @@ claude.post("/reset", async (c) => {
1926
2022
  );
1927
2023
  }
1928
2024
  });
2025
+ claude.post("/interrupt", async (c) => {
2026
+ try {
2027
+ const result = await claudeManager.interrupt();
2028
+ const response = result;
2029
+ return c.json(response);
2030
+ } catch (error) {
2031
+ console.error("Error in /claude/interrupt:", error);
2032
+ return c.json(
2033
+ {
2034
+ error: "Failed to interrupt session",
2035
+ details: error instanceof Error ? error.message : "Unknown error"
2036
+ },
2037
+ 500
2038
+ );
2039
+ }
2040
+ });
1929
2041
  var claude_default = claude;
1930
2042
 
1931
2043
  // src/routes/plans.ts
@@ -2111,6 +2223,10 @@ var ClaudeTokenManager = class {
2111
2223
  REFRESH_INTERVAL_MS = 45 * 60 * 1e3;
2112
2224
  // 45 minutes
2113
2225
  async start() {
2226
+ if (process.env.ANTHROPIC_API_KEY) {
2227
+ console.log("[ClaudeTokenManager] Skipping: ANTHROPIC_API_KEY is set");
2228
+ return;
2229
+ }
2114
2230
  const monolithUrl = process.env.MONOLITH_URL;
2115
2231
  const workspaceId = process.env.WORKSPACE_ID;
2116
2232
  const engineSecret = process.env.REPLICAS_ENGINE_SECRET;
@@ -2135,6 +2251,9 @@ var ClaudeTokenManager = class {
2135
2251
  }
2136
2252
  }
2137
2253
  async refreshCredentials() {
2254
+ if (process.env.ANTHROPIC_API_KEY) {
2255
+ return;
2256
+ }
2138
2257
  const monolithUrl = process.env.MONOLITH_URL;
2139
2258
  const workspaceId = process.env.WORKSPACE_ID;
2140
2259
  const engineSecret = process.env.REPLICAS_ENGINE_SECRET;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.35",
3
+ "version": "0.1.36",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",