replicas-engine 0.1.220 → 0.1.222

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 +222 -78
  2. package/package.json +1 -1
package/dist/src/index.js CHANGED
@@ -32,6 +32,9 @@ function codexReasoningEffortForThinkingLevel(thinkingLevel) {
32
32
  }
33
33
 
34
34
  // ../shared/src/event.ts
35
+ var ACCEPTED_USER_MESSAGE_SOURCE = "replicas-chat-turn-accepted";
36
+ var USER_MESSAGE_ID_PAYLOAD_KEY = "replicasMessageId";
37
+ var CODEX_ASP_ITEM_ID_PAYLOAD_KEY = "codexAspItemId";
35
38
  var CODEX_QUOTA_STATUS_EVENT_TYPE = "codex-quota-status";
36
39
  var COMPACTION_STATUS_EVENT_TYPE = "compaction-status";
37
40
  var CHAT_GOAL_EVENT_TYPE = "chat-goal";
@@ -707,8 +710,8 @@ gh pr review 123 --request-changes --body "Changes needed"
707
710
  GitHub does NOT have a public API for uploading images to PRs/issues. When you need to include images:
708
711
  - Do NOT use placeholder image URLs
709
712
  - Do NOT commit screenshots as files to the repository
710
- - Upload images to Imgur (or another external host) and use the returned URLs in your PR markdown
711
- - If you were triggered from Slack, also upload the images to the Slack thread so the user can see them directly
713
+ - Use the \`replicas-agent\` skill to share the image
714
+ - If you were triggered from Slack, also upload the image to the Slack thread so the user can see it directly
712
715
  `;
713
716
  var GITHUB_ABILITY = {
714
717
  label: "GitHub",
@@ -1156,10 +1159,9 @@ After (or alongside) the embeds, include a \`[View in Replicas](<deep-link>)\` h
1156
1159
 
1157
1160
  ### On external platforms (Slack, Linear, GitHub, etc.)
1158
1161
 
1159
- Do **both** of these \u2014 neither alone is sufficient:
1162
+ Always include a \`[View in Replicas](<deep-link>)\` hyperlink \u2014 use the per-file deep link the CLI printed for that file (\`...?mode=media&media=<media-id>\`), so the recipient lands directly on that specific item.
1160
1163
 
1161
- 1. Upload the raw bytes via that platform's own upload API (Slack \`files.upload\`, Linear attachments, Imgur for GitHub PR/issue images, etc.) so the recipient actually sees the media.
1162
- 2. Include a \`[View in Replicas](<deep-link>)\` hyperlink \u2014 use the per-file deep link the CLI printed for that file (\`...?mode=media&media=<media-id>\`), so the recipient lands directly on that specific item.
1164
+ For platforms that support native uploads (Slack \`files.upload\`, Linear attachments, etc.), upload the raw bytes there too so the recipient sees the media inline in addition to the Replicas link. GitHub PRs/issues do not have a public upload API, so the \`View in Replicas\` link is the canonical way to share media there \u2014 do not commit screenshots to the repo and do not use placeholder URLs.
1163
1165
 
1164
1166
  Do **not** include the \`![filename](https://api.tryreplicas.com/...)\` markdown embed in external messages. It will render as a broken image / 401 for the recipient.
1165
1167
 
@@ -1205,7 +1207,7 @@ replicas media upload chart-a.svg chart-b.svg --kind image
1205
1207
  var MEDIA_ABILITY = {
1206
1208
  label: "Media",
1207
1209
  description: "Share screenshots, recordings, generated images, and audio clips.",
1208
- bullet: "- Sharing media (screenshots, screen recordings, generated diagrams, audio clips) \u2014 including in your Replicas chat reply and to external platforms",
1210
+ bullet: "- Producing or showing the user any media \u2014 screenshots, screen recordings, generated images or diagrams, audio clips \u2014 including in your Replicas chat reply, PR descriptions/comments, and other external platforms",
1209
1211
  section: SECTION6,
1210
1212
  referenceFile: { name: "MEDIA.md", content: REFERENCE6 }
1211
1213
  };
@@ -1761,7 +1763,7 @@ function parseReplicasConfigString(content, filename) {
1761
1763
  }
1762
1764
 
1763
1765
  // ../shared/src/engine/environment.ts
1764
- var DAYTONA_SNAPSHOT_ID = "27-05-2026-royal-york-v2";
1766
+ var DAYTONA_SNAPSHOT_ID = "27-05-2026-royal-york-v6";
1765
1767
 
1766
1768
  // ../shared/src/engine/types.ts
1767
1769
  var DEFAULT_CHAT_TITLES = {
@@ -1814,6 +1816,12 @@ function codexAuthEnvFromResponse(response) {
1814
1816
  return { OPENAI_API_KEY: response.apiKey, REPLICAS_CODEX_AUTH_METHOD: "api_key" };
1815
1817
  }
1816
1818
  }
1819
+ var CODEX_AUTH_ENV_KEYS_BY_METHOD = {
1820
+ none: [],
1821
+ oauth: ["REPLICAS_CODEX_AUTH_METHOD"],
1822
+ api_key: ["OPENAI_API_KEY", "REPLICAS_CODEX_AUTH_METHOD"],
1823
+ bedrock: []
1824
+ };
1817
1825
 
1818
1826
  // ../shared/src/routes/claude.ts
1819
1827
  var CLAUDE_AUTH_ENV_KEYS = [
@@ -1840,6 +1848,18 @@ function claudeAuthEnvFromResponse(response) {
1840
1848
  };
1841
1849
  }
1842
1850
  }
1851
+ var CLAUDE_AUTH_ENV_KEYS_BY_METHOD = {
1852
+ none: [],
1853
+ oauth: ["REPLICAS_CLAUDE_AUTH_METHOD"],
1854
+ api_key: ["ANTHROPIC_API_KEY", "REPLICAS_CLAUDE_AUTH_METHOD"],
1855
+ bedrock: [
1856
+ "CLAUDE_CODE_USE_BEDROCK",
1857
+ "AWS_ACCESS_KEY_ID",
1858
+ "AWS_SECRET_ACCESS_KEY",
1859
+ "AWS_REGION",
1860
+ "REPLICAS_CLAUDE_AUTH_METHOD"
1861
+ ]
1862
+ };
1843
1863
 
1844
1864
  // ../shared/src/routes/workspaces.ts
1845
1865
  var WORKSPACE_FILE_UPLOAD_MAX_SIZE_BYTES = 20 * 1024 * 1024;
@@ -1863,6 +1883,50 @@ var MEDIA_KIND = {
1863
1883
  };
1864
1884
  var MEDIA_KINDS = [MEDIA_KIND.IMAGE, MEDIA_KIND.VIDEO, MEDIA_KIND.AUDIO];
1865
1885
 
1886
+ // ../shared/src/agent-event-utils.ts
1887
+ function getUserMessage(event) {
1888
+ return event.type === "event_msg" && event.payload.type === "user_message" && typeof event.payload.message === "string" ? event.payload.message : null;
1889
+ }
1890
+ function getUserMessageId(event) {
1891
+ const messageId = event.payload[USER_MESSAGE_ID_PAYLOAD_KEY];
1892
+ return typeof messageId === "string" ? messageId : null;
1893
+ }
1894
+ function getUserMessageItemId(event) {
1895
+ const itemId = event.payload[CODEX_ASP_ITEM_ID_PAYLOAD_KEY];
1896
+ return typeof itemId === "string" ? itemId : null;
1897
+ }
1898
+ function getEventTimestampMs(event) {
1899
+ const value = Date.parse(event.timestamp);
1900
+ return Number.isNaN(value) ? 0 : value;
1901
+ }
1902
+ function areSameUserMessageEvents(a, b) {
1903
+ const aMessage = getUserMessage(a);
1904
+ const bMessage = getUserMessage(b);
1905
+ if (!aMessage || aMessage !== bMessage) return false;
1906
+ const aMessageId = getUserMessageId(a);
1907
+ const bMessageId = getUserMessageId(b);
1908
+ if (aMessageId || bMessageId) return aMessageId === bMessageId;
1909
+ const aItemId = getUserMessageItemId(a);
1910
+ const bItemId = getUserMessageItemId(b);
1911
+ if (aItemId || bItemId) return aItemId === bItemId;
1912
+ return Math.abs(getEventTimestampMs(a) - getEventTimestampMs(b)) <= 3e4;
1913
+ }
1914
+ function mergeAgentEvents(primary, supplemental, options) {
1915
+ const merged = [...primary];
1916
+ const { areDuplicates, mergeEvent } = options;
1917
+ for (const event of supplemental) {
1918
+ const existingIndex = merged.findIndex((existing) => areDuplicates(existing, event));
1919
+ if (existingIndex === -1) {
1920
+ merged.push(event);
1921
+ continue;
1922
+ }
1923
+ if (mergeEvent) {
1924
+ merged[existingIndex] = mergeEvent(merged[existingIndex], event);
1925
+ }
1926
+ }
1927
+ return merged;
1928
+ }
1929
+
1866
1930
  // src/runtime-env-loader.ts
1867
1931
  function loadRuntimeEnvFile() {
1868
1932
  let content;
@@ -2108,6 +2172,26 @@ var githubTokenManager = new GitHubTokenManager();
2108
2172
  // src/managers/claude-token-manager.ts
2109
2173
  import { promises as fs2 } from "fs";
2110
2174
  import path2 from "path";
2175
+
2176
+ // src/managers/auth-env-transition.ts
2177
+ function applyAuthEnvTransition(params) {
2178
+ const newOwned = new Set(params.authKeysByMethod[params.newMethod]);
2179
+ const prevOwned = new Set(params.authKeysByMethod[params.prevMethod]);
2180
+ for (const key of params.authKeys) {
2181
+ const value = params.newEnvVars[key];
2182
+ if (value !== void 0) {
2183
+ for (const env of params.envs) {
2184
+ env[key] = value;
2185
+ }
2186
+ } else if (prevOwned.has(key) && !newOwned.has(key)) {
2187
+ for (const env of params.envs) {
2188
+ delete env[key];
2189
+ }
2190
+ }
2191
+ }
2192
+ }
2193
+
2194
+ // src/managers/claude-token-manager.ts
2111
2195
  var ClaudeTokenManager = class extends BaseRefreshManager {
2112
2196
  constructor() {
2113
2197
  super("ClaudeTokenManager");
@@ -2175,34 +2259,14 @@ var ClaudeTokenManager = class extends BaseRefreshManager {
2175
2259
  await this.removeOauthCredentialsFile();
2176
2260
  }
2177
2261
  const envVars = claudeAuthEnvFromResponse(response);
2178
- for (const key of CLAUDE_AUTH_ENV_KEYS) {
2179
- switch (key) {
2180
- case "REPLICAS_CLAUDE_AUTH_METHOD":
2181
- ENGINE_ENV.REPLICAS_CLAUDE_AUTH_METHOD = envVars.REPLICAS_CLAUDE_AUTH_METHOD;
2182
- break;
2183
- case "ANTHROPIC_API_KEY":
2184
- ENGINE_ENV.ANTHROPIC_API_KEY = envVars.ANTHROPIC_API_KEY;
2185
- break;
2186
- case "CLAUDE_CODE_USE_BEDROCK":
2187
- ENGINE_ENV.CLAUDE_CODE_USE_BEDROCK = envVars.CLAUDE_CODE_USE_BEDROCK;
2188
- break;
2189
- case "AWS_ACCESS_KEY_ID":
2190
- ENGINE_ENV.AWS_ACCESS_KEY_ID = envVars.AWS_ACCESS_KEY_ID;
2191
- break;
2192
- case "AWS_SECRET_ACCESS_KEY":
2193
- ENGINE_ENV.AWS_SECRET_ACCESS_KEY = envVars.AWS_SECRET_ACCESS_KEY;
2194
- break;
2195
- case "AWS_REGION":
2196
- ENGINE_ENV.AWS_REGION = envVars.AWS_REGION;
2197
- break;
2198
- }
2199
- const value = envVars[key];
2200
- if (value !== void 0) {
2201
- process.env[key] = value;
2202
- } else {
2203
- delete process.env[key];
2204
- }
2205
- }
2262
+ applyAuthEnvTransition({
2263
+ prevMethod: ENGINE_ENV.REPLICAS_CLAUDE_AUTH_METHOD ?? "none",
2264
+ newMethod: envVars.REPLICAS_CLAUDE_AUTH_METHOD ?? "none",
2265
+ authKeys: CLAUDE_AUTH_ENV_KEYS,
2266
+ authKeysByMethod: CLAUDE_AUTH_ENV_KEYS_BY_METHOD,
2267
+ newEnvVars: envVars,
2268
+ envs: [ENGINE_ENV, process.env]
2269
+ });
2206
2270
  }
2207
2271
  async writeOauthCredentialsFile(credentials) {
2208
2272
  const workspaceHome = ENGINE_ENV.HOME_DIR;
@@ -2301,22 +2365,14 @@ var CodexTokenManager = class extends BaseRefreshManager {
2301
2365
  await this.removeOauthCredentialsFile();
2302
2366
  }
2303
2367
  const envVars = codexAuthEnvFromResponse(response);
2304
- for (const key of CODEX_AUTH_ENV_KEYS) {
2305
- switch (key) {
2306
- case "REPLICAS_CODEX_AUTH_METHOD":
2307
- ENGINE_ENV.REPLICAS_CODEX_AUTH_METHOD = envVars.REPLICAS_CODEX_AUTH_METHOD;
2308
- break;
2309
- case "OPENAI_API_KEY":
2310
- ENGINE_ENV.OPENAI_API_KEY = envVars.OPENAI_API_KEY;
2311
- break;
2312
- }
2313
- const value = envVars[key];
2314
- if (value !== void 0) {
2315
- process.env[key] = value;
2316
- } else {
2317
- delete process.env[key];
2318
- }
2319
- }
2368
+ applyAuthEnvTransition({
2369
+ prevMethod: ENGINE_ENV.REPLICAS_CODEX_AUTH_METHOD ?? "none",
2370
+ newMethod: envVars.REPLICAS_CODEX_AUTH_METHOD ?? "none",
2371
+ authKeys: CODEX_AUTH_ENV_KEYS,
2372
+ authKeysByMethod: CODEX_AUTH_ENV_KEYS_BY_METHOD,
2373
+ newEnvVars: envVars,
2374
+ envs: [ENGINE_ENV, process.env]
2375
+ });
2320
2376
  }
2321
2377
  async writeOauthCredentialsFile(credentials) {
2322
2378
  const workspaceHome = ENGINE_ENV.HOME_DIR;
@@ -6265,21 +6321,27 @@ function itemToAgentEventDrafts(item, lifecycle) {
6265
6321
  }
6266
6322
  function threadToHistoryEvents(thread) {
6267
6323
  const events = [];
6268
- for (const turn of thread.turns) {
6324
+ const turns = thread.turns.map((turn, index) => ({ turn, index })).sort((a, b) => (a.turn.startedAt ?? a.turn.completedAt ?? Number.MAX_SAFE_INTEGER) - (b.turn.startedAt ?? b.turn.completedAt ?? Number.MAX_SAFE_INTEGER) || a.index - b.index);
6325
+ for (const { turn } of turns) {
6269
6326
  const startedAt = timestampFromSeconds(turn.startedAt);
6270
6327
  const completedAt = timestampFromSeconds(turn.completedAt ?? turn.startedAt);
6271
- for (const item of turn.items) {
6272
- if (item.type === "userMessage") {
6273
- const message = item.content.filter((input) => input.type === "text").map((input) => input.text).join("\n");
6274
- if (message) {
6275
- events.push({
6276
- timestamp: startedAt,
6277
- type: "event_msg",
6278
- payload: { type: "user_message", message }
6279
- });
6280
- }
6281
- continue;
6328
+ const userMessages = turn.items.filter((item) => item.type === "userMessage");
6329
+ const agentItems = turn.items.filter((item) => item.type !== "userMessage");
6330
+ for (const item of userMessages) {
6331
+ const message = item.content.filter((input) => input.type === "text").map((input) => input.text).join("\n");
6332
+ if (message) {
6333
+ events.push({
6334
+ timestamp: startedAt,
6335
+ type: "event_msg",
6336
+ payload: {
6337
+ type: "user_message",
6338
+ message,
6339
+ [CODEX_ASP_ITEM_ID_PAYLOAD_KEY]: item.id
6340
+ }
6341
+ });
6282
6342
  }
6343
+ }
6344
+ for (const item of agentItems) {
6283
6345
  for (const draft of itemToAgentEventDrafts(item, "started")) {
6284
6346
  events.push({ timestamp: startedAt, ...draft });
6285
6347
  }
@@ -6380,6 +6442,49 @@ function dispatchAspNotification(notification, handlers) {
6380
6442
  }
6381
6443
 
6382
6444
  // src/managers/codex-asp/codex-asp-manager.ts
6445
+ function historyEventKey(event) {
6446
+ const payloadType = typeof event.payload.type === "string" ? event.payload.type : null;
6447
+ const callId = typeof event.payload.call_id === "string" ? event.payload.call_id : null;
6448
+ const itemId = typeof event.payload[CODEX_ASP_ITEM_ID_PAYLOAD_KEY] === "string" ? event.payload[CODEX_ASP_ITEM_ID_PAYLOAD_KEY] : null;
6449
+ const messageId = typeof event.payload[USER_MESSAGE_ID_PAYLOAD_KEY] === "string" ? event.payload[USER_MESSAGE_ID_PAYLOAD_KEY] : null;
6450
+ if (event.type === "response_item" && payloadType && callId) {
6451
+ return `${event.type}:${payloadType}:${callId}`;
6452
+ }
6453
+ if (event.type === "event_msg" && event.payload.type === "user_message") {
6454
+ if (messageId) return `${event.type}:user_message:message:${messageId}`;
6455
+ if (itemId) return `${event.type}:user_message:item:${itemId}`;
6456
+ const command = typeof event.payload.command === "string" ? event.payload.command : "";
6457
+ const message = typeof event.payload.message === "string" ? event.payload.message : "";
6458
+ return `${event.type}:user_message:${event.timestamp}:${command}:${message}`;
6459
+ }
6460
+ if (itemId && payloadType) {
6461
+ return `${event.type}:${payloadType}:item:${itemId}`;
6462
+ }
6463
+ return `${event.type}:${event.timestamp}:${JSON.stringify(event.payload)}`;
6464
+ }
6465
+ function areDuplicateHistoryEvents(a, b) {
6466
+ if (historyEventKey(a) === historyEventKey(b)) return true;
6467
+ return areSameUserMessageEvents(a, b);
6468
+ }
6469
+ function mergeHistoryEvent(current, candidate) {
6470
+ if (getUserMessage(current) && getUserMessage(current) === getUserMessage(candidate)) {
6471
+ return {
6472
+ ...current,
6473
+ timestamp: getEventTimestampMs(current) <= getEventTimestampMs(candidate) ? current.timestamp : candidate.timestamp,
6474
+ payload: {
6475
+ ...current.payload,
6476
+ ...candidate.payload
6477
+ }
6478
+ };
6479
+ }
6480
+ return candidate;
6481
+ }
6482
+ function mergeHistoryEvents(primary, supplemental) {
6483
+ return mergeAgentEvents(primary, supplemental, {
6484
+ areDuplicates: areDuplicateHistoryEvents,
6485
+ mergeEvent: mergeHistoryEvent
6486
+ });
6487
+ }
6383
6488
  var CodexAspManager = class extends CodingAgentManager {
6384
6489
  currentThreadId = null;
6385
6490
  activeTurnId = null;
@@ -6425,15 +6530,13 @@ var CodexAspManager = class extends CodingAgentManager {
6425
6530
  ),
6426
6531
  this.refreshThreadGoal(host, this.currentThreadId)
6427
6532
  ]);
6428
- const events = threadToHistoryEvents(response.thread);
6429
- if (events.length > 0) {
6430
- const supplementalEvents = this.historyEvents.filter((event) => event.type === CODEX_QUOTA_STATUS_EVENT_TYPE || event.type === CONTEXT_USAGE_EVENT_TYPE || event.type === CHAT_GOAL_EVENT_TYPE || event.type === "event_msg" && event.payload.command === "goal");
6431
- return {
6432
- thread_id: this.currentThreadId,
6433
- events: [...events, ...supplementalEvents].sort((a, b) => a.timestamp.localeCompare(b.timestamp)),
6434
- goal: this.currentGoal
6435
- };
6436
- }
6533
+ const events = mergeHistoryEvents(this.historyEvents, threadToHistoryEvents(response.thread));
6534
+ this.historyEvents.splice(0, this.historyEvents.length, ...events);
6535
+ return {
6536
+ thread_id: this.currentThreadId,
6537
+ events,
6538
+ goal: this.currentGoal
6539
+ };
6437
6540
  } catch {
6438
6541
  }
6439
6542
  return {
@@ -6492,8 +6595,8 @@ var CodexAspManager = class extends CodingAgentManager {
6492
6595
  async executeGoalClearCommand(request, recordUserMessage) {
6493
6596
  const host = await getCodexAspHost();
6494
6597
  const developerInstructions = this.buildCombinedInstructions(request.customInstructions);
6495
- const threadId = await this.ensureThread(host, request, developerInstructions);
6496
6598
  recordUserMessage({ command: "goal" });
6599
+ const threadId = await this.ensureThread(host, request, developerInstructions);
6497
6600
  await host.client.request(
6498
6601
  THREAD_GOAL_CLEAR_METHOD,
6499
6602
  { threadId }
@@ -6511,8 +6614,8 @@ var CodexAspManager = class extends CodingAgentManager {
6511
6614
  }
6512
6615
  }
6513
6616
  const developerInstructions = this.buildCombinedInstructions(request.customInstructions);
6514
- const threadId = await this.ensureThread(host, request, developerInstructions);
6515
6617
  recordUserMessage(options.userMessagePayload);
6618
+ const threadId = await this.ensureThread(host, request, developerInstructions);
6516
6619
  const runTurn = options.runTurn ?? ((aspHost, aspThreadId, aspInstructions) => this.runTurn(aspHost, aspThreadId, request, aspInstructions));
6517
6620
  let completedTurn;
6518
6621
  try {
@@ -6747,8 +6850,7 @@ var CodexAspManager = class extends CodingAgentManager {
6747
6850
  }
6748
6851
  seedHistoryFromThread(thread) {
6749
6852
  const events = threadToHistoryEvents(thread);
6750
- if (events.length === 0) return;
6751
- this.historyEvents.splice(0, this.historyEvents.length, ...events);
6853
+ this.historyEvents.splice(0, this.historyEvents.length, ...mergeHistoryEvents(this.historyEvents, events));
6752
6854
  }
6753
6855
  async refreshThreadGoal(host, threadId) {
6754
6856
  try {
@@ -7457,6 +7559,19 @@ function isPersistedChat(value) {
7457
7559
  const candidate = value;
7458
7560
  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");
7459
7561
  }
7562
+ function createUserMessageEvent(message, messageId) {
7563
+ return {
7564
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
7565
+ type: "event_msg",
7566
+ payload: {
7567
+ type: "user_message",
7568
+ message,
7569
+ source: ACCEPTED_USER_MESSAGE_SOURCE,
7570
+ [USER_MESSAGE_ID_PAYLOAD_KEY]: messageId
7571
+ }
7572
+ };
7573
+ }
7574
+ var isSameUserMessageEvent = areSameUserMessageEvents;
7460
7575
  var ChatService = class {
7461
7576
  constructor(workingDirectory) {
7462
7577
  this.workingDirectory = workingDirectory;
@@ -7534,7 +7649,9 @@ var ChatService = class {
7534
7649
  async sendMessage(chatId, request) {
7535
7650
  const chat = this.requireChat(chatId);
7536
7651
  const result = await chat.provider.enqueueMessage(request);
7652
+ const acceptedEvent = createUserMessageEvent(request.message, result.messageId);
7537
7653
  chat.pendingMessageIds.push(result.messageId);
7654
+ chat.acceptedUserEvents.set(result.messageId, acceptedEvent);
7538
7655
  this.touch(chat);
7539
7656
  let recordedSender;
7540
7657
  if (request.senderUserId && request.senderEmail) {
@@ -7553,6 +7670,7 @@ var ChatService = class {
7553
7670
  messageId: result.messageId,
7554
7671
  queued: result.queued,
7555
7672
  position: result.position,
7673
+ event: acceptedEvent,
7556
7674
  ...recordedSender ? { sender: recordedSender } : {}
7557
7675
  }
7558
7676
  });
@@ -7599,8 +7717,10 @@ var ChatService = class {
7599
7717
  const chat = this.requireChat(chatId);
7600
7718
  const result = await chat.provider.interrupt();
7601
7719
  chat.hasActiveTurn = false;
7720
+ chat.activeMessageId = null;
7602
7721
  keepAliveService.stop();
7603
7722
  chat.pendingMessageIds = [];
7723
+ chat.acceptedUserEvents.clear();
7604
7724
  this.touch(chat);
7605
7725
  await this.publish({
7606
7726
  type: "chat.interrupted",
@@ -7690,9 +7810,10 @@ var ChatService = class {
7690
7810
  chat.provider.getHistory(),
7691
7811
  this.readSenders(chatId)
7692
7812
  ]);
7813
+ const acceptedEvents = [...chat.acceptedUserEvents.values()].filter((acceptedEvent) => !history.events.some((event) => isSameUserMessageEvent(event, acceptedEvent)));
7693
7814
  return {
7694
7815
  thread_id: history.thread_id,
7695
- events: history.events,
7816
+ events: [...history.events, ...acceptedEvents],
7696
7817
  goal: history.goal ?? chat.provider.getGoal?.() ?? null,
7697
7818
  senders
7698
7819
  };
@@ -7760,6 +7881,8 @@ var ChatService = class {
7760
7881
  persisted,
7761
7882
  provider,
7762
7883
  pendingMessageIds: [],
7884
+ acceptedUserEvents: /* @__PURE__ */ new Map(),
7885
+ activeMessageId: null,
7763
7886
  hasActiveTurn: false,
7764
7887
  observedBranchesByRepo: /* @__PURE__ */ new Map()
7765
7888
  };
@@ -7801,6 +7924,7 @@ var ChatService = class {
7801
7924
  return;
7802
7925
  }
7803
7926
  chat.hasActiveTurn = true;
7927
+ chat.activeMessageId = messageId;
7804
7928
  keepAliveService.start();
7805
7929
  this.publish({
7806
7930
  type: "chat.turn.started",
@@ -7811,6 +7935,25 @@ var ChatService = class {
7811
7935
  }).catch(() => {
7812
7936
  });
7813
7937
  }
7938
+ let eventToPublish = event;
7939
+ if (event.type === "event_msg" && event.payload.type === "user_message") {
7940
+ if (chat.activeMessageId) {
7941
+ event.payload[USER_MESSAGE_ID_PAYLOAD_KEY] = chat.activeMessageId;
7942
+ eventToPublish = {
7943
+ ...event,
7944
+ payload: {
7945
+ ...event.payload,
7946
+ [USER_MESSAGE_ID_PAYLOAD_KEY]: chat.activeMessageId
7947
+ }
7948
+ };
7949
+ }
7950
+ for (const [messageId, acceptedEvent] of chat.acceptedUserEvents) {
7951
+ if (isSameUserMessageEvent(event, acceptedEvent)) {
7952
+ chat.acceptedUserEvents.delete(messageId);
7953
+ break;
7954
+ }
7955
+ }
7956
+ }
7814
7957
  this.touch(chat);
7815
7958
  this.observeCurrentBranches(chat).catch(() => {
7816
7959
  });
@@ -7818,7 +7961,7 @@ var ChatService = class {
7818
7961
  type: "chat.turn.delta",
7819
7962
  payload: {
7820
7963
  chatId,
7821
- event
7964
+ event: eventToPublish
7822
7965
  }
7823
7966
  }).catch(() => {
7824
7967
  });
@@ -7840,6 +7983,7 @@ var ChatService = class {
7840
7983
  return;
7841
7984
  }
7842
7985
  chat.hasActiveTurn = false;
7986
+ chat.activeMessageId = null;
7843
7987
  keepAliveService.stop();
7844
7988
  this.publish({
7845
7989
  type: "chat.turn.completed",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.220",
3
+ "version": "0.1.222",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",