replicas-engine 0.1.1276 → 0.1.1278

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-CQXHQFAC.js";
12
- import "./chunk-V5H4AKYX.js";
13
- import "./chunk-LKQHRWNH.js";
14
- import "./chunk-CFAC6DWV.js";
11
+ } from "./chunk-U3LAEGVS.js";
12
+ import "./chunk-HUUEA35W.js";
13
+ import "./chunk-AK562VZG.js";
14
+ import "./chunk-CQR3GSM4.js";
15
15
  import "./chunk-UZSNFLDQ.js";
16
- import "./chunk-XAAAOOWB.js";
16
+ import "./chunk-47JJX56J.js";
17
17
  import "./chunk-VEQXQN22.js";
18
18
  export {
19
19
  CODEX_ULTRAFAST_SERVICE_TIER,
@@ -1451,6 +1451,7 @@ var CHAT_INTERRUPTED_EVENT_TYPE = "replicas-interrupted";
1451
1451
  var CHAT_MESSAGE_REJECTED_EVENT_TYPE = "replicas-message-rejected";
1452
1452
  var CHAT_TURN_LOST_EVENT_TYPE = "replicas-turn-lost";
1453
1453
  var CHAT_GOAL_EVENT_TYPE = "chat-goal";
1454
+ var CLAUDE_PEER_MESSAGE_EVENT_TYPE = "claude-peer-message";
1454
1455
  var AUTH_RETRY_STATUS_EVENT_TYPE = "auth-retry-status";
1455
1456
  var CURSOR_CONNECTION_STATUS_EVENT_TYPE = "cursor-connection-status";
1456
1457
  var AUTH_FALLBACK_EVENT_TYPE = "auth-fallback";
@@ -1596,6 +1597,7 @@ var sendChatMessageRequestSchema = z4.object({
1596
1597
  messageId: z4.string().min(1).optional(),
1597
1598
  submittedAt: z4.string().datetime().optional(),
1598
1599
  message: z4.string().min(1),
1600
+ provider: z4.enum(VALID_AGENT_PROVIDERS).optional(),
1599
1601
  model: z4.string().optional(),
1600
1602
  customInstructions: z4.string().optional(),
1601
1603
  planMode: z4.boolean().optional(),
@@ -1808,6 +1810,12 @@ function applyCodexAspTranscriptDelta(current, delta) {
1808
1810
  turns
1809
1811
  };
1810
1812
  }
1813
+ var sendChatMessageResponseSchema = z4.object({
1814
+ messageId: z4.string(),
1815
+ queued: z4.boolean(),
1816
+ position: z4.number(),
1817
+ chatId: z4.string().optional()
1818
+ });
1811
1819
  function isLatestChatHistoryPage(params) {
1812
1820
  return params.beforeCursor === void 0 && params.beforeEvent === void 0 && params.beforeTurn === void 0;
1813
1821
  }
@@ -2213,6 +2221,12 @@ async function readSseStream(body, onEvent, shouldContinue = () => true, onChunk
2213
2221
  // ../shared/src/types.ts
2214
2222
  var ENGINE_SEND_MESSAGE_FAILED_ERROR = "Failed to send message";
2215
2223
 
2224
+ // ../shared/src/palette-search.ts
2225
+ var DAY_MS = 24 * 60 * 60 * 1e3;
2226
+ var RECENCY_HALF_LIFE_MS = 14 * DAY_MS;
2227
+ var SEARCH_OPEN_DEDUPE_MS = 10 * 60 * 1e3;
2228
+ var PICK_WINDOW_MS = 21 * DAY_MS;
2229
+
2216
2230
  // ../shared/src/auth-fallback.ts
2217
2231
  function describeCredential(provider2, credential) {
2218
2232
  const { ownerName, email, ordinal: ordinal2 } = credential.account ?? {};
@@ -2283,7 +2297,7 @@ var chatForkInfoSchema = z10.object({
2283
2297
  provider: z10.enum(VALID_AGENT_PROVIDERS),
2284
2298
  title: z10.string()
2285
2299
  }),
2286
- trigger: z10.enum(["manual", "auto", "side"]),
2300
+ trigger: z10.enum(["manual", "auto", "side", "switch"]),
2287
2301
  reason: fallbackReasonSchema.optional(),
2288
2302
  state: z10.enum(["preparing", "ready", "failed"]),
2289
2303
  error: z10.string().optional(),
@@ -2327,12 +2341,26 @@ var agentQuotaExhaustedPayloadSchema = z10.object({
2327
2341
  function defaultForkChatTitle(source, provider2) {
2328
2342
  return isDefaultChat(source) ? `Forked from ${source.title}` : `${source.title} (${getProviderDisplayName(provider2)})`;
2329
2343
  }
2330
- 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);
2331
2345
  function getChatContinuation(chats, chatId) {
2332
2346
  const source = chatId ? chats.find((chat) => chat.id === chatId) : void 0;
2333
2347
  const next = source && chats.find((chat) => continuesChat(chat, source));
2334
2348
  return next && (getChatContinuation(chats, next.id) ?? next);
2335
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
+ }
2336
2364
 
2337
2365
  // ../shared/src/credential-refresh.ts
2338
2366
  import { z as z11 } from "zod";
@@ -5270,11 +5298,13 @@ function formatChatForkHandoffMessage(handoff) {
5270
5298
  sourceProvider: handoff.source.provider,
5271
5299
  sourceTitle: handoff.source.title,
5272
5300
  trigger: handoff.trigger,
5273
- reason: handoff.reason
5301
+ reason: handoff.reason,
5302
+ sourceModel: handoff.sourceModel,
5303
+ model: handoff.model
5274
5304
  };
5275
5305
  const sourceName = getProviderDisplayName(handoff.source.provider, "long");
5276
5306
  const reason = describeChatForkReason(handoff.reason, handoff.source.provider);
5277
- 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`;
5278
5308
  const side = handoff.trigger === "side";
5279
5309
  const body = [
5280
5310
  side ? "# Side chat" : handoff.subagent ? "# Delegated task context" : `# Continuing a ${sourceName} chat`,
@@ -5313,9 +5343,35 @@ ${CLOSE_MARKER}
5313
5343
 
5314
5344
  ${message2}`;
5315
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
+ }
5316
5371
 
5317
5372
  // ../shared/src/display-message/chat-thread.ts
5318
5373
  function describeHarnessSwitch(message2) {
5374
+ if (message2.trigger === "switch") return `Switched from ${getProviderDisplayName(message2.fromProvider, "long")} to ${getProviderDisplayName(message2.toProvider, "long")}`;
5319
5375
  const reason = describeChatForkReason(message2.reason, message2.fromProvider) ?? `${getProviderDisplayName(message2.fromProvider)} stopped`;
5320
5376
  return `${reason}, continuing in ${getProviderDisplayName(message2.toProvider, "long")}`;
5321
5377
  }
@@ -5749,6 +5805,9 @@ function planItemsFromMessage(message2) {
5749
5805
  return [{ text: text3, status: status === "completed" || status === "in_progress" ? status : "pending" }];
5750
5806
  });
5751
5807
  }
5808
+ function peerChatName(message2) {
5809
+ return message2.chat ?? "another chat";
5810
+ }
5752
5811
  function todoItemStatus(item) {
5753
5812
  return item.itemStatus ?? (item.completed ? "completed" : "pending");
5754
5813
  }
@@ -5781,6 +5840,7 @@ ${operation.diff}` : ""}`).join("\n\n");
5781
5840
  if (message2.type === "harness_switch") return { kind: "error", title: describeHarnessSwitch(message2), summary: "", content: "" };
5782
5841
  if (message2.type === "interruption") return { kind: "error", title: "Agent stopped", summary: "", content: "" };
5783
5842
  if (message2.type === "capacity_retry") return { kind: "task", title: "Model at capacity", summary: `Retry ${message2.attempt} of ${message2.maxAttempts}`, content: "" };
5843
+ if (message2.type === "peer_message") return { kind: "task", title: `${message2.direction === "received" ? "Message from" : "Message to"} ${peerChatName(message2)}`, summary: "", content: message2.content };
5784
5844
  if (message2.type === "multitask_event") return { kind: "task", title: "Ship update", summary: message2.events.map((event) => `${event.agentName}: ${event.summary}`).join(" \xB7 "), content: "" };
5785
5845
  return { kind: "error", title: "Agent error", summary: "", content: message2.message };
5786
5846
  }
@@ -7475,6 +7535,8 @@ function parseClaudeEvents(events, parentToolUseId) {
7475
7535
  });
7476
7536
  const toolCallMap = /* @__PURE__ */ new Map();
7477
7537
  const taskMessageMap = /* @__PURE__ */ new Map();
7538
+ const peerNames = /* @__PURE__ */ new Map();
7539
+ const peerTimestamps = /* @__PURE__ */ new Map();
7478
7540
  const workflowAgentMessageMap = /* @__PURE__ */ new Map();
7479
7541
  const ambientTaskIds = /* @__PURE__ */ new Set();
7480
7542
  const partialIndexes = /* @__PURE__ */ new Map();
@@ -7572,6 +7634,22 @@ function parseClaudeEvents(events, parentToolUseId) {
7572
7634
  partialIndexes.set(payload.streamId, existing);
7573
7635
  return;
7574
7636
  }
7637
+ if (event.type === CLAUDE_PEER_MESSAGE_EVENT_TYPE) {
7638
+ const { name, from, body } = event.payload;
7639
+ if (typeof body !== "string") return;
7640
+ if (typeof name === "string" && typeof from === "string") peerNames.set(from, name);
7641
+ const sameTime = peerTimestamps.get(event.timestamp) ?? 0;
7642
+ peerTimestamps.set(event.timestamp, sameTime + 1);
7643
+ messages.push({
7644
+ id: `peer-${event.timestamp}-${sameTime}`,
7645
+ type: "peer_message",
7646
+ direction: "received",
7647
+ ...typeof name === "string" ? { chat: name } : {},
7648
+ content: body,
7649
+ timestamp: event.timestamp
7650
+ });
7651
+ return;
7652
+ }
7575
7653
  if (event.type === "claude-user") {
7576
7654
  const content = normalizeContentBlocks(event.payload.message?.content);
7577
7655
  const toolResult = content.find((c) => c.type === "tool_result");
@@ -7712,6 +7790,20 @@ function parseClaudeEvents(events, parentToolUseId) {
7712
7790
  timestamp: event.timestamp
7713
7791
  });
7714
7792
  }
7793
+ } else if (toolName === "SendMessage") {
7794
+ const parsed = typeof toolInput === "string" ? safeJsonParse(toolInput, {}) : toolInput;
7795
+ const input = isRecord(parsed) ? parsed : {};
7796
+ const to = typeof input.to === "string" ? input.to.replace(/\s+\[[0-9a-f]+\]$/, "") : "";
7797
+ const chat = peerNames.get(to) ?? (to.startsWith("uds:") ? void 0 : to || void 0);
7798
+ messages.push({
7799
+ id: `peer-${toolUseId}`,
7800
+ type: "peer_message",
7801
+ direction: "sent",
7802
+ ...chat ? { chat } : {},
7803
+ content: typeof input.message === "string" ? input.message : "",
7804
+ status: "in_progress",
7805
+ timestamp: event.timestamp
7806
+ });
7715
7807
  } else if (toolName === "WebSearch") {
7716
7808
  const inputObj = typeof toolInput === "string" ? safeJsonParse(toolInput, {}) : toolInput;
7717
7809
  const query = inputObj.query || "";
@@ -7882,6 +7974,9 @@ function parseClaudeEvents(events, parentToolUseId) {
7882
7974
  message2.status = status;
7883
7975
  } else if (message2.type === "web_search") {
7884
7976
  message2.status = status;
7977
+ } else if (message2.type === "peer_message") {
7978
+ const result2 = safeJsonParse(resultContent, {});
7979
+ message2.status = isError || isRecord(result2) && result2.success === false ? "failed" : "completed";
7885
7980
  } else if (message2.type === "tool_call") {
7886
7981
  message2.output = resultContent;
7887
7982
  message2.outputOmitted = outputOmitted;
@@ -10087,6 +10182,7 @@ var WORKSPACE_SECTION_ORDER_CLIENT_STATE_KEY_PREFIX = "replicas-workspace-sectio
10087
10182
  var WORKSPACE_STAGES_CLIENT_STATE_KEY_PREFIX = "replicas-workspace-stages-";
10088
10183
  var WORKSPACE_MARKED_UNREAD_CLIENT_STATE_KEY_PREFIX = "replicas-workspace-marked-unread-";
10089
10184
  var PROMPT_HISTORY_CLIENT_STATE_KEY_PREFIX = "replicas-prompt-history-";
10185
+ var WORKSPACE_SEARCH_OPENS_CLIENT_STATE_KEY_PREFIX = "replicas-workspace-search-opens-";
10090
10186
  var orgUserClientStateKey = (prefix) => (organizationId, userId) => `${prefix}${organizationId}-${userId}`;
10091
10187
  var workspaceLastViewedAtClientStateKey = orgUserClientStateKey(WORKSPACE_LAST_VIEWED_AT_CLIENT_STATE_KEY_PREFIX);
10092
10188
  var workspaceAttentionDoneClientStateKey = orgUserClientStateKey(WORKSPACE_ATTENTION_DONE_CLIENT_STATE_KEY_PREFIX);
@@ -10094,6 +10190,7 @@ var workspaceStagesClientStateKey = orgUserClientStateKey(WORKSPACE_STAGES_CLIEN
10094
10190
  var workspaceSectionOrderClientStateKey = orgUserClientStateKey(WORKSPACE_SECTION_ORDER_CLIENT_STATE_KEY_PREFIX);
10095
10191
  var workspaceMarkedUnreadClientStateKey = orgUserClientStateKey(WORKSPACE_MARKED_UNREAD_CLIENT_STATE_KEY_PREFIX);
10096
10192
  var promptHistoryClientStateKey = orgUserClientStateKey(PROMPT_HISTORY_CLIENT_STATE_KEY_PREFIX);
10193
+ var workspaceSearchOpensClientStateKey = orgUserClientStateKey(WORKSPACE_SEARCH_OPENS_CLIENT_STATE_KEY_PREFIX);
10097
10194
 
10098
10195
  // ../shared/src/routes/workspace-events.ts
10099
10196
  import { z as z24 } from "zod";
@@ -10534,6 +10631,8 @@ export {
10534
10631
  agentQuotaExhaustedPayloadSchema,
10535
10632
  defaultForkChatTitle,
10536
10633
  getChatContinuation,
10634
+ getCurrentChat,
10635
+ getChatThreadSources,
10537
10636
  engineCredentialRefreshFailureSchema,
10538
10637
  CLAUDE_PARTIAL_MESSAGE_EVENT_TYPE,
10539
10638
  ACCEPTED_USER_MESSAGE_SOURCE,
@@ -10551,6 +10650,7 @@ export {
10551
10650
  CHAT_INTERRUPTED_EVENT_TYPE,
10552
10651
  CHAT_TURN_LOST_EVENT_TYPE,
10553
10652
  CHAT_GOAL_EVENT_TYPE,
10653
+ CLAUDE_PEER_MESSAGE_EVENT_TYPE,
10554
10654
  AUTH_RETRY_STATUS_EVENT_TYPE,
10555
10655
  CURSOR_CONNECTION_STATUS_EVENT_TYPE,
10556
10656
  AUTH_FALLBACK_EVENT_TYPE,
@@ -10612,6 +10712,7 @@ export {
10612
10712
  CHAT_FORK_EXCERPT_OMITTED_NOTICE,
10613
10713
  formatChatForkHandoffMessage,
10614
10714
  formatChatForkUpdateMessage,
10715
+ threadHandoffText,
10615
10716
  fileContentDiff,
10616
10717
  skillNamesFromCommand,
10617
10718
  parseMcpToolName,
@@ -5,14 +5,14 @@ import {
5
5
  getOriginDefaultBranch,
6
6
  monolithRequest,
7
7
  runGitCommand
8
- } from "./chunk-V5H4AKYX.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-XAAAOOWB.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,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-CFAC6DWV.js";
6
+ } from "./chunk-CQR3GSM4.js";
7
7
  import {
8
8
  isVersionBelow
9
- } from "./chunk-XAAAOOWB.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.1276";
243
+ var ENGINE_PACKAGE_VERSION = "0.1.1278";
244
244
  var INITIALIZE_METHOD = "initialize";
245
245
  var INITIALIZED_NOTIFICATION = "initialized";
246
246
  var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
@@ -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-XAAAOOWB.js";
6
+ } from "./chunk-47JJX56J.js";
7
7
 
8
8
  // src/utils/exec.ts
9
9
  import { exec, execFile } from "child_process";
@@ -4,7 +4,7 @@ const require = __createRequire(import.meta.url);
4
4
  import {
5
5
  SUBPROCESS_MAX_BUFFER,
6
6
  execFileAsync
7
- } from "./chunk-CFAC6DWV.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-XAAAOOWB.js";
25
+ } from "./chunk-47JJX56J.js";
26
26
 
27
27
  // src/runtime-env-loader.ts
28
28
  import { readFileSync } from "fs";
@@ -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-LKQHRWNH.js";
6
+ } from "./chunk-AK562VZG.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-XAAAOOWB.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";
@@ -10,15 +10,15 @@ import {
10
10
  resolveDiffBase,
11
11
  runGitCommand,
12
12
  setAgentCredentialSnapshot
13
- } from "./chunk-V5H4AKYX.js";
13
+ } from "./chunk-HUUEA35W.js";
14
14
  import {
15
15
  AppServerProcess,
16
16
  buildCodexAgentEnv
17
- } from "./chunk-LKQHRWNH.js";
17
+ } from "./chunk-AK562VZG.js";
18
18
  import {
19
19
  SUBPROCESS_MAX_BUFFER,
20
20
  execFileAsync
21
- } from "./chunk-CFAC6DWV.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-XAAAOOWB.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-CGRUKNN4.js");
294
+ const { restartCodexAspHostIfRunning: restartCodexAspHostIfRunning2 } = await import("./asp-host-C7RJLYGI.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-CGRUKNN4.js");
316
+ const { restartCodexAspHostIfRunning: restartCodexAspHostIfRunning2 } = await import("./asp-host-C7RJLYGI.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-XAAAOOWB.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-FILX4PF6.js";
7
- import "./chunk-V5H4AKYX.js";
8
- import "./chunk-CFAC6DWV.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-XAAAOOWB.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-CQR2T35P.js";
16
+ } from "./chunk-LO5BJD7O.js";
17
17
  import {
18
18
  AppServerProcess,
19
19
  buildCodexAgentEnv
20
- } from "./chunk-LKQHRWNH.js";
21
- import "./chunk-CFAC6DWV.js";
20
+ } from "./chunk-AK562VZG.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-XAAAOOWB.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-FILX4PF6.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-CQR2T35P.js";
67
+ } from "./chunk-LO5BJD7O.js";
68
68
  import {
69
69
  publishedPullRequestUrls
70
- } from "./chunk-3HGIRXMN.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-CQXHQFAC.js";
143
+ } from "./chunk-U3LAEGVS.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-V5H4AKYX.js";
153
+ } from "./chunk-HUUEA35W.js";
154
154
  import {
155
155
  getCodexModelProvider
156
- } from "./chunk-LKQHRWNH.js";
156
+ } from "./chunk-AK562VZG.js";
157
157
  import {
158
158
  SUBPROCESS_MAX_BUFFER,
159
159
  execAsync,
160
160
  execFileAsync
161
- } from "./chunk-CFAC6DWV.js";
161
+ } from "./chunk-CQR3GSM4.js";
162
162
  import {
163
163
  isRecord as isRecord2
164
164
  } from "./chunk-UZSNFLDQ.js";
@@ -184,6 +184,7 @@ import {
184
184
  CLAUDE_HAIKU_4_5_BEDROCK_MODEL,
185
185
  CLAUDE_OPUS_4_8_BEDROCK_MODEL,
186
186
  CLAUDE_PARTIAL_MESSAGE_EVENT_TYPE,
187
+ CLAUDE_PEER_MESSAGE_EVENT_TYPE,
187
188
  CLAUDE_SONNET_5_BEDROCK_MODEL,
188
189
  CODEX_ASP_ITEM_ID_PAYLOAD_KEY,
189
190
  CODEX_HISTORY_FORMAT_EVENT_TYPE,
@@ -308,7 +309,9 @@ import {
308
309
  getChatContinuation,
309
310
  getChatHistoryPageSenders,
310
311
  getChatKind,
312
+ getChatThreadSources,
311
313
  getClaudeModelContextWindow,
314
+ getCurrentChat,
312
315
  getDefaultAgentModel,
313
316
  getDefaultOllamaModel,
314
317
  getEventStreamId,
@@ -393,6 +396,7 @@ import {
393
396
  spawnSubagentRequestSchema,
394
397
  splitMothershipInstructions,
395
398
  stripAgentDiagnosticErrors,
399
+ threadHandoffText,
396
400
  toClaudeCodeModel,
397
401
  updateChatDisplayNameRequestSchema,
398
402
  waitSubagentRequestSchema,
@@ -402,7 +406,7 @@ import {
402
406
  workspaceTokenRequestSchema,
403
407
  workspaceTokenResponseSchema,
404
408
  wrapInTag
405
- } from "./chunk-XAAAOOWB.js";
409
+ } from "./chunk-47JJX56J.js";
406
410
  import {
407
411
  cloneMcpConfig,
408
412
  ensureCompatibilityImports,
@@ -9657,6 +9661,7 @@ var EnvironmentDetailsService = class {
9657
9661
  details.supportsChatForkMessage = true;
9658
9662
  details.supportsDeferredChatForking = true;
9659
9663
  details.supportsMessageEditing = true;
9664
+ details.supportsHarnessSwitch = true;
9660
9665
  details.supportsSideChats = true;
9661
9666
  details.supportsMultipleSideChats = true;
9662
9667
  details.supportsCredentialSwitching = true;
@@ -10606,7 +10611,7 @@ import { join as join29 } from "path";
10606
10611
  import { randomUUID as randomUUID10 } from "crypto";
10607
10612
 
10608
10613
  // ../shared/src/workspace-chat.ts
10609
- var WORKSPACE_CHATS_QUERY_SCHEMA_VERSION = 15;
10614
+ var WORKSPACE_CHATS_QUERY_SCHEMA_VERSION = 16;
10610
10615
  var WORKSPACE_CHATS_QUERY_ROOT = `workspace-chats:v${WORKSPACE_CHATS_QUERY_SCHEMA_VERSION}`;
10611
10616
  function isAcceptedUserMessage(event) {
10612
10617
  return getUserMessage(event) !== null && event.payload.source === ACCEPTED_USER_MESSAGE_SOURCE;
@@ -11610,6 +11615,7 @@ var CodingAgentManager = class {
11610
11615
  topLevel;
11611
11616
  chatId;
11612
11617
  originChatId;
11618
+ chatTitle;
11613
11619
  hostOnTurnComplete;
11614
11620
  onProcessingChanged;
11615
11621
  onMessageStarted;
@@ -11622,6 +11628,7 @@ var CodingAgentManager = class {
11622
11628
  autoCompactTokens = null;
11623
11629
  unreadSteers = /* @__PURE__ */ new Map();
11624
11630
  steersInFlight = /* @__PURE__ */ new Map();
11631
+ steerSettlements = /* @__PURE__ */ new Set();
11625
11632
  lastSenderKey;
11626
11633
  // Unset until this process sends a turn, so a restart can't mistake the first one for a change.
11627
11634
  sentTurn = false;
@@ -11642,6 +11649,7 @@ var CodingAgentManager = class {
11642
11649
  this.topLevel = options.parentChatId === null;
11643
11650
  this.chatId = options.chatId;
11644
11651
  this.originChatId = options.originChatId;
11652
+ this.chatTitle = options.chatTitle;
11645
11653
  this.hostOnTurnComplete = options.onTurnComplete;
11646
11654
  this.onProcessingChanged = options.onProcessingChanged ?? (() => {
11647
11655
  });
@@ -11829,6 +11837,11 @@ ${request.message}` }, senderKey };
11829
11837
  isProcessing() {
11830
11838
  return this.messageQueue.isProcessing();
11831
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
+ }
11832
11845
  getQueue() {
11833
11846
  return [
11834
11847
  ...[...this.unreadSteers.values()].map(({ message, steeredAt }) => ({ ...toQueuedMessageInfo(message), steeredAt })),
@@ -11874,6 +11887,15 @@ ${request.message}` }, senderKey };
11874
11887
  }
11875
11888
  // Take the message off the queue before steering: a turn completing mid-steer could otherwise pop it too.
11876
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) {
11877
11899
  await this.initialized;
11878
11900
  const taken = this.messageQueue.takeFromQueue(messageId);
11879
11901
  if (!taken) {
@@ -12720,8 +12742,10 @@ var PromptStream = class {
12720
12742
  queue = [];
12721
12743
  closed = false;
12722
12744
  waiters = [];
12745
+ sentUuids = /* @__PURE__ */ new Set();
12723
12746
  push(message) {
12724
12747
  if (this.closed) return;
12748
+ if (message.uuid) this.sentUuids.add(message.uuid);
12725
12749
  const waiter = this.waiters.shift();
12726
12750
  if (waiter) {
12727
12751
  waiter({ value: message, done: false });
@@ -13048,6 +13072,32 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
13048
13072
  this.pendingTurn?.steers.delete(uuid);
13049
13073
  if (this.markSteerRead(steer.messageId)) await this.recordEvent(steer.userMessage, steer.messageId);
13050
13074
  }
13075
+ // Claude Code starts a command for another chat's SendMessage without streaming its text, and can start it a moment before writing it.
13076
+ async recordPeerMessage(uuid, query2) {
13077
+ for (const delayMs of [0, 100, 300, 600, 1e3]) {
13078
+ if (delayMs) await new Promise((resolve7) => setTimeout(resolve7, delayMs));
13079
+ if (this.activeQuery !== query2 || !this.sessionId) return;
13080
+ const entry = (await getSessionMessages(this.sessionId, { dir: this.workingDirectory })).findLast((message) => message.uuid === uuid);
13081
+ if (!entry) continue;
13082
+ const origin = "origin" in entry && isRecord(entry.origin) ? entry.origin : null;
13083
+ if (origin?.kind !== "peer" || origin.senderTaskId) return;
13084
+ const text = extractToolResultText(isRecord(entry.message) ? entry.message.content : void 0);
13085
+ const envelopes = [...text.matchAll(/<cross-session-message\b([^>]*)>\n?([\s\S]*?)\n?<\/cross-session-message>/g)].map(([, attributes, body]) => ({
13086
+ name: /\bfrom-name="([^"]*)"/.exec(attributes)?.[1],
13087
+ from: /\bfrom="([^"]*)"/.exec(attributes)?.[1],
13088
+ body
13089
+ }));
13090
+ const messages = envelopes.length > 0 ? envelopes : typeof origin.body === "string" ? [{ name: typeof origin.name === "string" ? origin.name : void 0, from: typeof origin.from === "string" ? origin.from : void 0, body: origin.body }] : [];
13091
+ for (const { name, from, body } of messages) {
13092
+ this.recordHistoryEvent(CLAUDE_PEER_MESSAGE_EVENT_TYPE, {
13093
+ ...name ? { name } : {},
13094
+ ...from ? { from } : {},
13095
+ body
13096
+ }, this.historyFile);
13097
+ }
13098
+ return;
13099
+ }
13100
+ }
13051
13101
  async abortAllPendingToolInputs() {
13052
13102
  const pending = Array.from(this.pendingToolInputs.values());
13053
13103
  this.pendingToolInputs.clear();
@@ -13717,8 +13767,11 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
13717
13767
  } : {}
13718
13768
  },
13719
13769
  model: claudeCodeModel,
13770
+ ...this.chatTitle ? { title: this.chatTitle() } : {},
13720
13771
  settings: {
13721
13772
  fastMode: signature.fastMode,
13773
+ // Every session in the sandbox is one of the user's chats; the default holds messages across permission modes, and a plan mode chat has no one to release them.
13774
+ crossSessionInbound: "accept",
13722
13775
  ...signature.autoCompactTokens ? { autoCompactWindow: signature.autoCompactTokens } : {},
13723
13776
  ...ultracode ? { enableWorkflows: true, ultracode: true } : {}
13724
13777
  },
@@ -13809,6 +13862,8 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
13809
13862
  const lifecycle2 = msg;
13810
13863
  if (isRecord(lifecycle2) && lifecycle2.type === "command_lifecycle" && lifecycle2.state === "started" && typeof lifecycle2.command_uuid === "string") {
13811
13864
  if (this.pendingTurn?.userMessageUuids.has(lifecycle2.command_uuid)) this.turnPromptRecorded = true;
13865
+ else if (!this.activePromptStream?.sentUuids.has(lifecycle2.command_uuid)) await this.recordPeerMessage(lifecycle2.command_uuid, response);
13866
+ if (this.activeQuery !== response) break;
13812
13867
  await this.recordSteerRead(lifecycle2.command_uuid);
13813
13868
  }
13814
13869
  if (this.pendingTurn && _ClaudeManager.isTurnActivity(msg)) {
@@ -14403,7 +14458,7 @@ var ChatBusyError = class extends Error {
14403
14458
  };
14404
14459
  var ChatProviderUnavailableError = class extends Error {
14405
14460
  constructor(provider) {
14406
- super(`${provider} is not configured in this workspace`);
14461
+ super(`${getProviderDisplayName(provider, "long")} is not connected in this workspace`);
14407
14462
  this.name = "ChatProviderUnavailableError";
14408
14463
  }
14409
14464
  };
@@ -59815,10 +59870,23 @@ function unseenConversationExcerpt(seenHashes, blocks) {
59815
59870
  const conversation = unseenBlocks(seenHashes, blocks).filter((block) => block.startsWith("## User") || block.startsWith("## Agent\n"));
59816
59871
  return excerptOf(conversation, CONVERSATION_UPDATE_MAX_CHARS);
59817
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
+ }
59818
59880
  async function buildForkHandoff(input) {
59819
- const rawHistoryPath = join28(HISTORY_DIR_BY_PROVIDER[input.sourceProvider], `${input.sourceChatId}.jsonl`);
59820
- const { content, truncated: sourceTruncated } = await readHistoryTail(rawHistoryPath, MAX_SANITIZED_SOURCE_BYTES);
59821
- 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);
59822
59890
  const fullHistory = input.turnLimit === void 0 && input.beforeMessageId === void 0;
59823
59891
  if (input.turnLimit !== void 0) {
59824
59892
  let turns = 0;
@@ -59828,7 +59896,8 @@ async function buildForkHandoff(input) {
59828
59896
  const forkDir = join28(FORKS_DIR, input.targetChatId);
59829
59897
  await mkdir17(forkDir, { recursive: true });
59830
59898
  const transcriptPath = join28(forkDir, input.transcriptName ?? "source-transcript.md");
59831
- 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.` : ""}_
59832
59901
 
59833
59902
  ` : "";
59834
59903
  await writeFile9(transcriptPath, `${header}${blocks.join("\n\n")}
@@ -59841,7 +59910,7 @@ async function buildForkHandoff(input) {
59841
59910
  blocks,
59842
59911
  resumeRequest: unansweredRequest(blocks),
59843
59912
  transcriptPath,
59844
- rawHistoryPath: fullHistory ? rawHistoryPath : void 0
59913
+ rawHistoryPath: fullHistory ? rawHistoryPaths.at(-1) : void 0
59845
59914
  };
59846
59915
  }
59847
59916
  async function readPullRequestDecisions(notesPath) {
@@ -59911,6 +59980,7 @@ function terminalErrorsFromEvent(event, provider) {
59911
59980
  }
59912
59981
  var LAST_MESSAGE_PREVIEW_MAX = 200;
59913
59982
  var FINAL_MESSAGE_MAX = 6e3;
59983
+ var TURN_REQUEST_MAX = 8e3;
59914
59984
  var MAX_ACCEPTED_SEND_RESPONSES = 50;
59915
59985
  var MAX_REMOVED_MESSAGE_IDS = 6e3;
59916
59986
  var SUBAGENT_POLL_INTERVAL_MS = 2e3;
@@ -59920,7 +59990,7 @@ var LIVE_AGENT_PROCESS_MB = 300;
59920
59990
  var maxLiveAgentProcesses = () => ENGINE_ENV.REPLICAS_MAX_LIVE_AGENT_PROCESSES ?? Math.max(1, Math.floor(totalmem() / 1024 / 1024 / 4 / LIVE_AGENT_PROCESS_MB));
59921
59991
  function createForkInfo(source, trigger, startedAt, reason) {
59922
59992
  return {
59923
- source: { chatId: source.id, provider: source.provider, title: source.title },
59993
+ source: { chatId: source.id, provider: source.provider, title: source.displayName ?? source.title },
59924
59994
  trigger,
59925
59995
  ...reason ? { reason } : {},
59926
59996
  state: "preparing",
@@ -60099,6 +60169,8 @@ var ChatService = class {
60099
60169
  providerSessionId: null,
60100
60170
  parentChatId,
60101
60171
  fork: request.fork ?? null,
60172
+ ...request.model ? { model: request.model } : {},
60173
+ ...request.pendingForkHandoff ? { pendingForkHandoff: true } : {},
60102
60174
  kind: getChatKind({ title, kind: request.kind }),
60103
60175
  ...request.displayName ? { displayName: request.displayName } : {},
60104
60176
  ...source ? {
@@ -60232,8 +60304,16 @@ var ChatService = class {
60232
60304
  const accepting2 = chat.acceptingSendResponses.get(idempotencyKey);
60233
60305
  if (accepting2) return accepting2;
60234
60306
  }
60307
+ if (chat.switching) await chat.switching.catch(() => {
60308
+ });
60235
60309
  const continuation = getChatContinuation(this.listChats(), chatId);
60236
- 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;
60237
60317
  const previousAcceptance = chat.lastSendAcceptance;
60238
60318
  const accepting = (async () => {
60239
60319
  chat.acceptingMessages += 1;
@@ -60248,9 +60328,10 @@ var ChatService = class {
60248
60328
  if (request.replacesMessageId) {
60249
60329
  request = { ...request, message: await this.rewindBefore(chat, request.replacesMessageId, request.message) };
60250
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 } : {};
60251
60332
  request = {
60252
60333
  ...request,
60253
- 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 })
60254
60335
  };
60255
60336
  } else if (fork && chat.persisted.pullRequestNotesPath && this.getChat(fork.source.chatId)) {
60256
60337
  request = { ...request, message: await this.followForkSource(chat, fork, request.message) };
@@ -60676,9 +60757,9 @@ ${decisions}` };
60676
60757
  target.persisted.updatedAt = restoredAt;
60677
60758
  await this.publish({ type: "chat.created", payload: { chat: this.toSummary(target) } });
60678
60759
  }
60679
- for (let fork = chat.persisted.fork; fork?.trigger === "auto"; ) {
60760
+ for (let fork = chat.persisted.fork; fork?.trigger === "auto" || fork?.trigger === "switch"; ) {
60680
60761
  const source = this.chats.get(fork.source.chatId);
60681
- if (!source?.persisted.deletedAt || source.persisted.updatedAt > fork.startedAt) break;
60762
+ if (!source?.persisted.deletedAt || fork.trigger === "auto" && source.persisted.updatedAt > fork.startedAt) break;
60682
60763
  source.persisted.deletedAt = null;
60683
60764
  await this.publish({ type: "chat.created", payload: { chat: this.toSummary(source) } });
60684
60765
  fork = source.persisted.fork;
@@ -60711,6 +60792,52 @@ ${decisions}` };
60711
60792
  await rm5(chatMessageSendersFilePath(persisted.id), { force: true });
60712
60793
  await rm5(join29(FORKS_DIR, persisted.id), { recursive: true, force: true });
60713
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
+ }
60714
60841
  /** Resolves once the target chat exists; the handoff and its first turn run in the background. */
60715
60842
  async forkChat(sourceChatId, request, trigger = "manual", reason) {
60716
60843
  const target = await this.createForkTarget(sourceChatId, request, trigger, reason);
@@ -60721,30 +60848,35 @@ ${decisions}` };
60721
60848
  }
60722
60849
  async createForkTarget(sourceChatId, request, trigger, reason) {
60723
60850
  const source = this.requireChat(sourceChatId);
60724
- if (trigger === "manual" && !isAgentAvailable(request.provider)) {
60851
+ if (trigger !== "auto" && !isAgentAvailable(request.provider)) {
60725
60852
  throw new ChatProviderUnavailableError(request.provider);
60726
60853
  }
60727
60854
  const sourceName = source.persisted.displayName ?? source.persisted.title;
60855
+ const continues = trigger === "auto" || trigger === "switch";
60728
60856
  return this.createChat({
60729
60857
  id: request.id,
60730
60858
  provider: request.provider,
60731
60859
  title: request.title?.trim() || defaultForkChatTitle(source.persisted, request.provider),
60732
- ...trigger === "auto" ? {
60860
+ ...continues ? {
60733
60861
  displayName: sourceName === DEFAULT_CHAT_TITLES[source.persisted.provider] ? DEFAULT_CHAT_TITLES[request.provider] : sourceName
60734
60862
  } : {},
60863
+ ...trigger === "switch" ? { pendingForkHandoff: true, model: request.model } : {},
60735
60864
  clientRequestId: request.clientRequestId,
60736
60865
  fork: createForkInfo(source.persisted, trigger, (/* @__PURE__ */ new Date()).toISOString(), reason),
60737
- ...trigger === "auto" ? { kind: source.persisted.kind } : {}
60866
+ ...continues ? { kind: source.persisted.kind } : {}
60738
60867
  });
60739
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
+ }
60740
60874
  async createForkHandoffMessage(targetChatId, options) {
60741
- const source = this.requireChat(options.source.chatId);
60742
- 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);
60743
60877
  const handoff = await buildForkHandoff({
60744
- sourceChatId: options.source.chatId,
60745
- sourceProvider: source.persisted.provider,
60878
+ segments: await this.threadSegments(source),
60746
60879
  targetChatId,
60747
- senders: await this.readSenders(options.source.chatId),
60748
60880
  turnLimit: options.turnLimit
60749
60881
  });
60750
60882
  if (!options.subagent && !options.followsSource && options.trigger !== "side" && await this.forkProviderSession(source, this.requireChat(targetChatId))) {
@@ -60777,12 +60909,9 @@ ${note}`;
60777
60909
 
60778
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)}`;
60779
60911
  }
60780
- await chat.provider.getHistory({ limit: 1 });
60781
60912
  const handoff = await buildForkHandoff({
60782
- sourceChatId: chat.persisted.id,
60783
- sourceProvider: chat.persisted.provider,
60913
+ segments: await this.threadSegments(chat),
60784
60914
  targetChatId: chat.persisted.id,
60785
- senders: await this.readSenders(chat.persisted.id),
60786
60915
  beforeMessageId: messageId,
60787
60916
  transcriptName: "transcript-before-edit.md"
60788
60917
  });
@@ -60819,7 +60948,7 @@ ${wrapInTag(note, REPLICAS_INSTRUCTIONS_TAG)}`;
60819
60948
  }
60820
60949
  }
60821
60950
  async followForkSource(chat, fork, message) {
60822
- const source = this.requireChat(fork.source.chatId);
60951
+ const source = this.requireChat(getCurrentChat(this.listChats(), fork.source.chatId)?.id ?? fork.source.chatId);
60823
60952
  if (source.persisted.provider === chat.persisted.provider && source.provider.forkPoint && chat.provider.forkFrom) {
60824
60953
  if (chat.provider.isProcessing() || chat.provider.getQueue().length > 0) return message;
60825
60954
  const point = await source.provider.forkPoint().catch(() => null);
@@ -60876,13 +61005,12 @@ ${note}`;
60876
61005
  await this.persistAllChats();
60877
61006
  await this.publish({ type: "chat.updated", payload: { chat: this.toSummary(chat) } });
60878
61007
  }
60879
- /** 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. */
60880
61009
  forkChainProviders(chat) {
60881
61010
  const providers = /* @__PURE__ */ new Set();
60882
61011
  for (let current = chat; current; ) {
60883
61012
  providers.add(current.persisted.provider);
60884
- providers.add(current.persisted.provider);
60885
- if (!current.persisted.fork) break;
61013
+ if (current.persisted.fork?.trigger !== "auto") break;
60886
61014
  const source = this.chats.get(current.persisted.fork.source.chatId);
60887
61015
  if (!source) providers.add(current.persisted.fork.source.provider);
60888
61016
  current = source;
@@ -60892,8 +61020,8 @@ ${note}`;
60892
61020
  async autoForkAfterQuota(chat, exhaustion) {
60893
61021
  const target = exhaustion.forkTo;
60894
61022
  if (!target || chat.persisted.deletedAt || chat.persisted.fork?.trigger === "side") return false;
60895
- const alreadyForked = [...this.chats.values()].some((other) => other.persisted.fork?.trigger === "auto" && other.persisted.fork.source.chatId === chat.persisted.id && !other.persisted.deletedAt);
60896
- 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;
60897
61025
  const last = chat.lastSendRequest;
60898
61026
  const request = {
60899
61027
  provider: target.provider,
@@ -61142,6 +61270,7 @@ ${note}`;
61142
61270
  fallbackContext,
61143
61271
  chatId: persisted.id,
61144
61272
  originChatId,
61273
+ chatTitle: () => persisted.title,
61145
61274
  onModelSwitched,
61146
61275
  onChatCredential,
61147
61276
  lastSenderKey: persisted.lastSenderKey,
@@ -61195,6 +61324,7 @@ ${note}`;
61195
61324
  unpublishedHistoryStarts: [],
61196
61325
  live: false,
61197
61326
  release: null,
61327
+ switching: null,
61198
61328
  errorNotificationTargets: /* @__PURE__ */ new Map(),
61199
61329
  activeErrorNotificationTargets: [],
61200
61330
  quotaExhaustion: null,
@@ -61246,7 +61376,7 @@ ${note}`;
61246
61376
  chat.live = !!chat.provider.dispose;
61247
61377
  this.enforceLiveProcessCap();
61248
61378
  const request = chat.acceptedUserEvents.get(messageId)?.payload.message;
61249
- 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;
61250
61380
  chat.turnAnswer = null;
61251
61381
  const context = this.agentChatActivityTrackerService.noteTurnStarted(
61252
61382
  chat.persisted.id,
@@ -62987,7 +63117,7 @@ function createV1Routes(deps) {
62987
63117
  if (error instanceof ChatMessagePersistenceError) {
62988
63118
  return c.json(jsonError("Failed to save user message", details), 500);
62989
63119
  }
62990
- if (error instanceof ChatBusyError) {
63120
+ if (error instanceof ChatBusyError || error instanceof ChatProviderUnavailableError) {
62991
63121
  return c.json(jsonError(ENGINE_SEND_MESSAGE_FAILED_ERROR, details), 409);
62992
63122
  }
62993
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-3HGIRXMN.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-XAAAOOWB.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-XAAAOOWB.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.1276",
3
+ "version": "0.1.1278",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",