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/{chunk-2ZP6PIHD.js → chunk-35NHHD6W.js} +2 -2
- package/dist/src/{chunk-YBI3HWHT.js → chunk-5K3MABJT.js} +3468 -3166
- package/dist/src/{chunk-5U46EQRM.js → chunk-BRXEUTMC.js} +13 -1
- package/dist/src/{chunk-MWD54BEE.js → chunk-KZQGTP36.js} +1 -1
- package/dist/src/command-protection-hook.js +2 -2
- package/dist/src/deepseek-command-protection-plugin.js +3 -3
- package/dist/src/headless-agent.js +2 -2
- package/dist/src/index.js +458 -242
- package/dist/src/post-tool-pr-hook.js +2 -2
- package/package.json +1 -1
package/dist/src/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
monolithService,
|
|
9
9
|
reportCommandProtectionBlock,
|
|
10
10
|
setAgentCredentialSnapshot
|
|
11
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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
|
|
1711
|
-
import { join as
|
|
1712
|
-
import { homedir as
|
|
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/
|
|
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
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
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
|
|
1783
|
-
return
|
|
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 (!
|
|
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 =
|
|
1881
|
-
details.codexAuthMethod =
|
|
1882
|
-
details.cursorAuthMethod =
|
|
1883
|
-
details.deepseekAuthMethod =
|
|
1884
|
-
details.fxAuthMethod =
|
|
1885
|
-
details.kimiAuthMethod =
|
|
1886
|
-
details.museAuthMethod =
|
|
1887
|
-
details.opencodeAuthMethod =
|
|
1888
|
-
details.piAuthMethod =
|
|
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 =
|
|
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
|
|
1969
|
-
import { join as
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
2092
|
-
if (!
|
|
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(
|
|
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:
|
|
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(
|
|
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
|
|
2492
|
-
import { join as
|
|
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 =
|
|
2495
|
-
var EVENTS_FILE =
|
|
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
|
|
2553
|
+
import { existsSync as existsSync5 } from "fs";
|
|
2528
2554
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
2529
|
-
import { homedir as
|
|
2530
|
-
import { dirname as dirname2, join as
|
|
2531
|
-
var PREVIEW_PORTS_FILE =
|
|
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 (!
|
|
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 {
|
|
2642
|
-
import {
|
|
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
|
|
2835
|
+
import { dirname as dirname4, join as join16 } from "path";
|
|
2811
2836
|
import { mkdir as mkdir10 } from "fs/promises";
|
|
2812
|
-
import { homedir as
|
|
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
|
|
3198
|
-
import { join as
|
|
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 =
|
|
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 =
|
|
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
|
|
3321
|
-
import { join as
|
|
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) =>
|
|
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
|
|
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:
|
|
4176
|
-
{ source: "agents", root:
|
|
4177
|
-
{ source: "codex", root:
|
|
4178
|
-
{ source: "claude", root:
|
|
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 =
|
|
4220
|
+
const skillDir = join15(root, dirent.name);
|
|
4193
4221
|
if (!await isDirectoryDirent(dirent, skillDir)) continue;
|
|
4194
|
-
if (await fileExists(
|
|
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 =
|
|
4232
|
+
const skillDir = join15(registryDir, dirent.name);
|
|
4205
4233
|
if (!await isDirectoryDirent(dirent, skillDir)) continue;
|
|
4206
|
-
if (await fileExists(
|
|
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 =
|
|
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(
|
|
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(
|
|
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
|
|
4302
|
+
return join15(homeDir, REGISTRY_ROOT_DIR);
|
|
4275
4303
|
}
|
|
4276
4304
|
function getManifestPath(homeDir) {
|
|
4277
|
-
return
|
|
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 ??
|
|
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
|
|
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
|
|
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 (!
|
|
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
|
|
6538
|
-
import { join as
|
|
6539
|
-
var ENGINE_DIR2 =
|
|
6540
|
-
var CHATS_FILE =
|
|
6541
|
-
var CLAUDE_HISTORY_DIR =
|
|
6542
|
-
var RELAY_HISTORY_DIR =
|
|
6543
|
-
var CODEX_HISTORY_DIR =
|
|
6544
|
-
var CURSOR_HISTORY_DIR =
|
|
6545
|
-
var OPENCODE_HISTORY_DIR =
|
|
6546
|
-
var PI_HISTORY_DIR =
|
|
6547
|
-
var DEEPSEEK_HISTORY_DIR =
|
|
6548
|
-
var FX_HISTORY_DIR =
|
|
6549
|
-
var KIMI_HISTORY_DIR =
|
|
6550
|
-
var MUSE_HISTORY_DIR =
|
|
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(
|
|
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
|
|
6978
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
8018
|
-
|
|
8019
|
-
].find(
|
|
8059
|
+
join19(moduleDirectory, "command-protection-hook.js"),
|
|
8060
|
+
join19(moduleDirectory, "..", "command-protection-hook.ts")
|
|
8061
|
+
].find(existsSync8);
|
|
8020
8062
|
var postToolHookPath = [
|
|
8021
|
-
|
|
8022
|
-
|
|
8023
|
-
].find(
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 ??
|
|
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) =>
|
|
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
|
|
8572
|
+
import { dirname as dirname7, join as join21 } from "path";
|
|
8531
8573
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
8532
|
-
import { existsSync as
|
|
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 =
|
|
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
|
-
|
|
8549
|
-
|
|
8550
|
-
].find(
|
|
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
|
-
|
|
8553
|
-
|
|
8554
|
-
].find(
|
|
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 ??
|
|
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 =
|
|
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:
|
|
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
|
|
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
|
|
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 =
|
|
9935
|
-
var OPENCODE_COMMAND_PROTECTION_PLUGIN_PATH =
|
|
9936
|
-
var OPENCODE_LEGACY_COMMAND_PROTECTION_PLUGIN_PATH =
|
|
9937
|
-
var OPENCODE_CONFIG_PATH =
|
|
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 (!
|
|
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 ??
|
|
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 (!
|
|
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
|
|
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 ??
|
|
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:
|
|
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
|
|
11516
|
-
import { join as
|
|
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 =
|
|
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 (!
|
|
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(
|
|
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
|
-
|
|
11602
|
-
|
|
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(
|
|
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(
|
|
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
|
|
12460
|
-
import { join as
|
|
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
|
-
|
|
12503
|
-
|
|
12504
|
-
|
|
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) =>
|
|
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 =
|
|
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 =
|
|
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
|
|
12787
|
+
import { basename as basename3, join as join27 } from "path";
|
|
12745
12788
|
|
|
12746
12789
|
// src/services/chat/chat-senders.ts
|
|
12747
|
-
import { join as
|
|
12748
|
-
var CHAT_SENDERS_DIR =
|
|
12790
|
+
import { join as join26 } from "path";
|
|
12791
|
+
var CHAT_SENDERS_DIR = join26(ENGINE_DIR2, "chat-senders");
|
|
12749
12792
|
function chatMessageSendersFilePath(chatId) {
|
|
12750
|
-
return
|
|
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
|
-
|
|
12767
|
-
|
|
12768
|
-
|
|
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,
|
|
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
|
|
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 =
|
|
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
|
|
13156
|
-
await
|
|
13157
|
-
await
|
|
13158
|
-
await
|
|
13159
|
-
await
|
|
13160
|
-
await
|
|
13161
|
-
await
|
|
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 =
|
|
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:
|
|
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:
|
|
13742
|
-
opencodeAvailable:
|
|
13743
|
-
piAvailable:
|
|
13744
|
-
cursorAvailable:
|
|
13745
|
-
deepseekAvailable:
|
|
13746
|
-
fxAvailable:
|
|
13747
|
-
kimiAvailable:
|
|
13748
|
-
museAvailable:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
|
14687
|
+
import { join as join33 } from "path";
|
|
14488
14688
|
|
|
14489
14689
|
// src/services/warm-hook-logs-service.ts
|
|
14490
|
-
import { mkdir as
|
|
14491
|
-
import { homedir as
|
|
14492
|
-
import { join as
|
|
14493
|
-
var LOGS_DIR2 =
|
|
14494
|
-
var 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
|
|
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
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
16179
|
+
await mkdir22(target, { recursive: true });
|
|
15964
16180
|
await cp(source, target, { recursive: true, force: true });
|
|
15965
16181
|
}));
|
|
15966
16182
|
}
|