replicas-engine 0.1.469 → 0.1.471

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 +204 -138
  2. package/package.json +1 -1
package/dist/src/index.js CHANGED
@@ -582,7 +582,7 @@ var WORKSPACE_SIZES = ["small", "large"];
582
582
  var INVALID_WORKSPACE_SIZE_ERROR = `Invalid size: must be one of ${WORKSPACE_SIZES.join(", ")}`;
583
583
 
584
584
  // ../shared/src/e2b.ts
585
- var E2B_TEMPLATE_NAME = "replicas-sandbox-2026-07-20-v1";
585
+ var E2B_TEMPLATE_NAME = "replicas-sandbox-2026-07-21-v1";
586
586
 
587
587
  // ../shared/src/runtime-env.ts
588
588
  function shellQuotePosix(value) {
@@ -1190,7 +1190,7 @@ GitHub does NOT have a public API for uploading images to PRs/issues. When you n
1190
1190
  - Do NOT commit screenshots as files to the repository
1191
1191
  - Run \`replicas media upload <image> --share\` and put the printed \`Forge embed\` Markdown in the PR body or comment
1192
1192
  - For video, embed a shared poster image linked to the recording's **View in Replicas** URL; GitHub does not reliably render externally hosted video inline
1193
- - Treat the public URL as an opt-in bearer capability and revoke it with \`replicas media revoke <media-id>\` when appropriate
1193
+ - Treat the public URL as an opt-in bearer capability; revoke it with \`replicas media revoke <media-id>\` only when the user asks or the media should stop rendering \u2014 revoking breaks embeds already posted on the PR
1194
1194
  - If you were triggered from Slack, also upload the image to the Slack thread so the user can see it directly
1195
1195
  `;
1196
1196
  var GITHUB_ABILITY = {
@@ -1969,7 +1969,7 @@ GitHub does not reliably render arbitrary external videos as inline players. For
1969
1969
  [![Demo recording](<public-poster-url>)](<recording-dashboard-url>)
1970
1970
  \`\`\`
1971
1971
 
1972
- Public forge shares are bearer capabilities. For private repositories, create one only when the user asked for inline PR media, and revoke it with \`replicas media revoke <media-id>\` when it should stop resolving.
1972
+ Public forge shares are bearer capabilities. For private repositories, create one only when the user asked for inline PR media. Revoke with \`replicas media revoke <media-id>\` **only** when the user asks or the media should intentionally stop rendering \u2014 never as routine post-task cleanup, since revoking breaks every embed already posted on the PR.
1973
1973
 
1974
1974
  Do **not** commit screenshots to the repo, use placeholder URLs, expose the authenticated \`/v1/media/<id>\` URL, or automate GitHub's undocumented browser upload flow.
1975
1975
 
@@ -2796,10 +2796,19 @@ function hasChatStarted(chat) {
2796
2796
  var DESKTOP_NOVNC_PORT = 6080;
2797
2797
 
2798
2798
  // ../shared/src/engine/v1.ts
2799
+ import { z as z2 } from "zod";
2799
2800
  var MERGED_MESSAGE_SEPARATOR = "\n\n<!-- replicas:merged -->\n\n";
2800
2801
  var ENGINE_HEALTH_WAIT_HEADER = "X-Replicas-Health-Wait";
2801
2802
  var ENGINE_HEALTH_WAIT_QUERY_PARAM = "wait_ms";
2802
2803
  var ENGINE_HEALTH_MAX_WAIT_MS = 2e3;
2804
+ var createChatRequestSchema = z2.object({
2805
+ id: z2.string().uuid().optional(),
2806
+ createdAt: z2.string().datetime().optional(),
2807
+ provider: z2.enum(VALID_AGENT_PROVIDERS),
2808
+ title: z2.string().min(1).optional(),
2809
+ parentChatId: z2.string().uuid().optional(),
2810
+ clientRequestId: z2.string().min(1).max(128).optional()
2811
+ });
2803
2812
  function normalizeCodexAspTranscriptStatus(status, failed = false) {
2804
2813
  if (failed || status === "failed" || status === "declined") return "failed";
2805
2814
  if (status === "completed") return "completed";
@@ -2817,6 +2826,20 @@ function imageContentToUserMessageImages(images) {
2817
2826
  }
2818
2827
  return userImages.length > 0 ? userImages : void 0;
2819
2828
  }
2829
+ function createAcceptedUserMessageEvent(message, messageId, timestamp, images) {
2830
+ const eventImages = imageContentToUserMessageImages(images);
2831
+ return {
2832
+ timestamp,
2833
+ type: "event_msg",
2834
+ payload: {
2835
+ type: "user_message",
2836
+ message,
2837
+ source: ACCEPTED_USER_MESSAGE_SOURCE,
2838
+ [USER_MESSAGE_ID_PAYLOAD_KEY]: messageId,
2839
+ ...eventImages ? { images: eventImages } : {}
2840
+ }
2841
+ };
2842
+ }
2820
2843
  function isCodexAspTranscript(value) {
2821
2844
  if (!isRecord(value)) return false;
2822
2845
  return typeof value.threadId === "string" && typeof value.updatedAt === "string" && Array.isArray(value.turns);
@@ -3207,6 +3230,22 @@ function safeJsonParse(str, fallback) {
3207
3230
  }
3208
3231
  }
3209
3232
 
3233
+ // ../shared/src/display-message/parsers/utils.ts
3234
+ function userMessageImages(value) {
3235
+ if (!Array.isArray(value)) return void 0;
3236
+ const images = value.filter((item) => isRecord(item) && item.type === "image" && typeof item.mediaType === "string" && typeof item.data === "string");
3237
+ return images.length > 0 ? images : void 0;
3238
+ }
3239
+ function stringifyDisplayValue(value) {
3240
+ if (value === void 0 || value === null) return void 0;
3241
+ if (typeof value === "string") return value;
3242
+ try {
3243
+ return JSON.stringify(value, null, 2);
3244
+ } catch {
3245
+ return String(value);
3246
+ }
3247
+ }
3248
+
3210
3249
  // ../shared/src/display-message/parsers/codex-parser.ts
3211
3250
  function getStatusFromExitCode(exitCode) {
3212
3251
  return exitCode === 0 ? "completed" : "failed";
@@ -3219,14 +3258,6 @@ function displayId(event, eventIndex, prefix) {
3219
3258
  const stableId = getPayloadString(event, USER_MESSAGE_ID_PAYLOAD_KEY) ?? getPayloadString(event, CODEX_ASP_ITEM_ID_PAYLOAD_KEY);
3220
3259
  return stableId ? `${prefix}-${stableId}` : `${prefix}-${event.timestamp}-${eventIndex}`;
3221
3260
  }
3222
- function userMessageImages(value) {
3223
- if (!Array.isArray(value)) return void 0;
3224
- const images = value.filter((item) => {
3225
- if (!isRecord(item)) return false;
3226
- return item.type === "image" && typeof item.mediaType === "string" && typeof item.data === "string";
3227
- });
3228
- return images.length > 0 ? images : void 0;
3229
- }
3230
3261
  function parseShellOutput(raw) {
3231
3262
  const exitCodeMatch = raw.match(/^Exit code: (\d+)/m) || raw.match(/Process exited with code (\d+)/);
3232
3263
  const exitCode = exitCodeMatch ? parseInt(exitCodeMatch[1], 10) : 0;
@@ -3450,17 +3481,6 @@ function parseCodexEvents(events) {
3450
3481
  return messages;
3451
3482
  }
3452
3483
 
3453
- // ../shared/src/display-message/parsers/utils.ts
3454
- function stringifyDisplayValue(value) {
3455
- if (value === void 0 || value === null) return void 0;
3456
- if (typeof value === "string") return value;
3457
- try {
3458
- return JSON.stringify(value, null, 2);
3459
- } catch {
3460
- return String(value);
3461
- }
3462
- }
3463
-
3464
3484
  // ../shared/src/display-message/parsers/cursor-parser.ts
3465
3485
  function getTextContent(value) {
3466
3486
  if (!Array.isArray(value)) return "";
@@ -4021,6 +4041,7 @@ function parseClaudeEvents(events, parentToolUseId) {
4021
4041
  let liveStreamId = null;
4022
4042
  const assistantThinking = /* @__PURE__ */ new Map();
4023
4043
  const assistantTextCounts = /* @__PURE__ */ new Map();
4044
+ const acceptedUserMessageIndexes = /* @__PURE__ */ new Set();
4024
4045
  const taskAccumulator = new TaskAccumulator();
4025
4046
  const taskSnapshot = () => taskAccumulator.getTasks().map((task) => ({
4026
4047
  text: task.subject,
@@ -4028,6 +4049,21 @@ function parseClaudeEvents(events, parentToolUseId) {
4028
4049
  itemStatus: task.status
4029
4050
  }));
4030
4051
  filteredEvents.forEach((event) => {
4052
+ if (event.type === "event_msg" && event.payload.type === "user_message") {
4053
+ const content = typeof event.payload.message === "string" ? event.payload.message : "";
4054
+ const images = userMessageImages(event.payload.images);
4055
+ if (content || images) {
4056
+ const messageId = event.payload[USER_MESSAGE_ID_PAYLOAD_KEY];
4057
+ acceptedUserMessageIndexes.add(messages.push({
4058
+ id: typeof messageId === "string" ? messageId : `user-${event.timestamp}`,
4059
+ type: "user",
4060
+ content,
4061
+ images,
4062
+ timestamp: event.timestamp
4063
+ }) - 1);
4064
+ }
4065
+ return;
4066
+ }
4031
4067
  if (event.type === CLAUDE_PARTIAL_MESSAGE_EVENT_TYPE) {
4032
4068
  const payload = coerceClaudePartialMessagePayload(event.payload);
4033
4069
  if (!payload) return;
@@ -4085,13 +4121,24 @@ function parseClaudeEvents(events, parentToolUseId) {
4085
4121
  };
4086
4122
  }).filter((img) => img.data);
4087
4123
  if (textContent || images.length > 0) {
4088
- messages.push({
4089
- id: `user-${event.timestamp}`,
4124
+ const acceptedIndex = [...acceptedUserMessageIndexes].find((index) => {
4125
+ const accepted2 = messages[index];
4126
+ return accepted2?.type === "user" && accepted2.content === textContent;
4127
+ });
4128
+ const accepted = acceptedIndex === void 0 ? void 0 : messages[acceptedIndex];
4129
+ const message = {
4130
+ id: accepted?.id ?? `user-${event.timestamp}`,
4090
4131
  type: "user",
4091
4132
  content: textContent || (images.length > 0 ? `[${images.length} image${images.length > 1 ? "s" : ""} attached]` : ""),
4092
- images: images.length > 0 ? images : void 0,
4133
+ images: images.length > 0 ? images : accepted?.type === "user" ? accepted.images : void 0,
4093
4134
  timestamp: event.timestamp
4094
- });
4135
+ };
4136
+ if (acceptedIndex === void 0) {
4137
+ messages.push(message);
4138
+ } else {
4139
+ messages[acceptedIndex] = message;
4140
+ acceptedUserMessageIndexes.delete(acceptedIndex);
4141
+ }
4095
4142
  }
4096
4143
  }
4097
4144
  if (event.type === "claude-assistant") {
@@ -7610,11 +7657,11 @@ var MessageQueueService = class {
7610
7657
  position: this.queue.length
7611
7658
  };
7612
7659
  }
7613
- const messageId = this.generateMessageId();
7660
+ const messageId = request.messageId ?? this.generateMessageId();
7614
7661
  const queuedMessage = {
7615
7662
  id: messageId,
7616
7663
  ...request,
7617
- queuedAt: (/* @__PURE__ */ new Date()).toISOString()
7664
+ queuedAt: request.submittedAt ?? (/* @__PURE__ */ new Date()).toISOString()
7618
7665
  };
7619
7666
  if (this.processing) {
7620
7667
  this.queue.push(queuedMessage);
@@ -9785,7 +9832,7 @@ var DEFAULT_CODEX_ARGS = ["app-server", "--listen", "stdio://"];
9785
9832
  var MIN_CODEX_CLI_VERSION = "0.144.6";
9786
9833
  var CODEX_UPGRADE_TIMEOUT_MS = 12e4;
9787
9834
  var codexCliVersionEnsured = null;
9788
- var ENGINE_PACKAGE_VERSION = "0.1.469";
9835
+ var ENGINE_PACKAGE_VERSION = "0.1.471";
9789
9836
  var INITIALIZE_METHOD = "initialize";
9790
9837
  var INITIALIZED_NOTIFICATION = "initialized";
9791
9838
  var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
@@ -12016,6 +12063,7 @@ var CursorManager = class extends CodingAgentManager {
12016
12063
  abortActiveTurn = null;
12017
12064
  blockedToolCallIds = /* @__PURE__ */ new Set();
12018
12065
  pendingCursorBlockReason = null;
12066
+ instructionsDelivered = false;
12019
12067
  historyFilePath;
12020
12068
  historyFile;
12021
12069
  slashCommandsCache = null;
@@ -12089,16 +12137,18 @@ var CursorManager = class extends CodingAgentManager {
12089
12137
  throw new Error("Cursor API key is not configured for this workspace.");
12090
12138
  }
12091
12139
  const model = { id: request.model ?? DEFAULT_CURSOR_MODEL };
12140
+ const settingSources = ["project", "user"];
12141
+ const local = { cwd: this.workingDirectory, settingSources };
12092
12142
  this.agent = this.initialSessionId ? await CursorAgent.resume(this.initialSessionId, {
12093
12143
  apiKey,
12094
12144
  model,
12095
- local: { cwd: this.workingDirectory }
12145
+ local
12096
12146
  }) : await CursorAgent.create({
12097
12147
  apiKey,
12098
12148
  model,
12099
- local: { cwd: this.workingDirectory }
12149
+ local
12100
12150
  });
12101
- await this.onSaveSessionId(this.agent.agentId);
12151
+ if (this.initialSessionId) await this.onSaveSessionId(this.agent.agentId);
12102
12152
  return this.agent;
12103
12153
  }
12104
12154
  async processMessageInternal(request) {
@@ -12109,8 +12159,9 @@ var CursorManager = class extends CodingAgentManager {
12109
12159
  const linearSessionId = ENGINE_ENV.LINEAR_SESSION_ID;
12110
12160
  const linearForwarder = new LinearEventForwarder(linearSessionId);
12111
12161
  try {
12162
+ const includeInstructions = !this.initialSessionId && !this.instructionsDelivered;
12163
+ const message = await this.toCursorMessage(request, includeInstructions);
12112
12164
  const agent = await this.ensureAgent(request);
12113
- const message = await this.toCursorMessage(request);
12114
12165
  const model = request.model ?? DEFAULT_CURSOR_MODEL;
12115
12166
  this.recordHistoryEvent("event_msg", {
12116
12167
  type: "user_message",
@@ -12164,6 +12215,10 @@ var CursorManager = class extends CodingAgentManager {
12164
12215
  if (result === TIMEOUT) {
12165
12216
  throw new Error("Cursor run did not report a result after its event stream ended");
12166
12217
  }
12218
+ if (includeInstructions) {
12219
+ this.instructionsDelivered = true;
12220
+ await this.onSaveSessionId(agent.agentId);
12221
+ }
12167
12222
  if (result.status === "error") {
12168
12223
  this.recordHistoryEvent("cursor-error", {
12169
12224
  type: "error",
@@ -12222,13 +12277,19 @@ var CursorManager = class extends CodingAgentManager {
12222
12277
  }
12223
12278
  this.pendingCursorBlockReason = message;
12224
12279
  }
12225
- async toCursorMessage(request) {
12280
+ async toCursorMessage(request, includeInstructions) {
12281
+ const instructions = includeInstructions ? this.buildCombinedInstructions(request.customInstructions) : void 0;
12282
+ const text = instructions ? `<workspace-instructions>
12283
+ ${instructions}
12284
+ </workspace-instructions>
12285
+
12286
+ ${request.message}` : request.message;
12226
12287
  if (!request.images || request.images.length === 0) {
12227
- return request.message;
12288
+ return text;
12228
12289
  }
12229
12290
  const images = await normalizeImages(request.images);
12230
12291
  return {
12231
- text: request.message,
12292
+ text,
12232
12293
  images: images.map((image) => ({
12233
12294
  data: image.source.data,
12234
12295
  mimeType: image.source.media_type
@@ -12310,7 +12371,7 @@ import { delimiter, dirname as dirname6, join as join18 } from "path";
12310
12371
  import { randomBytes as randomBytes2 } from "crypto";
12311
12372
  import { fileURLToPath } from "url";
12312
12373
  import { Agent } from "undici";
12313
- import { z as z2 } from "zod";
12374
+ import { z as z3 } from "zod";
12314
12375
  import {
12315
12376
  createOpencodeClient,
12316
12377
  createOpencodeServer
@@ -12347,9 +12408,9 @@ var OPENCODE_VARIANT_CANDIDATES_BY_THINKING_LEVEL = {
12347
12408
  ultra: ["max", "xhigh", "high"],
12348
12409
  ultracode: ["max", "xhigh", "high"]
12349
12410
  };
12350
- var opencodeAuthSchema = z2.record(z2.string(), z2.object({
12351
- type: z2.string().optional(),
12352
- key: z2.string().optional()
12411
+ var opencodeAuthSchema = z3.record(z3.string(), z3.object({
12412
+ type: z3.string().optional(),
12413
+ key: z3.string().optional()
12353
12414
  }));
12354
12415
  async function hasOpenCodeGoCredentials() {
12355
12416
  if (!existsSync7(OPENCODE_AUTH_PATH2)) return false;
@@ -13194,7 +13255,7 @@ var PiManager = class extends CodingAgentManager {
13194
13255
 
13195
13256
  // src/managers/relay-tools.ts
13196
13257
  import { createSdkMcpServer, tool } from "@anthropic-ai/claude-agent-sdk";
13197
- import { z as z3 } from "zod";
13258
+ import { z as z4 } from "zod";
13198
13259
 
13199
13260
  // src/managers/relay-providers.ts
13200
13261
  function isRelaySubagentProviderAllowed(provider, allowedProviders) {
@@ -13309,7 +13370,7 @@ function buildSpawnAgentTool(parentChatId, availability = {}, getAllowedProvider
13309
13370
  const cursorAvailable = availability.cursorAvailable ?? false;
13310
13371
  const opencodeAvailable = availability.opencodeAvailable ?? false;
13311
13372
  const availableProviders = getAvailableRelayProviders(availability);
13312
- const providerEnum = z3.enum(availableProviders);
13373
+ const providerEnum = z4.enum(availableProviders);
13313
13374
  const codeProviders = getAvailableCodeProviders(availability);
13314
13375
  const providerDesc = codeProviders.length > 0 ? `Which agent to use. Prefer ${codeProviders.join(" or ")} for code writing, claude for exploration/analysis, relay for complex multi-step orchestration.` : "Which agent to use. Use claude for code writing, exploration, and analysis. Use relay for complex multi-step orchestration.";
13315
13376
  const useCases = codeProviders.length > 0 ? `- Complex code writing tasks (use provider '${codeProviders.join("' or '")}' with a capable model)
@@ -13328,18 +13389,18 @@ The tool blocks until the subagent completes and returns its final response.
13328
13389
  You will also receive the chatId so you can send follow-up messages or clean up the chat.`,
13329
13390
  {
13330
13391
  provider: providerEnum.describe(providerDesc),
13331
- prompt: z3.string().describe("The full prompt/instructions for the subagent. Be detailed - it has no context from your conversation."),
13332
- model: z3.string().optional().describe([
13392
+ prompt: z4.string().describe("The full prompt/instructions for the subagent. Be detailed - it has no context from your conversation."),
13393
+ model: z4.string().optional().describe([
13333
13394
  `Model override. Claude: ${AGENT_MODELS.claude.join(", ")} (opus is the default; sonnet is faster).`,
13334
13395
  codexAvailable ? `Codex: ${AGENT_MODELS.codex.join(", ")}.` : null,
13335
13396
  cursorAvailable ? `Cursor: ${AGENT_MODELS.cursor.join(", ")}.` : null,
13336
13397
  opencodeAvailable ? `Opencode: ${AGENT_MODELS.opencode.join(", ")}.` : null
13337
13398
  ].filter(Boolean).join(" ")),
13338
- thinking_level: z3.enum(VALID_THINKING_LEVELS).optional().describe(
13399
+ thinking_level: z4.enum(VALID_THINKING_LEVELS).optional().describe(
13339
13400
  "Controls how much thinking/reasoning the subagent applies. low = light thinking, medium = moderate, high = deep reasoning, xhigh = extended effort, max = maximum effort, ultra = Codex ultra, ultracode = Claude Code dynamic workflows. Defaults: Claude = high, Codex = medium, Cursor = medium, Opencode = medium."
13340
13401
  ),
13341
- title: z3.string().optional().describe("Optional title for the subagent chat (for identification)."),
13342
- timeout_minutes: z3.number().positive().optional().describe("Timeout in minutes for the subagent to complete (default: 10). Set higher for large tasks to avoid losing work.")
13402
+ title: z4.string().optional().describe("Optional title for the subagent chat (for identification)."),
13403
+ timeout_minutes: z4.number().positive().optional().describe("Timeout in minutes for the subagent to complete (default: 10). Set higher for large tasks to avoid losing work.")
13343
13404
  },
13344
13405
  async (args) => {
13345
13406
  try {
@@ -13414,13 +13475,13 @@ var messageAgentTool = tool(
13414
13475
 
13415
13476
  The tool blocks until the subagent completes and returns its response.`,
13416
13477
  {
13417
- chatId: z3.string().describe("The chat ID of the subagent (returned by spawn_agent)."),
13418
- message: z3.string().describe("The follow-up message to send."),
13419
- model: z3.string().optional().describe("Optional model override for this message."),
13420
- thinking_level: z3.enum(VALID_THINKING_LEVELS).optional().describe(
13478
+ chatId: z4.string().describe("The chat ID of the subagent (returned by spawn_agent)."),
13479
+ message: z4.string().describe("The follow-up message to send."),
13480
+ model: z4.string().optional().describe("Optional model override for this message."),
13481
+ thinking_level: z4.enum(VALID_THINKING_LEVELS).optional().describe(
13421
13482
  "Controls how much thinking/reasoning the subagent applies. low = light thinking, medium = moderate, high = deep reasoning, xhigh = extended effort, max = maximum effort, ultra = Codex ultra, ultracode = Claude Code dynamic workflows. Defaults: Claude = high, Codex = medium, Cursor = medium, Opencode = medium."
13422
13483
  ),
13423
- timeout_minutes: z3.number().positive().optional().describe("Timeout in minutes for the subagent to complete (default: 10). Set higher for large tasks to avoid losing work.")
13484
+ timeout_minutes: z4.number().positive().optional().describe("Timeout in minutes for the subagent to complete (default: 10). Set higher for large tasks to avoid losing work.")
13424
13485
  },
13425
13486
  async (args) => {
13426
13487
  try {
@@ -13458,7 +13519,7 @@ var deleteAgentTool = tool(
13458
13519
  "delete_agent",
13459
13520
  `Delete a subagent chat to free resources. Use this after a subagent has completed its work and you no longer need to send it follow-up messages.`,
13460
13521
  {
13461
- chatId: z3.string().describe("The chat ID of the subagent to delete.")
13522
+ chatId: z4.string().describe("The chat ID of the subagent to delete.")
13462
13523
  },
13463
13524
  async (args) => {
13464
13525
  try {
@@ -14234,7 +14295,11 @@ function normalizePersistedChat(chat) {
14234
14295
  providerSessionId: isLegacyCodexSdkChat ? null : chat.providerSessionId,
14235
14296
  parentChatId: chat.parentChatId ?? null,
14236
14297
  lastMessageText: chat.lastMessageText ?? null,
14237
- deletedAt: chat.deletedAt ?? null
14298
+ deletedAt: chat.deletedAt ?? null,
14299
+ acceptedSendResponses: isRecord4(chat.acceptedSendResponses) ? Object.fromEntries(Object.entries(chat.acceptedSendResponses).filter((entry) => {
14300
+ const response = entry[1];
14301
+ return isRecord4(response) && typeof response.messageId === "string" && typeof response.queued === "boolean" && typeof response.position === "number";
14302
+ })) : {}
14238
14303
  };
14239
14304
  }
14240
14305
  function parsePersistedChatsContent(content) {
@@ -14247,20 +14312,6 @@ function parsePersistedChatsContent(content) {
14247
14312
  function corruptChatsFilePath() {
14248
14313
  return `${CHATS_FILE}.corrupt-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
14249
14314
  }
14250
- function createUserMessageEvent(message, messageId, images) {
14251
- const eventImages = imageContentToUserMessageImages(images);
14252
- return {
14253
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
14254
- type: "event_msg",
14255
- payload: {
14256
- type: "user_message",
14257
- message,
14258
- source: ACCEPTED_USER_MESSAGE_SOURCE,
14259
- [USER_MESSAGE_ID_PAYLOAD_KEY]: messageId,
14260
- ...eventImages ? { images: eventImages } : {}
14261
- }
14262
- };
14263
- }
14264
14315
  var ChatService = class {
14265
14316
  constructor(workingDirectory) {
14266
14317
  this.workingDirectory = workingDirectory;
@@ -14323,17 +14374,30 @@ var ChatService = class {
14323
14374
  };
14324
14375
  }
14325
14376
  async createChat(request) {
14326
- const now = (/* @__PURE__ */ new Date()).toISOString();
14377
+ const now = request.createdAt ?? (/* @__PURE__ */ new Date()).toISOString();
14327
14378
  const title = request.title?.trim() || `${request.provider} chat`;
14328
- if (isDefaultChat({ provider: request.provider, title }) && Array.from(this.chats.values()).some((chat) => !chat.persisted.deletedAt && chat.persisted.provider === request.provider && isDefaultChat(chat.persisted))) {
14329
- throw new DuplicateDefaultChatError(request.provider);
14379
+ if (request.id) {
14380
+ const existing = this.chats.get(request.id);
14381
+ if (existing && !existing.persisted.deletedAt) {
14382
+ if (existing.persisted.provider !== request.provider) {
14383
+ throw new Error(`Chat ${request.id} already exists with a different provider`);
14384
+ }
14385
+ return this.toSummary(existing);
14386
+ }
14387
+ }
14388
+ const existingDefault = isDefaultChat({ provider: request.provider, title }) ? Array.from(this.chats.values()).find((chat) => !chat.persisted.deletedAt && chat.persisted.provider === request.provider && isDefaultChat(chat.persisted)) : void 0;
14389
+ if (existingDefault) {
14390
+ if (!request.id || hasChatStarted(this.toSummary(existingDefault))) {
14391
+ throw new DuplicateDefaultChatError(request.provider);
14392
+ }
14393
+ this.chats.delete(existingDefault.persisted.id);
14330
14394
  }
14331
14395
  const parentChatId = request.parentChatId ?? null;
14332
14396
  if (parentChatId && !this.chats.has(parentChatId)) {
14333
14397
  throw new ChatNotFoundError(parentChatId);
14334
14398
  }
14335
14399
  const persisted = {
14336
- id: randomUUID5(),
14400
+ id: request.id ?? randomUUID5(),
14337
14401
  provider: request.provider,
14338
14402
  title,
14339
14403
  createdAt: now,
@@ -14368,8 +14432,14 @@ var ChatService = class {
14368
14432
  if (chat.acceptedSendResponses.size <= MAX_ACCEPTED_SEND_RESPONSES) break;
14369
14433
  chat.acceptedSendResponses.delete(key);
14370
14434
  }
14435
+ chat.persisted.acceptedSendResponses = Object.fromEntries(chat.acceptedSendResponses);
14371
14436
  }
14372
- const acceptedEvent = createUserMessageEvent(request.message, result.messageId, request.images);
14437
+ const acceptedEvent = createAcceptedUserMessageEvent(
14438
+ request.message,
14439
+ result.messageId,
14440
+ request.submittedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
14441
+ request.images
14442
+ );
14373
14443
  chat.pendingMessageIds.push(result.messageId);
14374
14444
  if (request.errorNotificationTarget) {
14375
14445
  chat.errorNotificationTargets.set(result.messageId, request.errorNotificationTarget);
@@ -14384,7 +14454,7 @@ var ChatService = class {
14384
14454
  senderEmail: request.senderEmail,
14385
14455
  ...request.senderDisplayName ? { senderDisplayName: request.senderDisplayName } : {},
14386
14456
  ...request.senderAvatarUrl ? { senderAvatarUrl: request.senderAvatarUrl } : {},
14387
- recordedAt: (/* @__PURE__ */ new Date()).toISOString()
14457
+ recordedAt: request.submittedAt ?? (/* @__PURE__ */ new Date()).toISOString()
14388
14458
  };
14389
14459
  await this.appendSender(chatId, recordedSender);
14390
14460
  }
@@ -14736,7 +14806,7 @@ var ChatService = class {
14736
14806
  provider,
14737
14807
  pendingMessageIds: [],
14738
14808
  acceptedUserEvents: /* @__PURE__ */ new Map(),
14739
- acceptedSendResponses: /* @__PURE__ */ new Map(),
14809
+ acceptedSendResponses: new Map(Object.entries(persisted.acceptedSendResponses ?? {})),
14740
14810
  activeMessageId: null,
14741
14811
  hasActiveTurn: false,
14742
14812
  observedBranchesByRepo: /* @__PURE__ */ new Map(),
@@ -15275,7 +15345,7 @@ var RepoFileService = class {
15275
15345
 
15276
15346
  // src/v1-routes.ts
15277
15347
  import { Hono } from "hono";
15278
- import { z as z4 } from "zod";
15348
+ import { z as z5 } from "zod";
15279
15349
  import { readdir as readdir9, stat as stat5, readFile as readFile18 } from "fs/promises";
15280
15350
  import { join as join26, resolve as resolve3 } from "path";
15281
15351
 
@@ -15783,79 +15853,75 @@ var TerminalService = class {
15783
15853
  var terminalService = new TerminalService();
15784
15854
 
15785
15855
  // src/v1-routes.ts
15786
- var setWorkspaceNameSchema = z4.object({
15787
- name: z4.string().min(1).max(48)
15788
- });
15789
- var createChatSchema = z4.object({
15790
- provider: z4.enum(["claude", "codex", "cursor", "opencode", "pi", "relay"]),
15791
- title: z4.string().min(1).optional(),
15792
- parentChatId: z4.string().uuid().optional(),
15793
- clientRequestId: z4.string().min(1).max(128).optional()
15856
+ var setWorkspaceNameSchema = z5.object({
15857
+ name: z5.string().min(1).max(48)
15794
15858
  });
15795
- var imageMediaTypeSchema = z4.enum(IMAGE_MEDIA_TYPES);
15796
- var createPreviewSchema = z4.object({
15797
- port: z4.number().int().min(1).max(65535),
15798
- publicUrl: z4.string().min(1)
15859
+ var imageMediaTypeSchema = z5.enum(IMAGE_MEDIA_TYPES);
15860
+ var createPreviewSchema = z5.object({
15861
+ port: z5.number().int().min(1).max(65535),
15862
+ publicUrl: z5.string().min(1)
15799
15863
  });
15800
- var terminalSizeSchema = z4.object({
15801
- cols: z4.number().int().min(2).max(500),
15802
- rows: z4.number().int().min(1).max(200)
15864
+ var terminalSizeSchema = z5.object({
15865
+ cols: z5.number().int().min(2).max(500),
15866
+ rows: z5.number().int().min(1).max(200)
15803
15867
  });
15804
- var writeTerminalSessionSchema = z4.object({
15805
- data: z4.string().max(64 * 1024),
15806
- generation: z4.number().int().nonnegative(),
15807
- sequence: z4.number().int().nonnegative()
15868
+ var writeTerminalSessionSchema = z5.object({
15869
+ data: z5.string().max(64 * 1024),
15870
+ generation: z5.number().int().nonnegative(),
15871
+ sequence: z5.number().int().nonnegative()
15808
15872
  });
15809
- var sendMessageSchema = z4.object({
15810
- message: z4.string().min(1),
15811
- model: z4.string().optional(),
15812
- customInstructions: z4.string().optional(),
15813
- planMode: z4.boolean().optional(),
15814
- images: z4.array(z4.object({
15815
- type: z4.literal("image"),
15816
- source: z4.union([
15817
- z4.object({
15818
- type: z4.literal("base64"),
15873
+ var sendMessageSchema = z5.object({
15874
+ messageId: z5.string().min(1).optional(),
15875
+ submittedAt: z5.string().datetime().optional(),
15876
+ message: z5.string().min(1),
15877
+ model: z5.string().optional(),
15878
+ customInstructions: z5.string().optional(),
15879
+ planMode: z5.boolean().optional(),
15880
+ images: z5.array(z5.object({
15881
+ type: z5.literal("image"),
15882
+ source: z5.union([
15883
+ z5.object({
15884
+ type: z5.literal("base64"),
15819
15885
  media_type: imageMediaTypeSchema,
15820
- data: z4.string().min(1)
15886
+ data: z5.string().min(1)
15821
15887
  }),
15822
- z4.object({
15823
- type: z4.literal("url"),
15824
- url: z4.string().url()
15888
+ z5.object({
15889
+ type: z5.literal("url"),
15890
+ url: z5.string().url()
15825
15891
  })
15826
15892
  ])
15827
15893
  })).optional(),
15828
- thinkingLevel: z4.enum(VALID_THINKING_LEVELS).optional(),
15829
- goalMode: z4.boolean().optional(),
15830
- fastMode: z4.boolean().optional(),
15831
- enableInteractiveTools: z4.boolean().optional(),
15832
- type: z4.string().min(1).optional(),
15833
- merge: z4.boolean().optional(),
15834
- idempotencyKey: z4.string().min(1).max(128).optional(),
15835
- senderUserId: z4.string().optional(),
15836
- senderEmail: z4.string().optional(),
15837
- senderDisplayName: z4.string().optional(),
15838
- senderAvatarUrl: z4.string().optional(),
15839
- errorNotificationTarget: z4.discriminatedUnion("type", [
15840
- z4.object({ type: z4.literal("slack") }),
15841
- z4.object({ type: z4.literal("linear"), sessionId: z4.string().min(1) }),
15842
- z4.object({
15843
- type: z4.literal("code_host"),
15844
- provider: z4.enum(["github", "gitlab"]),
15845
- resource: z4.enum(["issue", "pull_request"]),
15846
- repositoryId: z4.string().min(1),
15847
- resourceNumber: z4.number().int().positive()
15894
+ thinkingLevel: z5.enum(VALID_THINKING_LEVELS).optional(),
15895
+ goalMode: z5.boolean().optional(),
15896
+ fastMode: z5.boolean().optional(),
15897
+ enableInteractiveTools: z5.boolean().optional(),
15898
+ type: z5.string().min(1).optional(),
15899
+ merge: z5.boolean().optional(),
15900
+ idempotencyKey: z5.string().min(1).max(128).optional(),
15901
+ senderUserId: z5.string().optional(),
15902
+ senderEmail: z5.string().optional(),
15903
+ senderDisplayName: z5.string().optional(),
15904
+ senderAvatarUrl: z5.string().optional(),
15905
+ errorNotificationTarget: z5.discriminatedUnion("type", [
15906
+ z5.object({ type: z5.literal("slack") }),
15907
+ z5.object({ type: z5.literal("linear"), sessionId: z5.string().min(1) }),
15908
+ z5.object({
15909
+ type: z5.literal("code_host"),
15910
+ provider: z5.enum(["github", "gitlab"]),
15911
+ resource: z5.enum(["issue", "pull_request"]),
15912
+ repositoryId: z5.string().min(1),
15913
+ resourceNumber: z5.number().int().positive()
15848
15914
  }),
15849
- z4.object({ type: z4.literal("automation"), executionId: z4.string().min(1) })
15915
+ z5.object({ type: z5.literal("automation"), executionId: z5.string().min(1) })
15850
15916
  ]).optional()
15851
15917
  });
15852
- var respondToolInputSchema = z4.object({
15853
- requestId: z4.string().min(1),
15854
- selectionId: z4.string().min(1)
15918
+ var respondToolInputSchema = z5.object({
15919
+ requestId: z5.string().min(1),
15920
+ selectionId: z5.string().min(1)
15855
15921
  });
15856
- var updateGoalSchema = z4.object({
15857
- objective: z4.string().trim().min(1).max(MAX_CODEX_GOAL_OBJECTIVE_CHARS).optional(),
15858
- status: z4.enum(["active", "paused"]).optional()
15922
+ var updateGoalSchema = z5.object({
15923
+ objective: z5.string().trim().min(1).max(MAX_CODEX_GOAL_OBJECTIVE_CHARS).optional(),
15924
+ status: z5.enum(["active", "paused"]).optional()
15859
15925
  }).refine((body) => body.objective !== void 0 || body.status !== void 0, {
15860
15926
  message: "Goal objective or status required"
15861
15927
  });
@@ -15937,7 +16003,7 @@ function createV1Routes(deps) {
15937
16003
  });
15938
16004
  app2.post("/chats", async (c) => {
15939
16005
  try {
15940
- const body = createChatSchema.parse(await c.req.json());
16006
+ const body = createChatRequestSchema.parse(await c.req.json());
15941
16007
  const chat = await deps.chatService.createChat(body);
15942
16008
  const response = { chat };
15943
16009
  return c.json(response, 201);
@@ -16046,7 +16112,7 @@ function createV1Routes(deps) {
16046
16112
  const result = await deps.chatService.updateGoal(c.req.param("chatId"), body);
16047
16113
  return c.json(result);
16048
16114
  } catch (error) {
16049
- if (error instanceof z4.ZodError) {
16115
+ if (error instanceof z5.ZodError) {
16050
16116
  return c.json(jsonError(error.issues[0]?.message || "Invalid goal update"), 400);
16051
16117
  }
16052
16118
  if (error instanceof ChatNotFoundError) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.469",
3
+ "version": "0.1.471",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",