replicas-engine 0.1.733 → 0.1.734

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.
package/dist/src/index.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  monolithService,
9
9
  reportCommandProtectionBlock,
10
10
  setAgentCredentialSnapshot
11
- } from "./chunk-5U46EQRM.js";
11
+ } from "./chunk-BRXEUTMC.js";
12
12
  import {
13
13
  ACCOUNT_RATE_LIMITS_UPDATED_METHOD,
14
14
  AGENT_MESSAGE_DELTA_METHOD,
@@ -36,7 +36,7 @@ import {
36
36
  execFileAsync,
37
37
  putPresignedFile,
38
38
  recoverCompletedTurn
39
- } from "./chunk-2ZP6PIHD.js";
39
+ } from "./chunk-35NHHD6W.js";
40
40
  import {
41
41
  isRecord as isRecord2
42
42
  } from "./chunk-2RB7SIP3.js";
@@ -44,6 +44,7 @@ import {
44
44
  ACCEPTED_USER_MESSAGE_SOURCE,
45
45
  AGENT,
46
46
  AGENT_MODELS,
47
+ AGENT_QUOTA_EXHAUSTED_EVENT_TYPE,
47
48
  AI_GATEWAY_BASE_URL,
48
49
  ASTER_BASE_URL,
49
50
  ASTER_MODELS,
@@ -115,6 +116,7 @@ import {
115
116
  VALID_AGENT_PROVIDERS,
116
117
  VALID_RELAY_SUBAGENT_PROVIDERS,
117
118
  VALID_THINKING_LEVELS,
119
+ agentQuotaExhaustedPayloadSchema,
118
120
  applyCodexAspTranscriptDelta,
119
121
  areSameUserMessageEvents,
120
122
  areUserMessagesWithinMatchWindow,
@@ -124,6 +126,7 @@ import {
124
126
  buildHookOutputPreview,
125
127
  buildOutputPreview,
126
128
  canUseClaudeFastMode,
129
+ canonicalizeChatTranscriptBlocks,
127
130
  chatHistoryPageParamsFromQuery,
128
131
  clampTokensToWindow,
129
132
  clampWarmHookTimeoutMs,
@@ -132,6 +135,7 @@ import {
132
135
  codexAuthEnvFromResponse,
133
136
  codexReasoningEffortForThinkingLevel,
134
137
  coerceBackgroundTaskPayload,
138
+ coerceChatForkInfo,
135
139
  coerceChatGoalPayload,
136
140
  coerceClaudeResultPayload,
137
141
  createAcceptedUserMessageEvent,
@@ -143,12 +147,15 @@ import {
143
147
  createSuccessResult,
144
148
  createUuidFromHex,
145
149
  decodePathSegments,
150
+ defaultForkChatTitle,
146
151
  detectAgentQuotaLimit,
147
152
  detectLanguageByPath,
148
153
  extractErrorText,
149
154
  extractToolResultText,
150
155
  fetchAiGatewayModels,
151
156
  fetchModelsDevCatalog,
157
+ forkChatRequestSchema,
158
+ formatChatForkHandoffMessage,
152
159
  getChatHistoryPageSenders,
153
160
  getChatHistoryPageWindow,
154
161
  getChatTranscriptHistorySize,
@@ -191,6 +198,7 @@ import {
191
198
  isTransientErrorText,
192
199
  isValidAgentProvider,
193
200
  isValidEngineLogSessionId,
201
+ lastUserBlockText,
194
202
  mayCreatePullRequest,
195
203
  mergeSlashCommands,
196
204
  normalizeAutoCompactThreshold,
@@ -220,7 +228,7 @@ import {
220
228
  shellQuotePosix,
221
229
  stripAgentDiagnosticErrors,
222
230
  withTimeout
223
- } from "./chunk-YBI3HWHT.js";
231
+ } from "./chunk-5K3MABJT.js";
224
232
 
225
233
  // src/index.ts
226
234
  import { serve } from "@hono/node-server";
@@ -1707,13 +1715,18 @@ var engineLogger = new EngineLogger();
1707
1715
 
1708
1716
  // src/services/replicas-config-service.ts
1709
1717
  import { readFile as readFile6, appendFile, writeFile as writeFile4, mkdir as mkdir6 } from "fs/promises";
1710
- import { existsSync as existsSync3 } from "fs";
1711
- import { join as join9 } from "path";
1712
- import { homedir as homedir6 } from "os";
1718
+ import { existsSync as existsSync4 } from "fs";
1719
+ import { join as join10 } from "path";
1720
+ import { homedir as homedir7 } from "os";
1713
1721
  import { spawn as spawn2 } from "child_process";
1714
1722
 
1715
1723
  // src/services/environment-details-service.ts
1716
1724
  import { mkdir as mkdir4, readFile as readFile4 } from "fs/promises";
1725
+ import { existsSync as existsSync3 } from "fs";
1726
+ import { homedir as homedir5 } from "os";
1727
+ import { join as join8 } from "path";
1728
+
1729
+ // src/managers/agent-auth-detection.ts
1717
1730
  import { existsSync as existsSync2 } from "fs";
1718
1731
  import { homedir as homedir4 } from "os";
1719
1732
  import { join as join7 } from "path";
@@ -1746,12 +1759,8 @@ function detectCodexAuthMethod() {
1746
1759
  return ENGINE_ENV.REPLICAS_CODEX_AUTH_METHOD ?? (ENGINE_ENV.OPENAI_API_KEY ? "api_key" : "none");
1747
1760
  }
1748
1761
 
1749
- // src/services/environment-details-service.ts
1750
- var REPLICAS_DIR = join7(homedir4(), ".replicas");
1751
- var DETAILS_FILE = join7(REPLICAS_DIR, "environment-details.json");
1762
+ // src/managers/agent-auth-detection.ts
1752
1763
  var CLAUDE_CREDENTIALS_PATH = join7(homedir4(), ".claude", ".credentials.json");
1753
- var GH_HOSTS_PATH = join7(homedir4(), ".config", "gh", "hosts.yml");
1754
- var GIT_CREDENTIALS_PATH = join7(homedir4(), ".git-credentials");
1755
1764
  function detectClaudeAuthMethod() {
1756
1765
  if (existsSync2(CLAUDE_CREDENTIALS_PATH)) {
1757
1766
  return "oauth";
@@ -1767,21 +1776,38 @@ function detectClaudeAuthMethod() {
1767
1776
  }
1768
1777
  return "none";
1769
1778
  }
1770
- function detectCursorAuthMethod() {
1771
- return ENGINE_ENV.CURSOR_API_KEY ? "api_key" : "none";
1772
- }
1773
- function detectOpencodeAuthMethod() {
1774
- return existsSync2(OPENCODE_AUTH_PATH) ? "api_key" : "none";
1775
- }
1776
- function detectDeepseekAuthMethod() {
1777
- return getDeepseekApiKey() ? "api_key" : "none";
1778
- }
1779
- function detectFxAuthMethod() {
1780
- return ENGINE_ENV.AI_GATEWAY_API_KEY ? "api_key" : "none";
1779
+ function detectAgentAuthMethod(provider) {
1780
+ switch (provider) {
1781
+ // Relay orchestrates subagents but authenticates as Claude.
1782
+ case AGENT.CLAUDE:
1783
+ case AGENT.RELAY:
1784
+ return detectClaudeAuthMethod();
1785
+ case AGENT.CODEX:
1786
+ return detectCodexAuthMethod();
1787
+ case AGENT.CURSOR:
1788
+ return ENGINE_ENV.CURSOR_API_KEY ? "api_key" : "none";
1789
+ case AGENT.DEEPSEEK:
1790
+ return getDeepseekApiKey() ? "api_key" : "none";
1791
+ case AGENT.FX:
1792
+ case AGENT.KIMI:
1793
+ return ENGINE_ENV.AI_GATEWAY_API_KEY ? "api_key" : "none";
1794
+ case AGENT.MUSE:
1795
+ return existsSync2(MUSE_AUTH_PATH) ? "oauth" : "none";
1796
+ case AGENT.OPENCODE:
1797
+ return existsSync2(OPENCODE_AUTH_PATH) ? "api_key" : "none";
1798
+ case AGENT.PI:
1799
+ return existsSync2(PI_AUTH_PATH) ? "api_key" : "none";
1800
+ }
1781
1801
  }
1782
- function detectPiAuthMethod() {
1783
- return existsSync2(PI_AUTH_PATH) ? "api_key" : "none";
1802
+ function isAgentAvailable(provider) {
1803
+ return detectAgentAuthMethod(provider) !== "none";
1784
1804
  }
1805
+
1806
+ // src/services/environment-details-service.ts
1807
+ var REPLICAS_DIR = join8(homedir5(), ".replicas");
1808
+ var DETAILS_FILE = join8(REPLICAS_DIR, "environment-details.json");
1809
+ var GH_HOSTS_PATH = join8(homedir5(), ".config", "gh", "hosts.yml");
1810
+ var GIT_CREDENTIALS_PATH = join8(homedir5(), ".git-credentials");
1785
1811
  async function detectGitIdentityConfigured() {
1786
1812
  try {
1787
1813
  const { stdout } = await execFileAsync("git", ["config", "--global", "user.name"]);
@@ -1848,7 +1874,7 @@ function createDefaultDetails() {
1848
1874
  }
1849
1875
  async function readDetails() {
1850
1876
  try {
1851
- if (!existsSync2(DETAILS_FILE)) {
1877
+ if (!existsSync3(DETAILS_FILE)) {
1852
1878
  return createDefaultDetails();
1853
1879
  }
1854
1880
  const raw = await readFile4(DETAILS_FILE, "utf-8");
@@ -1877,18 +1903,18 @@ var EnvironmentDetailsService = class {
1877
1903
  details.supportsClaudeMidTurnSteering = true;
1878
1904
  details.supportsGoalUpdates = true;
1879
1905
  details.supportsQueuedMessageEditing = true;
1880
- details.claudeAuthMethod = detectClaudeAuthMethod();
1881
- details.codexAuthMethod = detectCodexAuthMethod();
1882
- details.cursorAuthMethod = detectCursorAuthMethod();
1883
- details.deepseekAuthMethod = detectDeepseekAuthMethod();
1884
- details.fxAuthMethod = detectFxAuthMethod();
1885
- details.kimiAuthMethod = detectFxAuthMethod();
1886
- details.museAuthMethod = existsSync2(MUSE_AUTH_PATH) ? "oauth" : "none";
1887
- details.opencodeAuthMethod = detectOpencodeAuthMethod();
1888
- details.piAuthMethod = detectPiAuthMethod();
1906
+ details.claudeAuthMethod = detectAgentAuthMethod(AGENT.CLAUDE);
1907
+ details.codexAuthMethod = detectAgentAuthMethod(AGENT.CODEX);
1908
+ details.cursorAuthMethod = detectAgentAuthMethod(AGENT.CURSOR);
1909
+ details.deepseekAuthMethod = detectAgentAuthMethod(AGENT.DEEPSEEK);
1910
+ details.fxAuthMethod = detectAgentAuthMethod(AGENT.FX);
1911
+ details.kimiAuthMethod = detectAgentAuthMethod(AGENT.KIMI);
1912
+ details.museAuthMethod = detectAgentAuthMethod(AGENT.MUSE);
1913
+ details.opencodeAuthMethod = detectAgentAuthMethod(AGENT.OPENCODE);
1914
+ details.piAuthMethod = detectAgentAuthMethod(AGENT.PI);
1889
1915
  details.credentialFallbacks = listCredentialFallbacks();
1890
1916
  details.gitIdentityConfigured = gitIdentityConfigured;
1891
- const ghConfigured = existsSync2(GH_HOSTS_PATH);
1917
+ const ghConfigured = existsSync3(GH_HOSTS_PATH);
1892
1918
  details.githubAccessConfigured = ghConfigured;
1893
1919
  details.githubCredentialsConfigured = ghConfigured;
1894
1920
  details.gitlabAccessConfigured = gitlabAccessConfigured;
@@ -1965,8 +1991,8 @@ var environmentDetailsService = new EnvironmentDetailsService();
1965
1991
 
1966
1992
  // src/services/start-hook-logs-service.ts
1967
1993
  import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile3, readdir as readdir2 } from "fs/promises";
1968
- import { homedir as homedir5 } from "os";
1969
- import { join as join8 } from "path";
1994
+ import { homedir as homedir6 } from "os";
1995
+ import { join as join9 } from "path";
1970
1996
 
1971
1997
  // src/services/hook-log-files.ts
1972
1998
  import { createHash } from "crypto";
@@ -1978,7 +2004,7 @@ function repoHookLogFilename(repoName) {
1978
2004
  }
1979
2005
 
1980
2006
  // src/services/start-hook-logs-service.ts
1981
- var LOGS_DIR = join8(homedir5(), ".replicas", "start-hook-logs");
2007
+ var LOGS_DIR = join9(homedir6(), ".replicas", "start-hook-logs");
1982
2008
  function withPreview(stored) {
1983
2009
  const preview = buildHookOutputPreview(stored.output);
1984
2010
  return { ...stored, ...preview };
@@ -2016,7 +2042,7 @@ var StartHookLogsService = class {
2016
2042
  await this.ensureDir();
2017
2043
  const log = { hookType, hookName, repoName: hookName, ...entry };
2018
2044
  const filename = hookType === "environment" ? ENVIRONMENT_HOOK_LOG_FILENAME : repoHookLogFilename(hookName);
2019
- await writeFile3(join8(LOGS_DIR, filename), `${JSON.stringify(log, null, 2)}
2045
+ await writeFile3(join9(LOGS_DIR, filename), `${JSON.stringify(log, null, 2)}
2020
2046
  `, "utf-8");
2021
2047
  }
2022
2048
  async saveEnvironmentLog(entry) {
@@ -2041,7 +2067,7 @@ var StartHookLogsService = class {
2041
2067
  continue;
2042
2068
  }
2043
2069
  try {
2044
- const raw = await readFile5(join8(LOGS_DIR, file), "utf-8");
2070
+ const raw = await readFile5(join9(LOGS_DIR, file), "utf-8");
2045
2071
  const stored = normalizeStored(JSON.parse(raw));
2046
2072
  if (stored) {
2047
2073
  logs.push(withPreview(stored));
@@ -2060,7 +2086,7 @@ var StartHookLogsService = class {
2060
2086
  async getFullOutput(hookType, hookName) {
2061
2087
  const filename = hookType === "environment" ? ENVIRONMENT_HOOK_LOG_FILENAME : repoHookLogFilename(hookName);
2062
2088
  try {
2063
- const raw = await readFile5(join8(LOGS_DIR, filename), "utf-8");
2089
+ const raw = await readFile5(join9(LOGS_DIR, filename), "utf-8");
2064
2090
  const stored = normalizeStored(JSON.parse(raw));
2065
2091
  if (!stored || stored.hookType !== hookType || stored.hookName !== hookName) {
2066
2092
  return null;
@@ -2077,7 +2103,7 @@ var StartHookLogsService = class {
2077
2103
  var startHookLogsService = new StartHookLogsService();
2078
2104
 
2079
2105
  // src/services/replicas-config-service.ts
2080
- var START_HOOKS_LOG = join9(homedir6(), ".replicas", "startHooks.log");
2106
+ var START_HOOKS_LOG = join10(homedir7(), ".replicas", "startHooks.log");
2081
2107
  var START_HOOKS_RUNNING_PROMPT = `IMPORTANT - Start Hooks Running:
2082
2108
  Start hooks are shell commands/scripts set by repository owners that run on workspace startup.
2083
2109
  These hooks are currently executing in the background. You can:
@@ -2088,8 +2114,8 @@ The start hooks may install dependencies, build projects, or perform other setup
2088
2114
  If your task depends on setup being complete, check the log file before proceeding.`;
2089
2115
  async function readReplicasConfigFromDir(dirPath) {
2090
2116
  for (const filename of REPLICAS_CONFIG_FILENAMES) {
2091
- const configPath = join9(dirPath, filename);
2092
- if (!existsSync3(configPath)) {
2117
+ const configPath = join10(dirPath, filename);
2118
+ if (!existsSync4(configPath)) {
2093
2119
  continue;
2094
2120
  }
2095
2121
  const data = await readFile6(configPath, "utf-8");
@@ -2158,7 +2184,7 @@ var ReplicasConfigService = class {
2158
2184
  const logLine = `[${timestamp}] ${message}
2159
2185
  `;
2160
2186
  try {
2161
- await mkdir6(join9(homedir6(), ".replicas"), { recursive: true });
2187
+ await mkdir6(join10(homedir7(), ".replicas"), { recursive: true });
2162
2188
  await appendFile(START_HOOKS_LOG, logLine, "utf-8");
2163
2189
  } catch (error) {
2164
2190
  console.error("Failed to write to start hooks log:", error);
@@ -2170,7 +2196,7 @@ var ReplicasConfigService = class {
2170
2196
  command: params.content,
2171
2197
  label: "env start hook",
2172
2198
  repoName: "environment",
2173
- cwd: homedir6(),
2199
+ cwd: homedir7(),
2174
2200
  timeout: params.timeoutMs ?? DEFAULT_START_HOOK_TIMEOUT_MS,
2175
2201
  onOutputChunk: params.onOutputChunk
2176
2202
  });
@@ -2293,7 +2319,7 @@ var ReplicasConfigService = class {
2293
2319
  this.hooksCompleted = false;
2294
2320
  this.hooksFailed = false;
2295
2321
  try {
2296
- await mkdir6(join9(homedir6(), ".replicas"), { recursive: true });
2322
+ await mkdir6(join10(homedir7(), ".replicas"), { recursive: true });
2297
2323
  await writeFile4(
2298
2324
  START_HOOKS_LOG,
2299
2325
  `=== Start Hooks Execution Log ===
@@ -2488,11 +2514,11 @@ var replicasConfigService = new ReplicasConfigService();
2488
2514
 
2489
2515
  // src/services/event-service.ts
2490
2516
  import { mkdir as mkdir7 } from "fs/promises";
2491
- import { homedir as homedir7 } from "os";
2492
- import { join as join10 } from "path";
2517
+ import { homedir as homedir8 } from "os";
2518
+ import { join as join11 } from "path";
2493
2519
  import { randomUUID as randomUUID2 } from "crypto";
2494
- var ENGINE_DIR = join10(homedir7(), ".replicas", "engine");
2495
- var EVENTS_FILE = join10(ENGINE_DIR, "events.jsonl");
2520
+ var ENGINE_DIR = join11(homedir8(), ".replicas", "engine");
2521
+ var EVENTS_FILE = join11(ENGINE_DIR, "events.jsonl");
2496
2522
  var EventService = class {
2497
2523
  subscribers = /* @__PURE__ */ new Map();
2498
2524
  writer = new StreamWriter();
@@ -2524,14 +2550,14 @@ var eventService = new EventService();
2524
2550
 
2525
2551
  // src/services/preview-service.ts
2526
2552
  import { mkdir as mkdir8, readFile as readFile7 } from "fs/promises";
2527
- import { existsSync as existsSync4 } from "fs";
2553
+ import { existsSync as existsSync5 } from "fs";
2528
2554
  import { randomUUID as randomUUID3 } from "crypto";
2529
- import { homedir as homedir8 } from "os";
2530
- import { dirname as dirname2, join as join11 } from "path";
2531
- var PREVIEW_PORTS_FILE = join11(homedir8(), ".replicas", "preview-ports.json");
2555
+ import { homedir as homedir9 } from "os";
2556
+ import { dirname as dirname2, join as join12 } from "path";
2557
+ var PREVIEW_PORTS_FILE = join12(homedir9(), ".replicas", "preview-ports.json");
2532
2558
  async function readPreviewsFile() {
2533
2559
  try {
2534
- if (!existsSync4(PREVIEW_PORTS_FILE)) {
2560
+ if (!existsSync5(PREVIEW_PORTS_FILE)) {
2535
2561
  return { previews: [] };
2536
2562
  }
2537
2563
  const raw = await readFile7(PREVIEW_PORTS_FILE, "utf-8");
@@ -2638,9 +2664,8 @@ async function registerDesktopPreview() {
2638
2664
  }
2639
2665
 
2640
2666
  // src/services/chat/chat-service.ts
2641
- import { existsSync as existsSync11 } from "fs";
2642
- import { appendFile as appendFile4, copyFile as copyFile2, mkdir as mkdir19, readFile as readFile20, rename as rename3, rm as rm3 } from "fs/promises";
2643
- import { join as join28 } from "path";
2667
+ import { appendFile as appendFile4, copyFile as copyFile2, mkdir as mkdir20, readFile as readFile20, rename as rename3, rm as rm3 } from "fs/promises";
2668
+ import { join as join30 } from "path";
2644
2669
  import { randomUUID as randomUUID9 } from "crypto";
2645
2670
 
2646
2671
  // ../shared/src/workspace-chat.ts
@@ -2807,9 +2832,9 @@ import {
2807
2832
  query
2808
2833
  } from "@anthropic-ai/claude-agent-sdk";
2809
2834
  import { randomUUID as randomUUID5 } from "crypto";
2810
- import { dirname as dirname4, join as join15 } from "path";
2835
+ import { dirname as dirname4, join as join16 } from "path";
2811
2836
  import { mkdir as mkdir10 } from "fs/promises";
2812
- import { homedir as homedir10 } from "os";
2837
+ import { homedir as homedir11 } from "os";
2813
2838
 
2814
2839
  // src/utils/linear-converter.ts
2815
2840
  function linearThoughtToResponse(thought) {
@@ -3194,8 +3219,8 @@ function extractPlanFromCodexAspNotification(notification) {
3194
3219
  // src/utils/image-utils.ts
3195
3220
  import { randomUUID as randomUUID4 } from "crypto";
3196
3221
  import { mkdir as mkdir9, unlink as unlink2, writeFile as writeFile5 } from "fs/promises";
3197
- import { homedir as homedir9 } from "os";
3198
- import { join as join12 } from "path";
3222
+ import { homedir as homedir10 } from "os";
3223
+ import { join as join13 } from "path";
3199
3224
  function isImageMediaType(value) {
3200
3225
  return IMAGE_MEDIA_TYPES.includes(value);
3201
3226
  }
@@ -3295,14 +3320,14 @@ async function normalizeImages(images) {
3295
3320
  }
3296
3321
  return normalized;
3297
3322
  }
3298
- async function saveNormalizedImagesToTempFiles(images, tempImageDir = join12(homedir9(), ".replicas", "codex", "temp-images")) {
3323
+ async function saveNormalizedImagesToTempFiles(images, tempImageDir = join13(homedir10(), ".replicas", "codex", "temp-images")) {
3299
3324
  await mkdir9(tempImageDir, { recursive: true });
3300
3325
  const tempPaths = [];
3301
3326
  try {
3302
3327
  for (const image of images) {
3303
3328
  const ext = image.source.media_type.split("/")[1] || "png";
3304
3329
  const filename = `img_${randomUUID4()}.${ext}`;
3305
- const filepath = join12(tempImageDir, filename);
3330
+ const filepath = join13(tempImageDir, filename);
3306
3331
  await writeFile5(filepath, Buffer.from(image.source.data, "base64"));
3307
3332
  tempPaths.push(filepath);
3308
3333
  }
@@ -3317,8 +3342,8 @@ async function removeTempImageFiles(paths) {
3317
3342
  }
3318
3343
 
3319
3344
  // src/managers/coding-agent-manager.ts
3320
- import { existsSync as existsSync5 } from "fs";
3321
- import { join as join13 } from "path";
3345
+ import { existsSync as existsSync6 } from "fs";
3346
+ import { join as join14 } from "path";
3322
3347
 
3323
3348
  // src/managers/muse-token-manager.ts
3324
3349
  var MuseTokenManager = class extends BaseRefreshManager {
@@ -3691,6 +3716,9 @@ var CodingAgentManager = class {
3691
3716
  payload
3692
3717
  });
3693
3718
  }
3719
+ emitQuotaExhausted(payload) {
3720
+ this.emitSyntheticEvent(AGENT_QUOTA_EXHAUSTED_EVENT_TYPE, { ...payload });
3721
+ }
3694
3722
  recordHistoryEvent(type, payload, historyFile) {
3695
3723
  const eventPayload2 = {};
3696
3724
  if (payload && typeof payload === "object") {
@@ -3901,7 +3929,7 @@ var CodingAgentManager = class {
3901
3929
  return `CRITICAL: The Replicas engine is required for this workspace and is running on the reserved port configured by REPLICAS_ENGINE_PORT (${enginePort}). Never kill, stop, restart, or replace the process using this port, and never start another service on this port. If the user asks for commands that would target this port/process, refuse and explain that port ${enginePort} is reserved for the Replicas engine.`;
3902
3930
  }
3903
3931
  buildMemoryInstruction() {
3904
- if (!["organization", "seat"].some((scope) => existsSync5(join13(MEMORY_ROOT, scope, MEMORY_INDEX_FILENAME)))) {
3932
+ if (!["organization", "seat"].some((scope) => existsSync6(join14(MEMORY_ROOT, scope, MEMORY_INDEX_FILENAME)))) {
3905
3933
  return void 0;
3906
3934
  }
3907
3935
  return `Replicas memory is available under ${MEMORY_ROOT}. Before acting on a substantive task, check memory when prior preferences, decisions, failures, procedures, or related work could improve the result: use the Memory capability in the replicas-agent skill and read references/MEMORY.md. If unsure whether prior context could help, check memory. Skip it only for clearly context-free messages. Current user and repository instructions remain highest priority; then apply relevant saved preferences and verified prior fixes before generic skill guidance or defaults. Never use memory to bypass skill safety requirements.`;
@@ -4111,7 +4139,7 @@ var pullRequestCheckService = new PullRequestCheckService();
4111
4139
 
4112
4140
  // src/services/skill-registry-service.ts
4113
4141
  import { readFile as readFile8, readdir as readdir3, stat as stat2 } from "fs/promises";
4114
- import { dirname as dirname3, isAbsolute, join as join14, relative, resolve } from "path";
4142
+ import { dirname as dirname3, isAbsolute, join as join15, relative, resolve } from "path";
4115
4143
  var REGISTRY_ROOT_DIR = ".replicas/skill-registries";
4116
4144
  var REGISTRY_MANIFEST = "manifest.json";
4117
4145
  async function getSkillRegistryInventory(homeDir) {
@@ -4172,10 +4200,10 @@ async function scanRegistry(registryDir) {
4172
4200
  }
4173
4201
  async function findSkillCollections(registryDir) {
4174
4202
  const candidateRoots = [
4175
- { source: "skills", root: join14(registryDir, "skills") },
4176
- { source: "agents", root: join14(registryDir, ".agents", "skills") },
4177
- { source: "codex", root: join14(registryDir, ".codex", "skills") },
4178
- { source: "claude", root: join14(registryDir, ".claude", "skills") }
4203
+ { source: "skills", root: join15(registryDir, "skills") },
4204
+ { source: "agents", root: join15(registryDir, ".agents", "skills") },
4205
+ { source: "codex", root: join15(registryDir, ".codex", "skills") },
4206
+ { source: "claude", root: join15(registryDir, ".claude", "skills") }
4179
4207
  ];
4180
4208
  const collections = [];
4181
4209
  for (const { source, root } of candidateRoots) {
@@ -4189,9 +4217,9 @@ async function findSkillCollections(registryDir) {
4189
4217
  async function findSkillDirsInRoot(root) {
4190
4218
  const skillDirs = [];
4191
4219
  for (const dirent of await safeReadDir(root)) {
4192
- const skillDir = join14(root, dirent.name);
4220
+ const skillDir = join15(root, dirent.name);
4193
4221
  if (!await isDirectoryDirent(dirent, skillDir)) continue;
4194
- if (await fileExists(join14(skillDir, "SKILL.md"))) {
4222
+ if (await fileExists(join15(skillDir, "SKILL.md"))) {
4195
4223
  skillDirs.push(skillDir);
4196
4224
  }
4197
4225
  }
@@ -4201,9 +4229,9 @@ async function findTopLevelSkills(registryDir) {
4201
4229
  const skills = [];
4202
4230
  for (const dirent of await safeReadDir(registryDir)) {
4203
4231
  if (dirent.name.startsWith(".") || dirent.name === "skills" || dirent.name === "plugins") continue;
4204
- const skillDir = join14(registryDir, dirent.name);
4232
+ const skillDir = join15(registryDir, dirent.name);
4205
4233
  if (!await isDirectoryDirent(dirent, skillDir)) continue;
4206
- if (await fileExists(join14(skillDir, "SKILL.md"))) {
4234
+ if (await fileExists(join15(skillDir, "SKILL.md"))) {
4207
4235
  skills.push({ source: "root", skillDir });
4208
4236
  }
4209
4237
  }
@@ -4214,10 +4242,10 @@ async function findClaudePluginRoots(registryDir) {
4214
4242
  async function walk(dir) {
4215
4243
  for (const dirent of await safeReadDir(dir)) {
4216
4244
  if (dirent.name === ".git") continue;
4217
- const child = join14(dir, dirent.name);
4245
+ const child = join15(dir, dirent.name);
4218
4246
  if (!await isDirectoryDirent(dirent, child)) continue;
4219
4247
  if (dirent.name === ".claude-plugin") {
4220
- if (await fileExists(join14(child, "plugin.json"))) {
4248
+ if (await fileExists(join15(child, "plugin.json"))) {
4221
4249
  roots.push(dirname3(child));
4222
4250
  }
4223
4251
  continue;
@@ -4229,7 +4257,7 @@ async function findClaudePluginRoots(registryDir) {
4229
4257
  return uniqueStrings(roots);
4230
4258
  }
4231
4259
  async function hasCodexMarketplace(registryDir) {
4232
- return await fileExists(join14(registryDir, ".agents", "plugins", "marketplace.json")) || await fileExists(join14(registryDir, ".codex", "plugins", "marketplace.json"));
4260
+ return await fileExists(join15(registryDir, ".agents", "plugins", "marketplace.json")) || await fileExists(join15(registryDir, ".codex", "plugins", "marketplace.json"));
4233
4261
  }
4234
4262
  async function installCodexRegistryPlugins(client2, inventory) {
4235
4263
  const cwds = inventory.codexMarketplaceCwds;
@@ -4271,10 +4299,10 @@ async function readManifest(homeDir) {
4271
4299
  }
4272
4300
  }
4273
4301
  function getRegistryRoot(homeDir) {
4274
- return join14(homeDir, REGISTRY_ROOT_DIR);
4302
+ return join15(homeDir, REGISTRY_ROOT_DIR);
4275
4303
  }
4276
4304
  function getManifestPath(homeDir) {
4277
- return join14(getRegistryRoot(homeDir), REGISTRY_MANIFEST);
4305
+ return join15(getRegistryRoot(homeDir), REGISTRY_MANIFEST);
4278
4306
  }
4279
4307
  function emptyInventory(registryRoot) {
4280
4308
  return {
@@ -4725,7 +4753,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
4725
4753
  authRetrying = false;
4726
4754
  constructor(options) {
4727
4755
  super({ ...options, provider: AGENT.CLAUDE });
4728
- this.historyFilePath = options.historyFilePath ?? join15(homedir10(), ".replicas", "claude", "history.jsonl");
4756
+ this.historyFilePath = options.historyFilePath ?? join16(homedir11(), ".replicas", "claude", "history.jsonl");
4729
4757
  this.historyFile = new CodexHistoryFile(this.historyFilePath);
4730
4758
  this.systemPromptOverride = options.systemPromptOverride;
4731
4759
  this.toolsOverride = options.tools;
@@ -5095,6 +5123,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
5095
5123
  const limit = kind === "out_of_credits" ? "Claude ran out of credits on the credential this workspace is using." : "Claude hit the usage limit on the credential this workspace is using.";
5096
5124
  const next = outcome === "failed" ? "The configured fallback credential could not be used. Check it in Settings \u2192 Agents and try again." : outcome === "not_configured" ? "No fallback credential is configured for this workspace. Add one in Settings \u2192 Agents, or try again once the limit resets." : "No other credential is available for this workspace. Check Settings \u2192 Agents, or try again once the limit resets.";
5097
5125
  await this.emitTerminalErrorResult(detail, [`${limit} ${next}`]);
5126
+ this.emitQuotaExhausted({ provider: "claude", reason: kind, detail });
5098
5127
  }
5099
5128
  async emitMidTurnExhaustedEvent(error) {
5100
5129
  const detail = error instanceof Error ? error.message : String(error);
@@ -5800,7 +5829,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
5800
5829
 
5801
5830
  // src/managers/codex-asp/codex-asp-manager.ts
5802
5831
  import { readdir as readdir4 } from "fs/promises";
5803
- import { join as join17 } from "path";
5832
+ import { join as join18 } from "path";
5804
5833
 
5805
5834
  // src/managers/codex-asp/asp-host.ts
5806
5835
  var hostPromise = null;
@@ -5915,7 +5944,7 @@ var CodexQuotaStatusTracker = class {
5915
5944
  };
5916
5945
 
5917
5946
  // src/managers/codex-asp/mappers.ts
5918
- import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
5947
+ import { existsSync as existsSync7, readFileSync as readFileSync3 } from "fs";
5919
5948
  var localImageCache = /* @__PURE__ */ new Map();
5920
5949
  var DEFAULT_MODEL = DEFAULT_CODEX_MODEL;
5921
5950
  var THREAD_START_METHOD = "thread/start";
@@ -6039,7 +6068,7 @@ function transcriptItemsForTurn(turn) {
6039
6068
  function userImageForLocalPath(path6) {
6040
6069
  const cached = localImageCache.get(path6);
6041
6070
  if (cached) return cached;
6042
- if (!existsSync6(path6)) return null;
6071
+ if (!existsSync7(path6)) return null;
6043
6072
  const image = {
6044
6073
  type: "image",
6045
6074
  mediaType: inferMediaType(path6),
@@ -6534,20 +6563,21 @@ var TranscriptUpdateCoalescer = class {
6534
6563
  };
6535
6564
 
6536
6565
  // src/services/chat/history-paths.ts
6537
- import { homedir as homedir11 } from "os";
6538
- import { join as join16 } from "path";
6539
- var ENGINE_DIR2 = join16(homedir11(), ".replicas", "engine");
6540
- var CHATS_FILE = join16(ENGINE_DIR2, "chats.json");
6541
- var CLAUDE_HISTORY_DIR = join16(ENGINE_DIR2, "claude-histories");
6542
- var RELAY_HISTORY_DIR = join16(ENGINE_DIR2, "relay-histories");
6543
- var CODEX_HISTORY_DIR = join16(ENGINE_DIR2, "codex-histories");
6544
- var CURSOR_HISTORY_DIR = join16(ENGINE_DIR2, "cursor-histories");
6545
- var OPENCODE_HISTORY_DIR = join16(ENGINE_DIR2, "opencode-histories");
6546
- var PI_HISTORY_DIR = join16(ENGINE_DIR2, "pi-histories");
6547
- var DEEPSEEK_HISTORY_DIR = join16(ENGINE_DIR2, "deepseek-histories");
6548
- var FX_HISTORY_DIR = join16(ENGINE_DIR2, "fx-histories");
6549
- var KIMI_HISTORY_DIR = join16(ENGINE_DIR2, "kimi-histories");
6550
- var MUSE_HISTORY_DIR = join16(ENGINE_DIR2, "muse-histories");
6566
+ import { homedir as homedir12 } from "os";
6567
+ import { join as join17 } from "path";
6568
+ var ENGINE_DIR2 = join17(homedir12(), ".replicas", "engine");
6569
+ var CHATS_FILE = join17(ENGINE_DIR2, "chats.json");
6570
+ var CLAUDE_HISTORY_DIR = join17(ENGINE_DIR2, "claude-histories");
6571
+ var RELAY_HISTORY_DIR = join17(ENGINE_DIR2, "relay-histories");
6572
+ var CODEX_HISTORY_DIR = join17(ENGINE_DIR2, "codex-histories");
6573
+ var CURSOR_HISTORY_DIR = join17(ENGINE_DIR2, "cursor-histories");
6574
+ var OPENCODE_HISTORY_DIR = join17(ENGINE_DIR2, "opencode-histories");
6575
+ var PI_HISTORY_DIR = join17(ENGINE_DIR2, "pi-histories");
6576
+ var DEEPSEEK_HISTORY_DIR = join17(ENGINE_DIR2, "deepseek-histories");
6577
+ var FX_HISTORY_DIR = join17(ENGINE_DIR2, "fx-histories");
6578
+ var KIMI_HISTORY_DIR = join17(ENGINE_DIR2, "kimi-histories");
6579
+ var MUSE_HISTORY_DIR = join17(ENGINE_DIR2, "muse-histories");
6580
+ var FORKS_DIR = join17(ENGINE_DIR2, "forks");
6551
6581
  var HISTORY_DIR_BY_PROVIDER = {
6552
6582
  claude: CLAUDE_HISTORY_DIR,
6553
6583
  relay: RELAY_HISTORY_DIR,
@@ -6587,6 +6617,12 @@ var ChatMessagePersistenceError = class extends Error {
6587
6617
  this.cause = cause;
6588
6618
  }
6589
6619
  };
6620
+ var ChatProviderUnavailableError = class extends Error {
6621
+ constructor(provider) {
6622
+ super(`${provider} is not configured in this workspace`);
6623
+ this.name = "ChatProviderUnavailableError";
6624
+ }
6625
+ };
6590
6626
 
6591
6627
  // src/managers/codex-asp/codex-asp-manager.ts
6592
6628
  var CodexQuotaError = class extends Error {
@@ -6614,7 +6650,7 @@ async function readCodexAspThreadHistory(threadId) {
6614
6650
  }
6615
6651
  for (const entry of entries) {
6616
6652
  if (!entry.isFile() || !entry.name.endsWith(".jsonl") || entry.name.endsWith(".pages.jsonl")) continue;
6617
- const history = await new CodexHistoryFile(join17(CODEX_HISTORY_DIR, entry.name)).load();
6653
+ const history = await new CodexHistoryFile(join18(CODEX_HISTORY_DIR, entry.name)).load();
6618
6654
  const transcript = history.transcriptsByThreadId.get(threadId);
6619
6655
  if (transcript) {
6620
6656
  return {
@@ -6974,8 +7010,14 @@ var CodexAspManager = class extends CodingAgentManager {
6974
7010
  const retryFailure = await this.restartAndRetry(dispatch);
6975
7011
  if (!retryFailure) return;
6976
7012
  terminalError = retryFailure.error;
6977
- } else if (await this.confirmOutOfCredits(await getCodexAspHost())) {
6978
- return;
7013
+ } else {
7014
+ const outOfCredits = await this.confirmOutOfCredits(await getCodexAspHost());
7015
+ this.emitQuotaExhausted({
7016
+ provider: "codex",
7017
+ reason: outOfCredits ? "out_of_credits" : detectAgentQuotaLimit(terminalError.message) ?? "rate_limit",
7018
+ detail: terminalError.message
7019
+ });
7020
+ if (outOfCredits) return;
6979
7021
  }
6980
7022
  }
6981
7023
  if (terminalError instanceof CodexAspAuthMethodChangedError) {
@@ -8003,24 +8045,24 @@ var CodexAspManager = class extends CodingAgentManager {
8003
8045
 
8004
8046
  // src/managers/cursor-manager.ts
8005
8047
  import { mkdir as mkdir12, readFile as readFile11, readdir as readdir5 } from "fs/promises";
8006
- import { basename as basename2, dirname as dirname6, extname, join as join19 } from "path";
8048
+ import { basename as basename2, dirname as dirname6, extname, join as join20 } from "path";
8007
8049
  import { parse as parseYaml } from "yaml";
8008
8050
  import { Agent as CursorAgent } from "@cursor/sdk";
8009
8051
 
8010
8052
  // src/services/native-command-protection-hooks.ts
8011
- import { existsSync as existsSync7 } from "fs";
8053
+ import { existsSync as existsSync8 } from "fs";
8012
8054
  import { mkdir as mkdir11, readFile as readFile10, writeFile as writeFile6 } from "fs/promises";
8013
- import { basename, dirname as dirname5, join as join18 } from "path";
8055
+ import { basename, dirname as dirname5, join as join19 } from "path";
8014
8056
  import { fileURLToPath } from "url";
8015
8057
  var moduleDirectory = dirname5(fileURLToPath(import.meta.url));
8016
8058
  var hookPath = [
8017
- join18(moduleDirectory, "command-protection-hook.js"),
8018
- join18(moduleDirectory, "..", "command-protection-hook.ts")
8019
- ].find(existsSync7);
8059
+ join19(moduleDirectory, "command-protection-hook.js"),
8060
+ join19(moduleDirectory, "..", "command-protection-hook.ts")
8061
+ ].find(existsSync8);
8020
8062
  var postToolHookPath = [
8021
- join18(moduleDirectory, "post-tool-pr-hook.js"),
8022
- join18(moduleDirectory, "..", "post-tool-pr-hook.ts")
8023
- ].find(existsSync7);
8063
+ join19(moduleDirectory, "post-tool-pr-hook.js"),
8064
+ join19(moduleDirectory, "..", "post-tool-pr-hook.ts")
8065
+ ].find(existsSync8);
8024
8066
  function shellCommand(path6, provider) {
8025
8067
  if (!path6) throw new Error("Replicas hook executable is missing.");
8026
8068
  const args = [
@@ -8040,7 +8082,7 @@ function getPostToolPrHookPath() {
8040
8082
  return postToolHookPath;
8041
8083
  }
8042
8084
  async function ensureCursorCommandProtectionHook() {
8043
- const path6 = join18(ENGINE_ENV.HOME_DIR, ".cursor", "hooks.json");
8085
+ const path6 = join19(ENGINE_ENV.HOME_DIR, ".cursor", "hooks.json");
8044
8086
  let config = {};
8045
8087
  try {
8046
8088
  const parsed = JSON.parse(await readFile10(path6, "utf8"));
@@ -8065,7 +8107,7 @@ async function ensureCursorCommandProtectionHook() {
8065
8107
  `, "utf8");
8066
8108
  }
8067
8109
  async function ensureKimiCommandProtectionHook() {
8068
- const path6 = join18(ENGINE_ENV.HOME_DIR, ".kimi-code", "config.toml");
8110
+ const path6 = join19(ENGINE_ENV.HOME_DIR, ".kimi-code", "config.toml");
8069
8111
  const start = "# Replicas command protection hook";
8070
8112
  const end = "# End Replicas command protection hook";
8071
8113
  let config = "";
@@ -8149,7 +8191,7 @@ async function listCursorCommandsInDirectory(directory) {
8149
8191
  const name = basename2(entry.name, ".md");
8150
8192
  let description;
8151
8193
  try {
8152
- description = extractCursorCommandDescription(await readFile11(join19(directory, entry.name), "utf8"));
8194
+ description = extractCursorCommandDescription(await readFile11(join20(directory, entry.name), "utf8"));
8153
8195
  } catch (error) {
8154
8196
  console.warn("[CursorManager] Failed to read slash command file:", error);
8155
8197
  }
@@ -8171,7 +8213,7 @@ var CursorManager = class extends CodingAgentManager {
8171
8213
  slashCommandsRequest = null;
8172
8214
  constructor(options) {
8173
8215
  super(options);
8174
- this.historyFilePath = options.historyFilePath ?? join19(ENGINE_ENV.HOME_DIR, ".replicas", "cursor", "history.jsonl");
8216
+ this.historyFilePath = options.historyFilePath ?? join20(ENGINE_ENV.HOME_DIR, ".replicas", "cursor", "history.jsonl");
8175
8217
  this.historyFile = new CodexHistoryFile(this.historyFilePath);
8176
8218
  this.initializeManager(this.processMessageInternal.bind(this));
8177
8219
  }
@@ -8246,7 +8288,7 @@ var CursorManager = class extends CodingAgentManager {
8246
8288
  this.slashCommandsRequest ??= (async () => {
8247
8289
  try {
8248
8290
  const repoDirectories = await getAgentAdditionalDirectories();
8249
- const commandDirectories = [this.workingDirectory, ...repoDirectories, ENGINE_ENV.HOME_DIR].map((directory) => join19(directory, ".cursor", "commands"));
8291
+ const commandDirectories = [this.workingDirectory, ...repoDirectories, ENGINE_ENV.HOME_DIR].map((directory) => join20(directory, ".cursor", "commands"));
8250
8292
  const commands = mergeSlashCommands(
8251
8293
  ...await Promise.all(commandDirectories.map(listCursorCommandsInDirectory))
8252
8294
  );
@@ -8527,9 +8569,9 @@ ${instructions}
8527
8569
  import { spawn as spawn3 } from "child_process";
8528
8570
  import { randomUUID as randomUUID6 } from "crypto";
8529
8571
  import { createRequire } from "module";
8530
- import { dirname as dirname7, join as join20 } from "path";
8572
+ import { dirname as dirname7, join as join21 } from "path";
8531
8573
  import { fileURLToPath as fileURLToPath2 } from "url";
8532
- import { existsSync as existsSync8 } from "fs";
8574
+ import { existsSync as existsSync9 } from "fs";
8533
8575
  import { mkdir as mkdir13, readFile as readFile12, writeFile as writeFile7 } from "fs/promises";
8534
8576
  import { z as z2 } from "zod";
8535
8577
  import WebSocket from "ws";
@@ -8542,16 +8584,16 @@ var questionSelectionSchema = z2.record(z2.string(), z2.object({
8542
8584
  custom: z2.string().optional()
8543
8585
  }));
8544
8586
  var require2 = createRequire(import.meta.url);
8545
- var DSH_BIN = join20(dirname7(require2.resolve("@deepseek-ai/dsh/package.json")), "lib", "bin.js");
8587
+ var DSH_BIN = join21(dirname7(require2.resolve("@deepseek-ai/dsh/package.json")), "lib", "bin.js");
8546
8588
  var MANAGER_DIR = dirname7(fileURLToPath2(import.meta.url));
8547
8589
  var DSH_PATCH = [
8548
- join20(MANAGER_DIR, "..", "..", "scripts", "deepseek", "replicas.cordis.patch.yml"),
8549
- join20(MANAGER_DIR, "..", "..", "..", "scripts", "deepseek", "replicas.cordis.patch.yml")
8550
- ].find(existsSync8) ?? "";
8590
+ join21(MANAGER_DIR, "..", "..", "scripts", "deepseek", "replicas.cordis.patch.yml"),
8591
+ join21(MANAGER_DIR, "..", "..", "..", "scripts", "deepseek", "replicas.cordis.patch.yml")
8592
+ ].find(existsSync9) ?? "";
8551
8593
  var DSH_COMMAND_PROTECTION_PLUGIN = [
8552
- join20(MANAGER_DIR, "deepseek-command-protection-plugin.js"),
8553
- join20(MANAGER_DIR, "..", "deepseek-command-protection-plugin.ts")
8554
- ].find(existsSync8) ?? "";
8594
+ join21(MANAGER_DIR, "deepseek-command-protection-plugin.js"),
8595
+ join21(MANAGER_DIR, "..", "deepseek-command-protection-plugin.ts")
8596
+ ].find(existsSync9) ?? "";
8555
8597
  var DeepseekApiClient = class extends AbstractApiClient {
8556
8598
  constructor(baseUrl) {
8557
8599
  super(3e4);
@@ -8664,7 +8706,7 @@ var DeepseekManager = class extends CodingAgentManager {
8664
8706
  constructor(options) {
8665
8707
  super(options);
8666
8708
  this.sessionId = options.initialSessionId ? sessionIdSchema.parse(options.initialSessionId) : null;
8667
- this.historyFilePath = options.historyFilePath ?? join20(ENGINE_ENV.HOME_DIR, ".replicas", "deepseek", "history.jsonl");
8709
+ this.historyFilePath = options.historyFilePath ?? join21(ENGINE_ENV.HOME_DIR, ".replicas", "deepseek", "history.jsonl");
8668
8710
  this.historyFile = new CodexHistoryFile(this.historyFilePath);
8669
8711
  this.initializeManager(this.processMessageInternal.bind(this));
8670
8712
  }
@@ -8744,7 +8786,7 @@ var DeepseekManager = class extends CodingAgentManager {
8744
8786
  async ensureClient() {
8745
8787
  if (this.client) return this.client;
8746
8788
  if (!DSH_PATCH || !DSH_COMMAND_PROTECTION_PLUGIN) throw new Error("DeepSeek Harness configuration is missing from the engine package.");
8747
- const runtimePatch = join20(ENGINE_ENV.HOME_DIR, ".replicas", "deepseek", "replicas.cordis.patch.yml");
8789
+ const runtimePatch = join21(ENGINE_ENV.HOME_DIR, ".replicas", "deepseek", "replicas.cordis.patch.yml");
8748
8790
  await mkdir13(dirname7(runtimePatch), { recursive: true });
8749
8791
  await writeFile7(runtimePatch, (await readFile12(DSH_PATCH, "utf8")).replace(
8750
8792
  "__REPLICAS_DSH_COMMAND_PROTECTION_PLUGIN__",
@@ -8768,7 +8810,7 @@ var DeepseekManager = class extends CodingAgentManager {
8768
8810
  ...process.env,
8769
8811
  DEEPSEEK_API_KEY: apiKey,
8770
8812
  DEEPSEEK_BASE_URL: "https://openrouter.ai/api/v1",
8771
- DSH_HOME: join20(ENGINE_ENV.HOME_DIR, ".replicas", "deepseek"),
8813
+ DSH_HOME: join21(ENGINE_ENV.HOME_DIR, ".replicas", "deepseek"),
8772
8814
  DSH_PERMISSION_MODE: "danger-full-access",
8773
8815
  DSH_TELEMETRY_DISABLED: "true"
8774
8816
  },
@@ -9869,9 +9911,9 @@ ${pending}
9869
9911
  };
9870
9912
 
9871
9913
  // src/managers/opencode-manager.ts
9872
- import { existsSync as existsSync9 } from "fs";
9914
+ import { existsSync as existsSync10 } from "fs";
9873
9915
  import { copyFile, mkdir as mkdir16, readFile as readFile15, rm as rm2 } from "fs/promises";
9874
- import { delimiter, dirname as dirname10, join as join21 } from "path";
9916
+ import { delimiter, dirname as dirname10, join as join22 } from "path";
9875
9917
  import { randomBytes as randomBytes3 } from "crypto";
9876
9918
  import { fileURLToPath as fileURLToPath3 } from "url";
9877
9919
  import { Agent } from "undici";
@@ -9931,10 +9973,10 @@ async function getAllowedOpenRouterModels() {
9931
9973
 
9932
9974
  // src/managers/opencode-manager.ts
9933
9975
  var OPENCODE_SHIM_DIR = dirname10(fileURLToPath3(new URL("../../scripts/opencode", import.meta.url)));
9934
- var OPENCODE_COMMAND_PROTECTION_PLUGIN_SOURCE = join21(OPENCODE_SHIM_DIR, "opencode-command-protection-plugin.ts");
9935
- var OPENCODE_COMMAND_PROTECTION_PLUGIN_PATH = join21(ENGINE_ENV.HOME_DIR, ".config", "opencode", "plugins", "replicas-command-protection.ts");
9936
- var OPENCODE_LEGACY_COMMAND_PROTECTION_PLUGIN_PATH = join21(ENGINE_ENV.HOME_DIR, ".config", "opencode", "plugins", "replicas-command-protection.mjs");
9937
- var OPENCODE_CONFIG_PATH = join21(ENGINE_ENV.HOME_DIR, ".config", "opencode", "opencode.json");
9976
+ var OPENCODE_COMMAND_PROTECTION_PLUGIN_SOURCE = join22(OPENCODE_SHIM_DIR, "opencode-command-protection-plugin.ts");
9977
+ var OPENCODE_COMMAND_PROTECTION_PLUGIN_PATH = join22(ENGINE_ENV.HOME_DIR, ".config", "opencode", "plugins", "replicas-command-protection.ts");
9978
+ var OPENCODE_LEGACY_COMMAND_PROTECTION_PLUGIN_PATH = join22(ENGINE_ENV.HOME_DIR, ".config", "opencode", "plugins", "replicas-command-protection.mjs");
9979
+ var OPENCODE_CONFIG_PATH = join22(ENGINE_ENV.HOME_DIR, ".config", "opencode", "opencode.json");
9938
9980
  var OPENCODE_FETCH_DISPATCHER = new Agent({ headersTimeout: 0, bodyTimeout: 0 });
9939
9981
  var OPENCODE_SERVER_STARTUP_TIMEOUT_MS = 3e4;
9940
9982
  var OPENCODE_WORKSPACE_PERMISSION = "allow";
@@ -9953,7 +9995,7 @@ var opencodeAuthSchema = z3.record(z3.string(), z3.object({
9953
9995
  key: z3.string().optional()
9954
9996
  }));
9955
9997
  async function hasOpencodeCredentials(provider) {
9956
- if (!existsSync9(OPENCODE_AUTH_PATH)) return false;
9998
+ if (!existsSync10(OPENCODE_AUTH_PATH)) return false;
9957
9999
  try {
9958
10000
  const auth = opencodeAuthSchema.safeParse(JSON.parse(await readFile15(OPENCODE_AUTH_PATH, "utf8")));
9959
10001
  return auth.success && auth.data[provider]?.type === "api" && Boolean(auth.data[provider]?.key);
@@ -10146,7 +10188,7 @@ var OpencodeManager = class extends CodingAgentManager {
10146
10188
  constructor(options) {
10147
10189
  super(options);
10148
10190
  this.sessionId = options.initialSessionId;
10149
- this.historyFilePath = options.historyFilePath ?? join21(ENGINE_ENV.HOME_DIR, ".replicas", "opencode", "history.jsonl");
10191
+ this.historyFilePath = options.historyFilePath ?? join22(ENGINE_ENV.HOME_DIR, ".replicas", "opencode", "history.jsonl");
10150
10192
  this.historyFile = new CodexHistoryFile(this.historyFilePath);
10151
10193
  this.initializeManager(this.processMessageInternal.bind(this));
10152
10194
  }
@@ -10248,7 +10290,7 @@ var OpencodeManager = class extends CodingAgentManager {
10248
10290
  if (!await hasOpencodeCredentials(providerId)) {
10249
10291
  throw new Error("OpenCode Go, Aster, or OpenRouter credentials are not configured for Opencode in this workspace.");
10250
10292
  }
10251
- if (!existsSync9(OPENCODE_COMMAND_PROTECTION_PLUGIN_SOURCE)) {
10293
+ if (!existsSync10(OPENCODE_COMMAND_PROTECTION_PLUGIN_SOURCE)) {
10252
10294
  throw new Error("Opencode command protection plugin is missing from the engine package.");
10253
10295
  }
10254
10296
  await mkdir16(dirname10(OPENCODE_COMMAND_PROTECTION_PLUGIN_PATH), { recursive: true });
@@ -10445,6 +10487,7 @@ var OpencodeManager = class extends CodingAgentManager {
10445
10487
  this.handledAccountRateLimit = true;
10446
10488
  const message = typeof status.message === "string" ? status.message : typeof action.message === "string" ? action.message : "OpenCode Go usage limit reached.";
10447
10489
  this.recordHistoryEvent("opencode-error", { message, code: "account_rate_limit" }, this.historyFile);
10490
+ this.emitQuotaExhausted({ provider: "opencode", reason: "rate_limit", detail: message });
10448
10491
  await this.abortActiveTurn();
10449
10492
  }
10450
10493
  recordOpencodeMessageRole(payload) {
@@ -10547,7 +10590,7 @@ var OpencodeManager = class extends CodingAgentManager {
10547
10590
 
10548
10591
  // src/managers/pi-manager.ts
10549
10592
  import { mkdir as mkdir17 } from "fs/promises";
10550
- import { dirname as dirname11, join as join22 } from "path";
10593
+ import { dirname as dirname11, join as join23 } from "path";
10551
10594
  import {
10552
10595
  AuthStorage,
10553
10596
  createAgentSession,
@@ -10661,7 +10704,7 @@ var PiManager = class extends CodingAgentManager {
10661
10704
  providerApiKey = null;
10662
10705
  constructor(options) {
10663
10706
  super(options);
10664
- this.historyFilePath = options.historyFilePath ?? join22(PI_HISTORY_DIR, `${Date.now()}.jsonl`);
10707
+ this.historyFilePath = options.historyFilePath ?? join23(PI_HISTORY_DIR, `${Date.now()}.jsonl`);
10665
10708
  this.historyFile = new CodexHistoryFile(this.historyFilePath);
10666
10709
  this.initializeManager(this.processMessageInternal.bind(this));
10667
10710
  }
@@ -10766,7 +10809,7 @@ var PiManager = class extends CodingAgentManager {
10766
10809
  const sessionManager = this.initialSessionId ? SessionManager.open(this.initialSessionId, PI_HISTORY_DIR, this.workingDirectory) : SessionManager.create(this.workingDirectory, PI_HISTORY_DIR);
10767
10810
  const resourceLoader = new DefaultResourceLoader({
10768
10811
  cwd: this.workingDirectory,
10769
- agentDir: join22(ENGINE_ENV.HOME_DIR, ".pi", "agent"),
10812
+ agentDir: join23(ENGINE_ENV.HOME_DIR, ".pi", "agent"),
10770
10813
  extensionFactories: [registerCommandProtection(
10771
10814
  this.workingDirectory,
10772
10815
  this.historyFile,
@@ -11512,8 +11555,8 @@ import {
11512
11555
  rename as rename2,
11513
11556
  unlink as unlink3
11514
11557
  } from "fs/promises";
11515
- import { existsSync as existsSync10, readFileSync as readFileSync4, readdirSync } from "fs";
11516
- import { join as join23 } from "path";
11558
+ import { existsSync as existsSync11, readFileSync as readFileSync4, readdirSync } from "fs";
11559
+ import { join as join24 } from "path";
11517
11560
  var MAX_FAILED_RECORDS = 500;
11518
11561
  var FLUSH_DEBOUNCE_MS = 15e3;
11519
11562
  var MAX_UPLOADED_SEGMENTS = 50;
@@ -11525,7 +11568,7 @@ var AnalyticsRecordBuffer = class {
11525
11568
  options.storageName,
11526
11569
  ...options.legacyStorageNames ?? []
11527
11570
  ];
11528
- this.liveFile = join23(ENGINE_DIR2, `${options.storageName}.jsonl`);
11571
+ this.liveFile = join24(ENGINE_DIR2, `${options.storageName}.jsonl`);
11529
11572
  this.segmentFilePatterns = this.storageNames.map(
11530
11573
  (storageName) => new RegExp(`^${storageName}\\.(\\d+)\\.jsonl$`)
11531
11574
  );
@@ -11560,10 +11603,10 @@ var AnalyticsRecordBuffer = class {
11560
11603
  void pending.finally(() => this.pendingAppends.delete(pending));
11561
11604
  }
11562
11605
  readStoredRecords() {
11563
- if (!existsSync10(ENGINE_DIR2)) return [];
11606
+ if (!existsSync11(ENGINE_DIR2)) return [];
11564
11607
  return readdirSync(ENGINE_DIR2, { withFileTypes: true }).filter((entry) => entry.isFile() && this.storageNames.some(
11565
11608
  (storageName) => entry.name === `${storageName}.jsonl` || entry.name.startsWith(`${storageName}.`) && (entry.name.endsWith(".jsonl") || entry.name.endsWith(`.jsonl${UPLOADED_SUFFIX}`))
11566
- )).map(({ name }) => name).sort().flatMap((name) => this.parseRecords(readFileSync4(join23(ENGINE_DIR2, name), "utf-8"))).map((record) => {
11609
+ )).map(({ name }) => name).sort().flatMap((name) => this.parseRecords(readFileSync4(join24(ENGINE_DIR2, name), "utf-8"))).map((record) => {
11567
11610
  const recordId = this.options.getRecordId?.(record);
11568
11611
  if (recordId) this.recordIds.add(recordId);
11569
11612
  return record;
@@ -11598,8 +11641,8 @@ var AnalyticsRecordBuffer = class {
11598
11641
  }
11599
11642
  for (const storageName of this.storageNames) {
11600
11643
  await rename2(
11601
- join23(ENGINE_DIR2, `${storageName}.jsonl`),
11602
- join23(ENGINE_DIR2, `${storageName}.${Date.now()}.jsonl`)
11644
+ join24(ENGINE_DIR2, `${storageName}.jsonl`),
11645
+ join24(ENGINE_DIR2, `${storageName}.${Date.now()}.jsonl`)
11603
11646
  ).catch(() => {
11604
11647
  });
11605
11648
  }
@@ -11609,7 +11652,7 @@ var AnalyticsRecordBuffer = class {
11609
11652
  for (const entry of entries) {
11610
11653
  if (!this.segmentFilePatterns.some((pattern) => pattern.test(entry))) continue;
11611
11654
  try {
11612
- await this.uploadSegment(join23(ENGINE_DIR2, entry));
11655
+ await this.uploadSegment(join24(ENGINE_DIR2, entry));
11613
11656
  flushed++;
11614
11657
  } catch (error) {
11615
11658
  failed++;
@@ -11638,7 +11681,7 @@ var AnalyticsRecordBuffer = class {
11638
11681
  (storageName) => entry.startsWith(`${storageName}.`)
11639
11682
  ) && entry.endsWith(UPLOADED_SUFFIX)).sort();
11640
11683
  for (const entry of uploaded.slice(0, -MAX_UPLOADED_SEGMENTS)) {
11641
- await unlink3(join23(ENGINE_DIR2, entry)).catch(() => {
11684
+ await unlink3(join24(ENGINE_DIR2, entry)).catch(() => {
11642
11685
  });
11643
11686
  }
11644
11687
  }
@@ -12456,8 +12499,8 @@ var keepAliveService = new KeepAliveService();
12456
12499
 
12457
12500
  // src/services/canvas-service.ts
12458
12501
  import { readdir as readdir7, readFile as readFile18, stat as stat4 } from "fs/promises";
12459
- import { homedir as homedir12 } from "os";
12460
- import { join as join24 } from "path";
12502
+ import { homedir as homedir13 } from "os";
12503
+ import { join as join25 } from "path";
12461
12504
 
12462
12505
  // src/utils/contained-file.ts
12463
12506
  import { realpath, stat as stat3 } from "fs/promises";
@@ -12499,15 +12542,15 @@ async function writeRevisionedTextFile(filePath, content, expectedRevision) {
12499
12542
 
12500
12543
  // src/services/canvas-service.ts
12501
12544
  var GLOBAL_CANVAS_DIRECTORIES = [
12502
- join24(homedir12(), ".claude", "plans"),
12503
- join24(process.env.XDG_DATA_HOME ?? join24(homedir12(), ".local", "share"), "opencode", "plans"),
12504
- join24(homedir12(), ".replicas", "canvas")
12545
+ join25(homedir13(), ".claude", "plans"),
12546
+ join25(process.env.XDG_DATA_HOME ?? join25(homedir13(), ".local", "share"), "opencode", "plans"),
12547
+ join25(homedir13(), ".replicas", "canvas")
12505
12548
  ];
12506
12549
  async function canvasDirectories() {
12507
12550
  const repositories = await gitService.listRepositories().catch(() => []);
12508
12551
  return [
12509
12552
  ...GLOBAL_CANVAS_DIRECTORIES,
12510
- ...repositories.map((repository) => join24(repository.path, ".opencode", "plans"))
12553
+ ...repositories.map((repository) => join25(repository.path, ".opencode", "plans"))
12511
12554
  ];
12512
12555
  }
12513
12556
  var CanvasService = class {
@@ -12532,7 +12575,7 @@ var CanvasService = class {
12532
12575
  for (const entry of entries) {
12533
12576
  if (entry.name.startsWith(".")) continue;
12534
12577
  const filename = current.relativePath ? `${current.relativePath}/${entry.name}` : entry.name;
12535
- const filePath = join24(current.directory, entry.name);
12578
+ const filePath = join25(current.directory, entry.name);
12536
12579
  if (entry.isDirectory()) {
12537
12580
  pending.push({ directory: filePath, relativePath: filename });
12538
12581
  continue;
@@ -12557,7 +12600,7 @@ var CanvasService = class {
12557
12600
  if (!safe) return null;
12558
12601
  const { kind, mimeType } = classifyCanvasFilename(safe);
12559
12602
  for (const directory of await this.directories()) {
12560
- const filePath = join24(directory, safe);
12603
+ const filePath = join25(directory, safe);
12561
12604
  let sizeBytes = 0;
12562
12605
  let updatedAt = "";
12563
12606
  try {
@@ -12741,13 +12784,13 @@ async function reconcileCanvasItems(filenames) {
12741
12784
  import { createReadStream } from "fs";
12742
12785
  import { createHash as createHash4 } from "crypto";
12743
12786
  import { readFile as readFile19, readdir as readdir8, stat as stat5 } from "fs/promises";
12744
- import { basename as basename3, join as join26 } from "path";
12787
+ import { basename as basename3, join as join27 } from "path";
12745
12788
 
12746
12789
  // src/services/chat/chat-senders.ts
12747
- import { join as join25 } from "path";
12748
- var CHAT_SENDERS_DIR = join25(ENGINE_DIR2, "chat-senders");
12790
+ import { join as join26 } from "path";
12791
+ var CHAT_SENDERS_DIR = join26(ENGINE_DIR2, "chat-senders");
12749
12792
  function chatMessageSendersFilePath(chatId) {
12750
- return join25(CHAT_SENDERS_DIR, `${chatId}.jsonl`);
12793
+ return join26(CHAT_SENDERS_DIR, `${chatId}.jsonl`);
12751
12794
  }
12752
12795
  function parseChatMessageSendersJsonl(content) {
12753
12796
  return content.split("\n").flatMap((line) => {
@@ -12763,9 +12806,9 @@ function parseChatMessageSendersJsonl(content) {
12763
12806
 
12764
12807
  // src/services/upload-chat-transcripts.ts
12765
12808
  var HISTORY_DIRS = [
12766
- join26(ENGINE_DIR2, "claude-histories"),
12767
- join26(ENGINE_DIR2, "relay-histories"),
12768
- join26(ENGINE_DIR2, "codex-histories")
12809
+ join27(ENGINE_DIR2, "claude-histories"),
12810
+ join27(ENGINE_DIR2, "relay-histories"),
12811
+ join27(ENGINE_DIR2, "codex-histories")
12769
12812
  ];
12770
12813
  async function flushAllChatTranscripts(chatsById = /* @__PURE__ */ new Map(), capture) {
12771
12814
  let flushed = 0;
@@ -12783,7 +12826,7 @@ async function flushAllChatTranscripts(chatsById = /* @__PURE__ */ new Map(), ca
12783
12826
  if (!entry.endsWith(".jsonl")) continue;
12784
12827
  const chatId = basename3(entry, ".jsonl");
12785
12828
  tasks.push(
12786
- uploadChatTranscript(chatId, join26(dir, entry), chatsById.get(chatId), capture).then((artifact) => {
12829
+ uploadChatTranscript(chatId, join27(dir, entry), chatsById.get(chatId), capture).then((artifact) => {
12787
12830
  flushed++;
12788
12831
  if (artifact && capture) revisions.push(artifact);
12789
12832
  }).catch((err) => {
@@ -12807,7 +12850,8 @@ async function uploadChatTranscript(chatId, filePath, chat, capture) {
12807
12850
  createdAt: chat.createdAt,
12808
12851
  updatedAt: chat.updatedAt,
12809
12852
  parentChatId: chat.parentChatId,
12810
- deletedAt: chat.deletedAt ?? null
12853
+ deletedAt: chat.deletedAt ?? null,
12854
+ fork: chat.fork ?? null
12811
12855
  } : {};
12812
12856
  if (capture) {
12813
12857
  const hash = createHash4("sha256");
@@ -12868,7 +12912,7 @@ async function uploadChatTranscript(chatId, filePath, chat, capture) {
12868
12912
  // src/services/upload-engine-logs.ts
12869
12913
  import { createReadStream as createReadStream2 } from "fs";
12870
12914
  import { readdir as readdir9, stat as stat6 } from "fs/promises";
12871
- import { join as join27 } from "path";
12915
+ import { join as join28 } from "path";
12872
12916
  var MAX_ENGINE_LOG_FLUSH_SESSIONS = 10;
12873
12917
  var MAX_ENGINE_LOG_FLUSH_BYTES = 5 * 1024 * 1024;
12874
12918
  var ENGINE_LOG_FLUSH_TIMEOUT_MS = 2e4;
@@ -12897,7 +12941,7 @@ async function flushAllEngineLogs() {
12897
12941
  const candidates = (await Promise.all(filenames.slice(0, MAX_ENGINE_LOG_FLUSH_SESSIONS).map(async (filename) => {
12898
12942
  try {
12899
12943
  const sessionId = filename.slice(0, -".log".length);
12900
- const filePath = join27(LOG_DIR, filename);
12944
+ const filePath = join28(LOG_DIR, filename);
12901
12945
  const fileStat = await runBeforeDeadline(() => stat6(filePath), deadline);
12902
12946
  if (!fileStat.isFile()) {
12903
12947
  skipped++;
@@ -12992,32 +13036,67 @@ async function uploadEngineLog(input, timeoutMs) {
12992
13036
  }
12993
13037
  }
12994
13038
 
13039
+ // src/services/chat/fork-handoff.ts
13040
+ import { mkdir as mkdir19, open as open2, writeFile as writeFile9 } from "fs/promises";
13041
+ import { join as join29 } from "path";
13042
+ var EXCERPT_MAX_CHARS = 24e3;
13043
+ var MAX_SANITIZED_SOURCE_BYTES = 32 * 1024 * 1024;
13044
+ function unansweredRequest(blocks) {
13045
+ const lastUser = blocks.findLastIndex((block) => block.startsWith("## User"));
13046
+ if (lastUser === -1) return null;
13047
+ const answered = blocks.slice(lastUser + 1).some((block) => block.startsWith("## Agent"));
13048
+ return answered ? null : lastUserBlockText(blocks);
13049
+ }
13050
+ async function readHistoryTail(path6, maxBytes) {
13051
+ let handle;
13052
+ try {
13053
+ handle = await open2(path6, "r");
13054
+ } catch (error) {
13055
+ if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") return { content: "", truncated: false };
13056
+ throw error;
13057
+ }
13058
+ try {
13059
+ const { size } = await handle.stat();
13060
+ if (size <= maxBytes) return { content: await handle.readFile("utf-8"), truncated: false };
13061
+ const buffer = Buffer.alloc(maxBytes);
13062
+ await handle.read(buffer, 0, maxBytes, size - maxBytes);
13063
+ const firstNewline = buffer.indexOf(10);
13064
+ return { content: buffer.subarray(firstNewline + 1).toString("utf-8"), truncated: true };
13065
+ } finally {
13066
+ await handle.close();
13067
+ }
13068
+ }
13069
+ function tailBlocks(blocks, maxChars) {
13070
+ let start = blocks.length;
13071
+ while (start > 0 && blocks.slice(start - 1).join("\n\n").length <= maxChars) start--;
13072
+ const excerpt = start < blocks.length ? blocks.slice(start).join("\n\n") : `\u2026${blocks.at(-1)?.slice(-maxChars) ?? ""}`;
13073
+ return { excerpt, truncated: start > 0 };
13074
+ }
13075
+ async function buildForkHandoff(input) {
13076
+ const rawHistoryPath = join29(HISTORY_DIR_BY_PROVIDER[input.sourceProvider], `${input.sourceChatId}.jsonl`);
13077
+ const { content, truncated: sourceTruncated } = await readHistoryTail(rawHistoryPath, MAX_SANITIZED_SOURCE_BYTES);
13078
+ const blocks = canonicalizeChatTranscriptBlocks(content, input.sourceProvider, input.senders);
13079
+ const forkDir = join29(FORKS_DIR, input.targetChatId);
13080
+ await mkdir19(forkDir, { recursive: true });
13081
+ const transcriptPath = join29(forkDir, "source-transcript.md");
13082
+ const header = sourceTruncated ? `_This file covers only the most recent ${MAX_SANITIZED_SOURCE_BYTES / 1024 / 1024} MB of the source chat. The complete raw event log is at ${rawHistoryPath}; grep it for anything earlier._
13083
+
13084
+ ` : "";
13085
+ await writeFile9(transcriptPath, `${header}${blocks.join("\n\n")}
13086
+ `, "utf-8");
13087
+ const { excerpt, truncated } = tailBlocks(blocks, EXCERPT_MAX_CHARS);
13088
+ return {
13089
+ excerpt,
13090
+ excerptTruncated: truncated,
13091
+ sourceTruncated,
13092
+ resumeRequest: unansweredRequest(blocks),
13093
+ transcriptPath,
13094
+ rawHistoryPath
13095
+ };
13096
+ }
13097
+
12995
13098
  // src/services/chat/chat-service.ts
12996
13099
  var CHATS_BACKUP_FILE = `${CHATS_FILE}.bak`;
12997
- function isCodexAvailable() {
12998
- return detectCodexAuthMethod() !== "none";
12999
- }
13000
- function isOpencodeAvailable() {
13001
- return existsSync11(OPENCODE_AUTH_PATH);
13002
- }
13003
- function isDeepseekAvailable() {
13004
- return Boolean(getDeepseekApiKey());
13005
- }
13006
- function isFxAvailable() {
13007
- return Boolean(ENGINE_ENV.AI_GATEWAY_API_KEY);
13008
- }
13009
- function isKimiAvailable() {
13010
- return Boolean(ENGINE_ENV.AI_GATEWAY_API_KEY);
13011
- }
13012
- function isMuseAvailable() {
13013
- return existsSync11(MUSE_AUTH_PATH);
13014
- }
13015
- function isPiAvailable() {
13016
- return existsSync11(PI_AUTH_PATH);
13017
- }
13018
- function isCursorAvailable() {
13019
- return Boolean(ENGINE_ENV.CURSOR_API_KEY);
13020
- }
13021
13100
  function createTurnMetadata(message, messageId, recordedAt) {
13022
13101
  const event = createAcceptedUserMessageEvent(
13023
13102
  message.message,
@@ -13095,11 +13174,14 @@ function acceptedEventInCodexTranscript(acceptedEvent, transcript) {
13095
13174
  }
13096
13175
  var LAST_MESSAGE_PREVIEW_MAX = 200;
13097
13176
  var MAX_ACCEPTED_SEND_RESPONSES = 50;
13177
+ function isForkPreparing(chat) {
13178
+ return chat.persisted.fork?.state === "preparing";
13179
+ }
13098
13180
  function hasActiveAgentWork(chat) {
13099
- return chat.provider.isProcessing() || (chat.provider.hasActiveBackgroundTasks?.() ?? false);
13181
+ return chat.provider.isProcessing() || (chat.provider.hasActiveBackgroundTasks?.() ?? false) || isForkPreparing(chat);
13100
13182
  }
13101
13183
  function hasActiveChatTurn(chat) {
13102
- return chat.hasActiveTurn || chat.pendingMessageIds.length > 0 || (chat.provider.hasActiveBackgroundTasks?.() ?? false);
13184
+ return chat.hasActiveTurn || chat.pendingMessageIds.length > 0 || (chat.provider.hasActiveBackgroundTasks?.() ?? false) || isForkPreparing(chat);
13103
13185
  }
13104
13186
  function isPersistedChat(value) {
13105
13187
  if (!isRecord2(value)) {
@@ -13120,6 +13202,7 @@ function normalizePersistedChat(chat) {
13120
13202
  parentChatId: chat.parentChatId ?? null,
13121
13203
  lastMessageText: chat.lastMessageText ?? null,
13122
13204
  deletedAt: chat.deletedAt ?? null,
13205
+ fork: coerceChatForkInfo(chat.fork),
13123
13206
  acceptedSendResponses: isRecord2(chat.acceptedSendResponses) ? Object.fromEntries(Object.entries(chat.acceptedSendResponses).filter((entry) => {
13124
13207
  const response = entry[1];
13125
13208
  return isRecord2(response) && typeof response.messageId === "string" && typeof response.queued === "boolean" && typeof response.position === "number";
@@ -13152,13 +13235,13 @@ var ChatService = class {
13152
13235
  persistPromise = null;
13153
13236
  persistQueued = false;
13154
13237
  async initialize() {
13155
- await mkdir19(ENGINE_DIR2, { recursive: true });
13156
- await mkdir19(CLAUDE_HISTORY_DIR, { recursive: true });
13157
- await mkdir19(RELAY_HISTORY_DIR, { recursive: true });
13158
- await mkdir19(CODEX_HISTORY_DIR, { recursive: true });
13159
- await mkdir19(CURSOR_HISTORY_DIR, { recursive: true });
13160
- await mkdir19(OPENCODE_HISTORY_DIR, { recursive: true });
13161
- await mkdir19(CHAT_SENDERS_DIR, { recursive: true });
13238
+ await mkdir20(ENGINE_DIR2, { recursive: true });
13239
+ await mkdir20(CLAUDE_HISTORY_DIR, { recursive: true });
13240
+ await mkdir20(RELAY_HISTORY_DIR, { recursive: true });
13241
+ await mkdir20(CODEX_HISTORY_DIR, { recursive: true });
13242
+ await mkdir20(CURSOR_HISTORY_DIR, { recursive: true });
13243
+ await mkdir20(OPENCODE_HISTORY_DIR, { recursive: true });
13244
+ await mkdir20(CHAT_SENDERS_DIR, { recursive: true });
13162
13245
  const persisted = await this.loadChats();
13163
13246
  for (const chat of persisted) {
13164
13247
  const runtime = this.createRuntimeChat(chat);
@@ -13231,7 +13314,8 @@ var ChatService = class {
13231
13314
  createdAt: now,
13232
13315
  updatedAt: now,
13233
13316
  providerSessionId: null,
13234
- parentChatId
13317
+ parentChatId,
13318
+ fork: request.fork ?? null
13235
13319
  };
13236
13320
  const runtime = this.createRuntimeChat(persisted);
13237
13321
  this.chats.set(persisted.id, runtime);
@@ -13331,6 +13415,7 @@ var ChatService = class {
13331
13415
  chat.persisted.updatedAt = previousUpdatedAt;
13332
13416
  throw new ChatMessagePersistenceError(error);
13333
13417
  }
13418
+ chat.lastSendRequest = request;
13334
13419
  this.agentChatActivityTrackerService.noteMessageAccepted(result.messageId, request);
13335
13420
  if (request.errorNotificationTarget) {
13336
13421
  chat.errorNotificationTargets.set(result.messageId, request.errorNotificationTarget);
@@ -13597,13 +13682,115 @@ var ChatService = class {
13597
13682
  return descendants;
13598
13683
  }
13599
13684
  async deleteHistoryFile(persisted) {
13600
- const historyPath = join28(HISTORY_DIR_BY_PROVIDER[persisted.provider], `${persisted.id}.jsonl`);
13685
+ const historyPath = join30(HISTORY_DIR_BY_PROVIDER[persisted.provider], `${persisted.id}.jsonl`);
13601
13686
  await Promise.all([
13602
13687
  historyPath,
13603
13688
  `${historyPath}.pages.jsonl`,
13604
13689
  `${historyPath}.pages.index.json`
13605
13690
  ].map((path6) => rm3(path6, { force: true })));
13606
13691
  await rm3(chatMessageSendersFilePath(persisted.id), { force: true });
13692
+ await rm3(join30(FORKS_DIR, persisted.id), { recursive: true, force: true });
13693
+ }
13694
+ /** Resolves once the target chat exists; the handoff and its first turn run in the background. */
13695
+ async forkChat(sourceChatId, request, trigger = "manual", reason) {
13696
+ const source = this.requireChat(sourceChatId);
13697
+ if (!isAgentAvailable(request.provider)) throw new ChatProviderUnavailableError(request.provider);
13698
+ const fork = {
13699
+ source: { chatId: source.persisted.id, provider: source.persisted.provider, title: source.persisted.title },
13700
+ trigger,
13701
+ ...reason ? { reason } : {},
13702
+ state: "preparing",
13703
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
13704
+ };
13705
+ const target = await this.createChat({
13706
+ id: request.id,
13707
+ provider: request.provider,
13708
+ title: request.title?.trim() || defaultForkChatTitle(source.persisted, request.provider),
13709
+ clientRequestId: request.clientRequestId,
13710
+ fork
13711
+ });
13712
+ this.runFork(target.id, request).catch((error) => {
13713
+ console.error("[ChatService] Fork failed:", { sourceChatId, targetChatId: target.id, error });
13714
+ });
13715
+ return target;
13716
+ }
13717
+ async runFork(targetId, request) {
13718
+ const target = this.chats.get(targetId);
13719
+ const fork = target?.persisted.fork;
13720
+ if (!target || !fork) return;
13721
+ try {
13722
+ const handoff = await buildForkHandoff({
13723
+ sourceChatId: fork.source.chatId,
13724
+ sourceProvider: fork.source.provider,
13725
+ targetChatId: targetId,
13726
+ senders: await this.readSenders(fork.source.chatId)
13727
+ });
13728
+ if (target.persisted.deletedAt) return;
13729
+ await this.sendMessage(targetId, {
13730
+ message: formatChatForkHandoffMessage({
13731
+ source: fork.source,
13732
+ trigger: fork.trigger,
13733
+ ...fork.reason ? { reason: fork.reason } : {},
13734
+ excerpt: handoff.excerpt,
13735
+ excerptTruncated: handoff.excerptTruncated,
13736
+ sourceTruncated: handoff.sourceTruncated,
13737
+ transcriptPath: handoff.transcriptPath,
13738
+ rawHistoryPath: handoff.rawHistoryPath,
13739
+ resumeRequest: handoff.resumeRequest
13740
+ }),
13741
+ model: request.model,
13742
+ thinkingLevel: request.thinkingLevel,
13743
+ enableInteractiveTools: request.enableInteractiveTools,
13744
+ senderUserId: request.senderUserId,
13745
+ senderEmail: request.senderEmail,
13746
+ senderDisplayName: request.senderDisplayName,
13747
+ senderAvatarUrl: request.senderAvatarUrl,
13748
+ idempotencyKey: `chat-fork:${targetId}`
13749
+ });
13750
+ await this.updateFork(target, { state: "ready", completedAt: (/* @__PURE__ */ new Date()).toISOString() });
13751
+ } catch (error) {
13752
+ if (target.persisted.deletedAt) return;
13753
+ const message = error instanceof Error ? error.message : String(error);
13754
+ await this.updateFork(target, { state: "failed", error: message.slice(0, 500), completedAt: (/* @__PURE__ */ new Date()).toISOString() });
13755
+ throw error;
13756
+ }
13757
+ }
13758
+ async updateFork(chat, patch) {
13759
+ if (!chat.persisted.fork) return;
13760
+ chat.persisted.fork = { ...chat.persisted.fork, ...patch };
13761
+ chat.persisted.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
13762
+ await this.persistAllChats();
13763
+ await this.publish({ type: "chat.updated", payload: { chat: this.toSummary(chat) } });
13764
+ }
13765
+ /** Providers of this chat and every chat it was forked from, so a fallback never loops back. */
13766
+ forkChainProviders(chat) {
13767
+ const providers = [chat.persisted.provider];
13768
+ for (let current = chat; current?.persisted.fork; current = this.chats.get(current.persisted.fork.source.chatId)) {
13769
+ providers.push(current.persisted.fork.source.provider);
13770
+ }
13771
+ return providers;
13772
+ }
13773
+ async autoForkAfterQuota(chat, exhaustion) {
13774
+ if (chat.persisted.parentChatId !== null || chat.persisted.deletedAt) return;
13775
+ const alreadyForked = [...this.chats.values()].some((other) => other.persisted.fork?.trigger === "auto" && other.persisted.fork.source.chatId === chat.persisted.id && !other.persisted.deletedAt);
13776
+ if (alreadyForked) return;
13777
+ const last = chat.lastSendRequest;
13778
+ const fallback = await monolithService.getChatForkFallback({
13779
+ provider: chat.persisted.provider,
13780
+ exclude: this.forkChainProviders(chat),
13781
+ userId: last?.senderUserId
13782
+ });
13783
+ if (!fallback?.provider || !isAgentAvailable(fallback.provider)) return;
13784
+ await this.forkChat(chat.persisted.id, {
13785
+ provider: fallback.provider,
13786
+ model: fallback.model,
13787
+ thinkingLevel: fallback.thinkingLevel,
13788
+ enableInteractiveTools: last?.enableInteractiveTools,
13789
+ senderUserId: last?.senderUserId,
13790
+ senderEmail: last?.senderEmail,
13791
+ senderDisplayName: last?.senderDisplayName,
13792
+ senderAvatarUrl: last?.senderAvatarUrl
13793
+ }, "auto", exhaustion.reason);
13607
13794
  }
13608
13795
  async getChatHistory(chatId, page = {}) {
13609
13796
  const chat = this.requireChat(chatId);
@@ -13728,7 +13915,7 @@ var ChatService = class {
13728
13915
  if (persisted.provider === "claude") {
13729
13916
  provider = new ClaudeManager({
13730
13917
  workingDirectory: this.workingDirectory,
13731
- historyFilePath: join28(CLAUDE_HISTORY_DIR, `${persisted.id}.jsonl`),
13918
+ historyFilePath: join30(CLAUDE_HISTORY_DIR, `${persisted.id}.jsonl`),
13732
13919
  initialSessionId: persisted.providerSessionId,
13733
13920
  onSaveSessionId: saveSession,
13734
13921
  onTurnComplete: onProviderTurnComplete,
@@ -13738,18 +13925,18 @@ var ChatService = class {
13738
13925
  });
13739
13926
  } else if (persisted.provider === "relay") {
13740
13927
  const getProviderAvailability = () => ({
13741
- codexAvailable: isCodexAvailable(),
13742
- opencodeAvailable: isOpencodeAvailable(),
13743
- piAvailable: isPiAvailable(),
13744
- cursorAvailable: isCursorAvailable(),
13745
- deepseekAvailable: isDeepseekAvailable(),
13746
- fxAvailable: isFxAvailable(),
13747
- kimiAvailable: isKimiAvailable(),
13748
- museAvailable: isMuseAvailable()
13928
+ codexAvailable: isAgentAvailable(AGENT.CODEX),
13929
+ opencodeAvailable: isAgentAvailable(AGENT.OPENCODE),
13930
+ piAvailable: isAgentAvailable(AGENT.PI),
13931
+ cursorAvailable: isAgentAvailable(AGENT.CURSOR),
13932
+ deepseekAvailable: isAgentAvailable(AGENT.DEEPSEEK),
13933
+ fxAvailable: isAgentAvailable(AGENT.FX),
13934
+ kimiAvailable: isAgentAvailable(AGENT.KIMI),
13935
+ museAvailable: isAgentAvailable(AGENT.MUSE)
13749
13936
  });
13750
13937
  provider = new RelayManager({
13751
13938
  workingDirectory: this.workingDirectory,
13752
- historyFilePath: join28(RELAY_HISTORY_DIR, `${persisted.id}.jsonl`),
13939
+ historyFilePath: join30(RELAY_HISTORY_DIR, `${persisted.id}.jsonl`),
13753
13940
  initialSessionId: persisted.providerSessionId,
13754
13941
  onSaveSessionId: saveSession,
13755
13942
  onTurnComplete: onProviderTurnComplete,
@@ -13763,7 +13950,7 @@ var ChatService = class {
13763
13950
  } else if (persisted.provider === "cursor") {
13764
13951
  provider = new CursorManager({
13765
13952
  workingDirectory: this.workingDirectory,
13766
- historyFilePath: join28(CURSOR_HISTORY_DIR, `${persisted.id}.jsonl`),
13953
+ historyFilePath: join30(CURSOR_HISTORY_DIR, `${persisted.id}.jsonl`),
13767
13954
  initialSessionId: persisted.providerSessionId,
13768
13955
  onSaveSessionId: saveSession,
13769
13956
  onTurnComplete: onProviderTurnComplete,
@@ -13775,7 +13962,7 @@ var ChatService = class {
13775
13962
  } else if (persisted.provider === "deepseek") {
13776
13963
  provider = new DeepseekManager({
13777
13964
  workingDirectory: this.workingDirectory,
13778
- historyFilePath: join28(DEEPSEEK_HISTORY_DIR, `${persisted.id}.jsonl`),
13965
+ historyFilePath: join30(DEEPSEEK_HISTORY_DIR, `${persisted.id}.jsonl`),
13779
13966
  initialSessionId: persisted.providerSessionId,
13780
13967
  onSaveSessionId: saveSession,
13781
13968
  onTurnComplete: onProviderTurnComplete,
@@ -13787,7 +13974,7 @@ var ChatService = class {
13787
13974
  } else if (persisted.provider === "fx") {
13788
13975
  provider = new FxManager({
13789
13976
  workingDirectory: this.workingDirectory,
13790
- historyFilePath: join28(FX_HISTORY_DIR, `${persisted.id}.jsonl`),
13977
+ historyFilePath: join30(FX_HISTORY_DIR, `${persisted.id}.jsonl`),
13791
13978
  initialSessionId: persisted.providerSessionId,
13792
13979
  onSaveSessionId: saveSession,
13793
13980
  onTurnComplete: onProviderTurnComplete,
@@ -13798,7 +13985,7 @@ var ChatService = class {
13798
13985
  } else if (persisted.provider === "kimi") {
13799
13986
  provider = new KimiManager({
13800
13987
  workingDirectory: this.workingDirectory,
13801
- historyFilePath: join28(KIMI_HISTORY_DIR, `${persisted.id}.jsonl`),
13988
+ historyFilePath: join30(KIMI_HISTORY_DIR, `${persisted.id}.jsonl`),
13802
13989
  initialSessionId: persisted.providerSessionId,
13803
13990
  onSaveSessionId: saveSession,
13804
13991
  onTurnComplete: onProviderTurnComplete,
@@ -13809,7 +13996,7 @@ var ChatService = class {
13809
13996
  } else if (persisted.provider === "muse") {
13810
13997
  provider = new MuseManager({
13811
13998
  workingDirectory: this.workingDirectory,
13812
- historyFilePath: join28(MUSE_HISTORY_DIR, `${persisted.id}.jsonl`),
13999
+ historyFilePath: join30(MUSE_HISTORY_DIR, `${persisted.id}.jsonl`),
13813
14000
  initialSessionId: persisted.providerSessionId,
13814
14001
  onSaveSessionId: saveSession,
13815
14002
  onTurnComplete: onProviderTurnComplete,
@@ -13821,7 +14008,7 @@ var ChatService = class {
13821
14008
  } else if (persisted.provider === "opencode") {
13822
14009
  provider = new OpencodeManager({
13823
14010
  workingDirectory: this.workingDirectory,
13824
- historyFilePath: join28(OPENCODE_HISTORY_DIR, `${persisted.id}.jsonl`),
14011
+ historyFilePath: join30(OPENCODE_HISTORY_DIR, `${persisted.id}.jsonl`),
13825
14012
  initialSessionId: persisted.providerSessionId,
13826
14013
  onSaveSessionId: saveSession,
13827
14014
  onTurnComplete: onProviderTurnComplete,
@@ -13833,7 +14020,7 @@ var ChatService = class {
13833
14020
  } else if (persisted.provider === "pi") {
13834
14021
  provider = new PiManager({
13835
14022
  workingDirectory: this.workingDirectory,
13836
- historyFilePath: join28(PI_HISTORY_DIR, `${persisted.id}.jsonl`),
14023
+ historyFilePath: join30(PI_HISTORY_DIR, `${persisted.id}.jsonl`),
13837
14024
  initialSessionId: persisted.providerSessionId,
13838
14025
  onSaveSessionId: saveSession,
13839
14026
  onTurnComplete: onProviderTurnComplete,
@@ -13850,7 +14037,7 @@ var ChatService = class {
13850
14037
  );
13851
14038
  provider = new CodexAspManager({
13852
14039
  workingDirectory: this.workingDirectory,
13853
- historyFilePath: join28(CODEX_HISTORY_DIR, `${persisted.id}.jsonl`),
14040
+ historyFilePath: join30(CODEX_HISTORY_DIR, `${persisted.id}.jsonl`),
13854
14041
  initialSessionId: persisted.providerSessionId,
13855
14042
  onSaveSessionId: saveSession,
13856
14043
  onTurnComplete: onProviderTurnComplete,
@@ -13876,7 +14063,9 @@ var ChatService = class {
13876
14063
  observedBranchesByRepo: /* @__PURE__ */ new Map(),
13877
14064
  lastTurnErrors: null,
13878
14065
  errorNotificationTargets: /* @__PURE__ */ new Map(),
13879
- activeErrorNotificationTarget: null
14066
+ activeErrorNotificationTarget: null,
14067
+ quotaExhaustion: null,
14068
+ lastSendRequest: null
13880
14069
  };
13881
14070
  }
13882
14071
  touch(chat) {
@@ -13900,7 +14089,8 @@ var ChatService = class {
13900
14089
  goal: chat.provider.getGoal?.() ?? null,
13901
14090
  parentChatId: chat.persisted.parentChatId,
13902
14091
  lastMessageText: chat.persisted.lastMessageText ?? null,
13903
- deletedAt: chat.persisted.deletedAt ?? null
14092
+ deletedAt: chat.persisted.deletedAt ?? null,
14093
+ fork: chat.persisted.fork ?? null
13904
14094
  };
13905
14095
  }
13906
14096
  requireChat(chatId) {
@@ -13977,6 +14167,9 @@ var ChatService = class {
13977
14167
  if (terminalErrors !== void 0) {
13978
14168
  chat.lastTurnErrors = terminalErrors;
13979
14169
  }
14170
+ if (event.type === AGENT_QUOTA_EXHAUSTED_EVENT_TYPE) {
14171
+ chat.quotaExhaustion = agentQuotaExhaustedPayloadSchema.safeParse(event.payload).data ?? null;
14172
+ }
13980
14173
  let eventToPublish = event;
13981
14174
  if (event.type === "event_msg" && event.payload.type === "user_message") {
13982
14175
  if (chat.activeMessageId) {
@@ -14058,7 +14251,7 @@ var ChatService = class {
14058
14251
  });
14059
14252
  uploadChatTranscript(
14060
14253
  chatId,
14061
- join28(HISTORY_DIR_BY_PROVIDER[chat.persisted.provider], `${chatId}.jsonl`),
14254
+ join30(HISTORY_DIR_BY_PROVIDER[chat.persisted.provider], `${chatId}.jsonl`),
14062
14255
  this.toSummary(chat)
14063
14256
  ).catch((err) => {
14064
14257
  console.error("[ChatService] Failed to upload chat transcript:", { chatId, err });
@@ -14067,6 +14260,13 @@ var ChatService = class {
14067
14260
  console.error("[ChatService] Failed to upload canvas items:", { chatId, err });
14068
14261
  });
14069
14262
  await this.publishAgentTurnCompleteWebhook(chat, completedAt, processing);
14263
+ const exhaustion = chat.quotaExhaustion;
14264
+ chat.quotaExhaustion = null;
14265
+ if (exhaustion) {
14266
+ this.autoForkAfterQuota(chat, exhaustion).catch((error) => {
14267
+ console.error("[ChatService] Automatic fork after quota limit failed:", { chatId, error });
14268
+ });
14269
+ }
14070
14270
  }
14071
14271
  getRuntimeChat(chatId) {
14072
14272
  const chat = this.chats.get(chatId) ?? null;
@@ -14478,20 +14678,20 @@ var RepoFileService = class {
14478
14678
  import { Hono } from "hono";
14479
14679
  import { z as z5 } from "zod";
14480
14680
  import { readdir as readdir11, stat as stat7, readFile as readFile24 } from "fs/promises";
14481
- import { join as join32, resolve as resolve3 } from "path";
14681
+ import { join as join34, resolve as resolve3 } from "path";
14482
14682
 
14483
14683
  // src/services/warm-hooks-service.ts
14484
14684
  import { spawn as spawn7 } from "child_process";
14485
14685
  import { readFile as readFile23 } from "fs/promises";
14486
14686
  import { existsSync as existsSync12 } from "fs";
14487
- import { join as join31 } from "path";
14687
+ import { join as join33 } from "path";
14488
14688
 
14489
14689
  // src/services/warm-hook-logs-service.ts
14490
- import { mkdir as mkdir20, readFile as readFile22, writeFile as writeFile9, readdir as readdir10, appendFile as appendFile5, unlink as unlink4 } from "fs/promises";
14491
- import { homedir as homedir13 } from "os";
14492
- import { join as join30 } from "path";
14493
- var LOGS_DIR2 = join30(homedir13(), ".replicas", "warm-hook-logs");
14494
- var CURRENT_RUN_LOG = join30(LOGS_DIR2, "current-run.log");
14690
+ import { mkdir as mkdir21, readFile as readFile22, writeFile as writeFile10, readdir as readdir10, appendFile as appendFile5, unlink as unlink4 } from "fs/promises";
14691
+ import { homedir as homedir14 } from "os";
14692
+ import { join as join32 } from "path";
14693
+ var LOGS_DIR2 = join32(homedir14(), ".replicas", "warm-hook-logs");
14694
+ var CURRENT_RUN_LOG = join32(LOGS_DIR2, "current-run.log");
14495
14695
  var GLOBAL_FILENAME = "global.json";
14496
14696
  function withPreview2(stored) {
14497
14697
  const preview = buildHookOutputPreview(stored.output);
@@ -14499,7 +14699,7 @@ function withPreview2(stored) {
14499
14699
  }
14500
14700
  var WarmHookLogsService = class {
14501
14701
  async ensureDir() {
14502
- await mkdir20(LOGS_DIR2, { recursive: true });
14702
+ await mkdir21(LOGS_DIR2, { recursive: true });
14503
14703
  }
14504
14704
  async saveGlobalHookLog(entry) {
14505
14705
  await this.ensureDir();
@@ -14508,7 +14708,7 @@ var WarmHookLogsService = class {
14508
14708
  hookName: "organization",
14509
14709
  ...entry
14510
14710
  };
14511
- await writeFile9(join30(LOGS_DIR2, GLOBAL_FILENAME), `${JSON.stringify(log, null, 2)}
14711
+ await writeFile10(join32(LOGS_DIR2, GLOBAL_FILENAME), `${JSON.stringify(log, null, 2)}
14512
14712
  `, "utf-8");
14513
14713
  }
14514
14714
  async saveEnvironmentHookLog(entry) {
@@ -14518,7 +14718,7 @@ var WarmHookLogsService = class {
14518
14718
  hookName: "environment",
14519
14719
  ...entry
14520
14720
  };
14521
- await writeFile9(join30(LOGS_DIR2, ENVIRONMENT_HOOK_LOG_FILENAME), `${JSON.stringify(log, null, 2)}
14721
+ await writeFile10(join32(LOGS_DIR2, ENVIRONMENT_HOOK_LOG_FILENAME), `${JSON.stringify(log, null, 2)}
14522
14722
  `, "utf-8");
14523
14723
  }
14524
14724
  async saveRepoHookLog(repoName, entry) {
@@ -14528,7 +14728,7 @@ var WarmHookLogsService = class {
14528
14728
  hookName: repoName,
14529
14729
  ...entry
14530
14730
  };
14531
- await writeFile9(join30(LOGS_DIR2, repoHookLogFilename(repoName)), `${JSON.stringify(log, null, 2)}
14731
+ await writeFile10(join32(LOGS_DIR2, repoHookLogFilename(repoName)), `${JSON.stringify(log, null, 2)}
14532
14732
  `, "utf-8");
14533
14733
  }
14534
14734
  async getAllLogs() {
@@ -14547,7 +14747,7 @@ var WarmHookLogsService = class {
14547
14747
  continue;
14548
14748
  }
14549
14749
  try {
14550
- const raw = await readFile22(join30(LOGS_DIR2, file), "utf-8");
14750
+ const raw = await readFile22(join32(LOGS_DIR2, file), "utf-8");
14551
14751
  const stored = JSON.parse(raw);
14552
14752
  logs.push(withPreview2(stored));
14553
14753
  } catch {
@@ -14585,7 +14785,7 @@ var WarmHookLogsService = class {
14585
14785
  async getFullOutput(hookType, hookName) {
14586
14786
  const filename = hookType === "global" ? GLOBAL_FILENAME : hookType === "environment" ? ENVIRONMENT_HOOK_LOG_FILENAME : repoHookLogFilename(hookName);
14587
14787
  try {
14588
- const raw = await readFile22(join30(LOGS_DIR2, filename), "utf-8");
14788
+ const raw = await readFile22(join32(LOGS_DIR2, filename), "utf-8");
14589
14789
  const stored = JSON.parse(raw);
14590
14790
  if (stored.hookType !== hookType || stored.hookName !== hookName) {
14591
14791
  return null;
@@ -14604,7 +14804,7 @@ var warmHookLogsService = new WarmHookLogsService();
14604
14804
  // src/services/warm-hooks-service.ts
14605
14805
  async function readRepoWarmHook(repoPath) {
14606
14806
  for (const filename of REPLICAS_CONFIG_FILENAMES) {
14607
- const configPath = join31(repoPath, filename);
14807
+ const configPath = join33(repoPath, filename);
14608
14808
  if (!existsSync12(configPath)) {
14609
14809
  continue;
14610
14810
  }
@@ -15122,6 +15322,22 @@ function createV1Routes(deps) {
15122
15322
  return c.json(jsonError("Invalid chat request", details), 400);
15123
15323
  }
15124
15324
  });
15325
+ app2.post("/chats/:chatId/fork", async (c) => {
15326
+ try {
15327
+ const body = forkChatRequestSchema.strip().parse(await c.req.json());
15328
+ const chat = await deps.chatService.forkChat(c.req.param("chatId"), body);
15329
+ return c.json({ chat }, 201);
15330
+ } catch (error) {
15331
+ const details = error instanceof Error ? error.message : "Unknown error";
15332
+ if (error instanceof ChatNotFoundError) {
15333
+ return c.json(jsonError("Failed to fork chat", details), 404);
15334
+ }
15335
+ if (error instanceof ChatProviderUnavailableError) {
15336
+ return c.json(jsonError("Failed to fork chat", details), 409);
15337
+ }
15338
+ return c.json(jsonError("Failed to fork chat", details), 400);
15339
+ }
15340
+ });
15125
15341
  app2.get("/chats/:chatId", (c) => {
15126
15342
  const chat = deps.chatService.getChat(c.req.param("chatId"));
15127
15343
  if (!chat) {
@@ -15844,7 +16060,7 @@ data: ${JSON.stringify("Terminal session not found")}
15844
16060
  const logFiles = files.filter((f) => f.endsWith(".log"));
15845
16061
  const sessions = await Promise.all(
15846
16062
  logFiles.map(async (filename) => {
15847
- const filePath = join32(LOG_DIR, filename);
16063
+ const filePath = join34(LOG_DIR, filename);
15848
16064
  const fileStat = await stat7(filePath);
15849
16065
  const sessionId = filename.replace(/\.log$/, "");
15850
16066
  return {
@@ -15950,7 +16166,7 @@ var HeartbeatService = class _HeartbeatService {
15950
16166
  var heartbeatService = new HeartbeatService();
15951
16167
 
15952
16168
  // src/services/workspace-sdk-service.ts
15953
- import { cp, mkdir as mkdir21 } from "fs/promises";
16169
+ import { cp, mkdir as mkdir22 } from "fs/promises";
15954
16170
  import { dirname as dirname12, resolve as resolve4 } from "path";
15955
16171
  import { fileURLToPath as fileURLToPath4 } from "url";
15956
16172
  async function installWorkspaceSdk() {
@@ -15960,7 +16176,7 @@ async function installWorkspaceSdk() {
15960
16176
  "/tmp/node_modules/@replicas/sdk"
15961
16177
  ];
15962
16178
  await Promise.all(targets.map(async (target) => {
15963
- await mkdir21(target, { recursive: true });
16179
+ await mkdir22(target, { recursive: true });
15964
16180
  await cp(source, target, { recursive: true, force: true });
15965
16181
  }));
15966
16182
  }