replicas-engine 0.1.1277 → 0.1.1279

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.
@@ -8,12 +8,12 @@ import {
8
8
  listRunningCodexModels,
9
9
  restartCodexAspHost,
10
10
  restartCodexAspHostIfRunning
11
- } from "./chunk-RFV64KYY.js";
12
- import "./chunk-CMBVI5RY.js";
13
- import "./chunk-DOIUIFTK.js";
14
- import "./chunk-JXPRQY2T.js";
11
+ } from "./chunk-VCCUBYU3.js";
12
+ import "./chunk-HUUEA35W.js";
13
+ import "./chunk-GJN2TDJ7.js";
14
+ import "./chunk-CQR3GSM4.js";
15
15
  import "./chunk-UZSNFLDQ.js";
16
- import "./chunk-YQMNXI2D.js";
16
+ import "./chunk-47JJX56J.js";
17
17
  import "./chunk-VEQXQN22.js";
18
18
  export {
19
19
  CODEX_ULTRAFAST_SERVICE_TIER,
@@ -3,7 +3,7 @@ import { createRequire as __createRequire } from 'node:module';
3
3
  const require = __createRequire(import.meta.url);
4
4
  import {
5
5
  AspClient
6
- } from "./chunk-DOIUIFTK.js";
6
+ } from "./chunk-GJN2TDJ7.js";
7
7
  import {
8
8
  ASTER_BASE_URL,
9
9
  ASTER_PROVIDER,
@@ -13,7 +13,7 @@ import {
13
13
  isRecord,
14
14
  readOllamaAuthEnv,
15
15
  readOpenAICompatibleAuthEnv
16
- } from "./chunk-YQMNXI2D.js";
16
+ } from "./chunk-47JJX56J.js";
17
17
 
18
18
  // src/managers/codex-asp/notification-dispatch.ts
19
19
  var TURN_STARTED_METHOD = "turn/started";
@@ -1597,6 +1597,7 @@ var sendChatMessageRequestSchema = z4.object({
1597
1597
  messageId: z4.string().min(1).optional(),
1598
1598
  submittedAt: z4.string().datetime().optional(),
1599
1599
  message: z4.string().min(1),
1600
+ provider: z4.enum(VALID_AGENT_PROVIDERS).optional(),
1600
1601
  model: z4.string().optional(),
1601
1602
  customInstructions: z4.string().optional(),
1602
1603
  planMode: z4.boolean().optional(),
@@ -1809,6 +1810,12 @@ function applyCodexAspTranscriptDelta(current, delta) {
1809
1810
  turns
1810
1811
  };
1811
1812
  }
1813
+ var sendChatMessageResponseSchema = z4.object({
1814
+ messageId: z4.string(),
1815
+ queued: z4.boolean(),
1816
+ position: z4.number(),
1817
+ chatId: z4.string().optional()
1818
+ });
1812
1819
  function isLatestChatHistoryPage(params) {
1813
1820
  return params.beforeCursor === void 0 && params.beforeEvent === void 0 && params.beforeTurn === void 0;
1814
1821
  }
@@ -2290,7 +2297,7 @@ var chatForkInfoSchema = z10.object({
2290
2297
  provider: z10.enum(VALID_AGENT_PROVIDERS),
2291
2298
  title: z10.string()
2292
2299
  }),
2293
- trigger: z10.enum(["manual", "auto", "side"]),
2300
+ trigger: z10.enum(["manual", "auto", "side", "switch"]),
2294
2301
  reason: fallbackReasonSchema.optional(),
2295
2302
  state: z10.enum(["preparing", "ready", "failed"]),
2296
2303
  error: z10.string().optional(),
@@ -2334,12 +2341,26 @@ var agentQuotaExhaustedPayloadSchema = z10.object({
2334
2341
  function defaultForkChatTitle(source, provider2) {
2335
2342
  return isDefaultChat(source) ? `Forked from ${source.title}` : `${source.title} (${getProviderDisplayName(provider2)})`;
2336
2343
  }
2337
- var continuesChat = (chat, source) => !chat.deletedAt && chat.fork?.trigger === "auto" && chat.fork.source.chatId === source.id && source.updatedAt <= chat.fork.startedAt;
2344
+ var continuesChat = (chat, source) => !chat.deletedAt && chat.fork?.source.chatId === source.id && (chat.fork.trigger === "switch" || chat.fork.trigger === "auto" && source.updatedAt <= chat.fork.startedAt);
2338
2345
  function getChatContinuation(chats, chatId) {
2339
2346
  const source = chatId ? chats.find((chat) => chat.id === chatId) : void 0;
2340
2347
  const next = source && chats.find((chat) => continuesChat(chat, source));
2341
2348
  return next && (getChatContinuation(chats, next.id) ?? next);
2342
2349
  }
2350
+ function getCurrentChat(chats, chatId) {
2351
+ return getChatContinuation(chats, chatId) ?? chats.find((chat) => chat.id === chatId);
2352
+ }
2353
+ function getChatThreadSources(chats, chat) {
2354
+ const sources = [];
2355
+ for (let current = chat; current; ) {
2356
+ const fork = current;
2357
+ const source = chats.find((candidate) => continuesChat(fork, candidate));
2358
+ if (!source) break;
2359
+ sources.unshift(source);
2360
+ current = source;
2361
+ }
2362
+ return sources;
2363
+ }
2343
2364
 
2344
2365
  // ../shared/src/credential-refresh.ts
2345
2366
  import { z as z11 } from "zod";
@@ -5277,11 +5298,13 @@ function formatChatForkHandoffMessage(handoff) {
5277
5298
  sourceProvider: handoff.source.provider,
5278
5299
  sourceTitle: handoff.source.title,
5279
5300
  trigger: handoff.trigger,
5280
- reason: handoff.reason
5301
+ reason: handoff.reason,
5302
+ sourceModel: handoff.sourceModel,
5303
+ model: handoff.model
5281
5304
  };
5282
5305
  const sourceName = getProviderDisplayName(handoff.source.provider, "long");
5283
5306
  const reason = describeChatForkReason(handoff.reason, handoff.source.provider);
5284
- const why = handoff.trigger === "auto" ? `until ${reason ?? "it stopped"}, so Replicas moved the conversation to you automatically` : "until the user forked it to you";
5307
+ const why = handoff.trigger === "auto" ? `until ${reason ?? "it stopped"}, so Replicas moved the conversation to you automatically` : `until the user ${handoff.trigger === "switch" ? "switched" : "forked"} it to you`;
5285
5308
  const side = handoff.trigger === "side";
5286
5309
  const body = [
5287
5310
  side ? "# Side chat" : handoff.subagent ? "# Delegated task context" : `# Continuing a ${sourceName} chat`,
@@ -5320,9 +5343,35 @@ ${CLOSE_MARKER}
5320
5343
 
5321
5344
  ${message2}`;
5322
5345
  }
5346
+ function readChatForkEnvelope(content) {
5347
+ if (!content.startsWith(OPEN_MARKER)) return null;
5348
+ const metaEnd = content.indexOf(OPEN_MARKER_END, OPEN_MARKER.length);
5349
+ if (metaEnd === -1) return null;
5350
+ let meta;
5351
+ try {
5352
+ meta = JSON.parse(content.slice(OPEN_MARKER.length, metaEnd));
5353
+ } catch {
5354
+ return null;
5355
+ }
5356
+ if (!isRecord(meta)) return null;
5357
+ const closeIndex = content.lastIndexOf(CLOSE_MARKER);
5358
+ const closed = closeIndex > metaEnd;
5359
+ return {
5360
+ meta,
5361
+ closed,
5362
+ inner: content.slice(metaEnd + OPEN_MARKER_END.length, closed ? closeIndex : void 0).trim(),
5363
+ replyText: closed ? content.slice(closeIndex + CLOSE_MARKER.length).trim() : ""
5364
+ };
5365
+ }
5366
+ function threadHandoffText(content) {
5367
+ const envelope = readChatForkEnvelope(content);
5368
+ if (!envelope?.closed) return null;
5369
+ return envelope.meta.trigger === "auto" ? "" : envelope.replyText;
5370
+ }
5323
5371
 
5324
5372
  // ../shared/src/display-message/chat-thread.ts
5325
5373
  function describeHarnessSwitch(message2) {
5374
+ if (message2.trigger === "switch") return `Switched from ${getProviderDisplayName(message2.fromProvider, "long")} to ${getProviderDisplayName(message2.toProvider, "long")}`;
5326
5375
  const reason = describeChatForkReason(message2.reason, message2.fromProvider) ?? `${getProviderDisplayName(message2.fromProvider)} stopped`;
5327
5376
  return `${reason}, continuing in ${getProviderDisplayName(message2.toProvider, "long")}`;
5328
5377
  }
@@ -10582,6 +10631,8 @@ export {
10582
10631
  agentQuotaExhaustedPayloadSchema,
10583
10632
  defaultForkChatTitle,
10584
10633
  getChatContinuation,
10634
+ getCurrentChat,
10635
+ getChatThreadSources,
10585
10636
  engineCredentialRefreshFailureSchema,
10586
10637
  CLAUDE_PARTIAL_MESSAGE_EVENT_TYPE,
10587
10638
  ACCEPTED_USER_MESSAGE_SOURCE,
@@ -10661,6 +10712,7 @@ export {
10661
10712
  CHAT_FORK_EXCERPT_OMITTED_NOTICE,
10662
10713
  formatChatForkHandoffMessage,
10663
10714
  formatChatForkUpdateMessage,
10715
+ threadHandoffText,
10664
10716
  fileContentDiff,
10665
10717
  skillNamesFromCommand,
10666
10718
  parseMcpToolName,
@@ -5,14 +5,14 @@ import {
5
5
  getOriginDefaultBranch,
6
6
  monolithRequest,
7
7
  runGitCommand
8
- } from "./chunk-CMBVI5RY.js";
8
+ } from "./chunk-HUUEA35W.js";
9
9
  import {
10
10
  extractCommandProtectionCommandText,
11
11
  findCommandProtectionGitLookups,
12
12
  findGitCommitSignals,
13
13
  findPrMergeSignals,
14
14
  parseCommandProtectionRequest
15
- } from "./chunk-YQMNXI2D.js";
15
+ } from "./chunk-47JJX56J.js";
16
16
 
17
17
  // src/services/command-protection-service.ts
18
18
  import { readFile, stat } from "fs/promises";
@@ -3,7 +3,7 @@ import { createRequire as __createRequire } from 'node:module';
3
3
  const require = __createRequire(import.meta.url);
4
4
  import {
5
5
  HOOK_EXEC_MAX_BUFFER_BYTES
6
- } from "./chunk-YQMNXI2D.js";
6
+ } from "./chunk-47JJX56J.js";
7
7
 
8
8
  // src/utils/exec.ts
9
9
  import { exec, execFile } from "child_process";
@@ -3,10 +3,10 @@ import { createRequire as __createRequire } from 'node:module';
3
3
  const require = __createRequire(import.meta.url);
4
4
  import {
5
5
  execFileAsync
6
- } from "./chunk-JXPRQY2T.js";
6
+ } from "./chunk-CQR3GSM4.js";
7
7
  import {
8
8
  isVersionBelow
9
- } from "./chunk-YQMNXI2D.js";
9
+ } from "./chunk-47JJX56J.js";
10
10
 
11
11
  // src/utils/codex-agent-env.ts
12
12
  function buildCodexAgentEnv(source = process.env) {
@@ -240,7 +240,7 @@ function getCodexModelProvider(method) {
240
240
  if (method === "foundry") return "azure";
241
241
  return method === "ollama" || method === "openai-compatible" ? "replicas_openai_compatible" : "openai";
242
242
  }
243
- var ENGINE_PACKAGE_VERSION = "0.1.1277";
243
+ var ENGINE_PACKAGE_VERSION = "0.1.1279";
244
244
  var INITIALIZE_METHOD = "initialize";
245
245
  var INITIALIZED_NOTIFICATION = "initialized";
246
246
  var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
@@ -4,7 +4,7 @@ const require = __createRequire(import.meta.url);
4
4
  import {
5
5
  SUBPROCESS_MAX_BUFFER,
6
6
  execFileAsync
7
- } from "./chunk-JXPRQY2T.js";
7
+ } from "./chunk-CQR3GSM4.js";
8
8
  import {
9
9
  isRecord as isRecord2
10
10
  } from "./chunk-UZSNFLDQ.js";
@@ -22,7 +22,7 @@ import {
22
22
  provisionedDeviceSchema,
23
23
  readReplicasRuntimeEnv,
24
24
  serializePosixEnvFile
25
- } from "./chunk-YQMNXI2D.js";
25
+ } from "./chunk-47JJX56J.js";
26
26
 
27
27
  // src/runtime-env-loader.ts
28
28
  import { readFileSync } from "fs";
@@ -10,15 +10,15 @@ import {
10
10
  resolveDiffBase,
11
11
  runGitCommand,
12
12
  setAgentCredentialSnapshot
13
- } from "./chunk-CMBVI5RY.js";
13
+ } from "./chunk-HUUEA35W.js";
14
14
  import {
15
15
  AppServerProcess,
16
16
  buildCodexAgentEnv
17
- } from "./chunk-DOIUIFTK.js";
17
+ } from "./chunk-GJN2TDJ7.js";
18
18
  import {
19
19
  SUBPROCESS_MAX_BUFFER,
20
20
  execFileAsync
21
- } from "./chunk-JXPRQY2T.js";
21
+ } from "./chunk-CQR3GSM4.js";
22
22
  import {
23
23
  isRecord
24
24
  } from "./chunk-UZSNFLDQ.js";
@@ -55,7 +55,7 @@ import {
55
55
  resolveOpenAICompatibleModelsUrl,
56
56
  splitMothershipInstructions,
57
57
  withTimeout
58
- } from "./chunk-YQMNXI2D.js";
58
+ } from "./chunk-47JJX56J.js";
59
59
 
60
60
  // src/managers/codex-token-manager.ts
61
61
  import { promises as fs } from "fs";
@@ -291,7 +291,7 @@ var CodexTokenManager = class extends BaseRefreshManager {
291
291
  await this.applyCredentialUpdate(++this.credentialGeneration, async () => {
292
292
  await this.applyCredentialsResponse(data);
293
293
  setAgentCredentialSnapshot("codex", data);
294
- const { restartCodexAspHostIfRunning: restartCodexAspHostIfRunning2 } = await import("./asp-host-WNBX4TA5.js");
294
+ const { restartCodexAspHostIfRunning: restartCodexAspHostIfRunning2 } = await import("./asp-host-CYLNJ6JB.js");
295
295
  await restartCodexAspHostIfRunning2();
296
296
  });
297
297
  await this.start(true);
@@ -313,7 +313,7 @@ var CodexTokenManager = class extends BaseRefreshManager {
313
313
  const accountChanged = ENGINE_ENV.REPLICAS_AGENT_CREDENTIALS.codex?.credentialId !== data.credentialId;
314
314
  await this.applyCredentialsResponse(data);
315
315
  if (restartOnChange && (accountChanged || CODEX_AUTH_ENV_KEYS.some((key, index) => process.env[key] !== previousEnv[index]))) {
316
- const { restartCodexAspHostIfRunning: restartCodexAspHostIfRunning2 } = await import("./asp-host-WNBX4TA5.js");
316
+ const { restartCodexAspHostIfRunning: restartCodexAspHostIfRunning2 } = await import("./asp-host-CYLNJ6JB.js");
317
317
  await restartCodexAspHostIfRunning2();
318
318
  }
319
319
  setAgentCredentialSnapshot("codex", data);
@@ -5,7 +5,7 @@ import {
5
5
  extractCommandProtectionCommandText,
6
6
  isRecord,
7
7
  parseCodeHostPrUrl
8
- } from "./chunk-YQMNXI2D.js";
8
+ } from "./chunk-47JJX56J.js";
9
9
 
10
10
  // src/services/published-pull-request-urls.ts
11
11
  function publishedPullRequestUrls(result) {
@@ -3,13 +3,13 @@ import { createRequire as __createRequire } from 'node:module';
3
3
  const require = __createRequire(import.meta.url);
4
4
  import {
5
5
  evaluateCommandProtection
6
- } from "./chunk-ZWLSWMXF.js";
7
- import "./chunk-CMBVI5RY.js";
8
- import "./chunk-JXPRQY2T.js";
6
+ } from "./chunk-5WJNG5IX.js";
7
+ import "./chunk-HUUEA35W.js";
8
+ import "./chunk-CQR3GSM4.js";
9
9
  import {
10
10
  isRecord
11
11
  } from "./chunk-UZSNFLDQ.js";
12
- import "./chunk-YQMNXI2D.js";
12
+ import "./chunk-47JJX56J.js";
13
13
  import "./chunk-VEQXQN22.js";
14
14
 
15
15
  // src/command-protection-hook.ts
@@ -13,12 +13,12 @@ import {
13
13
  recoverCompletedTurn,
14
14
  spawnMuseMsp,
15
15
  startMuseSession
16
- } from "./chunk-SBWWSTV3.js";
16
+ } from "./chunk-32B2T6C3.js";
17
17
  import {
18
18
  AppServerProcess,
19
19
  buildCodexAgentEnv
20
- } from "./chunk-DOIUIFTK.js";
21
- import "./chunk-JXPRQY2T.js";
20
+ } from "./chunk-GJN2TDJ7.js";
21
+ import "./chunk-CQR3GSM4.js";
22
22
  import {
23
23
  AGENT,
24
24
  OPENCODE_GO_PROVIDER,
@@ -28,7 +28,7 @@ import {
28
28
  headlessAgentUsagePath,
29
29
  isRecord,
30
30
  withTimeout
31
- } from "./chunk-YQMNXI2D.js";
31
+ } from "./chunk-47JJX56J.js";
32
32
  import "./chunk-VEQXQN22.js";
33
33
 
34
34
  // src/headless-agent.ts
package/dist/src/index.js CHANGED
@@ -27,7 +27,7 @@ import {
27
27
  evaluateCommandProtection,
28
28
  extractToolCommand,
29
29
  reportCommandProtectionBlock
30
- } from "./chunk-ZWLSWMXF.js";
30
+ } from "./chunk-5WJNG5IX.js";
31
31
  import {
32
32
  ACCOUNT_RATE_LIMITS_UPDATED_METHOD,
33
33
  AGENT_MESSAGE_DELTA_METHOD,
@@ -64,10 +64,10 @@ import {
64
64
  spawnMuseMsp,
65
65
  startMuseSession,
66
66
  uuidV7
67
- } from "./chunk-SBWWSTV3.js";
67
+ } from "./chunk-32B2T6C3.js";
68
68
  import {
69
69
  publishedPullRequestUrls
70
- } from "./chunk-QGEYP7R5.js";
70
+ } from "./chunk-YD76ZL7D.js";
71
71
  import {
72
72
  ACCOUNT_RATE_LIMITS_READ_METHOD,
73
73
  AsyncLock,
@@ -140,7 +140,7 @@ import {
140
140
  timestampFromSeconds,
141
141
  upsertCredentialFileLines,
142
142
  writeSecureCredentialFile
143
- } from "./chunk-RFV64KYY.js";
143
+ } from "./chunk-VCCUBYU3.js";
144
144
  import {
145
145
  ENGINE_ENV,
146
146
  monolithRequest,
@@ -150,15 +150,15 @@ import {
150
150
  resolveDiffBase,
151
151
  setAgentCredentialSnapshot,
152
152
  setLinearSessionId
153
- } from "./chunk-CMBVI5RY.js";
153
+ } from "./chunk-HUUEA35W.js";
154
154
  import {
155
155
  getCodexModelProvider
156
- } from "./chunk-DOIUIFTK.js";
156
+ } from "./chunk-GJN2TDJ7.js";
157
157
  import {
158
158
  SUBPROCESS_MAX_BUFFER,
159
159
  execAsync,
160
160
  execFileAsync
161
- } from "./chunk-JXPRQY2T.js";
161
+ } from "./chunk-CQR3GSM4.js";
162
162
  import {
163
163
  isRecord as isRecord2
164
164
  } from "./chunk-UZSNFLDQ.js";
@@ -309,7 +309,9 @@ import {
309
309
  getChatContinuation,
310
310
  getChatHistoryPageSenders,
311
311
  getChatKind,
312
+ getChatThreadSources,
312
313
  getClaudeModelContextWindow,
314
+ getCurrentChat,
313
315
  getDefaultAgentModel,
314
316
  getDefaultOllamaModel,
315
317
  getEventStreamId,
@@ -394,6 +396,7 @@ import {
394
396
  spawnSubagentRequestSchema,
395
397
  splitMothershipInstructions,
396
398
  stripAgentDiagnosticErrors,
399
+ threadHandoffText,
397
400
  toClaudeCodeModel,
398
401
  updateChatDisplayNameRequestSchema,
399
402
  waitSubagentRequestSchema,
@@ -403,7 +406,7 @@ import {
403
406
  workspaceTokenRequestSchema,
404
407
  workspaceTokenResponseSchema,
405
408
  wrapInTag
406
- } from "./chunk-YQMNXI2D.js";
409
+ } from "./chunk-47JJX56J.js";
407
410
  import {
408
411
  cloneMcpConfig,
409
412
  ensureCompatibilityImports,
@@ -9658,6 +9661,7 @@ var EnvironmentDetailsService = class {
9658
9661
  details.supportsChatForkMessage = true;
9659
9662
  details.supportsDeferredChatForking = true;
9660
9663
  details.supportsMessageEditing = true;
9664
+ details.supportsHarnessSwitch = true;
9661
9665
  details.supportsSideChats = true;
9662
9666
  details.supportsMultipleSideChats = true;
9663
9667
  details.supportsCredentialSwitching = true;
@@ -10607,7 +10611,7 @@ import { join as join29 } from "path";
10607
10611
  import { randomUUID as randomUUID10 } from "crypto";
10608
10612
 
10609
10613
  // ../shared/src/workspace-chat.ts
10610
- var WORKSPACE_CHATS_QUERY_SCHEMA_VERSION = 15;
10614
+ var WORKSPACE_CHATS_QUERY_SCHEMA_VERSION = 16;
10611
10615
  var WORKSPACE_CHATS_QUERY_ROOT = `workspace-chats:v${WORKSPACE_CHATS_QUERY_SCHEMA_VERSION}`;
10612
10616
  function isAcceptedUserMessage(event) {
10613
10617
  return getUserMessage(event) !== null && event.payload.source === ACCEPTED_USER_MESSAGE_SOURCE;
@@ -11624,6 +11628,7 @@ var CodingAgentManager = class {
11624
11628
  autoCompactTokens = null;
11625
11629
  unreadSteers = /* @__PURE__ */ new Map();
11626
11630
  steersInFlight = /* @__PURE__ */ new Map();
11631
+ steerSettlements = /* @__PURE__ */ new Set();
11627
11632
  lastSenderKey;
11628
11633
  // Unset until this process sends a turn, so a restart can't mistake the first one for a change.
11629
11634
  sentTurn = false;
@@ -11832,6 +11837,11 @@ ${request.message}` }, senderKey };
11832
11837
  isProcessing() {
11833
11838
  return this.messageQueue.isProcessing();
11834
11839
  }
11840
+ // A steer still in flight either reaches the agent or goes back on the queue, so it is read once it settles.
11841
+ async queuedRequests() {
11842
+ while (this.steerSettlements.size > 0) await Promise.allSettled(this.steerSettlements);
11843
+ return [...[...this.unreadSteers.values()].map(({ message }) => message), ...this.messageQueue.getQueue()];
11844
+ }
11835
11845
  getQueue() {
11836
11846
  return [
11837
11847
  ...[...this.unreadSteers.values()].map(({ message, steeredAt }) => ({ ...toQueuedMessageInfo(message), steeredAt })),
@@ -11877,6 +11887,15 @@ ${request.message}` }, senderKey };
11877
11887
  }
11878
11888
  // Take the message off the queue before steering: a turn completing mid-steer could otherwise pop it too.
11879
11889
  async steerFromQueue(messageId) {
11890
+ const steer = this.steerQueued(messageId);
11891
+ this.steerSettlements.add(steer);
11892
+ try {
11893
+ return await steer;
11894
+ } finally {
11895
+ this.steerSettlements.delete(steer);
11896
+ }
11897
+ }
11898
+ async steerQueued(messageId) {
11880
11899
  await this.initialized;
11881
11900
  const taken = this.messageQueue.takeFromQueue(messageId);
11882
11901
  if (!taken) {
@@ -14439,7 +14458,7 @@ var ChatBusyError = class extends Error {
14439
14458
  };
14440
14459
  var ChatProviderUnavailableError = class extends Error {
14441
14460
  constructor(provider) {
14442
- super(`${provider} is not configured in this workspace`);
14461
+ super(`${getProviderDisplayName(provider, "long")} is not connected in this workspace`);
14443
14462
  this.name = "ChatProviderUnavailableError";
14444
14463
  }
14445
14464
  };
@@ -59851,10 +59870,23 @@ function unseenConversationExcerpt(seenHashes, blocks) {
59851
59870
  const conversation = unseenBlocks(seenHashes, blocks).filter((block) => block.startsWith("## User") || block.startsWith("## Agent\n"));
59852
59871
  return excerptOf(conversation, CONVERSATION_UPDATE_MAX_CHARS);
59853
59872
  }
59873
+ function unwrapHandoffBlock(block) {
59874
+ const newline = block.indexOf("\n");
59875
+ const text = block.startsWith("## User") ? threadHandoffText(block.slice(newline + 1)) : null;
59876
+ if (text === null) return block;
59877
+ return text ? `${block.slice(0, newline)}
59878
+ ${text}` : null;
59879
+ }
59854
59880
  async function buildForkHandoff(input) {
59855
- const rawHistoryPath = join28(HISTORY_DIR_BY_PROVIDER[input.sourceProvider], `${input.sourceChatId}.jsonl`);
59856
- const { content, truncated: sourceTruncated } = await readHistoryTail(rawHistoryPath, MAX_SANITIZED_SOURCE_BYTES);
59857
- let blocks = canonicalizeChatTranscriptBlocks(content, input.sourceProvider, input.senders, input.beforeMessageId);
59881
+ const sources = await Promise.all(input.segments.map(async (segment, index) => {
59882
+ const rawHistoryPath = join28(HISTORY_DIR_BY_PROVIDER[segment.provider], `${segment.chatId}.jsonl`);
59883
+ const { content, truncated: truncated2 } = await readHistoryTail(rawHistoryPath, MAX_SANITIZED_SOURCE_BYTES);
59884
+ const last = index === input.segments.length - 1;
59885
+ const blocks2 = canonicalizeChatTranscriptBlocks(content, segment.provider, segment.senders, last ? input.beforeMessageId : void 0);
59886
+ return { rawHistoryPath, truncated: truncated2, blocks: index === 0 ? blocks2 : blocks2.flatMap((block) => unwrapHandoffBlock(block) ?? []) };
59887
+ }));
59888
+ let blocks = sources.flatMap((source) => source.blocks);
59889
+ const sourceTruncated = sources.some((source) => source.truncated);
59858
59890
  const fullHistory = input.turnLimit === void 0 && input.beforeMessageId === void 0;
59859
59891
  if (input.turnLimit !== void 0) {
59860
59892
  let turns = 0;
@@ -59864,7 +59896,8 @@ async function buildForkHandoff(input) {
59864
59896
  const forkDir = join28(FORKS_DIR, input.targetChatId);
59865
59897
  await mkdir17(forkDir, { recursive: true });
59866
59898
  const transcriptPath = join28(forkDir, input.transcriptName ?? "source-transcript.md");
59867
- const header = sourceTruncated ? `_This file was generated from only the most recent ${MAX_SANITIZED_SOURCE_BYTES / 1024 / 1024} MB of the source chat.${fullHistory ? ` The complete raw event log is at ${rawHistoryPath}; grep it for anything earlier.` : ""}_
59899
+ const rawHistoryPaths = sources.map((source) => source.rawHistoryPath);
59900
+ const header = sourceTruncated ? `_This file was generated from only the most recent ${MAX_SANITIZED_SOURCE_BYTES / 1024 / 1024} MB of ${sources.length > 1 ? "each part of " : ""}the source chat.${fullHistory ? ` The complete raw event ${sources.length > 1 ? `logs are, oldest first: ${rawHistoryPaths.join(", ")}` : `log is at ${rawHistoryPaths[0]}`}; grep ${sources.length > 1 ? "them" : "it"} for anything earlier.` : ""}_
59868
59901
 
59869
59902
  ` : "";
59870
59903
  await writeFile9(transcriptPath, `${header}${blocks.join("\n\n")}
@@ -59877,7 +59910,7 @@ async function buildForkHandoff(input) {
59877
59910
  blocks,
59878
59911
  resumeRequest: unansweredRequest(blocks),
59879
59912
  transcriptPath,
59880
- rawHistoryPath: fullHistory ? rawHistoryPath : void 0
59913
+ rawHistoryPath: fullHistory ? rawHistoryPaths.at(-1) : void 0
59881
59914
  };
59882
59915
  }
59883
59916
  async function readPullRequestDecisions(notesPath) {
@@ -59947,6 +59980,7 @@ function terminalErrorsFromEvent(event, provider) {
59947
59980
  }
59948
59981
  var LAST_MESSAGE_PREVIEW_MAX = 200;
59949
59982
  var FINAL_MESSAGE_MAX = 6e3;
59983
+ var TURN_REQUEST_MAX = 8e3;
59950
59984
  var MAX_ACCEPTED_SEND_RESPONSES = 50;
59951
59985
  var MAX_REMOVED_MESSAGE_IDS = 6e3;
59952
59986
  var SUBAGENT_POLL_INTERVAL_MS = 2e3;
@@ -59956,7 +59990,7 @@ var LIVE_AGENT_PROCESS_MB = 300;
59956
59990
  var maxLiveAgentProcesses = () => ENGINE_ENV.REPLICAS_MAX_LIVE_AGENT_PROCESSES ?? Math.max(1, Math.floor(totalmem() / 1024 / 1024 / 4 / LIVE_AGENT_PROCESS_MB));
59957
59991
  function createForkInfo(source, trigger, startedAt, reason) {
59958
59992
  return {
59959
- source: { chatId: source.id, provider: source.provider, title: source.title },
59993
+ source: { chatId: source.id, provider: source.provider, title: source.displayName ?? source.title },
59960
59994
  trigger,
59961
59995
  ...reason ? { reason } : {},
59962
59996
  state: "preparing",
@@ -60135,6 +60169,8 @@ var ChatService = class {
60135
60169
  providerSessionId: null,
60136
60170
  parentChatId,
60137
60171
  fork: request.fork ?? null,
60172
+ ...request.model ? { model: request.model } : {},
60173
+ ...request.pendingForkHandoff ? { pendingForkHandoff: true } : {},
60138
60174
  kind: getChatKind({ title, kind: request.kind }),
60139
60175
  ...request.displayName ? { displayName: request.displayName } : {},
60140
60176
  ...source ? {
@@ -60268,8 +60304,16 @@ var ChatService = class {
60268
60304
  const accepting2 = chat.acceptingSendResponses.get(idempotencyKey);
60269
60305
  if (accepting2) return accepting2;
60270
60306
  }
60307
+ if (chat.switching) await chat.switching.catch(() => {
60308
+ });
60271
60309
  const continuation = getChatContinuation(this.listChats(), chatId);
60272
- if (continuation) return { ...await this.sendMessage(continuation.id, request), chatId: continuation.id };
60310
+ if (continuation) {
60311
+ const forwarded = await this.sendMessage(continuation.id, request);
60312
+ return { ...forwarded, chatId: forwarded.chatId ?? continuation.id };
60313
+ }
60314
+ const { provider, ...sameHarnessRequest } = request;
60315
+ if (provider && provider !== chat.persisted.provider) return this.switchHarness(chat, { ...sameHarnessRequest, provider });
60316
+ request = sameHarnessRequest;
60273
60317
  const previousAcceptance = chat.lastSendAcceptance;
60274
60318
  const accepting = (async () => {
60275
60319
  chat.acceptingMessages += 1;
@@ -60284,9 +60328,10 @@ var ChatService = class {
60284
60328
  if (request.replacesMessageId) {
60285
60329
  request = { ...request, message: await this.rewindBefore(chat, request.replacesMessageId, request.message) };
60286
60330
  } else if (fork && (chat.persisted.pendingForkHandoff || fork.trigger === "side")) {
60331
+ const models = fork.trigger === "switch" ? { sourceModel: this.chats.get(fork.source.chatId)?.persisted.model, model: request.model } : {};
60287
60332
  request = {
60288
60333
  ...request,
60289
- message: await this.createForkHandoffMessage(chatId, { source: fork.source, trigger: fork.trigger, message: request.message })
60334
+ message: await this.createForkHandoffMessage(chatId, { source: fork.source, trigger: fork.trigger, message: request.message, ...models })
60290
60335
  };
60291
60336
  } else if (fork && chat.persisted.pullRequestNotesPath && this.getChat(fork.source.chatId)) {
60292
60337
  request = { ...request, message: await this.followForkSource(chat, fork, request.message) };
@@ -60712,9 +60757,9 @@ ${decisions}` };
60712
60757
  target.persisted.updatedAt = restoredAt;
60713
60758
  await this.publish({ type: "chat.created", payload: { chat: this.toSummary(target) } });
60714
60759
  }
60715
- for (let fork = chat.persisted.fork; fork?.trigger === "auto"; ) {
60760
+ for (let fork = chat.persisted.fork; fork?.trigger === "auto" || fork?.trigger === "switch"; ) {
60716
60761
  const source = this.chats.get(fork.source.chatId);
60717
- if (!source?.persisted.deletedAt || source.persisted.updatedAt > fork.startedAt) break;
60762
+ if (!source?.persisted.deletedAt || fork.trigger === "auto" && source.persisted.updatedAt > fork.startedAt) break;
60718
60763
  source.persisted.deletedAt = null;
60719
60764
  await this.publish({ type: "chat.created", payload: { chat: this.toSummary(source) } });
60720
60765
  fork = source.persisted.fork;
@@ -60747,6 +60792,52 @@ ${decisions}` };
60747
60792
  await rm5(chatMessageSendersFilePath(persisted.id), { force: true });
60748
60793
  await rm5(join29(FORKS_DIR, persisted.id), { recursive: true, force: true });
60749
60794
  }
60795
+ switchHarness(source, request) {
60796
+ const switching = (async () => {
60797
+ if (source.persisted.parentChatId !== null) throw new ChatBusyError("Side chats and subagents keep their harness.");
60798
+ if (source.provider.getGoal?.()) throw new ChatBusyError("Clear the goal before switching harness.");
60799
+ if (request.replacesMessageId) throw new ChatBusyError("Send the edited message first, then switch harness.");
60800
+ if (!isAgentAvailable(request.provider)) throw new ChatProviderUnavailableError(request.provider);
60801
+ await source.lastSendAcceptance?.catch(() => {
60802
+ });
60803
+ const carried = (await source.provider.queuedRequests()).map(({ id: _id, queuedAt: _queuedAt, engineTurn: _engineTurn, ...queued }) => queued);
60804
+ if (hasActiveChatTurn(source) || carried.length > 0) await this.interrupt(source.persisted.id);
60805
+ const { provider, ...message } = request;
60806
+ let target;
60807
+ let response;
60808
+ try {
60809
+ target = this.requireChat((await this.createForkTarget(source.persisted.id, { provider, model: request.model }, "switch")).id);
60810
+ response = await this.sendMessage(target.persisted.id, message);
60811
+ const harnessOptions = { model: message.model, thinkingLevel: message.thinkingLevel, inheritThinkingLevel: message.inheritThinkingLevel, fastMode: message.fastMode, ultrafastMode: message.ultrafastMode };
60812
+ for (const queued of carried) await this.sendMessage(target.persisted.id, { ...queued, ...harnessOptions });
60813
+ } catch (error) {
60814
+ if (target) {
60815
+ await this.deleteChat(target.persisted.id).catch((deleteError) => {
60816
+ console.error("[ChatService] Failed to remove the chat of a failed harness switch:", { chatId: target?.persisted.id, error: deleteError });
60817
+ });
60818
+ }
60819
+ source.switching = null;
60820
+ for (const queued of carried) {
60821
+ const key = queued.idempotencyKey ?? queued.messageId;
60822
+ if (key) source.acceptedSendResponses.delete(key);
60823
+ await this.sendMessage(source.persisted.id, queued).catch((restoreError) => {
60824
+ console.error("[ChatService] Failed to requeue a message after a failed harness switch:", { chatId: source.persisted.id, error: restoreError });
60825
+ });
60826
+ }
60827
+ throw error;
60828
+ }
60829
+ const children = [...this.chats.values()].filter((chat) => chat.persisted.parentChatId === source.persisted.id);
60830
+ for (const child of children) child.persisted.parentChatId = target.persisted.id;
60831
+ await this.persistAllChats();
60832
+ await Promise.all(children.map((child) => this.publish({ type: "chat.updated", payload: { chat: this.toSummary(child) } })));
60833
+ void this.releaseAgentProcess(source);
60834
+ return { ...response, chatId: target.persisted.id };
60835
+ })();
60836
+ source.switching = switching;
60837
+ return switching.finally(() => {
60838
+ if (source.switching === switching) source.switching = null;
60839
+ });
60840
+ }
60750
60841
  /** Resolves once the target chat exists; the handoff and its first turn run in the background. */
60751
60842
  async forkChat(sourceChatId, request, trigger = "manual", reason) {
60752
60843
  const target = await this.createForkTarget(sourceChatId, request, trigger, reason);
@@ -60757,30 +60848,35 @@ ${decisions}` };
60757
60848
  }
60758
60849
  async createForkTarget(sourceChatId, request, trigger, reason) {
60759
60850
  const source = this.requireChat(sourceChatId);
60760
- if (trigger === "manual" && !isAgentAvailable(request.provider)) {
60851
+ if (trigger !== "auto" && !isAgentAvailable(request.provider)) {
60761
60852
  throw new ChatProviderUnavailableError(request.provider);
60762
60853
  }
60763
60854
  const sourceName = source.persisted.displayName ?? source.persisted.title;
60855
+ const continues = trigger === "auto" || trigger === "switch";
60764
60856
  return this.createChat({
60765
60857
  id: request.id,
60766
60858
  provider: request.provider,
60767
60859
  title: request.title?.trim() || defaultForkChatTitle(source.persisted, request.provider),
60768
- ...trigger === "auto" ? {
60860
+ ...continues ? {
60769
60861
  displayName: sourceName === DEFAULT_CHAT_TITLES[source.persisted.provider] ? DEFAULT_CHAT_TITLES[request.provider] : sourceName
60770
60862
  } : {},
60863
+ ...trigger === "switch" ? { pendingForkHandoff: true, model: request.model } : {},
60771
60864
  clientRequestId: request.clientRequestId,
60772
60865
  fork: createForkInfo(source.persisted, trigger, (/* @__PURE__ */ new Date()).toISOString(), reason),
60773
- ...trigger === "auto" ? { kind: source.persisted.kind } : {}
60866
+ ...continues ? { kind: source.persisted.kind } : {}
60774
60867
  });
60775
60868
  }
60869
+ async threadSegments(chat) {
60870
+ const thread = [...getChatThreadSources(this.listChats(), this.toSummary(chat)).map(({ id }) => this.requireChat(id)), chat];
60871
+ await chat.provider.getHistory({ limit: 1 });
60872
+ return Promise.all(thread.map(async ({ persisted }) => ({ chatId: persisted.id, provider: persisted.provider, senders: await this.readSenders(persisted.id) })));
60873
+ }
60776
60874
  async createForkHandoffMessage(targetChatId, options) {
60777
- const source = this.requireChat(options.source.chatId);
60778
- await source.provider.getHistory({ limit: 1 });
60875
+ const follows = options.trigger === "side" || options.followsSource;
60876
+ const source = this.requireChat(follows && getCurrentChat(this.listChats(), options.source.chatId)?.id || options.source.chatId);
60779
60877
  const handoff = await buildForkHandoff({
60780
- sourceChatId: options.source.chatId,
60781
- sourceProvider: source.persisted.provider,
60878
+ segments: await this.threadSegments(source),
60782
60879
  targetChatId,
60783
- senders: await this.readSenders(options.source.chatId),
60784
60880
  turnLimit: options.turnLimit
60785
60881
  });
60786
60882
  if (!options.subagent && !options.followsSource && options.trigger !== "side" && await this.forkProviderSession(source, this.requireChat(targetChatId))) {
@@ -60813,12 +60909,9 @@ ${note}`;
60813
60909
 
60814
60910
  ${wrapInTag(`The user edited one of their earlier messages: the conversation above ends where that message was, and this is its edited version. ${files}`, REPLICAS_INSTRUCTIONS_TAG)}`;
60815
60911
  }
60816
- await chat.provider.getHistory({ limit: 1 });
60817
60912
  const handoff = await buildForkHandoff({
60818
- sourceChatId: chat.persisted.id,
60819
- sourceProvider: chat.persisted.provider,
60913
+ segments: await this.threadSegments(chat),
60820
60914
  targetChatId: chat.persisted.id,
60821
- senders: await this.readSenders(chat.persisted.id),
60822
60915
  beforeMessageId: messageId,
60823
60916
  transcriptName: "transcript-before-edit.md"
60824
60917
  });
@@ -60855,7 +60948,7 @@ ${wrapInTag(note, REPLICAS_INSTRUCTIONS_TAG)}`;
60855
60948
  }
60856
60949
  }
60857
60950
  async followForkSource(chat, fork, message) {
60858
- const source = this.requireChat(fork.source.chatId);
60951
+ const source = this.requireChat(getCurrentChat(this.listChats(), fork.source.chatId)?.id ?? fork.source.chatId);
60859
60952
  if (source.persisted.provider === chat.persisted.provider && source.provider.forkPoint && chat.provider.forkFrom) {
60860
60953
  if (chat.provider.isProcessing() || chat.provider.getQueue().length > 0) return message;
60861
60954
  const point = await source.provider.forkPoint().catch(() => null);
@@ -60912,13 +61005,12 @@ ${note}`;
60912
61005
  await this.persistAllChats();
60913
61006
  await this.publish({ type: "chat.updated", payload: { chat: this.toSummary(chat) } });
60914
61007
  }
60915
- /** Providers of this chat and every chat it was forked from, so a fallback never loops back. */
61008
+ /** Providers of this chat and every chat it fell back from, so a fallback never loops back. A user's switch starts over. */
60916
61009
  forkChainProviders(chat) {
60917
61010
  const providers = /* @__PURE__ */ new Set();
60918
61011
  for (let current = chat; current; ) {
60919
61012
  providers.add(current.persisted.provider);
60920
- providers.add(current.persisted.provider);
60921
- if (!current.persisted.fork) break;
61013
+ if (current.persisted.fork?.trigger !== "auto") break;
60922
61014
  const source = this.chats.get(current.persisted.fork.source.chatId);
60923
61015
  if (!source) providers.add(current.persisted.fork.source.provider);
60924
61016
  current = source;
@@ -60928,8 +61020,8 @@ ${note}`;
60928
61020
  async autoForkAfterQuota(chat, exhaustion) {
60929
61021
  const target = exhaustion.forkTo;
60930
61022
  if (!target || chat.persisted.deletedAt || chat.persisted.fork?.trigger === "side") return false;
60931
- const alreadyForked = [...this.chats.values()].some((other) => other.persisted.fork?.trigger === "auto" && other.persisted.fork.source.chatId === chat.persisted.id && !other.persisted.deletedAt);
60932
- if (alreadyForked) return false;
61023
+ const alreadyContinued = Boolean(chat.switching) || [...this.chats.values()].some((other) => (other.persisted.fork?.trigger === "auto" || other.persisted.fork?.trigger === "switch") && other.persisted.fork.source.chatId === chat.persisted.id && !other.persisted.deletedAt);
61024
+ if (alreadyContinued) return false;
60933
61025
  const last = chat.lastSendRequest;
60934
61026
  const request = {
60935
61027
  provider: target.provider,
@@ -61232,6 +61324,7 @@ ${note}`;
61232
61324
  unpublishedHistoryStarts: [],
61233
61325
  live: false,
61234
61326
  release: null,
61327
+ switching: null,
61235
61328
  errorNotificationTargets: /* @__PURE__ */ new Map(),
61236
61329
  activeErrorNotificationTargets: [],
61237
61330
  quotaExhaustion: null,
@@ -61283,7 +61376,7 @@ ${note}`;
61283
61376
  chat.live = !!chat.provider.dispose;
61284
61377
  this.enforceLiveProcessCap();
61285
61378
  const request = chat.acceptedUserEvents.get(messageId)?.payload.message;
61286
- chat.turnRequest = typeof request === "string" ? request.trim().slice(0, LAST_MESSAGE_PREVIEW_MAX) : null;
61379
+ chat.turnRequest = typeof request === "string" ? request.trim().slice(0, TURN_REQUEST_MAX) : null;
61287
61380
  chat.turnAnswer = null;
61288
61381
  const context = this.agentChatActivityTrackerService.noteTurnStarted(
61289
61382
  chat.persisted.id,
@@ -63024,7 +63117,7 @@ function createV1Routes(deps) {
63024
63117
  if (error instanceof ChatMessagePersistenceError) {
63025
63118
  return c.json(jsonError("Failed to save user message", details), 500);
63026
63119
  }
63027
- if (error instanceof ChatBusyError) {
63120
+ if (error instanceof ChatBusyError || error instanceof ChatProviderUnavailableError) {
63028
63121
  return c.json(jsonError(ENGINE_SEND_MESSAGE_FAILED_ERROR, details), 409);
63029
63122
  }
63030
63123
  return c.json(jsonError(ENGINE_SEND_MESSAGE_FAILED_ERROR, details), 400);
@@ -3,13 +3,13 @@ import { createRequire as __createRequire } from 'node:module';
3
3
  const require = __createRequire(import.meta.url);
4
4
  import {
5
5
  publishedPullRequestUrls
6
- } from "./chunk-QGEYP7R5.js";
6
+ } from "./chunk-YD76ZL7D.js";
7
7
  import {
8
8
  isRecord
9
9
  } from "./chunk-UZSNFLDQ.js";
10
10
  import {
11
11
  shouldRefreshPullRequests
12
- } from "./chunk-YQMNXI2D.js";
12
+ } from "./chunk-47JJX56J.js";
13
13
  import "./chunk-VEQXQN22.js";
14
14
 
15
15
  // src/services/post-tool-pr-notifier.ts
@@ -11,7 +11,7 @@ import {
11
11
  spawnSubagentRequestSchema,
12
12
  subagentResponseSchema,
13
13
  waitSubagentRequestSchema
14
- } from "./chunk-YQMNXI2D.js";
14
+ } from "./chunk-47JJX56J.js";
15
15
  import "./chunk-VEQXQN22.js";
16
16
 
17
17
  // src/subagent-mcp.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.1277",
3
+ "version": "0.1.1279",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",