replicas-engine 0.1.328 → 0.1.330

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 +249 -69
  2. package/package.json +2 -1
package/dist/src/index.js CHANGED
@@ -287,7 +287,7 @@ var WORKSPACE_SIZES = ["small", "large"];
287
287
  var INVALID_WORKSPACE_SIZE_ERROR = `Invalid size: must be one of ${WORKSPACE_SIZES.join(", ")}`;
288
288
 
289
289
  // ../shared/src/e2b.ts
290
- var E2B_TEMPLATE_NAME = "replicas-sandbox-2026-06-19-v2";
290
+ var E2B_TEMPLATE_NAME = "replicas-sandbox-2026-06-20-v2";
291
291
 
292
292
  // ../shared/src/runtime-env.ts
293
293
  function parsePosixEnvFile(content) {
@@ -2123,11 +2123,13 @@ var DESKTOP_NOVNC_PORT = 6080;
2123
2123
  var DEFAULT_CHAT_TITLES = {
2124
2124
  claude: "Claude Code",
2125
2125
  codex: "Codex",
2126
+ cursor: "Cursor",
2126
2127
  relay: "Relay"
2127
2128
  };
2128
2129
  var CLAUDE_OPUS_1M_MODEL = "opus[1m]";
2129
2130
  var LEGACY_CLAUDE_OPUS_1M_MODEL = "opus-1m";
2130
2131
  var DEFAULT_CODEX_MODEL = "gpt-5.5";
2132
+ var DEFAULT_CURSOR_MODEL = "composer-2";
2131
2133
  function normalizeClaudeModel(model) {
2132
2134
  if (model === LEGACY_CLAUDE_OPUS_1M_MODEL) {
2133
2135
  return CLAUDE_OPUS_1M_MODEL;
@@ -2137,6 +2139,7 @@ function normalizeClaudeModel(model) {
2137
2139
  var AGENT_MODELS = {
2138
2140
  claude: [CLAUDE_OPUS_1M_MODEL, "sonnet", "haiku"],
2139
2141
  codex: [DEFAULT_CODEX_MODEL, "gpt-5.4", "gpt-5.4-mini", "gpt-5.3-codex", "gpt-5.2"],
2142
+ cursor: [DEFAULT_CURSOR_MODEL, "composer-2.5"],
2140
2143
  relay: [CLAUDE_OPUS_1M_MODEL, "sonnet"]
2141
2144
  };
2142
2145
  var MODEL_LABELS = {
@@ -2146,6 +2149,8 @@ var MODEL_LABELS = {
2146
2149
  [LEGACY_CLAUDE_OPUS_1M_MODEL]: "Opus 4.8 (1M)",
2147
2150
  haiku: "Haiku 4.5",
2148
2151
  [DEFAULT_CODEX_MODEL]: "GPT-5.5",
2152
+ [DEFAULT_CURSOR_MODEL]: "Composer 2",
2153
+ "composer-2.5": "Composer 2.5",
2149
2154
  "gpt-5.4": "GPT-5.4",
2150
2155
  "gpt-5.4-mini": "GPT-5.4 Mini",
2151
2156
  "gpt-5.3-codex": "GPT-5.3 Codex",
@@ -2825,6 +2830,7 @@ function loadEngineEnv() {
2825
2830
  SLACK_THREAD_TS: readEnv("SLACK_THREAD_TS"),
2826
2831
  ANTHROPIC_API_KEY: readEnv("ANTHROPIC_API_KEY"),
2827
2832
  OPENAI_API_KEY: readEnv("OPENAI_API_KEY"),
2833
+ CURSOR_API_KEY: readEnv("CURSOR_API_KEY"),
2828
2834
  CLAUDE_CODE_USE_BEDROCK: readEnv("CLAUDE_CODE_USE_BEDROCK"),
2829
2835
  AWS_ACCESS_KEY_ID: readEnv("AWS_ACCESS_KEY_ID"),
2830
2836
  AWS_SECRET_ACCESS_KEY: readEnv("AWS_SECRET_ACCESS_KEY"),
@@ -4198,6 +4204,9 @@ function detectCodexAuthMethod() {
4198
4204
  }
4199
4205
  return "none";
4200
4206
  }
4207
+ function detectCursorAuthMethod() {
4208
+ return ENGINE_ENV.CURSOR_API_KEY ? "api_key" : "none";
4209
+ }
4201
4210
  async function detectGitIdentityConfigured() {
4202
4211
  try {
4203
4212
  const { stdout } = await execFileAsync("git", ["config", "--global", "user.name"]);
@@ -4237,6 +4246,7 @@ function createDefaultDetails() {
4237
4246
  googleAccessConfigured: false,
4238
4247
  claudeAuthMethod: "none",
4239
4248
  codexAuthMethod: "none",
4249
+ cursorAuthMethod: "none",
4240
4250
  lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
4241
4251
  };
4242
4252
  }
@@ -4267,6 +4277,7 @@ var EnvironmentDetailsService = class {
4267
4277
  details.engineVersion = E2B_TEMPLATE_NAME;
4268
4278
  details.claudeAuthMethod = detectClaudeAuthMethod();
4269
4279
  details.codexAuthMethod = detectCodexAuthMethod();
4280
+ details.cursorAuthMethod = detectCursorAuthMethod();
4270
4281
  details.gitIdentityConfigured = gitIdentityConfigured;
4271
4282
  const ghConfigured = existsSync3(GH_HOSTS_PATH);
4272
4283
  details.githubAccessConfigured = ghConfigured;
@@ -5015,9 +5026,9 @@ async function registerDesktopPreview() {
5015
5026
 
5016
5027
  // src/services/chat/chat-service.ts
5017
5028
  import { existsSync as existsSync7 } from "fs";
5018
- import { appendFile as appendFile4, copyFile, mkdir as mkdir11, readFile as readFile13, rename as rename2, rm } from "fs/promises";
5029
+ import { appendFile as appendFile5, copyFile, mkdir as mkdir12, readFile as readFile13, rename as rename2, rm } from "fs/promises";
5019
5030
  import { homedir as homedir14 } from "os";
5020
- import { join as join17 } from "path";
5031
+ import { join as join18 } from "path";
5021
5032
  import { randomUUID as randomUUID5 } from "crypto";
5022
5033
 
5023
5034
  // src/managers/claude-manager.ts
@@ -6290,15 +6301,19 @@ var ClaudeAuthError = class extends Error {
6290
6301
  }
6291
6302
  };
6292
6303
  var ClaudeTransientTurnError = class extends Error {
6293
- constructor(message) {
6304
+ constructor(message, midTurn = false) {
6294
6305
  super(message);
6306
+ this.midTurn = midTurn;
6295
6307
  this.name = "ClaudeTransientTurnError";
6296
6308
  }
6309
+ midTurn;
6297
6310
  };
6298
6311
  var MAX_AUTH_RETRIES = 2;
6299
6312
  var MAX_TRANSIENT_RETRIES = 2;
6313
+ var MAX_MIDTURN_CONTINUE_RETRIES = 2;
6300
6314
  var TRANSIENT_RETRY_DELAYS_MS = [1e3, 2500];
6301
6315
  var CLAUDE_TRANSIENT_HTTP_STATUSES = [408, 500, 502, 503, 504, 529];
6316
+ var CLAUDE_MIDTURN_CONTINUE_PROMPT = "Your previous turn was interrupted by a transient network error before it could finish. Continue from exactly where you left off. Do not repeat any tool calls, commits, messages, or other actions you have already completed \u2014 first check what is already done, then do only the remaining work.";
6302
6317
  var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
6303
6318
  historyFile;
6304
6319
  sessionId = null;
@@ -6468,10 +6483,12 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
6468
6483
  let attempt = 0;
6469
6484
  let authRetries = 0;
6470
6485
  let transientRetries = 0;
6486
+ let midTurnContinues = 0;
6487
+ let currentRequest = request;
6471
6488
  try {
6472
6489
  while (true) {
6473
6490
  try {
6474
- await this.executeQuery(request, { skipUserMessageRecord: attempt > 0 });
6491
+ await this.executeQuery(currentRequest, { skipUserMessageRecord: attempt > 0 });
6475
6492
  return;
6476
6493
  } catch (error) {
6477
6494
  lastError = error;
@@ -6496,6 +6513,23 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
6496
6513
  attempt++;
6497
6514
  continue;
6498
6515
  }
6516
+ if (error instanceof ClaudeTransientTurnError && error.midTurn) {
6517
+ if (midTurnContinues >= MAX_MIDTURN_CONTINUE_RETRIES) {
6518
+ await this.emitMidTurnExhaustedEvent(error);
6519
+ return;
6520
+ }
6521
+ midTurnContinues++;
6522
+ const delayMs = TRANSIENT_RETRY_DELAYS_MS[midTurnContinues - 1] ?? TRANSIENT_RETRY_DELAYS_MS[TRANSIENT_RETRY_DELAYS_MS.length - 1];
6523
+ console.warn(
6524
+ `[ClaudeManager] Mid-turn transient failure detected (attempt ${midTurnContinues}/${MAX_MIDTURN_CONTINUE_RETRIES}), resuming session and continuing in ${delayMs}ms...`,
6525
+ error
6526
+ );
6527
+ await this.tearDownSession();
6528
+ await new Promise((resolve4) => setTimeout(resolve4, delayMs));
6529
+ currentRequest = { ...request, message: CLAUDE_MIDTURN_CONTINUE_PROMPT, images: [] };
6530
+ attempt++;
6531
+ continue;
6532
+ }
6499
6533
  if (_ClaudeManager.isTransientTurnError(error) && transientRetries < MAX_TRANSIENT_RETRIES) {
6500
6534
  transientRetries++;
6501
6535
  const delayMs = TRANSIENT_RETRY_DELAYS_MS[transientRetries - 1] ?? TRANSIENT_RETRY_DELAYS_MS[TRANSIENT_RETRY_DELAYS_MS.length - 1];
@@ -6523,8 +6557,9 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
6523
6557
  }
6524
6558
  }
6525
6559
  }
6526
- async emitAuthRetryExhaustedEvent(error) {
6527
- const detail = error instanceof Error ? error.message : String(error);
6560
+ // `errors` is the field the dashboard renders (see claude-parser); `result` is
6561
+ // only retained on the raw event for debugging. The user-facing message goes in `errors`.
6562
+ async emitTerminalErrorResult(result, errors, logLabel) {
6528
6563
  const event = {
6529
6564
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
6530
6565
  type: "claude-result",
@@ -6532,8 +6567,8 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
6532
6567
  type: "result",
6533
6568
  subtype: "error_during_execution",
6534
6569
  is_error: true,
6535
- result: "Couldn't authenticate with Claude after multiple attempts. Check your credentials in Settings \u2192 Agents and try again.",
6536
- errors: [detail],
6570
+ result,
6571
+ errors,
6537
6572
  session_id: this.sessionId ?? "",
6538
6573
  parent_tool_use_id: null
6539
6574
  }
@@ -6541,10 +6576,26 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
6541
6576
  try {
6542
6577
  await appendFile2(this.historyFile, JSON.stringify(event) + "\n", "utf-8");
6543
6578
  } catch (writeError) {
6544
- console.error("[ClaudeManager] Failed to record auth-retry-exhausted event:", writeError);
6579
+ console.error(`[ClaudeManager] Failed to record ${logLabel} event:`, writeError);
6545
6580
  }
6546
6581
  this.onEvent(event);
6547
6582
  }
6583
+ async emitAuthRetryExhaustedEvent(error) {
6584
+ const detail = error instanceof Error ? error.message : String(error);
6585
+ await this.emitTerminalErrorResult(
6586
+ detail,
6587
+ ["Couldn't authenticate with Claude after multiple attempts. Check your credentials in Settings \u2192 Agents and try again."],
6588
+ "auth-retry-exhausted"
6589
+ );
6590
+ }
6591
+ async emitMidTurnExhaustedEvent(error) {
6592
+ const detail = error instanceof Error ? error.message : String(error);
6593
+ await this.emitTerminalErrorResult(
6594
+ detail,
6595
+ ["The connection to Claude dropped mid-response and could not be resumed after multiple attempts."],
6596
+ "mid-turn-exhausted"
6597
+ );
6598
+ }
6548
6599
  async executeQuery(request, options = {}) {
6549
6600
  const {
6550
6601
  message,
@@ -6772,12 +6823,10 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
6772
6823
  }
6773
6824
  return;
6774
6825
  }
6775
- const transientErrorMessage = _ClaudeManager.detectTransientTurnErrorInMessage(
6776
- msg,
6777
- this.pendingTurn?.sawActivity ?? false
6778
- );
6826
+ const transientErrorMessage = _ClaudeManager.detectTransientTurnErrorInMessage(msg);
6779
6827
  if (transientErrorMessage) {
6780
- this.failPendingTurn(new ClaudeTransientTurnError(transientErrorMessage));
6828
+ const sawActivity = this.pendingTurn?.sawActivity ?? false;
6829
+ this.failPendingTurn(new ClaudeTransientTurnError(transientErrorMessage, sawActivity));
6781
6830
  try {
6782
6831
  response.close();
6783
6832
  } catch (err) {
@@ -6809,7 +6858,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
6809
6858
  } finally {
6810
6859
  this.sessionLinearForwarder?.flushThoughtAsResponse();
6811
6860
  const pending = this.pendingTurn;
6812
- const pendingError = loopError ? pending?.sawActivity && _ClaudeManager.isTransientTurnError(loopError) ? new Error("Claude session ended unexpectedly") : loopError : pending && !pending.sawActivity ? new ClaudeTransientTurnError("Claude session ended unexpectedly before producing a response") : new Error("Claude session ended unexpectedly");
6861
+ const pendingError = loopError ? pending?.sawActivity && _ClaudeManager.isTransientTurnError(loopError) ? new ClaudeTransientTurnError("Claude session ended unexpectedly mid-response", true) : loopError : pending && !pending.sawActivity ? new ClaudeTransientTurnError("Claude session ended unexpectedly before producing a response") : new Error("Claude session ended unexpectedly");
6813
6862
  this.failPendingTurn(pendingError);
6814
6863
  if (this.activeQuery === response) {
6815
6864
  this.clearSessionState();
@@ -6931,8 +6980,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
6931
6980
  static isTransientTurnErrorText(text) {
6932
6981
  return isTransientErrorText(text, { httpStatuses: CLAUDE_TRANSIENT_HTTP_STATUSES });
6933
6982
  }
6934
- static detectTransientTurnErrorInMessage(message, sawActivity) {
6935
- if (sawActivity) return null;
6983
+ static detectTransientTurnErrorInMessage(message) {
6936
6984
  if (message.type === "assistant" && "error" in message && typeof message.error === "string") {
6937
6985
  return _ClaudeManager.isTransientTurnErrorText(message.error) ? message.error : null;
6938
6986
  }
@@ -7291,7 +7339,7 @@ var AspClient = class {
7291
7339
  // src/managers/codex-asp/app-server-process.ts
7292
7340
  var DEFAULT_CODEX_BINARY = "codex";
7293
7341
  var DEFAULT_CODEX_ARGS = ["app-server", "--listen", "stdio://"];
7294
- var ENGINE_PACKAGE_VERSION = "0.1.328";
7342
+ var ENGINE_PACKAGE_VERSION = "0.1.330";
7295
7343
  var INITIALIZE_METHOD = "initialize";
7296
7344
  var INITIALIZED_NOTIFICATION = "initialized";
7297
7345
  var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
@@ -9089,6 +9137,120 @@ var CodexAspManager = class extends CodingAgentManager {
9089
9137
  }
9090
9138
  };
9091
9139
 
9140
+ // src/managers/cursor-manager.ts
9141
+ import { appendFile as appendFile4, mkdir as mkdir11 } from "fs/promises";
9142
+ import { dirname as dirname4, join as join15 } from "path";
9143
+ import { Agent as CursorAgent } from "@cursor/sdk";
9144
+ var CursorManager = class extends CodingAgentManager {
9145
+ agent = null;
9146
+ activeRun = null;
9147
+ historyFile;
9148
+ constructor(options) {
9149
+ super(options);
9150
+ this.historyFile = options.historyFilePath ?? join15(ENGINE_ENV.HOME_DIR, ".replicas", "cursor", "history.jsonl");
9151
+ this.initializeManager(this.processMessageInternal.bind(this));
9152
+ }
9153
+ async initialize() {
9154
+ await mkdir11(dirname4(this.historyFile), { recursive: true });
9155
+ }
9156
+ async interruptActiveTurn() {
9157
+ await this.activeRun?.cancel();
9158
+ }
9159
+ async getHistory() {
9160
+ return {
9161
+ thread_id: this.agent?.agentId ?? this.initialSessionId,
9162
+ events: await readJSONL(this.historyFile),
9163
+ goal: null
9164
+ };
9165
+ }
9166
+ async ensureAgent(request) {
9167
+ if (this.agent) return this.agent;
9168
+ const apiKey = ENGINE_ENV.CURSOR_API_KEY;
9169
+ if (!apiKey) {
9170
+ throw new Error("Cursor API key is not configured for this workspace.");
9171
+ }
9172
+ const model = { id: request.model ?? DEFAULT_CURSOR_MODEL };
9173
+ this.agent = this.initialSessionId ? await CursorAgent.resume(this.initialSessionId, {
9174
+ apiKey,
9175
+ model,
9176
+ local: { cwd: this.workingDirectory }
9177
+ }) : await CursorAgent.create({
9178
+ apiKey,
9179
+ model,
9180
+ local: { cwd: this.workingDirectory }
9181
+ });
9182
+ await this.onSaveSessionId(this.agent.agentId);
9183
+ return this.agent;
9184
+ }
9185
+ async processMessageInternal(request) {
9186
+ try {
9187
+ const agent = await this.ensureAgent(request);
9188
+ const message = await this.toCursorMessage(request);
9189
+ this.recordHistoryEvent("event_msg", {
9190
+ type: "user_message",
9191
+ message: request.message
9192
+ });
9193
+ const run = await agent.send(message, {
9194
+ model: { id: request.model ?? DEFAULT_CURSOR_MODEL },
9195
+ mode: request.planMode ? "plan" : "agent"
9196
+ });
9197
+ this.activeRun = run;
9198
+ for await (const event of run.stream()) {
9199
+ this.recordCursorEvent(event);
9200
+ }
9201
+ const result = await run.wait();
9202
+ if (result.status === "error") {
9203
+ this.recordHistoryEvent("cursor-error", {
9204
+ type: "error",
9205
+ message: result.result || "Cursor run failed",
9206
+ runId: result.id
9207
+ });
9208
+ }
9209
+ } catch (error) {
9210
+ this.recordHistoryEvent("cursor-error", {
9211
+ type: "error",
9212
+ message: error instanceof Error ? error.message : String(error)
9213
+ });
9214
+ } finally {
9215
+ this.activeRun = null;
9216
+ await this.onTurnComplete();
9217
+ }
9218
+ }
9219
+ async toCursorMessage(request) {
9220
+ if (!request.images || request.images.length === 0) {
9221
+ return request.message;
9222
+ }
9223
+ const images = await normalizeImages(request.images);
9224
+ return {
9225
+ text: request.message,
9226
+ images: images.map((image) => ({
9227
+ data: image.source.data,
9228
+ mimeType: image.source.media_type
9229
+ }))
9230
+ };
9231
+ }
9232
+ recordCursorEvent(event) {
9233
+ this.recordHistoryEvent(`cursor-${event.type}`, event);
9234
+ }
9235
+ recordHistoryEvent(type, payload) {
9236
+ const eventPayload = {};
9237
+ if (payload && typeof payload === "object") {
9238
+ Object.assign(eventPayload, payload);
9239
+ } else {
9240
+ eventPayload.value = payload;
9241
+ }
9242
+ const event = {
9243
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
9244
+ type,
9245
+ payload: eventPayload
9246
+ };
9247
+ this.onEvent(event);
9248
+ appendFile4(this.historyFile, `${JSON.stringify(event)}
9249
+ `, "utf-8").catch(() => {
9250
+ });
9251
+ }
9252
+ };
9253
+
9092
9254
  // src/managers/relay-tools.ts
9093
9255
  import { createSdkMcpServer, tool } from "@anthropic-ai/claude-agent-sdk";
9094
9256
  import { z } from "zod";
@@ -9670,10 +9832,10 @@ var keepAliveService = new KeepAliveService();
9670
9832
  // src/services/canvas-service.ts
9671
9833
  import { readdir as readdir4, readFile as readFile11, stat as stat3 } from "fs/promises";
9672
9834
  import { homedir as homedir12 } from "os";
9673
- import { join as join15 } from "path";
9835
+ import { join as join16 } from "path";
9674
9836
  var CANVAS_DIRECTORIES = [
9675
- join15(homedir12(), ".claude", "plans"),
9676
- join15(homedir12(), ".replicas", "canvas")
9837
+ join16(homedir12(), ".claude", "plans"),
9838
+ join16(homedir12(), ".replicas", "canvas")
9677
9839
  ];
9678
9840
  var CanvasService = class {
9679
9841
  async listItems() {
@@ -9692,7 +9854,7 @@ var CanvasService = class {
9692
9854
  const { kind } = classifyCanvasFilename(entry.name);
9693
9855
  let sizeBytes = 0;
9694
9856
  try {
9695
- const s = await stat3(join15(directory, entry.name));
9857
+ const s = await stat3(join16(directory, entry.name));
9696
9858
  sizeBytes = s.size;
9697
9859
  } catch {
9698
9860
  continue;
@@ -9707,7 +9869,7 @@ var CanvasService = class {
9707
9869
  if (!safe) return null;
9708
9870
  const { kind, mimeType } = classifyCanvasFilename(safe);
9709
9871
  for (const directory of CANVAS_DIRECTORIES) {
9710
- const filePath = join15(directory, safe);
9872
+ const filePath = join16(directory, safe);
9711
9873
  let sizeBytes = 0;
9712
9874
  let updatedAt = "";
9713
9875
  try {
@@ -9844,13 +10006,13 @@ async function reconcileCanvasItems(filenames) {
9844
10006
 
9845
10007
  // src/services/upload-chat-transcripts.ts
9846
10008
  import { readdir as readdir5, readFile as readFile12 } from "fs/promises";
9847
- import { basename, join as join16 } from "path";
10009
+ import { basename, join as join17 } from "path";
9848
10010
  import { homedir as homedir13 } from "os";
9849
- var ENGINE_DIR2 = join16(homedir13(), ".replicas", "engine");
10011
+ var ENGINE_DIR2 = join17(homedir13(), ".replicas", "engine");
9850
10012
  var HISTORY_DIRS = [
9851
- join16(ENGINE_DIR2, "claude-histories"),
9852
- join16(ENGINE_DIR2, "relay-histories"),
9853
- join16(ENGINE_DIR2, "codex-histories")
10013
+ join17(ENGINE_DIR2, "claude-histories"),
10014
+ join17(ENGINE_DIR2, "relay-histories"),
10015
+ join17(ENGINE_DIR2, "codex-histories")
9854
10016
  ];
9855
10017
  async function flushAllChatTranscripts(chatsById = /* @__PURE__ */ new Map()) {
9856
10018
  let flushed = 0;
@@ -9867,7 +10029,7 @@ async function flushAllChatTranscripts(chatsById = /* @__PURE__ */ new Map()) {
9867
10029
  if (!entry.endsWith(".jsonl")) continue;
9868
10030
  const chatId = basename(entry, ".jsonl");
9869
10031
  tasks.push(
9870
- uploadChatTranscript(chatId, join16(dir, entry), chatsById.get(chatId)).then(() => {
10032
+ uploadChatTranscript(chatId, join17(dir, entry), chatsById.get(chatId)).then(() => {
9871
10033
  flushed++;
9872
10034
  }).catch((err) => {
9873
10035
  failed++;
@@ -9937,18 +10099,20 @@ var DuplicateDefaultChatError = class extends Error {
9937
10099
  };
9938
10100
 
9939
10101
  // src/services/chat/chat-service.ts
9940
- var ENGINE_DIR3 = join17(homedir14(), ".replicas", "engine");
9941
- var CHATS_FILE = join17(ENGINE_DIR3, "chats.json");
9942
- var CLAUDE_HISTORY_DIR = join17(ENGINE_DIR3, "claude-histories");
9943
- var RELAY_HISTORY_DIR = join17(ENGINE_DIR3, "relay-histories");
9944
- var CODEX_HISTORY_DIR = join17(ENGINE_DIR3, "codex-histories");
10102
+ var ENGINE_DIR3 = join18(homedir14(), ".replicas", "engine");
10103
+ var CHATS_FILE = join18(ENGINE_DIR3, "chats.json");
10104
+ var CLAUDE_HISTORY_DIR = join18(ENGINE_DIR3, "claude-histories");
10105
+ var RELAY_HISTORY_DIR = join18(ENGINE_DIR3, "relay-histories");
10106
+ var CODEX_HISTORY_DIR = join18(ENGINE_DIR3, "codex-histories");
10107
+ var CURSOR_HISTORY_DIR = join18(ENGINE_DIR3, "cursor-histories");
9945
10108
  var HISTORY_DIR_BY_PROVIDER = {
9946
10109
  claude: CLAUDE_HISTORY_DIR,
9947
10110
  relay: RELAY_HISTORY_DIR,
9948
- codex: CODEX_HISTORY_DIR
10111
+ codex: CODEX_HISTORY_DIR,
10112
+ cursor: CURSOR_HISTORY_DIR
9949
10113
  };
9950
- var CHAT_SENDERS_DIR = join17(ENGINE_DIR3, "chat-senders");
9951
- var CODEX_AUTH_PATH2 = join17(homedir14(), ".codex", "auth.json");
10114
+ var CHAT_SENDERS_DIR = join18(ENGINE_DIR3, "chat-senders");
10115
+ var CODEX_AUTH_PATH2 = join18(homedir14(), ".codex", "auth.json");
9952
10116
  var CHATS_BACKUP_FILE = `${CHATS_FILE}.bak`;
9953
10117
  function isChatMessageSender(value) {
9954
10118
  if (!isRecord4(value)) return false;
@@ -9998,7 +10162,7 @@ function isPersistedChat(value) {
9998
10162
  return false;
9999
10163
  }
10000
10164
  const candidate = value;
10001
- return typeof candidate.id === "string" && (candidate.provider === "claude" || candidate.provider === "codex" || candidate.provider === "relay") && typeof candidate.title === "string" && typeof candidate.createdAt === "string" && typeof candidate.updatedAt === "string" && (candidate.providerSessionId === null || typeof candidate.providerSessionId === "string") && (candidate.parentChatId === void 0 || candidate.parentChatId === null || typeof candidate.parentChatId === "string");
10165
+ return typeof candidate.id === "string" && (candidate.provider === "claude" || candidate.provider === "codex" || candidate.provider === "cursor" || candidate.provider === "relay") && typeof candidate.title === "string" && typeof candidate.createdAt === "string" && typeof candidate.updatedAt === "string" && (candidate.providerSessionId === null || typeof candidate.providerSessionId === "string") && (candidate.parentChatId === void 0 || candidate.parentChatId === null || typeof candidate.parentChatId === "string");
10002
10166
  }
10003
10167
  function normalizePersistedChat(chat) {
10004
10168
  const isLegacyCodexSdkChat = chat.provider === "codex" && (chat.codexBackend === "sdk" || chat.codexBackend === void 0 && chat.providerSessionId !== null);
@@ -10046,11 +10210,12 @@ var ChatService = class {
10046
10210
  persistInFlight = false;
10047
10211
  persistQueued = false;
10048
10212
  async initialize() {
10049
- await mkdir11(ENGINE_DIR3, { recursive: true });
10050
- await mkdir11(CLAUDE_HISTORY_DIR, { recursive: true });
10051
- await mkdir11(RELAY_HISTORY_DIR, { recursive: true });
10052
- await mkdir11(CODEX_HISTORY_DIR, { recursive: true });
10053
- await mkdir11(CHAT_SENDERS_DIR, { recursive: true });
10213
+ await mkdir12(ENGINE_DIR3, { recursive: true });
10214
+ await mkdir12(CLAUDE_HISTORY_DIR, { recursive: true });
10215
+ await mkdir12(RELAY_HISTORY_DIR, { recursive: true });
10216
+ await mkdir12(CODEX_HISTORY_DIR, { recursive: true });
10217
+ await mkdir12(CURSOR_HISTORY_DIR, { recursive: true });
10218
+ await mkdir12(CHAT_SENDERS_DIR, { recursive: true });
10054
10219
  const persisted = await this.loadChats();
10055
10220
  for (const chat of persisted) {
10056
10221
  const runtime = this.createRuntimeChat(chat);
@@ -10062,12 +10227,18 @@ var ChatService = class {
10062
10227
  const hasCodexDefault = [...this.chats.values()].some(
10063
10228
  (c) => c.persisted.provider === "codex" && c.persisted.title === "Codex"
10064
10229
  );
10230
+ const hasCursorDefault = [...this.chats.values()].some(
10231
+ (c) => c.persisted.provider === "cursor" && c.persisted.title === "Cursor"
10232
+ );
10065
10233
  if (!hasClaudeDefault) {
10066
10234
  await this.createChat({ provider: "claude", title: "Claude Code" });
10067
10235
  }
10068
10236
  if (!hasCodexDefault) {
10069
10237
  await this.createChat({ provider: "codex", title: "Codex" });
10070
10238
  }
10239
+ if (!hasCursorDefault) {
10240
+ await this.createChat({ provider: "cursor", title: "Cursor" });
10241
+ }
10071
10242
  const hasRelayDefault = [...this.chats.values()].some(
10072
10243
  (c) => c.persisted.provider === "relay" && c.persisted.title === "Relay"
10073
10244
  );
@@ -10149,11 +10320,11 @@ var ChatService = class {
10149
10320
  };
10150
10321
  }
10151
10322
  senderFilePath(chatId) {
10152
- return join17(CHAT_SENDERS_DIR, `${chatId}.jsonl`);
10323
+ return join18(CHAT_SENDERS_DIR, `${chatId}.jsonl`);
10153
10324
  }
10154
10325
  async appendSender(chatId, sender) {
10155
10326
  try {
10156
- await appendFile4(this.senderFilePath(chatId), JSON.stringify(sender) + "\n", "utf-8");
10327
+ await appendFile5(this.senderFilePath(chatId), JSON.stringify(sender) + "\n", "utf-8");
10157
10328
  } catch (error) {
10158
10329
  console.error("[ChatService] Failed to append sender record:", error);
10159
10330
  }
@@ -10305,7 +10476,7 @@ var ChatService = class {
10305
10476
  return descendants;
10306
10477
  }
10307
10478
  async deleteHistoryFile(persisted) {
10308
- await rm(join17(HISTORY_DIR_BY_PROVIDER[persisted.provider], `${persisted.id}.jsonl`), { force: true });
10479
+ await rm(join18(HISTORY_DIR_BY_PROVIDER[persisted.provider], `${persisted.id}.jsonl`), { force: true });
10309
10480
  await rm(this.senderFilePath(persisted.id), { force: true });
10310
10481
  }
10311
10482
  async getChatHistory(chatId) {
@@ -10376,7 +10547,7 @@ var ChatService = class {
10376
10547
  if (persisted.provider === "claude") {
10377
10548
  provider = new ClaudeManager({
10378
10549
  workingDirectory: this.workingDirectory,
10379
- historyFilePath: join17(CLAUDE_HISTORY_DIR, `${persisted.id}.jsonl`),
10550
+ historyFilePath: join18(CLAUDE_HISTORY_DIR, `${persisted.id}.jsonl`),
10380
10551
  initialSessionId: persisted.providerSessionId,
10381
10552
  onSaveSessionId: saveSession,
10382
10553
  onTurnComplete: onProviderTurnComplete,
@@ -10385,7 +10556,7 @@ var ChatService = class {
10385
10556
  } else if (persisted.provider === "relay") {
10386
10557
  provider = new RelayManager({
10387
10558
  workingDirectory: this.workingDirectory,
10388
- historyFilePath: join17(RELAY_HISTORY_DIR, `${persisted.id}.jsonl`),
10559
+ historyFilePath: join18(RELAY_HISTORY_DIR, `${persisted.id}.jsonl`),
10389
10560
  initialSessionId: persisted.providerSessionId,
10390
10561
  onSaveSessionId: saveSession,
10391
10562
  onTurnComplete: onProviderTurnComplete,
@@ -10393,10 +10564,19 @@ var ChatService = class {
10393
10564
  chatId: persisted.id,
10394
10565
  codexAvailable: isCodexAvailable()
10395
10566
  });
10567
+ } else if (persisted.provider === "cursor") {
10568
+ provider = new CursorManager({
10569
+ workingDirectory: this.workingDirectory,
10570
+ historyFilePath: join18(CURSOR_HISTORY_DIR, `${persisted.id}.jsonl`),
10571
+ initialSessionId: persisted.providerSessionId,
10572
+ onSaveSessionId: saveSession,
10573
+ onTurnComplete: onProviderTurnComplete,
10574
+ onEvent: onProviderEvent
10575
+ });
10396
10576
  } else {
10397
10577
  provider = new CodexAspManager({
10398
10578
  workingDirectory: this.workingDirectory,
10399
- historyFilePath: join17(CODEX_HISTORY_DIR, `${persisted.id}.jsonl`),
10579
+ historyFilePath: join18(CODEX_HISTORY_DIR, `${persisted.id}.jsonl`),
10400
10580
  initialSessionId: persisted.providerSessionId,
10401
10581
  onSaveSessionId: saveSession,
10402
10582
  onTurnComplete: onProviderTurnComplete,
@@ -10535,7 +10715,7 @@ var ChatService = class {
10535
10715
  });
10536
10716
  uploadChatTranscript(
10537
10717
  chatId,
10538
- join17(HISTORY_DIR_BY_PROVIDER[chat.persisted.provider], `${chatId}.jsonl`),
10718
+ join18(HISTORY_DIR_BY_PROVIDER[chat.persisted.provider], `${chatId}.jsonl`),
10539
10719
  this.toSummary(chat)
10540
10720
  ).catch((err) => {
10541
10721
  console.error("[ChatService] Failed to upload chat transcript:", { chatId, err });
@@ -10651,7 +10831,7 @@ var ChatService = class {
10651
10831
  // src/services/repo-file-service.ts
10652
10832
  import { execFile as execFile2 } from "child_process";
10653
10833
  import { readFile as readFile14, realpath, stat as stat4 } from "fs/promises";
10654
- import { join as join18, resolve as resolve2, extname } from "path";
10834
+ import { join as join19, resolve as resolve2, extname } from "path";
10655
10835
  var CACHE_TTL_MS = 3e4;
10656
10836
  var SEARCH_TIMEOUT_MS = 15e3;
10657
10837
  var MAX_CONTENT_BYTES = 256 * 1024;
@@ -10811,7 +10991,7 @@ var RepoFileService = class {
10811
10991
  const repo = repos.find((r) => r.name === repoName);
10812
10992
  if (!repo) return null;
10813
10993
  try {
10814
- const fullPath = await realpath(resolve2(join18(repo.path, filePath)));
10994
+ const fullPath = await realpath(resolve2(join19(repo.path, filePath)));
10815
10995
  const repoRoot = await realpath(repo.path);
10816
10996
  const repoPrefix = repoRoot.endsWith("/") ? repoRoot : repoRoot + "/";
10817
10997
  if (!fullPath.startsWith(repoPrefix) && fullPath !== repoRoot) return null;
@@ -10919,20 +11099,20 @@ var RepoFileService = class {
10919
11099
  import { Hono } from "hono";
10920
11100
  import { z as z2 } from "zod";
10921
11101
  import { readdir as readdir7, stat as stat5, readFile as readFile17 } from "fs/promises";
10922
- import { join as join21, resolve as resolve3 } from "path";
11102
+ import { join as join22, resolve as resolve3 } from "path";
10923
11103
 
10924
11104
  // src/services/warm-hooks-service.ts
10925
11105
  import { spawn as spawn4 } from "child_process";
10926
11106
  import { readFile as readFile16 } from "fs/promises";
10927
11107
  import { existsSync as existsSync8 } from "fs";
10928
- import { join as join20 } from "path";
11108
+ import { join as join21 } from "path";
10929
11109
 
10930
11110
  // src/services/warm-hook-logs-service.ts
10931
- import { mkdir as mkdir12, readFile as readFile15, writeFile as writeFile6, readdir as readdir6, appendFile as appendFile5, unlink as unlink3 } from "fs/promises";
11111
+ import { mkdir as mkdir13, readFile as readFile15, writeFile as writeFile6, readdir as readdir6, appendFile as appendFile6, unlink as unlink3 } from "fs/promises";
10932
11112
  import { homedir as homedir15 } from "os";
10933
- import { join as join19 } from "path";
10934
- var LOGS_DIR2 = join19(homedir15(), ".replicas", "warm-hook-logs");
10935
- var CURRENT_RUN_LOG = join19(LOGS_DIR2, "current-run.log");
11113
+ import { join as join20 } from "path";
11114
+ var LOGS_DIR2 = join20(homedir15(), ".replicas", "warm-hook-logs");
11115
+ var CURRENT_RUN_LOG = join20(LOGS_DIR2, "current-run.log");
10936
11116
  var GLOBAL_FILENAME = "global.json";
10937
11117
  function withPreview2(stored) {
10938
11118
  const preview = buildHookOutputPreview(stored.output);
@@ -10940,7 +11120,7 @@ function withPreview2(stored) {
10940
11120
  }
10941
11121
  var WarmHookLogsService = class {
10942
11122
  async ensureDir() {
10943
- await mkdir12(LOGS_DIR2, { recursive: true });
11123
+ await mkdir13(LOGS_DIR2, { recursive: true });
10944
11124
  }
10945
11125
  async saveGlobalHookLog(entry) {
10946
11126
  await this.ensureDir();
@@ -10949,7 +11129,7 @@ var WarmHookLogsService = class {
10949
11129
  hookName: "organization",
10950
11130
  ...entry
10951
11131
  };
10952
- await writeFile6(join19(LOGS_DIR2, GLOBAL_FILENAME), `${JSON.stringify(log, null, 2)}
11132
+ await writeFile6(join20(LOGS_DIR2, GLOBAL_FILENAME), `${JSON.stringify(log, null, 2)}
10953
11133
  `, "utf-8");
10954
11134
  }
10955
11135
  async saveEnvironmentHookLog(entry) {
@@ -10959,7 +11139,7 @@ var WarmHookLogsService = class {
10959
11139
  hookName: "environment",
10960
11140
  ...entry
10961
11141
  };
10962
- await writeFile6(join19(LOGS_DIR2, ENVIRONMENT_HOOK_LOG_FILENAME), `${JSON.stringify(log, null, 2)}
11142
+ await writeFile6(join20(LOGS_DIR2, ENVIRONMENT_HOOK_LOG_FILENAME), `${JSON.stringify(log, null, 2)}
10963
11143
  `, "utf-8");
10964
11144
  }
10965
11145
  async saveRepoHookLog(repoName, entry) {
@@ -10969,7 +11149,7 @@ var WarmHookLogsService = class {
10969
11149
  hookName: repoName,
10970
11150
  ...entry
10971
11151
  };
10972
- await writeFile6(join19(LOGS_DIR2, repoHookLogFilename(repoName)), `${JSON.stringify(log, null, 2)}
11152
+ await writeFile6(join20(LOGS_DIR2, repoHookLogFilename(repoName)), `${JSON.stringify(log, null, 2)}
10973
11153
  `, "utf-8");
10974
11154
  }
10975
11155
  async getAllLogs() {
@@ -10988,7 +11168,7 @@ var WarmHookLogsService = class {
10988
11168
  continue;
10989
11169
  }
10990
11170
  try {
10991
- const raw = await readFile15(join19(LOGS_DIR2, file), "utf-8");
11171
+ const raw = await readFile15(join20(LOGS_DIR2, file), "utf-8");
10992
11172
  const stored = JSON.parse(raw);
10993
11173
  logs.push(withPreview2(stored));
10994
11174
  } catch {
@@ -11013,7 +11193,7 @@ var WarmHookLogsService = class {
11013
11193
  }
11014
11194
  async appendCurrentRunLog(chunk) {
11015
11195
  if (!chunk) return;
11016
- await appendFile5(CURRENT_RUN_LOG, chunk, "utf-8");
11196
+ await appendFile6(CURRENT_RUN_LOG, chunk, "utf-8");
11017
11197
  }
11018
11198
  async getCurrentRunLog() {
11019
11199
  try {
@@ -11026,7 +11206,7 @@ var WarmHookLogsService = class {
11026
11206
  async getFullOutput(hookType, hookName) {
11027
11207
  const filename = hookType === "global" ? GLOBAL_FILENAME : hookType === "environment" ? ENVIRONMENT_HOOK_LOG_FILENAME : repoHookLogFilename(hookName);
11028
11208
  try {
11029
- const raw = await readFile15(join19(LOGS_DIR2, filename), "utf-8");
11209
+ const raw = await readFile15(join20(LOGS_DIR2, filename), "utf-8");
11030
11210
  const stored = JSON.parse(raw);
11031
11211
  if (stored.hookType !== hookType || stored.hookName !== hookName) {
11032
11212
  return null;
@@ -11045,7 +11225,7 @@ var warmHookLogsService = new WarmHookLogsService();
11045
11225
  // src/services/warm-hooks-service.ts
11046
11226
  async function readRepoWarmHook(repoPath) {
11047
11227
  for (const filename of REPLICAS_CONFIG_FILENAMES) {
11048
- const configPath = join20(repoPath, filename);
11228
+ const configPath = join21(repoPath, filename);
11049
11229
  if (!existsSync8(configPath)) {
11050
11230
  continue;
11051
11231
  }
@@ -11310,7 +11490,7 @@ var setWorkspaceNameSchema = z2.object({
11310
11490
  name: z2.string().min(1).max(48)
11311
11491
  });
11312
11492
  var createChatSchema = z2.object({
11313
- provider: z2.enum(["claude", "codex", "relay"]),
11493
+ provider: z2.enum(["claude", "codex", "cursor", "relay"]),
11314
11494
  title: z2.string().min(1).optional(),
11315
11495
  parentChatId: z2.string().uuid().optional()
11316
11496
  });
@@ -12000,7 +12180,7 @@ function createV1Routes(deps) {
12000
12180
  const logFiles = files.filter((f) => f.endsWith(".log"));
12001
12181
  const sessions = await Promise.all(
12002
12182
  logFiles.map(async (filename) => {
12003
- const filePath = join21(LOG_DIR, filename);
12183
+ const filePath = join22(LOG_DIR, filename);
12004
12184
  const fileStat = await stat5(filePath);
12005
12185
  const sessionId = filename.replace(/\.log$/, "");
12006
12186
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.328",
3
+ "version": "0.1.330",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",
@@ -31,6 +31,7 @@
31
31
  "license": "MIT",
32
32
  "dependencies": {
33
33
  "@anthropic-ai/claude-agent-sdk": "0.3.168",
34
+ "@cursor/sdk": "1.0.19",
34
35
  "@hono/node-server": "^1.19.5",
35
36
  "hono": "^4.10.3",
36
37
  "smol-toml": "^1.6.0",