replicas-engine 0.1.653 → 0.1.655

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
@@ -38,6 +38,7 @@ import {
38
38
  DEFAULT_CLAUDE_MODEL,
39
39
  DEFAULT_CODEX_MODEL,
40
40
  DEFAULT_CURSOR_MODEL,
41
+ DEFAULT_DEEPSEEK_MODEL,
41
42
  DEFAULT_HOOK_OUTPUT_PREVIEW_CHARS,
42
43
  DEFAULT_OPENCODE_MODEL,
43
44
  DEFAULT_PI_MODEL,
@@ -80,6 +81,7 @@ import {
80
81
  TaskAccumulator,
81
82
  USER_MESSAGE_ID_PAYLOAD_KEY,
82
83
  VALID_AGENT_PROVIDERS,
84
+ VALID_RELAY_SUBAGENT_PROVIDERS,
83
85
  VALID_THINKING_LEVELS,
84
86
  agentCredentialSnapshotSchema,
85
87
  applyCodexAspTranscriptDelta,
@@ -120,6 +122,7 @@ import {
120
122
  getChatHistoryPageWindow,
121
123
  getClaudeModelContextWindow,
122
124
  getCodexAspTurnResponse,
125
+ getDeepseekAssistantMessageText,
123
126
  getDefaultAgentModel,
124
127
  getEventTimestampMs,
125
128
  getGoalCommand,
@@ -173,12 +176,12 @@ import {
173
176
  serializeCanvasContentResponse,
174
177
  shellQuotePosix,
175
178
  stripAgentDiagnosticErrors
176
- } from "./chunk-KWNVQJRM.js";
179
+ } from "./chunk-542J7B2X.js";
177
180
 
178
181
  // src/index.ts
179
182
  import { serve } from "@hono/node-server";
180
183
  import { Hono as Hono2 } from "hono";
181
- import { existsSync as existsSync10 } from "fs";
184
+ import { existsSync as existsSync11 } from "fs";
182
185
  import { randomUUID as randomUUID8 } from "crypto";
183
186
  import { connect } from "net";
184
187
 
@@ -1817,6 +1820,22 @@ import { homedir as homedir5 } from "os";
1817
1820
  import { join as join8 } from "path";
1818
1821
  var OPENCODE_AUTH_PATH = join8(homedir5(), ".local", "share", "opencode", "auth.json");
1819
1822
  var PI_AUTH_PATH = join8(homedir5(), ".pi", "agent", "auth.json");
1823
+ var DEEPSEEK_AUTH_PATH = join8(homedir5(), ".replicas", "deepseek-auth.json");
1824
+
1825
+ // src/managers/deepseek-auth.ts
1826
+ import { readFileSync as readFileSync4 } from "fs";
1827
+ import { z } from "zod";
1828
+ var authSchema = z.object({
1829
+ openrouter: z.object({ type: z.literal("api"), key: z.string().min(1) })
1830
+ });
1831
+ function getDeepseekApiKey() {
1832
+ try {
1833
+ const parsed = authSchema.safeParse(JSON.parse(readFileSync4(DEEPSEEK_AUTH_PATH, "utf8")));
1834
+ return parsed.success ? parsed.data.openrouter.key : null;
1835
+ } catch {
1836
+ return null;
1837
+ }
1838
+ }
1820
1839
 
1821
1840
  // src/services/environment-details-service.ts
1822
1841
  var REPLICAS_DIR = join9(homedir6(), ".replicas");
@@ -1856,6 +1875,9 @@ function detectCursorAuthMethod() {
1856
1875
  function detectOpencodeAuthMethod() {
1857
1876
  return existsSync2(OPENCODE_AUTH_PATH) ? "api_key" : "none";
1858
1877
  }
1878
+ function detectDeepseekAuthMethod() {
1879
+ return getDeepseekApiKey() ? "api_key" : "none";
1880
+ }
1859
1881
  function detectPiAuthMethod() {
1860
1882
  return existsSync2(PI_AUTH_PATH) ? "api_key" : "none";
1861
1883
  }
@@ -1914,6 +1936,7 @@ function createDefaultDetails() {
1914
1936
  claudeAuthMethod: "none",
1915
1937
  codexAuthMethod: "none",
1916
1938
  cursorAuthMethod: "none",
1939
+ deepseekAuthMethod: "none",
1917
1940
  opencodeAuthMethod: "none",
1918
1941
  piAuthMethod: "none",
1919
1942
  lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -1953,6 +1976,7 @@ var EnvironmentDetailsService = class {
1953
1976
  details.claudeAuthMethod = detectClaudeAuthMethod();
1954
1977
  details.codexAuthMethod = detectCodexAuthMethod();
1955
1978
  details.cursorAuthMethod = detectCursorAuthMethod();
1979
+ details.deepseekAuthMethod = detectDeepseekAuthMethod();
1956
1980
  details.opencodeAuthMethod = detectOpencodeAuthMethod();
1957
1981
  details.piAuthMethod = detectPiAuthMethod();
1958
1982
  details.credentialFallbacks = listCredentialFallbacks();
@@ -2707,10 +2731,10 @@ async function registerDesktopPreview() {
2707
2731
  }
2708
2732
 
2709
2733
  // src/services/chat/chat-service.ts
2710
- import { existsSync as existsSync7 } from "fs";
2711
- import { appendFile as appendFile4, copyFile, mkdir as mkdir15, readFile as readFile15, rename as rename3, rm as rm2 } from "fs/promises";
2734
+ import { existsSync as existsSync8 } from "fs";
2735
+ import { appendFile as appendFile4, copyFile, mkdir as mkdir16, readFile as readFile15, rename as rename3, rm as rm2 } from "fs/promises";
2712
2736
  import { homedir as homedir15 } from "os";
2713
- import { join as join28 } from "path";
2737
+ import { join as join29 } from "path";
2714
2738
  import { randomUUID as randomUUID6 } from "crypto";
2715
2739
 
2716
2740
  // src/managers/claude-manager.ts
@@ -3228,7 +3252,7 @@ async function removeTempImageFiles(paths) {
3228
3252
  }
3229
3253
 
3230
3254
  // src/managers/coding-agent-manager.ts
3231
- import { readFileSync as readFileSync4 } from "fs";
3255
+ import { readFileSync as readFileSync5 } from "fs";
3232
3256
  import { join as join15 } from "path";
3233
3257
 
3234
3258
  // src/managers/auth-fallback.ts
@@ -3687,7 +3711,7 @@ var CodingAgentManager = class {
3687
3711
  buildMemoryInstruction() {
3688
3712
  const readSummary = (scope) => {
3689
3713
  try {
3690
- return readFileSync4(join15(MEMORY_ROOT, scope, MEMORY_SUMMARY_FILENAME), "utf8").trim();
3714
+ return readFileSync5(join15(MEMORY_ROOT, scope, MEMORY_SUMMARY_FILENAME), "utf8").trim();
3691
3715
  } catch {
3692
3716
  return "";
3693
3717
  }
@@ -4950,12 +4974,14 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
4950
4974
  await this.recordEvent(userMessage);
4951
4975
  }
4952
4976
  const combinedInstructions = this.buildCombinedInstructions(customInstructions, true);
4977
+ const systemPrompt = this.systemPromptOverride?.(combinedInstructions);
4953
4978
  const resolvedModel = normalizeClaudeModel(model) || DEFAULT_CLAUDE_MODEL;
4954
4979
  const claudeCodeModel = toClaudeCodeModel(resolvedModel);
4955
4980
  const resolvedPermissionMode = planMode ? "plan" : "bypassPermissions";
4956
4981
  const resolvedFastMode = Boolean(fastMode && canUseClaudeFastMode(resolvedModel));
4957
4982
  const signature = {
4958
4983
  combinedInstructions,
4984
+ systemPrompt,
4959
4985
  thinkingLevel,
4960
4986
  // enableInteractiveTools only matters in plan mode; the session signature
4961
4987
  // tracks the effective flag so a mode change can hot-swap via setPermissionMode
@@ -4965,6 +4991,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
4965
4991
  };
4966
4992
  await this.ensureSession({
4967
4993
  signature,
4994
+ systemPrompt,
4968
4995
  combinedInstructions,
4969
4996
  resolvedModel,
4970
4997
  claudeCodeModel,
@@ -4989,6 +5016,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
4989
5016
  async ensureSession(args) {
4990
5017
  const {
4991
5018
  signature,
5019
+ systemPrompt,
4992
5020
  combinedInstructions,
4993
5021
  resolvedModel,
4994
5022
  claudeCodeModel,
@@ -5021,6 +5049,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
5021
5049
  }
5022
5050
  await this.startSession({
5023
5051
  combinedInstructions,
5052
+ systemPrompt,
5024
5053
  resolvedModel,
5025
5054
  claudeCodeModel,
5026
5055
  resolvedPermissionMode,
@@ -5030,7 +5059,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
5030
5059
  });
5031
5060
  }
5032
5061
  sessionSignaturesMatch(a, b) {
5033
- return a.combinedInstructions === b.combinedInstructions && a.thinkingLevel === b.thinkingLevel && a.enableInteractiveTools === b.enableInteractiveTools && a.fastMode === b.fastMode;
5062
+ return a.combinedInstructions === b.combinedInstructions && a.systemPrompt === b.systemPrompt && a.thinkingLevel === b.thinkingLevel && a.enableInteractiveTools === b.enableInteractiveTools && a.fastMode === b.fastMode;
5034
5063
  }
5035
5064
  /** Query inputs shared by real sessions and slash-command discovery. */
5036
5065
  async buildSharedQueryOptions() {
@@ -5050,6 +5079,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
5050
5079
  async startSession(args) {
5051
5080
  const {
5052
5081
  combinedInstructions,
5082
+ systemPrompt,
5053
5083
  resolvedModel,
5054
5084
  claudeCodeModel,
5055
5085
  resolvedPermissionMode,
@@ -5059,7 +5089,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
5059
5089
  } = args;
5060
5090
  const ultracode = thinkingLevel === "ultracode";
5061
5091
  const effort = thinkingLevel === "ultra" || ultracode ? "xhigh" : thinkingLevel;
5062
- const systemPrompt = this.systemPromptOverride ? this.systemPromptOverride(combinedInstructions) : {
5092
+ const resolvedSystemPrompt = systemPrompt ?? {
5063
5093
  type: "preset",
5064
5094
  preset: "claude_code",
5065
5095
  append: combinedInstructions
@@ -5089,7 +5119,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
5089
5119
  ...allowedTools ? { allowedTools } : {},
5090
5120
  ...disallowedTools.length > 0 ? { disallowedTools } : {},
5091
5121
  settingSources: ["user", "project", "local"],
5092
- systemPrompt,
5122
+ systemPrompt: resolvedSystemPrompt,
5093
5123
  ...this.mcpServersConfig ? { mcpServers: this.mcpServersConfig } : {},
5094
5124
  ...shared.plugins.length > 0 ? { plugins: shared.plugins } : {},
5095
5125
  ...shared.enableAllSkills ? { skills: "all" } : {},
@@ -5685,7 +5715,7 @@ var CodexQuotaStatusTracker = class {
5685
5715
  };
5686
5716
 
5687
5717
  // src/managers/codex-asp/mappers.ts
5688
- import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
5718
+ import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
5689
5719
  var localImageCache = /* @__PURE__ */ new Map();
5690
5720
  var DEFAULT_MODEL = DEFAULT_CODEX_MODEL;
5691
5721
  var THREAD_START_METHOD = "thread/start";
@@ -5813,7 +5843,7 @@ function userImageForLocalPath(path6) {
5813
5843
  const image = {
5814
5844
  type: "image",
5815
5845
  mediaType: inferMediaType(path6),
5816
- data: readFileSync5(path6).toString("base64")
5846
+ data: readFileSync6(path6).toString("base64")
5817
5847
  };
5818
5848
  if (image.data.length > 0) localImageCache.set(path6, image);
5819
5849
  return image;
@@ -6317,11 +6347,13 @@ var CODEX_HISTORY_DIR = join18(ENGINE_DIR2, "codex-histories");
6317
6347
  var CURSOR_HISTORY_DIR = join18(ENGINE_DIR2, "cursor-histories");
6318
6348
  var OPENCODE_HISTORY_DIR = join18(ENGINE_DIR2, "opencode-histories");
6319
6349
  var PI_HISTORY_DIR = join18(ENGINE_DIR2, "pi-histories");
6350
+ var DEEPSEEK_HISTORY_DIR = join18(ENGINE_DIR2, "deepseek-histories");
6320
6351
  var HISTORY_DIR_BY_PROVIDER = {
6321
6352
  claude: CLAUDE_HISTORY_DIR,
6322
6353
  relay: RELAY_HISTORY_DIR,
6323
6354
  codex: CODEX_HISTORY_DIR,
6324
6355
  cursor: CURSOR_HISTORY_DIR,
6356
+ deepseek: DEEPSEEK_HISTORY_DIR,
6325
6357
  opencode: OPENCODE_HISTORY_DIR,
6326
6358
  pi: PI_HISTORY_DIR
6327
6359
  };
@@ -8060,26 +8092,416 @@ ${instructions}
8060
8092
  }
8061
8093
  };
8062
8094
 
8063
- // src/managers/opencode-manager.ts
8095
+ // src/managers/deepseek-manager.ts
8096
+ import { spawn as spawn3 } from "child_process";
8097
+ import { createRequire } from "module";
8098
+ import { dirname as dirname6, join as join21 } from "path";
8099
+ import { fileURLToPath } from "url";
8064
8100
  import { existsSync as existsSync6 } from "fs";
8065
- import { mkdir as mkdir12, readFile as readFile11 } from "fs/promises";
8066
- import { delimiter, dirname as dirname6, join as join21 } from "path";
8101
+ import { mkdir as mkdir12 } from "fs/promises";
8102
+ import { z as z2 } from "zod";
8103
+ import WebSocket from "ws";
8104
+ import { AbstractApiClient } from "@deepseek-ai/dsh-host-apiproxy/client";
8105
+ import { hostFrameSchema, muxFrameSchema } from "@deepseek-ai/dsh-host-apiproxy/api/events.schema";
8106
+ import { serverRequestSchema } from "@deepseek-ai/dsh-host-apiproxy/api/rpc.schema";
8107
+ import { sessionIdSchema } from "@deepseek-ai/dsh-host-apiproxy/api/sessions.schema";
8108
+ var questionSelectionSchema = z2.record(z2.string(), z2.object({
8109
+ options: z2.array(z2.string()).optional(),
8110
+ custom: z2.string().optional()
8111
+ }));
8112
+ var require2 = createRequire(import.meta.url);
8113
+ var DSH_BIN = join21(dirname6(require2.resolve("@deepseek-ai/dsh/package.json")), "lib", "bin.js");
8114
+ var MANAGER_DIR = dirname6(fileURLToPath(import.meta.url));
8115
+ var DSH_PATCH = [
8116
+ join21(MANAGER_DIR, "..", "..", "scripts", "deepseek", "replicas.cordis.patch.yml"),
8117
+ join21(MANAGER_DIR, "..", "..", "..", "scripts", "deepseek", "replicas.cordis.patch.yml")
8118
+ ].find(existsSync6) ?? "";
8119
+ var DeepseekApiClient = class extends AbstractApiClient {
8120
+ constructor(baseUrl) {
8121
+ super(3e4);
8122
+ this.baseUrl = baseUrl;
8123
+ }
8124
+ baseUrl;
8125
+ doFetch(input, init) {
8126
+ return fetch(input, init);
8127
+ }
8128
+ resolveBase() {
8129
+ return this.baseUrl;
8130
+ }
8131
+ openMux(_payload, signal, onOpen) {
8132
+ return this.readWebSocket("/api/events.mux", signal, muxFrameSchema, onOpen);
8133
+ }
8134
+ openHost(_payload, signal, onOpen) {
8135
+ return this.readWebSocket("/api/events.host", signal, hostFrameSchema, onOpen);
8136
+ }
8137
+ async *readWebSocket(path6, signal, schema, onOpen) {
8138
+ const url = new URL(path6, this.baseUrl);
8139
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
8140
+ const socket = new WebSocket(url);
8141
+ const inbox = [];
8142
+ let wake;
8143
+ const enqueue = (item) => {
8144
+ inbox.push(item);
8145
+ wake?.();
8146
+ wake = void 0;
8147
+ };
8148
+ const abort = () => socket.close();
8149
+ socket.on("open", onOpen ?? (() => {
8150
+ }));
8151
+ socket.on("message", (data) => {
8152
+ try {
8153
+ const envelope = serverRequestSchema.parse(JSON.parse(data.toString()));
8154
+ enqueue({ rpcId: envelope.rpcId, payload: schema.parse(envelope.payload) });
8155
+ } catch (error) {
8156
+ console.error("[DeepseekManager] Dropped malformed WebSocket frame:", error);
8157
+ }
8158
+ });
8159
+ socket.once("close", () => enqueue(null));
8160
+ signal.addEventListener("abort", abort, { once: true });
8161
+ try {
8162
+ while (true) {
8163
+ while (inbox.length > 0) {
8164
+ const item = inbox.shift();
8165
+ if (item === null) return;
8166
+ if (item) yield item;
8167
+ }
8168
+ await new Promise((resolve5) => {
8169
+ wake = resolve5;
8170
+ });
8171
+ }
8172
+ } finally {
8173
+ signal.removeEventListener("abort", abort);
8174
+ socket.close();
8175
+ }
8176
+ }
8177
+ };
8178
+ function unwrap(response) {
8179
+ if (!response.result.ok) throw new Error(response.result.error.message);
8180
+ return response.result.value;
8181
+ }
8182
+ function reasoningEffort(level) {
8183
+ if (!level) return void 0;
8184
+ if (level === "low") return "off";
8185
+ return level === "medium" || level === "high" ? "high" : "max";
8186
+ }
8187
+ async function getOpenRouterKey() {
8188
+ const apiKey = getDeepseekApiKey();
8189
+ if (!apiKey) {
8190
+ throw new Error("OpenRouter authentication is missing for DeepSeek Harness. Add an API key in Settings \u2192 Coding agents.");
8191
+ }
8192
+ return apiKey;
8193
+ }
8194
+ var DeepseekManager = class extends CodingAgentManager {
8195
+ historyFilePath;
8196
+ historyFile;
8197
+ process = null;
8198
+ client = null;
8199
+ sessionId;
8200
+ streamController = null;
8201
+ turnCompletion = null;
8202
+ resolveTurnCompletion = null;
8203
+ pendingResponses = /* @__PURE__ */ new Map();
8204
+ contextPressure = {};
8205
+ contextBreakdown = {};
8206
+ tokenUsage = {};
8207
+ selectedModel = DEFAULT_DEEPSEEK_MODEL;
8208
+ constructor(options) {
8209
+ super(options);
8210
+ this.sessionId = options.initialSessionId ? sessionIdSchema.parse(options.initialSessionId) : null;
8211
+ this.historyFilePath = options.historyFilePath ?? join21(ENGINE_ENV.HOME_DIR, ".replicas", "deepseek", "history.jsonl");
8212
+ this.historyFile = new CodexHistoryFile(this.historyFilePath);
8213
+ this.initializeManager(this.processMessageInternal.bind(this));
8214
+ }
8215
+ async initialize() {
8216
+ await mkdir12(dirname6(this.historyFilePath), { recursive: true });
8217
+ }
8218
+ getHistorySink() {
8219
+ return this.historyFile;
8220
+ }
8221
+ async interruptActiveTurn() {
8222
+ if (this.client && this.sessionId) unwrap(await this.client.sessions.cancel({ sessionId: this.sessionId }));
8223
+ }
8224
+ async steerRequest(request) {
8225
+ if (!this.client || !this.sessionId || !this.isProcessing()) return false;
8226
+ unwrap(await this.client.sessions.prompt({
8227
+ sessionId: this.sessionId,
8228
+ mode: "steer",
8229
+ content: [{ type: "text", text: request.message }]
8230
+ }));
8231
+ this.recordHistoryEvent("event_msg", { type: "user_message", message: request.message }, this.historyFile);
8232
+ return true;
8233
+ }
8234
+ async getHistory(page = {}) {
8235
+ await this.historyFile.flush();
8236
+ return { thread_id: this.sessionId ?? this.initialSessionId, ...await this.historyFile.loadEventsPage(page), goal: null };
8237
+ }
8238
+ async listSlashCommands() {
8239
+ await this.initialized;
8240
+ const client = await this.ensureClient();
8241
+ if (!this.sessionId) {
8242
+ this.sessionId = unwrap(await client.sessions.create({ cwd: this.workingDirectory })).sessionId;
8243
+ await this.onSaveSessionId(this.sessionId);
8244
+ }
8245
+ const { skills } = unwrap(await client.skills.list({ sessionId: this.sessionId }));
8246
+ return mergeSlashCommands(skills.map((skill) => createProviderSlashCommand("deepseek", skill.name, skill.description)).filter((command) => Boolean(command)));
8247
+ }
8248
+ isAwaitingInput() {
8249
+ return this.pendingResponses.size > 0;
8250
+ }
8251
+ async respondToToolInput(requestId, selectionId) {
8252
+ const pending = this.pendingResponses.get(requestId);
8253
+ if (!pending || !this.client || !this.sessionId) return false;
8254
+ let result;
8255
+ if (pending.frame.type === "approval/requested") {
8256
+ result = { ok: true, value: { sessionId: this.sessionId, outcome: selectionId === "allow" ? "allowed-once" : "rejected" } };
8257
+ } else {
8258
+ const parsed = questionSelectionSchema.safeParse(selectionId === "declined" ? {} : JSON.parse(selectionId));
8259
+ if (!parsed.success) return false;
8260
+ result = {
8261
+ ok: true,
8262
+ value: {
8263
+ sessionId: this.sessionId,
8264
+ answer: {
8265
+ answers: pending.frame.questions.map((question) => {
8266
+ const answer = parsed.data[question.id];
8267
+ return {
8268
+ id: question.id,
8269
+ selected: (answer?.options ?? []).map((option) => option.startsWith(`${question.id}:`) ? option.slice(question.id.length + 1) : option),
8270
+ ...answer?.custom ? { custom: answer.custom } : {}
8271
+ };
8272
+ })
8273
+ }
8274
+ }
8275
+ };
8276
+ }
8277
+ const receipt = await this.client.respond({ type: "client-response", rpcId: pending.rpcId, result });
8278
+ if (!receipt.accepted) return false;
8279
+ this.pendingResponses.delete(requestId);
8280
+ return true;
8281
+ }
8282
+ dispose() {
8283
+ this.streamController?.abort();
8284
+ this.process?.kill("SIGTERM");
8285
+ this.process = null;
8286
+ this.client = null;
8287
+ }
8288
+ async ensureClient() {
8289
+ if (this.client) return this.client;
8290
+ if (!DSH_PATCH) throw new Error("DeepSeek Harness configuration is missing from the engine package.");
8291
+ const apiKey = await getOpenRouterKey();
8292
+ const child = spawn3(process.execPath, [DSH_BIN, "--profile", "web", "--patch", DSH_PATCH, "--host", "127.0.0.1", "--port", "0"], {
8293
+ cwd: this.workingDirectory,
8294
+ env: {
8295
+ ...process.env,
8296
+ DEEPSEEK_API_KEY: apiKey,
8297
+ DEEPSEEK_BASE_URL: "https://openrouter.ai/api/v1",
8298
+ DSH_HOME: join21(ENGINE_ENV.HOME_DIR, ".replicas", "deepseek"),
8299
+ DSH_PERMISSION_MODE: "workspace-write",
8300
+ DSH_TELEMETRY_DISABLED: "true"
8301
+ },
8302
+ stdio: ["ignore", "pipe", "pipe"]
8303
+ });
8304
+ this.process = child;
8305
+ const baseUrl = await new Promise((resolve5, reject) => {
8306
+ let stdout = "";
8307
+ let stderr = "";
8308
+ const timeout = setTimeout(() => {
8309
+ child.kill("SIGTERM");
8310
+ reject(new Error(`DeepSeek Harness startup timed out: ${stderr.trim()}`));
8311
+ }, 3e4);
8312
+ child.stdout?.on("data", (chunk) => {
8313
+ stdout += chunk.toString();
8314
+ const match = stdout.match(/dsh web:\s+(http:\/\/[^\s]+)/);
8315
+ if (!match?.[1]) return;
8316
+ clearTimeout(timeout);
8317
+ resolve5(match[1]);
8318
+ });
8319
+ child.stderr?.on("data", (chunk) => {
8320
+ stderr = (stderr + chunk.toString()).slice(-8e3);
8321
+ });
8322
+ child.once("exit", (code) => {
8323
+ clearTimeout(timeout);
8324
+ reject(new Error(`DeepSeek Harness exited during startup (${code}): ${stderr.trim()}`));
8325
+ });
8326
+ child.once("error", reject);
8327
+ });
8328
+ const client = new DeepseekApiClient(baseUrl);
8329
+ this.client = client;
8330
+ await this.startStreams(client);
8331
+ return client;
8332
+ }
8333
+ async startStreams(client) {
8334
+ const controller = new AbortController();
8335
+ this.streamController = controller;
8336
+ const muxReady = Promise.withResolvers();
8337
+ const hostReady = Promise.withResolvers();
8338
+ void (async () => {
8339
+ for await (const envelope of client.events.mux({}, controller.signal, muxReady.resolve)) this.handleMuxFrame(envelope.rpcId, envelope.payload);
8340
+ })().catch((error) => {
8341
+ muxReady.reject(error);
8342
+ if (!controller.signal.aborted) this.recordHistoryEvent("deepseek-error", { message: String(error) }, this.historyFile);
8343
+ });
8344
+ void (async () => {
8345
+ for await (const envelope of client.events.host({}, controller.signal, hostReady.resolve)) this.handleHostFrame(envelope.payload);
8346
+ })().catch((error) => {
8347
+ hostReady.reject(error);
8348
+ if (!controller.signal.aborted) this.recordHistoryEvent("deepseek-error", { message: String(error) }, this.historyFile);
8349
+ });
8350
+ await Promise.all([muxReady.promise, hostReady.promise]);
8351
+ }
8352
+ handleMuxFrame(rpcId, frame) {
8353
+ if ("sessionId" in frame && this.sessionId && frame.sessionId !== this.sessionId) return;
8354
+ if (frame.type === "session/event") {
8355
+ this.recordHistoryEvent("deepseek-session-event", { event: frame.event, view: frame.view }, this.historyFile);
8356
+ return;
8357
+ }
8358
+ if (frame.type === "session/jobs") {
8359
+ this.recordHistoryEvent("deepseek-jobs", { jobs: frame.jobs }, this.historyFile);
8360
+ return;
8361
+ }
8362
+ if (frame.type === "session/projection") {
8363
+ if (frame.key === "contextPressure" && isRecord2(frame.value)) this.contextPressure = frame.value;
8364
+ else if (frame.key === "contextBreakdown" && isRecord2(frame.value)) this.contextBreakdown = frame.value;
8365
+ else if (frame.key === "tokenUsage" && isRecord2(frame.value)) this.tokenUsage = frame.value;
8366
+ else return;
8367
+ this.emitDeepseekContextUsage();
8368
+ return;
8369
+ }
8370
+ if (frame.type === "approval/resolved" || frame.type === "question/resolved") {
8371
+ const requestId2 = frame.type === "approval/resolved" ? `approval:${frame.approvalId}` : `question:${frame.questionRpcId}`;
8372
+ this.pendingResponses.delete(requestId2);
8373
+ this.recordHistoryEvent("deepseek-input-resolved", {
8374
+ requestId: requestId2,
8375
+ status: frame.type === "question/resolved" && frame.outcome === "cancelled" ? "aborted" : "resolved"
8376
+ }, this.historyFile);
8377
+ return;
8378
+ }
8379
+ if (frame.type !== "approval/requested" && frame.type !== "question/requested") return;
8380
+ const requestId = frame.type === "approval/requested" ? `approval:${frame.approvalId}` : `question:${rpcId}`;
8381
+ this.pendingResponses.set(requestId, { rpcId, frame });
8382
+ this.recordHistoryEvent("deepseek-input-request", {
8383
+ requestId,
8384
+ kind: frame.type === "approval/requested" ? "approval" : "question",
8385
+ ...frame.type === "approval/requested" ? { toolName: frame.toolName, reason: frame.reason } : { questions: frame.questions }
8386
+ }, this.historyFile);
8387
+ }
8388
+ handleHostFrame(frame) {
8389
+ if (!("sessionId" in frame) || !this.sessionId || frame.sessionId !== this.sessionId) return;
8390
+ if (frame.type === "host/agent-error") {
8391
+ this.recordHistoryEvent("deepseek-error", { message: frame.message }, this.historyFile);
8392
+ }
8393
+ if (frame.type === "host/session-status" && !frame.running) {
8394
+ this.resolveTurnCompletion?.();
8395
+ this.resolveTurnCompletion = null;
8396
+ }
8397
+ }
8398
+ emitDeepseekContextUsage() {
8399
+ const totalTokens = typeof this.contextPressure.projectedTokens === "number" ? this.contextPressure.projectedTokens : this.contextPressure.pressureTokens;
8400
+ if (typeof totalTokens !== "number") return;
8401
+ const maxTokens = typeof this.contextPressure.contextWindow === "number" ? this.contextPressure.contextWindow : null;
8402
+ const categories = [
8403
+ ["System", this.contextBreakdown.systemTokens],
8404
+ ["Tools", this.contextBreakdown.toolsTokens],
8405
+ ["Messages", this.contextBreakdown.messageTokens]
8406
+ ].flatMap(([name, tokens]) => typeof tokens === "number" ? [{ name: String(name), tokens, percentage: percentage(tokens, maxTokens) }] : []);
8407
+ this.emitContextUsage({
8408
+ provider: "deepseek",
8409
+ source: "provider_usage",
8410
+ model: this.selectedModel,
8411
+ totalTokens,
8412
+ maxTokens,
8413
+ percentage: percentage(totalTokens, maxTokens),
8414
+ compactsAutomatically: true,
8415
+ categories,
8416
+ apiUsage: {
8417
+ inputTokens: typeof this.tokenUsage.uncachedInputTokens === "number" ? this.tokenUsage.uncachedInputTokens : void 0,
8418
+ outputTokens: typeof this.tokenUsage.outputTokens === "number" ? this.tokenUsage.outputTokens : void 0,
8419
+ cacheCreationInputTokens: typeof this.tokenUsage.cacheWriteTokens === "number" ? this.tokenUsage.cacheWriteTokens : void 0,
8420
+ cacheReadInputTokens: typeof this.tokenUsage.cacheReadTokens === "number" ? this.tokenUsage.cacheReadTokens : void 0
8421
+ },
8422
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
8423
+ });
8424
+ }
8425
+ async processMessageInternal(request) {
8426
+ try {
8427
+ const client = await this.ensureClient();
8428
+ if (!this.sessionId) {
8429
+ this.sessionId = unwrap(await client.sessions.create({ cwd: this.workingDirectory })).sessionId;
8430
+ await this.onSaveSessionId(this.sessionId);
8431
+ }
8432
+ const model = request.model ?? DEFAULT_DEEPSEEK_MODEL;
8433
+ this.selectedModel = model;
8434
+ const current = unwrap(await client.sessions.models({ sessionId: this.sessionId })).current;
8435
+ const effort = reasoningEffort(request.thinkingLevel);
8436
+ if (current.model !== model || current.reasoningEffort !== effort) {
8437
+ unwrap(await client.sessions.selectModel({
8438
+ sessionId: this.sessionId,
8439
+ provider: "deepseek-official",
8440
+ model,
8441
+ ...effort ? { reasoningEffort: effort } : {}
8442
+ }));
8443
+ }
8444
+ this.recordHistoryEvent("event_msg", { type: "user_message", message: request.message }, this.historyFile);
8445
+ this.turnCompletion = new Promise((resolve5) => {
8446
+ this.resolveTurnCompletion = resolve5;
8447
+ });
8448
+ const images = await normalizeImages(request.images ?? []);
8449
+ const combinedInstructions = this.buildCombinedInstructions(request.customInstructions);
8450
+ const prompt = combinedInstructions ? `<system_instructions>
8451
+ ${combinedInstructions}
8452
+ </system_instructions>
8453
+
8454
+ ${request.message}` : request.message;
8455
+ if (!request.planMode) {
8456
+ unwrap(await client.sessions.prompt({
8457
+ sessionId: this.sessionId,
8458
+ mode: "queue",
8459
+ content: [{ type: "text", text: "/plan off" }]
8460
+ }));
8461
+ }
8462
+ unwrap(await client.sessions.prompt({
8463
+ sessionId: this.sessionId,
8464
+ mode: "queue",
8465
+ content: [
8466
+ { type: "text", text: request.planMode ? `/plan ${prompt}` : prompt },
8467
+ ...images.map((image) => ({
8468
+ type: "image",
8469
+ mediaType: image.source.media_type,
8470
+ data: image.source.data
8471
+ }))
8472
+ ]
8473
+ }));
8474
+ await this.turnCompletion;
8475
+ } catch (error) {
8476
+ this.recordHistoryEvent("deepseek-error", { message: error instanceof Error ? error.message : String(error) }, this.historyFile);
8477
+ } finally {
8478
+ this.turnCompletion = null;
8479
+ await this.historyFile.flush();
8480
+ await this.onTurnComplete();
8481
+ }
8482
+ }
8483
+ };
8484
+
8485
+ // src/managers/opencode-manager.ts
8486
+ import { existsSync as existsSync7 } from "fs";
8487
+ import { mkdir as mkdir13, readFile as readFile11 } from "fs/promises";
8488
+ import { delimiter, dirname as dirname7, join as join22 } from "path";
8067
8489
  import { randomBytes as randomBytes2 } from "crypto";
8068
- import { fileURLToPath } from "url";
8490
+ import { fileURLToPath as fileURLToPath2 } from "url";
8069
8491
  import { Agent } from "undici";
8070
- import { z as z2 } from "zod";
8492
+ import { z as z4 } from "zod";
8071
8493
 
8072
8494
  // ../shared/src/credentials/opencode-go.ts
8073
- import { z } from "zod";
8495
+ import { z as z3 } from "zod";
8074
8496
  var OPENCODE_GO_PROVIDER = "opencode-go";
8075
8497
  var DEFAULT_OPENCODE_GO_MODEL = "glm-5.2";
8076
- var opencodeGoModelsDevSchema = z.object({
8077
- "opencode-go": z.object({
8078
- models: z.record(z.string(), z.object({
8079
- id: z.string().optional(),
8080
- name: z.string(),
8081
- description: z.string().optional(),
8082
- status: z.enum(["alpha", "beta", "deprecated"]).optional()
8498
+ var opencodeGoModelsDevSchema = z3.object({
8499
+ "opencode-go": z3.object({
8500
+ models: z3.record(z3.string(), z3.object({
8501
+ id: z3.string().optional(),
8502
+ name: z3.string(),
8503
+ description: z3.string().optional(),
8504
+ status: z3.enum(["alpha", "beta", "deprecated"]).optional()
8083
8505
  }))
8084
8506
  }).optional()
8085
8507
  });
@@ -8109,8 +8531,8 @@ async function getAllowedOpenRouterModels() {
8109
8531
  }
8110
8532
 
8111
8533
  // src/managers/opencode-manager.ts
8112
- var OPENCODE_SHIM_DIR = dirname6(fileURLToPath(new URL("../../scripts/opencode", import.meta.url)));
8113
- var OPENCODE_CONFIG_PATH = join21(ENGINE_ENV.HOME_DIR, ".config", "opencode", "opencode.json");
8534
+ var OPENCODE_SHIM_DIR = dirname7(fileURLToPath2(new URL("../../scripts/opencode", import.meta.url)));
8535
+ var OPENCODE_CONFIG_PATH = join22(ENGINE_ENV.HOME_DIR, ".config", "opencode", "opencode.json");
8114
8536
  var OPENCODE_FETCH_DISPATCHER = new Agent({ headersTimeout: 0, bodyTimeout: 0 });
8115
8537
  var OPENCODE_SERVER_STARTUP_TIMEOUT_MS = 3e4;
8116
8538
  var OPENCODE_WORKSPACE_PERMISSION = {
@@ -8140,12 +8562,12 @@ var OPENCODE_VARIANT_CANDIDATES_BY_THINKING_LEVEL = {
8140
8562
  ultra: ["max", "xhigh", "high"],
8141
8563
  ultracode: ["max", "xhigh", "high"]
8142
8564
  };
8143
- var opencodeAuthSchema = z2.record(z2.string(), z2.object({
8144
- type: z2.string().optional(),
8145
- key: z2.string().optional()
8565
+ var opencodeAuthSchema = z4.record(z4.string(), z4.object({
8566
+ type: z4.string().optional(),
8567
+ key: z4.string().optional()
8146
8568
  }));
8147
8569
  async function hasOpencodeCredentials(provider) {
8148
- if (!existsSync6(OPENCODE_AUTH_PATH)) return false;
8570
+ if (!existsSync7(OPENCODE_AUTH_PATH)) return false;
8149
8571
  try {
8150
8572
  const auth = opencodeAuthSchema.safeParse(JSON.parse(await readFile11(OPENCODE_AUTH_PATH, "utf8")));
8151
8573
  return auth.success && auth.data[provider]?.type === "api" && Boolean(auth.data[provider]?.key);
@@ -8360,12 +8782,12 @@ var OpencodeManager = class extends CodingAgentManager {
8360
8782
  constructor(options) {
8361
8783
  super(options);
8362
8784
  this.sessionId = options.initialSessionId;
8363
- this.historyFilePath = options.historyFilePath ?? join21(ENGINE_ENV.HOME_DIR, ".replicas", "opencode", "history.jsonl");
8785
+ this.historyFilePath = options.historyFilePath ?? join22(ENGINE_ENV.HOME_DIR, ".replicas", "opencode", "history.jsonl");
8364
8786
  this.historyFile = new CodexHistoryFile(this.historyFilePath);
8365
8787
  this.initializeManager(this.processMessageInternal.bind(this));
8366
8788
  }
8367
8789
  async initialize() {
8368
- await mkdir12(dirname6(this.historyFilePath), { recursive: true });
8790
+ await mkdir13(dirname7(this.historyFilePath), { recursive: true });
8369
8791
  }
8370
8792
  getHistorySink() {
8371
8793
  return this.historyFile;
@@ -8869,8 +9291,8 @@ var OpencodeManager = class extends CodingAgentManager {
8869
9291
  };
8870
9292
 
8871
9293
  // src/managers/pi-manager.ts
8872
- import { mkdir as mkdir13 } from "fs/promises";
8873
- import { dirname as dirname7, join as join22 } from "path";
9294
+ import { mkdir as mkdir14 } from "fs/promises";
9295
+ import { dirname as dirname8, join as join23 } from "path";
8874
9296
  import {
8875
9297
  AuthStorage,
8876
9298
  createAgentSession,
@@ -8953,12 +9375,12 @@ var PiManager = class extends CodingAgentManager {
8953
9375
  providerApiKey = null;
8954
9376
  constructor(options) {
8955
9377
  super(options);
8956
- this.historyFilePath = options.historyFilePath ?? join22(PI_HISTORY_DIR, `${Date.now()}.jsonl`);
9378
+ this.historyFilePath = options.historyFilePath ?? join23(PI_HISTORY_DIR, `${Date.now()}.jsonl`);
8957
9379
  this.historyFile = new CodexHistoryFile(this.historyFilePath);
8958
9380
  this.initializeManager(this.processMessageInternal.bind(this));
8959
9381
  }
8960
9382
  async initialize() {
8961
- await mkdir13(dirname7(this.historyFilePath), { recursive: true });
9383
+ await mkdir14(dirname8(this.historyFilePath), { recursive: true });
8962
9384
  }
8963
9385
  getHistorySink() {
8964
9386
  return this.historyFile;
@@ -9053,7 +9475,7 @@ var PiManager = class extends CodingAgentManager {
9053
9475
  const sessionManager = this.initialSessionId ? SessionManager.open(this.initialSessionId, PI_HISTORY_DIR, this.workingDirectory) : SessionManager.create(this.workingDirectory, PI_HISTORY_DIR);
9054
9476
  const resourceLoader = new DefaultResourceLoader({
9055
9477
  cwd: this.workingDirectory,
9056
- agentDir: join22(ENGINE_ENV.HOME_DIR, ".pi", "agent"),
9478
+ agentDir: join23(ENGINE_ENV.HOME_DIR, ".pi", "agent"),
9057
9479
  extensionFactories: [registerCommandProtection(
9058
9480
  this.workingDirectory,
9059
9481
  this.historyFile,
@@ -9119,7 +9541,7 @@ var PiManager = class extends CodingAgentManager {
9119
9541
 
9120
9542
  // src/managers/relay-tools.ts
9121
9543
  import { createSdkMcpServer, tool } from "@anthropic-ai/claude-agent-sdk";
9122
- import { z as z3 } from "zod";
9544
+ import { z as z5 } from "zod";
9123
9545
 
9124
9546
  // src/managers/relay-providers.ts
9125
9547
  function isRelaySubagentProviderAllowed(provider, allowedProviders) {
@@ -9128,19 +9550,18 @@ function isRelaySubagentProviderAllowed(provider, allowedProviders) {
9128
9550
  function getAvailableRelayProviders(availability) {
9129
9551
  const codexAvailable = availability.codexAvailable ?? false;
9130
9552
  const cursorAvailable = availability.cursorAvailable ?? false;
9553
+ const deepseekAvailable = availability.deepseekAvailable ?? false;
9131
9554
  const opencodeAvailable = availability.opencodeAvailable ?? false;
9132
9555
  const piAvailable = availability.piAvailable ?? false;
9133
9556
  const providers = ["claude"];
9134
9557
  if (codexAvailable) providers.push("codex");
9135
9558
  if (cursorAvailable) providers.push("cursor");
9559
+ if (deepseekAvailable) providers.push("deepseek");
9136
9560
  if (opencodeAvailable) providers.push("opencode");
9137
9561
  if (piAvailable) providers.push("pi");
9138
9562
  providers.push("relay");
9139
9563
  return providers;
9140
9564
  }
9141
- function getAvailableCodeProviders(availability) {
9142
- return getAvailableRelayProviders(availability).filter((provider) => provider === "codex" || provider === "cursor" || provider === "opencode" || provider === "pi");
9143
- }
9144
9565
 
9145
9566
  // src/managers/relay-tools.ts
9146
9567
  var POLL_INTERVAL_MS = 2e3;
@@ -9221,6 +9642,10 @@ function extractFinalResponse(history) {
9221
9642
  const text = extractTextBlocks(message?.content, "text", "");
9222
9643
  if (text) return text;
9223
9644
  }
9645
+ if (event.type === "deepseek-session-event") {
9646
+ const text = getDeepseekAssistantMessageText(payload.event, "text");
9647
+ if (text) return text;
9648
+ }
9224
9649
  }
9225
9650
  return null;
9226
9651
  }
@@ -9237,42 +9662,36 @@ async function getChatFinalResponse(chatId) {
9237
9662
  }
9238
9663
  return extractFinalResponse(history) ?? "[No response from subagent]";
9239
9664
  }
9240
- function buildSpawnAgentTool(parentChatId, availability = {}, getAllowedProviders = () => monolithService.getRelaySubagentProviders()) {
9241
- const codexAvailable = availability.codexAvailable ?? false;
9242
- const cursorAvailable = availability.cursorAvailable ?? false;
9243
- const opencodeAvailable = availability.opencodeAvailable ?? false;
9244
- const availableProviders = getAvailableRelayProviders(availability);
9245
- const providerEnum = z3.enum(availableProviders);
9246
- const codeProviders = getAvailableCodeProviders(availability);
9247
- const providerDesc = codeProviders.length > 0 ? `Which agent to use. Prefer ${codeProviders.join(" or ")} for code writing, claude for exploration/analysis, relay for complex multi-step orchestration.` : "Which agent to use. Use claude for code writing, exploration, and analysis. Use relay for complex multi-step orchestration.";
9248
- const useCases = codeProviders.length > 0 ? `- Complex code writing tasks (use provider '${codeProviders.join("' or '")}' with a capable model)
9249
- - Codebase exploration that would consume many tokens (use provider 'claude')` : `- Complex code writing tasks (use provider 'claude')
9250
- - Codebase exploration that would consume many tokens (use provider 'claude')`;
9665
+ function buildSpawnAgentTool(parentChatId, availability = {}, getAllowedProviders = () => monolithService.getRelaySubagentProviders(), getProviderAvailability = () => availability) {
9666
+ const providerEnum = z5.enum(VALID_RELAY_SUBAGENT_PROVIDERS);
9251
9667
  return tool(
9252
9668
  "spawn_agent",
9253
9669
  `Spawn a new subagent to perform a task. The subagent runs in its own chat with a fresh context window.
9254
9670
 
9255
9671
  Use this for:
9256
- ${useCases}
9672
+ - Complex code writing tasks (use a currently available code provider with a capable model)
9673
+ - Codebase exploration that would consume many tokens (use provider 'claude')
9257
9674
  - Browser testing, large test runs, or other token-heavy operations
9258
9675
  - Any task you want to delegate to preserve your own context
9259
9676
 
9260
9677
  The tool blocks until the subagent completes and returns its final response.
9261
9678
  You will also receive the chatId so you can send follow-up messages or clean up the chat.`,
9262
9679
  {
9263
- provider: providerEnum.describe(providerDesc),
9264
- prompt: z3.string().describe("The full prompt/instructions for the subagent. Be detailed - it has no context from your conversation."),
9265
- model: z3.string().optional().describe([
9680
+ provider: providerEnum.describe("Which agent to use. Choose from the providers listed as currently available in Relay guidance. Prefer an available code provider for code writing, claude for exploration/analysis, and relay for complex multi-step orchestration."),
9681
+ prompt: z5.string().describe("The full prompt/instructions for the subagent. Be detailed - it has no context from your conversation."),
9682
+ model: z5.string().optional().describe([
9266
9683
  `Model override. Claude: ${AGENT_MODELS.claude.join(", ")} (opus is the default; sonnet is faster).`,
9267
- codexAvailable ? `Codex: ${AGENT_MODELS.codex.join(", ")}.` : null,
9268
- cursorAvailable ? `Cursor: ${AGENT_MODELS.cursor.join(", ")}.` : null,
9269
- opencodeAvailable ? `Opencode: ${AGENT_MODELS.opencode.join(", ")}.` : null
9270
- ].filter(Boolean).join(" ")),
9271
- thinking_level: z3.enum(VALID_THINKING_LEVELS).optional().describe(
9272
- "Controls how much thinking/reasoning the subagent applies. low = light thinking, medium = moderate, high = deep reasoning, xhigh = extended effort, max = maximum effort, ultra = Codex ultra, ultracode = Claude Code dynamic workflows. Defaults: Claude = high, Codex = medium, Cursor = medium, Opencode = medium."
9684
+ `Codex: ${AGENT_MODELS.codex.join(", ")}.`,
9685
+ `Cursor: ${AGENT_MODELS.cursor.join(", ")}.`,
9686
+ `DeepSeek Harness: ${AGENT_MODELS.deepseek.join(", ")}.`,
9687
+ `Opencode: ${AGENT_MODELS.opencode.join(", ")}.`,
9688
+ `Pi: ${AGENT_MODELS.pi.join(", ")}.`
9689
+ ].join(" ")),
9690
+ thinking_level: z5.enum(VALID_THINKING_LEVELS).optional().describe(
9691
+ "Controls how much thinking/reasoning the subagent applies. low = light thinking, medium = moderate, high = deep reasoning, xhigh = extended effort, max = maximum effort, ultra = Codex ultra, ultracode = Claude Code dynamic workflows. Defaults: Claude = high; Codex, Cursor, DeepSeek Harness, Opencode, and Pi = medium."
9273
9692
  ),
9274
- title: z3.string().optional().describe("Optional title for the subagent chat (for identification)."),
9275
- timeout_minutes: z3.number().positive().optional().describe("Timeout in minutes for the subagent to complete (default: 10). Set higher for large tasks to avoid losing work.")
9693
+ title: z5.string().optional().describe("Optional title for the subagent chat (for identification)."),
9694
+ timeout_minutes: z5.number().positive().optional().describe("Timeout in minutes for the subagent to complete (default: 10). Set higher for large tasks to avoid losing work.")
9276
9695
  },
9277
9696
  async (args) => {
9278
9697
  try {
@@ -9290,6 +9709,12 @@ You will also receive the chatId so you can send follow-up messages or clean up
9290
9709
  isError: true
9291
9710
  };
9292
9711
  }
9712
+ if (!isRelaySubagentProviderAllowed(provider, getAvailableRelayProviders(getProviderAvailability()))) {
9713
+ return {
9714
+ content: [{ type: "text", text: `Relay cannot use ${provider} because its credentials are unavailable.` }],
9715
+ isError: true
9716
+ };
9717
+ }
9293
9718
  const createBody = {
9294
9719
  provider,
9295
9720
  title: args.title || `Subagent (${provider})`
@@ -9347,13 +9772,13 @@ var messageAgentTool = tool(
9347
9772
 
9348
9773
  The tool blocks until the subagent completes and returns its response.`,
9349
9774
  {
9350
- chatId: z3.string().describe("The chat ID of the subagent (returned by spawn_agent)."),
9351
- message: z3.string().describe("The follow-up message to send."),
9352
- model: z3.string().optional().describe("Optional model override for this message."),
9353
- thinking_level: z3.enum(VALID_THINKING_LEVELS).optional().describe(
9775
+ chatId: z5.string().describe("The chat ID of the subagent (returned by spawn_agent)."),
9776
+ message: z5.string().describe("The follow-up message to send."),
9777
+ model: z5.string().optional().describe("Optional model override for this message."),
9778
+ thinking_level: z5.enum(VALID_THINKING_LEVELS).optional().describe(
9354
9779
  "Controls how much thinking/reasoning the subagent applies. low = light thinking, medium = moderate, high = deep reasoning, xhigh = extended effort, max = maximum effort, ultra = Codex ultra, ultracode = Claude Code dynamic workflows. Defaults: Claude = high, Codex = medium, Cursor = medium, Opencode = medium."
9355
9780
  ),
9356
- timeout_minutes: z3.number().positive().optional().describe("Timeout in minutes for the subagent to complete (default: 10). Set higher for large tasks to avoid losing work.")
9781
+ timeout_minutes: z5.number().positive().optional().describe("Timeout in minutes for the subagent to complete (default: 10). Set higher for large tasks to avoid losing work.")
9357
9782
  },
9358
9783
  async (args) => {
9359
9784
  try {
@@ -9391,7 +9816,7 @@ var deleteAgentTool = tool(
9391
9816
  "delete_agent",
9392
9817
  `Delete a subagent chat to free resources. Use this after a subagent has completed its work and you no longer need to send it follow-up messages.`,
9393
9818
  {
9394
- chatId: z3.string().describe("The chat ID of the subagent to delete.")
9819
+ chatId: z5.string().describe("The chat ID of the subagent to delete.")
9395
9820
  },
9396
9821
  async (args) => {
9397
9822
  try {
@@ -9409,11 +9834,11 @@ var deleteAgentTool = tool(
9409
9834
  }
9410
9835
  }
9411
9836
  );
9412
- function createRelayMcpServer(parentChatId, availability = {}, getAllowedProviders) {
9837
+ function createRelayMcpServer(parentChatId, availability = {}, getAllowedProviders, getProviderAvailability) {
9413
9838
  return createSdkMcpServer({
9414
9839
  name: "relay-subagent-tools",
9415
9840
  version: "1.0.0",
9416
- tools: [buildSpawnAgentTool(parentChatId, availability, getAllowedProviders), messageAgentTool, deleteAgentTool]
9841
+ tools: [buildSpawnAgentTool(parentChatId, availability, getAllowedProviders, getProviderAvailability), messageAgentTool, deleteAgentTool]
9417
9842
  });
9418
9843
  }
9419
9844
 
@@ -9515,12 +9940,13 @@ function getUsingToolsSection() {
9515
9940
  ];
9516
9941
  return [`# Using your tools`, ...prependBullets(items)].join("\n");
9517
9942
  }
9518
- function getDelegationSection(codexAvailable, cursorAvailable, opencodeAvailable, piAvailable) {
9519
- const providerList = getAvailableRelayProviders({ codexAvailable, cursorAvailable, opencodeAvailable, piAvailable }).join(", ");
9943
+ function getDelegationSection(codexAvailable, cursorAvailable, deepseekAvailable, opencodeAvailable, piAvailable) {
9944
+ const providerList = getAvailableRelayProviders({ codexAvailable, cursorAvailable, deepseekAvailable, opencodeAvailable, piAvailable }).join(", ");
9520
9945
  const spawnDesc = `Create a new subagent with a specific provider (${providerList}), send it a prompt, and wait for its response. Returns the chatId and the agent's final response. You can set a custom timeout via the timeout_minutes parameter (default: 10 minutes).`;
9521
9946
  const claudeModelList = AGENT_MODELS.claude.join(", ");
9522
9947
  const extraAgentLines = [
9523
9948
  codexAvailable ? `Use provider 'codex' for heavy code writing, implementation, and large refactors. Suggested models: ${AGENT_MODELS.codex.join(", ")} (${AGENT_MODELS.codex[0]} is the default).` : null,
9949
+ deepseekAvailable ? `Use provider 'deepseek' for tasks that benefit from the DeepSeek Harness tool loop. Suggested models: ${AGENT_MODELS.deepseek.join(", ")}.` : null,
9524
9950
  opencodeAvailable ? `Use provider 'opencode' for cheaper routine implementation tasks through OpenRouter-backed open source models. Suggested models: ${AGENT_MODELS.opencode.join(", ")}.` : null,
9525
9951
  piAvailable ? `Use provider 'pi' for coding tasks through Pi's OpenRouter-backed coding agent. Suggested models: ${AGENT_MODELS.pi.join(", ")}.` : null,
9526
9952
  cursorAvailable ? `Use provider 'cursor' for fast iteration on code changes. Suggested models: ${AGENT_MODELS.cursor.join(", ")}.` : null
@@ -9645,14 +10071,14 @@ function getEnvironmentSection() {
9645
10071
  ].join("\n");
9646
10072
  }
9647
10073
  function buildRelaySystemPrompt(options) {
9648
- const { customInstructions, codexAvailable, cursorAvailable, opencodeAvailable, piAvailable } = options ?? {};
10074
+ const { customInstructions, codexAvailable, cursorAvailable, deepseekAvailable, opencodeAvailable, piAvailable } = options ?? {};
9649
10075
  const sections = [
9650
10076
  getIntroSection(),
9651
10077
  getSystemSection(),
9652
10078
  getDoingTasksSection(),
9653
10079
  getActionsSection(),
9654
10080
  getUsingToolsSection(),
9655
- getDelegationSection(codexAvailable ?? false, cursorAvailable ?? false, opencodeAvailable ?? false, piAvailable ?? false),
10081
+ getDelegationSection(codexAvailable ?? false, cursorAvailable ?? false, deepseekAvailable ?? false, opencodeAvailable ?? false, piAvailable ?? false),
9656
10082
  getToneAndStyleSection(),
9657
10083
  getOutputEfficiencySection(),
9658
10084
  getEnvironmentSection(),
@@ -9684,14 +10110,22 @@ var RelayManager = class {
9684
10110
  constructor(options) {
9685
10111
  const codexAvailable = options.codexAvailable ?? false;
9686
10112
  const cursorAvailable = options.cursorAvailable ?? false;
10113
+ const deepseekAvailable = options.deepseekAvailable ?? false;
9687
10114
  const opencodeAvailable = options.opencodeAvailable ?? false;
9688
10115
  const piAvailable = options.piAvailable ?? false;
10116
+ const availability = { codexAvailable, cursorAvailable, deepseekAvailable, opencodeAvailable, piAvailable };
10117
+ const getProviderAvailability = options.getProviderAvailability ?? (() => availability);
9689
10118
  this.inner = new ClaudeManager({
9690
10119
  ...options,
9691
- systemPromptOverride: (customInstructions) => buildRelaySystemPrompt({ customInstructions, codexAvailable, cursorAvailable, opencodeAvailable, piAvailable }),
10120
+ systemPromptOverride: (customInstructions) => buildRelaySystemPrompt({ customInstructions, ...getProviderAvailability() }),
9692
10121
  tools: RELAY_TOOLS,
9693
10122
  mcpServers: {
9694
- "relay-subagent-tools": createRelayMcpServer(options.chatId, { codexAvailable, cursorAvailable, opencodeAvailable, piAvailable })
10123
+ "relay-subagent-tools": createRelayMcpServer(
10124
+ options.chatId,
10125
+ availability,
10126
+ void 0,
10127
+ getProviderAvailability
10128
+ )
9695
10129
  },
9696
10130
  envOverrides: {
9697
10131
  CLAUDE_CODE_STREAM_CLOSE_TIMEOUT: "900000"
@@ -9743,17 +10177,17 @@ var RelayManager = class {
9743
10177
  // src/analytics/agent/activity/agent-chat-activity-tracker-service.ts
9744
10178
  import {
9745
10179
  appendFile as appendFile3,
9746
- mkdir as mkdir14,
10180
+ mkdir as mkdir15,
9747
10181
  readFile as readFile12,
9748
10182
  readdir as readdir6,
9749
10183
  rename as rename2,
9750
10184
  unlink as unlink3
9751
10185
  } from "fs/promises";
9752
- import { join as join23 } from "path";
10186
+ import { join as join24 } from "path";
9753
10187
  import { randomUUID as randomUUID5 } from "crypto";
9754
10188
 
9755
10189
  // src/analytics/agent/activity/skill-mcp-call-extractor.ts
9756
- var NON_MCP_SERVERS = /* @__PURE__ */ new Set(["claude", "cursor", "opencode", "pi", "custom", "dynamic"]);
10190
+ var NON_MCP_SERVERS = /* @__PURE__ */ new Set(["claude", "cursor", "deepseek", "opencode", "pi", "custom", "dynamic"]);
9757
10191
  function mcpNameFromToolCall(message) {
9758
10192
  const parsedName = parseMcpToolName(message.tool);
9759
10193
  if (parsedName) return parsedName.server;
@@ -9800,7 +10234,7 @@ var AgentChatActivityBuffer = class {
9800
10234
  options.storageName,
9801
10235
  ...options.legacyStorageNames ?? []
9802
10236
  ];
9803
- this.liveFile = join23(ENGINE_DIR2, `${options.storageName}.jsonl`);
10237
+ this.liveFile = join24(ENGINE_DIR2, `${options.storageName}.jsonl`);
9804
10238
  this.segmentFilePatterns = this.storageNames.map(
9805
10239
  (storageName) => new RegExp(`^${storageName}\\.(\\d+)\\.jsonl$`)
9806
10240
  );
@@ -9817,7 +10251,7 @@ var AgentChatActivityBuffer = class {
9817
10251
  failed: 0
9818
10252
  });
9819
10253
  append(record) {
9820
- const pending = mkdir14(ENGINE_DIR2, { recursive: true }).then(
10254
+ const pending = mkdir15(ENGINE_DIR2, { recursive: true }).then(
9821
10255
  () => appendFile3(this.liveFile, `${JSON.stringify(record)}
9822
10256
  `, "utf-8")
9823
10257
  ).catch((error) => {
@@ -9859,8 +10293,8 @@ var AgentChatActivityBuffer = class {
9859
10293
  await Promise.allSettled([...this.pendingAppends]);
9860
10294
  for (const storageName of this.storageNames) {
9861
10295
  await rename2(
9862
- join23(ENGINE_DIR2, `${storageName}.jsonl`),
9863
- join23(ENGINE_DIR2, `${storageName}.${Date.now()}.jsonl`)
10296
+ join24(ENGINE_DIR2, `${storageName}.jsonl`),
10297
+ join24(ENGINE_DIR2, `${storageName}.${Date.now()}.jsonl`)
9864
10298
  ).catch(() => {
9865
10299
  });
9866
10300
  }
@@ -9871,7 +10305,7 @@ var AgentChatActivityBuffer = class {
9871
10305
  if (!this.segmentFilePatterns.some((pattern) => pattern.test(entry)))
9872
10306
  continue;
9873
10307
  try {
9874
- await this.uploadSegment(join23(ENGINE_DIR2, entry));
10308
+ await this.uploadSegment(join24(ENGINE_DIR2, entry));
9875
10309
  flushed++;
9876
10310
  } catch (error) {
9877
10311
  failed++;
@@ -9903,7 +10337,7 @@ var AgentChatActivityBuffer = class {
9903
10337
  ) && entry.endsWith(UPLOADED_SUFFIX)
9904
10338
  ).sort();
9905
10339
  for (const entry of uploaded.slice(0, -MAX_UPLOADED_SEGMENTS)) {
9906
- await unlink3(join23(ENGINE_DIR2, entry)).catch(() => {
10340
+ await unlink3(join24(ENGINE_DIR2, entry)).catch(() => {
9907
10341
  });
9908
10342
  }
9909
10343
  return { flushed, failed };
@@ -10136,17 +10570,17 @@ var keepAliveService = new KeepAliveService();
10136
10570
  // src/services/canvas-service.ts
10137
10571
  import { readdir as readdir7, readFile as readFile13, stat as stat3 } from "fs/promises";
10138
10572
  import { homedir as homedir14 } from "os";
10139
- import { join as join24 } from "path";
10573
+ import { join as join25 } from "path";
10140
10574
  var GLOBAL_CANVAS_DIRECTORIES = [
10141
- join24(homedir14(), ".claude", "plans"),
10142
- join24(process.env.XDG_DATA_HOME ?? join24(homedir14(), ".local", "share"), "opencode", "plans"),
10143
- join24(homedir14(), ".replicas", "canvas")
10575
+ join25(homedir14(), ".claude", "plans"),
10576
+ join25(process.env.XDG_DATA_HOME ?? join25(homedir14(), ".local", "share"), "opencode", "plans"),
10577
+ join25(homedir14(), ".replicas", "canvas")
10144
10578
  ];
10145
10579
  async function canvasDirectories() {
10146
10580
  const repositories = await gitService.listRepositories().catch(() => []);
10147
10581
  return [
10148
10582
  ...GLOBAL_CANVAS_DIRECTORIES,
10149
- ...repositories.map((repository) => join24(repository.path, ".opencode", "plans"))
10583
+ ...repositories.map((repository) => join25(repository.path, ".opencode", "plans"))
10150
10584
  ];
10151
10585
  }
10152
10586
  var CanvasService = class {
@@ -10170,7 +10604,7 @@ var CanvasService = class {
10170
10604
  for (const entry of entries) {
10171
10605
  if (entry.name.startsWith(".")) continue;
10172
10606
  const filename = current.relativePath ? `${current.relativePath}/${entry.name}` : entry.name;
10173
- const filePath = join24(current.directory, entry.name);
10607
+ const filePath = join25(current.directory, entry.name);
10174
10608
  if (entry.isDirectory()) {
10175
10609
  pending.push({ directory: filePath, relativePath: filename });
10176
10610
  continue;
@@ -10195,7 +10629,7 @@ var CanvasService = class {
10195
10629
  if (!safe) return null;
10196
10630
  const { kind, mimeType } = classifyCanvasFilename(safe);
10197
10631
  for (const directory of await this.directories()) {
10198
- const filePath = join24(directory, safe);
10632
+ const filePath = join25(directory, safe);
10199
10633
  let sizeBytes = 0;
10200
10634
  let updatedAt = "";
10201
10635
  try {
@@ -10367,13 +10801,13 @@ async function reconcileCanvasItems(filenames) {
10367
10801
  import { createReadStream } from "fs";
10368
10802
  import { createHash as createHash2 } from "crypto";
10369
10803
  import { readdir as readdir8, readFile as readFile14, stat as stat4 } from "fs/promises";
10370
- import { basename as basename2, join as join26 } from "path";
10804
+ import { basename as basename2, join as join27 } from "path";
10371
10805
 
10372
10806
  // src/services/chat/chat-senders.ts
10373
- import { join as join25 } from "path";
10374
- var CHAT_SENDERS_DIR = join25(ENGINE_DIR2, "chat-senders");
10807
+ import { join as join26 } from "path";
10808
+ var CHAT_SENDERS_DIR = join26(ENGINE_DIR2, "chat-senders");
10375
10809
  function chatMessageSendersFilePath(chatId) {
10376
- return join25(CHAT_SENDERS_DIR, `${chatId}.jsonl`);
10810
+ return join26(CHAT_SENDERS_DIR, `${chatId}.jsonl`);
10377
10811
  }
10378
10812
  function parseChatMessageSendersJsonl(content) {
10379
10813
  return content.split("\n").flatMap((line) => {
@@ -10389,9 +10823,9 @@ function parseChatMessageSendersJsonl(content) {
10389
10823
 
10390
10824
  // src/services/upload-chat-transcripts.ts
10391
10825
  var HISTORY_DIRS = [
10392
- join26(ENGINE_DIR2, "claude-histories"),
10393
- join26(ENGINE_DIR2, "relay-histories"),
10394
- join26(ENGINE_DIR2, "codex-histories")
10826
+ join27(ENGINE_DIR2, "claude-histories"),
10827
+ join27(ENGINE_DIR2, "relay-histories"),
10828
+ join27(ENGINE_DIR2, "codex-histories")
10395
10829
  ];
10396
10830
  async function flushAllChatTranscripts(chatsById = /* @__PURE__ */ new Map(), capture) {
10397
10831
  let flushed = 0;
@@ -10409,7 +10843,7 @@ async function flushAllChatTranscripts(chatsById = /* @__PURE__ */ new Map(), ca
10409
10843
  if (!entry.endsWith(".jsonl")) continue;
10410
10844
  const chatId = basename2(entry, ".jsonl");
10411
10845
  tasks.push(
10412
- uploadChatTranscript(chatId, join26(dir, entry), chatsById.get(chatId), capture).then((artifact) => {
10846
+ uploadChatTranscript(chatId, join27(dir, entry), chatsById.get(chatId), capture).then((artifact) => {
10413
10847
  flushed++;
10414
10848
  if (artifact && capture) revisions.push(artifact);
10415
10849
  }).catch((err) => {
@@ -10511,7 +10945,7 @@ async function flushRepoState() {
10511
10945
  // src/services/upload-engine-logs.ts
10512
10946
  import { createReadStream as createReadStream2 } from "fs";
10513
10947
  import { readdir as readdir9, stat as stat5 } from "fs/promises";
10514
- import { join as join27 } from "path";
10948
+ import { join as join28 } from "path";
10515
10949
  var MAX_ENGINE_LOG_FLUSH_SESSIONS = 10;
10516
10950
  var MAX_ENGINE_LOG_FLUSH_BYTES = 5 * 1024 * 1024;
10517
10951
  var ENGINE_LOG_FLUSH_TIMEOUT_MS = 2e4;
@@ -10540,7 +10974,7 @@ async function flushAllEngineLogs() {
10540
10974
  const candidates = (await Promise.all(filenames.slice(0, MAX_ENGINE_LOG_FLUSH_SESSIONS).map(async (filename) => {
10541
10975
  try {
10542
10976
  const sessionId = filename.slice(0, -".log".length);
10543
- const filePath = join27(LOG_DIR, filename);
10977
+ const filePath = join28(LOG_DIR, filename);
10544
10978
  const fileStat = await runBeforeDeadline(() => stat5(filePath), deadline);
10545
10979
  if (!fileStat.isFile()) {
10546
10980
  skipped++;
@@ -10636,16 +11070,19 @@ async function uploadEngineLog(input, timeoutMs) {
10636
11070
  }
10637
11071
 
10638
11072
  // src/services/chat/chat-service.ts
10639
- var CODEX_AUTH_PATH2 = join28(homedir15(), ".codex", "auth.json");
11073
+ var CODEX_AUTH_PATH2 = join29(homedir15(), ".codex", "auth.json");
10640
11074
  var CHATS_BACKUP_FILE = `${CHATS_FILE}.bak`;
10641
11075
  function isCodexAvailable() {
10642
- return existsSync7(CODEX_AUTH_PATH2) || Boolean(ENGINE_ENV.OPENAI_API_KEY);
11076
+ return existsSync8(CODEX_AUTH_PATH2) || Boolean(ENGINE_ENV.OPENAI_API_KEY);
10643
11077
  }
10644
11078
  function isOpencodeAvailable() {
10645
- return existsSync7(OPENCODE_AUTH_PATH);
11079
+ return existsSync8(OPENCODE_AUTH_PATH);
11080
+ }
11081
+ function isDeepseekAvailable() {
11082
+ return Boolean(getDeepseekApiKey());
10646
11083
  }
10647
11084
  function isPiAvailable() {
10648
- return existsSync7(PI_AUTH_PATH);
11085
+ return existsSync8(PI_AUTH_PATH);
10649
11086
  }
10650
11087
  function isCursorAvailable() {
10651
11088
  return Boolean(ENGINE_ENV.CURSOR_API_KEY);
@@ -10763,13 +11200,13 @@ var ChatService = class {
10763
11200
  persistInFlight = false;
10764
11201
  persistQueued = false;
10765
11202
  async initialize() {
10766
- await mkdir15(ENGINE_DIR2, { recursive: true });
10767
- await mkdir15(CLAUDE_HISTORY_DIR, { recursive: true });
10768
- await mkdir15(RELAY_HISTORY_DIR, { recursive: true });
10769
- await mkdir15(CODEX_HISTORY_DIR, { recursive: true });
10770
- await mkdir15(CURSOR_HISTORY_DIR, { recursive: true });
10771
- await mkdir15(OPENCODE_HISTORY_DIR, { recursive: true });
10772
- await mkdir15(CHAT_SENDERS_DIR, { recursive: true });
11203
+ await mkdir16(ENGINE_DIR2, { recursive: true });
11204
+ await mkdir16(CLAUDE_HISTORY_DIR, { recursive: true });
11205
+ await mkdir16(RELAY_HISTORY_DIR, { recursive: true });
11206
+ await mkdir16(CODEX_HISTORY_DIR, { recursive: true });
11207
+ await mkdir16(CURSOR_HISTORY_DIR, { recursive: true });
11208
+ await mkdir16(OPENCODE_HISTORY_DIR, { recursive: true });
11209
+ await mkdir16(CHAT_SENDERS_DIR, { recursive: true });
10773
11210
  const persisted = await this.loadChats();
10774
11211
  for (const chat of persisted) {
10775
11212
  const runtime = this.createRuntimeChat(chat);
@@ -11155,7 +11592,7 @@ var ChatService = class {
11155
11592
  return descendants;
11156
11593
  }
11157
11594
  async deleteHistoryFile(persisted) {
11158
- await rm2(join28(HISTORY_DIR_BY_PROVIDER[persisted.provider], `${persisted.id}.jsonl`), { force: true });
11595
+ await rm2(join29(HISTORY_DIR_BY_PROVIDER[persisted.provider], `${persisted.id}.jsonl`), { force: true });
11159
11596
  await rm2(chatMessageSendersFilePath(persisted.id), { force: true });
11160
11597
  }
11161
11598
  async getChatHistory(chatId, page = {}) {
@@ -11260,7 +11697,7 @@ var ChatService = class {
11260
11697
  if (persisted.provider === "claude") {
11261
11698
  provider = new ClaudeManager({
11262
11699
  workingDirectory: this.workingDirectory,
11263
- historyFilePath: join28(CLAUDE_HISTORY_DIR, `${persisted.id}.jsonl`),
11700
+ historyFilePath: join29(CLAUDE_HISTORY_DIR, `${persisted.id}.jsonl`),
11264
11701
  initialSessionId: persisted.providerSessionId,
11265
11702
  onSaveSessionId: saveSession,
11266
11703
  onTurnComplete: onProviderTurnComplete,
@@ -11268,24 +11705,39 @@ var ChatService = class {
11268
11705
  onProcessingChanged
11269
11706
  });
11270
11707
  } else if (persisted.provider === "relay") {
11708
+ const getProviderAvailability = () => ({
11709
+ codexAvailable: isCodexAvailable(),
11710
+ opencodeAvailable: isOpencodeAvailable(),
11711
+ piAvailable: isPiAvailable(),
11712
+ cursorAvailable: isCursorAvailable(),
11713
+ deepseekAvailable: isDeepseekAvailable()
11714
+ });
11271
11715
  provider = new RelayManager({
11272
11716
  workingDirectory: this.workingDirectory,
11273
- historyFilePath: join28(RELAY_HISTORY_DIR, `${persisted.id}.jsonl`),
11717
+ historyFilePath: join29(RELAY_HISTORY_DIR, `${persisted.id}.jsonl`),
11274
11718
  initialSessionId: persisted.providerSessionId,
11275
11719
  onSaveSessionId: saveSession,
11276
11720
  onTurnComplete: onProviderTurnComplete,
11277
11721
  onEvent: onProviderEvent,
11278
11722
  onProcessingChanged,
11279
11723
  chatId: persisted.id,
11280
- codexAvailable: isCodexAvailable(),
11281
- opencodeAvailable: isOpencodeAvailable(),
11282
- piAvailable: isPiAvailable(),
11283
- cursorAvailable: isCursorAvailable()
11724
+ ...getProviderAvailability(),
11725
+ getProviderAvailability
11284
11726
  });
11285
11727
  } else if (persisted.provider === "cursor") {
11286
11728
  provider = new CursorManager({
11287
11729
  workingDirectory: this.workingDirectory,
11288
- historyFilePath: join28(CURSOR_HISTORY_DIR, `${persisted.id}.jsonl`),
11730
+ historyFilePath: join29(CURSOR_HISTORY_DIR, `${persisted.id}.jsonl`),
11731
+ initialSessionId: persisted.providerSessionId,
11732
+ onSaveSessionId: saveSession,
11733
+ onTurnComplete: onProviderTurnComplete,
11734
+ onEvent: onProviderEvent,
11735
+ onProcessingChanged
11736
+ });
11737
+ } else if (persisted.provider === "deepseek") {
11738
+ provider = new DeepseekManager({
11739
+ workingDirectory: this.workingDirectory,
11740
+ historyFilePath: join29(DEEPSEEK_HISTORY_DIR, `${persisted.id}.jsonl`),
11289
11741
  initialSessionId: persisted.providerSessionId,
11290
11742
  onSaveSessionId: saveSession,
11291
11743
  onTurnComplete: onProviderTurnComplete,
@@ -11295,7 +11747,7 @@ var ChatService = class {
11295
11747
  } else if (persisted.provider === "opencode") {
11296
11748
  provider = new OpencodeManager({
11297
11749
  workingDirectory: this.workingDirectory,
11298
- historyFilePath: join28(OPENCODE_HISTORY_DIR, `${persisted.id}.jsonl`),
11750
+ historyFilePath: join29(OPENCODE_HISTORY_DIR, `${persisted.id}.jsonl`),
11299
11751
  initialSessionId: persisted.providerSessionId,
11300
11752
  onSaveSessionId: saveSession,
11301
11753
  onTurnComplete: onProviderTurnComplete,
@@ -11305,7 +11757,7 @@ var ChatService = class {
11305
11757
  } else if (persisted.provider === "pi") {
11306
11758
  provider = new PiManager({
11307
11759
  workingDirectory: this.workingDirectory,
11308
- historyFilePath: join28(PI_HISTORY_DIR, `${persisted.id}.jsonl`),
11760
+ historyFilePath: join29(PI_HISTORY_DIR, `${persisted.id}.jsonl`),
11309
11761
  initialSessionId: persisted.providerSessionId,
11310
11762
  onSaveSessionId: saveSession,
11311
11763
  onTurnComplete: onProviderTurnComplete,
@@ -11315,7 +11767,7 @@ var ChatService = class {
11315
11767
  } else {
11316
11768
  provider = new CodexAspManager({
11317
11769
  workingDirectory: this.workingDirectory,
11318
- historyFilePath: join28(CODEX_HISTORY_DIR, `${persisted.id}.jsonl`),
11770
+ historyFilePath: join29(CODEX_HISTORY_DIR, `${persisted.id}.jsonl`),
11319
11771
  initialSessionId: persisted.providerSessionId,
11320
11772
  onSaveSessionId: saveSession,
11321
11773
  onTurnComplete: onProviderTurnComplete,
@@ -11480,7 +11932,7 @@ var ChatService = class {
11480
11932
  });
11481
11933
  uploadChatTranscript(
11482
11934
  chatId,
11483
- join28(HISTORY_DIR_BY_PROVIDER[chat.persisted.provider], `${chatId}.jsonl`),
11935
+ join29(HISTORY_DIR_BY_PROVIDER[chat.persisted.provider], `${chatId}.jsonl`),
11484
11936
  this.toSummary(chat)
11485
11937
  ).catch((err) => {
11486
11938
  console.error("[ChatService] Failed to upload chat transcript:", { chatId, err });
@@ -11618,7 +12070,7 @@ var ChatService = class {
11618
12070
  // src/services/repo-file-service.ts
11619
12071
  import { execFile } from "child_process";
11620
12072
  import { readFile as readFile16, realpath, stat as stat6 } from "fs/promises";
11621
- import { join as join29, resolve as resolve2, extname as extname2 } from "path";
12073
+ import { join as join30, resolve as resolve2, extname as extname2 } from "path";
11622
12074
  var CACHE_TTL_MS = 3e4;
11623
12075
  var SEARCH_TIMEOUT_MS = 15e3;
11624
12076
  var MAX_CONTENT_BYTES = 256 * 1024;
@@ -11770,7 +12222,7 @@ var RepoFileService = class {
11770
12222
  const repo = repos.find((r) => r.name === repoName);
11771
12223
  if (!repo) return null;
11772
12224
  try {
11773
- const fullPath = await realpath(resolve2(join29(repo.path, filePath)));
12225
+ const fullPath = await realpath(resolve2(join30(repo.path, filePath)));
11774
12226
  const repoRoot = await realpath(repo.path);
11775
12227
  const repoPrefix = repoRoot.endsWith("/") ? repoRoot : repoRoot + "/";
11776
12228
  if (!fullPath.startsWith(repoPrefix) && fullPath !== repoRoot) return null;
@@ -11903,22 +12355,22 @@ var RepoFileService = class {
11903
12355
 
11904
12356
  // src/v1-routes.ts
11905
12357
  import { Hono } from "hono";
11906
- import { z as z4 } from "zod";
12358
+ import { z as z6 } from "zod";
11907
12359
  import { readdir as readdir11, stat as stat7, readFile as readFile19 } from "fs/promises";
11908
- import { join as join32, resolve as resolve3 } from "path";
12360
+ import { join as join33, resolve as resolve3 } from "path";
11909
12361
 
11910
12362
  // src/services/warm-hooks-service.ts
11911
- import { spawn as spawn3 } from "child_process";
12363
+ import { spawn as spawn4 } from "child_process";
11912
12364
  import { readFile as readFile18 } from "fs/promises";
11913
- import { existsSync as existsSync8 } from "fs";
11914
- import { join as join31 } from "path";
12365
+ import { existsSync as existsSync9 } from "fs";
12366
+ import { join as join32 } from "path";
11915
12367
 
11916
12368
  // src/services/warm-hook-logs-service.ts
11917
- import { mkdir as mkdir16, readFile as readFile17, writeFile as writeFile6, readdir as readdir10, appendFile as appendFile5, unlink as unlink4 } from "fs/promises";
12369
+ import { mkdir as mkdir17, readFile as readFile17, writeFile as writeFile6, readdir as readdir10, appendFile as appendFile5, unlink as unlink4 } from "fs/promises";
11918
12370
  import { homedir as homedir16 } from "os";
11919
- import { join as join30 } from "path";
11920
- var LOGS_DIR2 = join30(homedir16(), ".replicas", "warm-hook-logs");
11921
- var CURRENT_RUN_LOG = join30(LOGS_DIR2, "current-run.log");
12371
+ import { join as join31 } from "path";
12372
+ var LOGS_DIR2 = join31(homedir16(), ".replicas", "warm-hook-logs");
12373
+ var CURRENT_RUN_LOG = join31(LOGS_DIR2, "current-run.log");
11922
12374
  var GLOBAL_FILENAME = "global.json";
11923
12375
  function withPreview2(stored) {
11924
12376
  const preview = buildHookOutputPreview(stored.output);
@@ -11926,7 +12378,7 @@ function withPreview2(stored) {
11926
12378
  }
11927
12379
  var WarmHookLogsService = class {
11928
12380
  async ensureDir() {
11929
- await mkdir16(LOGS_DIR2, { recursive: true });
12381
+ await mkdir17(LOGS_DIR2, { recursive: true });
11930
12382
  }
11931
12383
  async saveGlobalHookLog(entry) {
11932
12384
  await this.ensureDir();
@@ -11935,7 +12387,7 @@ var WarmHookLogsService = class {
11935
12387
  hookName: "organization",
11936
12388
  ...entry
11937
12389
  };
11938
- await writeFile6(join30(LOGS_DIR2, GLOBAL_FILENAME), `${JSON.stringify(log, null, 2)}
12390
+ await writeFile6(join31(LOGS_DIR2, GLOBAL_FILENAME), `${JSON.stringify(log, null, 2)}
11939
12391
  `, "utf-8");
11940
12392
  }
11941
12393
  async saveEnvironmentHookLog(entry) {
@@ -11945,7 +12397,7 @@ var WarmHookLogsService = class {
11945
12397
  hookName: "environment",
11946
12398
  ...entry
11947
12399
  };
11948
- await writeFile6(join30(LOGS_DIR2, ENVIRONMENT_HOOK_LOG_FILENAME), `${JSON.stringify(log, null, 2)}
12400
+ await writeFile6(join31(LOGS_DIR2, ENVIRONMENT_HOOK_LOG_FILENAME), `${JSON.stringify(log, null, 2)}
11949
12401
  `, "utf-8");
11950
12402
  }
11951
12403
  async saveRepoHookLog(repoName, entry) {
@@ -11955,7 +12407,7 @@ var WarmHookLogsService = class {
11955
12407
  hookName: repoName,
11956
12408
  ...entry
11957
12409
  };
11958
- await writeFile6(join30(LOGS_DIR2, repoHookLogFilename(repoName)), `${JSON.stringify(log, null, 2)}
12410
+ await writeFile6(join31(LOGS_DIR2, repoHookLogFilename(repoName)), `${JSON.stringify(log, null, 2)}
11959
12411
  `, "utf-8");
11960
12412
  }
11961
12413
  async getAllLogs() {
@@ -11974,7 +12426,7 @@ var WarmHookLogsService = class {
11974
12426
  continue;
11975
12427
  }
11976
12428
  try {
11977
- const raw = await readFile17(join30(LOGS_DIR2, file), "utf-8");
12429
+ const raw = await readFile17(join31(LOGS_DIR2, file), "utf-8");
11978
12430
  const stored = JSON.parse(raw);
11979
12431
  logs.push(withPreview2(stored));
11980
12432
  } catch {
@@ -12012,7 +12464,7 @@ var WarmHookLogsService = class {
12012
12464
  async getFullOutput(hookType, hookName) {
12013
12465
  const filename = hookType === "global" ? GLOBAL_FILENAME : hookType === "environment" ? ENVIRONMENT_HOOK_LOG_FILENAME : repoHookLogFilename(hookName);
12014
12466
  try {
12015
- const raw = await readFile17(join30(LOGS_DIR2, filename), "utf-8");
12467
+ const raw = await readFile17(join31(LOGS_DIR2, filename), "utf-8");
12016
12468
  const stored = JSON.parse(raw);
12017
12469
  if (stored.hookType !== hookType || stored.hookName !== hookName) {
12018
12470
  return null;
@@ -12031,8 +12483,8 @@ var warmHookLogsService = new WarmHookLogsService();
12031
12483
  // src/services/warm-hooks-service.ts
12032
12484
  async function readRepoWarmHook(repoPath) {
12033
12485
  for (const filename of REPLICAS_CONFIG_FILENAMES) {
12034
- const configPath = join31(repoPath, filename);
12035
- if (!existsSync8(configPath)) {
12486
+ const configPath = join32(repoPath, filename);
12487
+ if (!existsSync9(configPath)) {
12036
12488
  continue;
12037
12489
  }
12038
12490
  try {
@@ -12083,7 +12535,7 @@ async function executeHookScriptStreaming(params) {
12083
12535
  params.onChunk(`$ ${params.label}
12084
12536
  `);
12085
12537
  return new Promise((resolve5) => {
12086
- const proc = spawn3("bash", ["-lc", params.content], {
12538
+ const proc = spawn4("bash", ["-lc", params.content], {
12087
12539
  cwd: params.cwd,
12088
12540
  env: process.env,
12089
12541
  stdio: ["pipe", "pipe", "pipe"]
@@ -12293,8 +12745,8 @@ ${combinedScript}` : combinedScript;
12293
12745
 
12294
12746
  // src/services/terminal-service.ts
12295
12747
  import { randomUUID as randomUUID7 } from "crypto";
12296
- import { existsSync as existsSync9 } from "fs";
12297
- import { spawn as spawn4 } from "node-pty";
12748
+ import { existsSync as existsSync10 } from "fs";
12749
+ import { spawn as spawn5 } from "node-pty";
12298
12750
  var MAX_REPLAY_CHARS = 1024 * 1024;
12299
12751
  var MAX_TERMINAL_SESSIONS = 8;
12300
12752
  var MAX_PENDING_INPUT = 64;
@@ -12312,8 +12764,8 @@ var TerminalService = class {
12312
12764
  });
12313
12765
  }
12314
12766
  const id = randomUUID7();
12315
- const shell = process.env.SHELL && existsSync9(process.env.SHELL) ? process.env.SHELL : "/bin/bash";
12316
- const pty = spawn4(shell, ["-l"], {
12767
+ const shell = process.env.SHELL && existsSync10(process.env.SHELL) ? process.env.SHELL : "/bin/bash";
12768
+ const pty = spawn5(shell, ["-l"], {
12317
12769
  name: "xterm-256color",
12318
12770
  cols,
12319
12771
  rows,
@@ -12411,75 +12863,75 @@ var TerminalService = class {
12411
12863
  var terminalService = new TerminalService();
12412
12864
 
12413
12865
  // src/v1-routes.ts
12414
- var imageMediaTypeSchema = z4.enum(IMAGE_MEDIA_TYPES);
12415
- var createPreviewSchema = z4.object({
12416
- port: z4.number().int().min(1).max(65535),
12417
- publicUrl: z4.string().min(1)
12866
+ var imageMediaTypeSchema = z6.enum(IMAGE_MEDIA_TYPES);
12867
+ var createPreviewSchema = z6.object({
12868
+ port: z6.number().int().min(1).max(65535),
12869
+ publicUrl: z6.string().min(1)
12418
12870
  });
12419
- var terminalSizeSchema = z4.object({
12420
- cols: z4.number().int().min(2).max(500),
12421
- rows: z4.number().int().min(1).max(200)
12871
+ var terminalSizeSchema = z6.object({
12872
+ cols: z6.number().int().min(2).max(500),
12873
+ rows: z6.number().int().min(1).max(200)
12422
12874
  });
12423
- var writeTerminalSessionSchema = z4.object({
12424
- data: z4.string().max(64 * 1024),
12425
- generation: z4.number().int().nonnegative(),
12426
- sequence: z4.number().int().nonnegative()
12875
+ var writeTerminalSessionSchema = z6.object({
12876
+ data: z6.string().max(64 * 1024),
12877
+ generation: z6.number().int().nonnegative(),
12878
+ sequence: z6.number().int().nonnegative()
12427
12879
  });
12428
- var mergePullRequestSchema = z4.object({
12429
- prUrl: z4.string().url()
12880
+ var mergePullRequestSchema = z6.object({
12881
+ prUrl: z6.string().url()
12430
12882
  });
12431
- var sendMessageSchema = z4.object({
12432
- messageId: z4.string().min(1).optional(),
12433
- submittedAt: z4.string().datetime().optional(),
12434
- message: z4.string().min(1),
12435
- model: z4.string().optional(),
12436
- customInstructions: z4.string().optional(),
12437
- planMode: z4.boolean().optional(),
12438
- images: z4.array(z4.object({
12439
- type: z4.literal("image"),
12440
- source: z4.union([
12441
- z4.object({
12442
- type: z4.literal("base64"),
12883
+ var sendMessageSchema = z6.object({
12884
+ messageId: z6.string().min(1).optional(),
12885
+ submittedAt: z6.string().datetime().optional(),
12886
+ message: z6.string().min(1),
12887
+ model: z6.string().optional(),
12888
+ customInstructions: z6.string().optional(),
12889
+ planMode: z6.boolean().optional(),
12890
+ images: z6.array(z6.object({
12891
+ type: z6.literal("image"),
12892
+ source: z6.union([
12893
+ z6.object({
12894
+ type: z6.literal("base64"),
12443
12895
  media_type: imageMediaTypeSchema,
12444
- data: z4.string().min(1)
12896
+ data: z6.string().min(1)
12445
12897
  }),
12446
- z4.object({
12447
- type: z4.literal("url"),
12448
- url: z4.string().url()
12898
+ z6.object({
12899
+ type: z6.literal("url"),
12900
+ url: z6.string().url()
12449
12901
  })
12450
12902
  ])
12451
12903
  })).optional(),
12452
- thinkingLevel: z4.enum(VALID_THINKING_LEVELS).optional(),
12453
- goalMode: z4.boolean().optional(),
12454
- fastMode: z4.boolean().optional(),
12455
- enableInteractiveTools: z4.boolean().optional(),
12456
- type: z4.string().min(1).optional(),
12457
- merge: z4.boolean().optional(),
12458
- idempotencyKey: z4.string().min(1).max(128).optional(),
12459
- senderUserId: z4.string().optional(),
12460
- senderEmail: z4.string().optional(),
12461
- senderDisplayName: z4.string().optional(),
12462
- senderAvatarUrl: z4.string().optional(),
12463
- errorNotificationTarget: z4.discriminatedUnion("type", [
12464
- z4.object({ type: z4.literal("slack") }),
12465
- z4.object({ type: z4.literal("linear"), sessionId: z4.string().min(1) }),
12466
- z4.object({
12467
- type: z4.literal("code_host"),
12468
- provider: z4.enum(["github", "gitlab"]),
12469
- resource: z4.enum(["issue", "pull_request"]),
12470
- repositoryId: z4.string().min(1),
12471
- resourceNumber: z4.number().int().positive()
12904
+ thinkingLevel: z6.enum(VALID_THINKING_LEVELS).optional(),
12905
+ goalMode: z6.boolean().optional(),
12906
+ fastMode: z6.boolean().optional(),
12907
+ enableInteractiveTools: z6.boolean().optional(),
12908
+ type: z6.string().min(1).optional(),
12909
+ merge: z6.boolean().optional(),
12910
+ idempotencyKey: z6.string().min(1).max(128).optional(),
12911
+ senderUserId: z6.string().optional(),
12912
+ senderEmail: z6.string().optional(),
12913
+ senderDisplayName: z6.string().optional(),
12914
+ senderAvatarUrl: z6.string().optional(),
12915
+ errorNotificationTarget: z6.discriminatedUnion("type", [
12916
+ z6.object({ type: z6.literal("slack") }),
12917
+ z6.object({ type: z6.literal("linear"), sessionId: z6.string().min(1) }),
12918
+ z6.object({
12919
+ type: z6.literal("code_host"),
12920
+ provider: z6.enum(["github", "gitlab"]),
12921
+ resource: z6.enum(["issue", "pull_request"]),
12922
+ repositoryId: z6.string().min(1),
12923
+ resourceNumber: z6.number().int().positive()
12472
12924
  }),
12473
- z4.object({ type: z4.literal("automation"), executionId: z4.string().min(1) })
12925
+ z6.object({ type: z6.literal("automation"), executionId: z6.string().min(1) })
12474
12926
  ]).optional()
12475
12927
  });
12476
- var respondToolInputSchema = z4.object({
12477
- requestId: z4.string().min(1),
12478
- selectionId: z4.string().min(1)
12928
+ var respondToolInputSchema = z6.object({
12929
+ requestId: z6.string().min(1),
12930
+ selectionId: z6.string().min(1)
12479
12931
  });
12480
- var updateGoalSchema = z4.object({
12481
- objective: z4.string().trim().min(1).max(MAX_CODEX_GOAL_OBJECTIVE_CHARS).optional(),
12482
- status: z4.enum(["active", "paused"]).optional()
12932
+ var updateGoalSchema = z6.object({
12933
+ objective: z6.string().trim().min(1).max(MAX_CODEX_GOAL_OBJECTIVE_CHARS).optional(),
12934
+ status: z6.enum(["active", "paused"]).optional()
12483
12935
  }).refine((body) => body.objective !== void 0 || body.status !== void 0, {
12484
12936
  message: "Goal objective or status required"
12485
12937
  });
@@ -12670,7 +13122,7 @@ function createV1Routes(deps) {
12670
13122
  const result = await deps.chatService.updateGoal(c.req.param("chatId"), body);
12671
13123
  return c.json(result);
12672
13124
  } catch (error) {
12673
- if (error instanceof z4.ZodError) {
13125
+ if (error instanceof z6.ZodError) {
12674
13126
  return c.json(jsonError(error.issues[0]?.message || "Invalid goal update"), 400);
12675
13127
  }
12676
13128
  if (error instanceof ChatNotFoundError) {
@@ -13295,7 +13747,7 @@ data: ${JSON.stringify("Terminal session not found")}
13295
13747
  const logFiles = files.filter((f) => f.endsWith(".log"));
13296
13748
  const sessions = await Promise.all(
13297
13749
  logFiles.map(async (filename) => {
13298
- const filePath = join32(LOG_DIR, filename);
13750
+ const filePath = join33(LOG_DIR, filename);
13299
13751
  const fileStat = await stat7(filePath);
13300
13752
  const sessionId = filename.replace(/\.log$/, "");
13301
13753
  return {
@@ -13401,17 +13853,17 @@ var HeartbeatService = class _HeartbeatService {
13401
13853
  var heartbeatService = new HeartbeatService();
13402
13854
 
13403
13855
  // src/services/workspace-sdk-service.ts
13404
- import { cp, mkdir as mkdir17 } from "fs/promises";
13405
- import { dirname as dirname8, resolve as resolve4 } from "path";
13406
- import { fileURLToPath as fileURLToPath2 } from "url";
13856
+ import { cp, mkdir as mkdir18 } from "fs/promises";
13857
+ import { dirname as dirname9, resolve as resolve4 } from "path";
13858
+ import { fileURLToPath as fileURLToPath3 } from "url";
13407
13859
  async function installWorkspaceSdk() {
13408
- const source = resolve4(dirname8(fileURLToPath2(import.meta.url)), "../../workspace-sdk");
13860
+ const source = resolve4(dirname9(fileURLToPath3(import.meta.url)), "../../workspace-sdk");
13409
13861
  const targets = [
13410
13862
  resolve4(ENGINE_ENV.HOME_DIR, "node_modules/@replicas/sdk"),
13411
13863
  "/tmp/node_modules/@replicas/sdk"
13412
13864
  ];
13413
13865
  await Promise.all(targets.map(async (target) => {
13414
- await mkdir17(target, { recursive: true });
13866
+ await mkdir18(target, { recursive: true });
13415
13867
  await cp(source, target, { recursive: true, force: true });
13416
13868
  }));
13417
13869
  }
@@ -13439,7 +13891,7 @@ async function timeStartupStep(name, fn) {
13439
13891
  async function waitForInitializationGate() {
13440
13892
  if (!ENGINE_ENV.REPLICAS_ENGINE_DEFER_INITIALIZATION) return;
13441
13893
  const deadline = Date.now() + ENGINE_INIT_GATE_TIMEOUT_MS;
13442
- while (!existsSync10(SANDBOX_PATHS.ENGINE_INIT_GATE)) {
13894
+ while (!existsSync11(SANDBOX_PATHS.ENGINE_INIT_GATE)) {
13443
13895
  if (Date.now() >= deadline) {
13444
13896
  throw new Error("Timed out waiting for deferred engine initialization gate");
13445
13897
  }