replicas-engine 0.1.664 → 0.1.666

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
@@ -4,6 +4,7 @@ import {
4
4
  AGENT,
5
5
  AGENT_MESSAGE_DELTA_METHOD,
6
6
  AGENT_MODELS,
7
+ AI_GATEWAY_BASE_URL,
7
8
  ASTER_BASE_URL,
8
9
  ASTER_MODELS,
9
10
  ASTER_MODEL_LABELS,
@@ -41,6 +42,7 @@ import {
41
42
  DEFAULT_DEEPSEEK_MODEL,
42
43
  DEFAULT_FX_MODEL,
43
44
  DEFAULT_HOOK_OUTPUT_PREVIEW_CHARS,
45
+ DEFAULT_KIMI_MODEL,
44
46
  DEFAULT_OPENCODE_MODEL,
45
47
  DEFAULT_PI_MODEL,
46
48
  DEFAULT_START_HOOK_TIMEOUT_MS,
@@ -120,6 +122,7 @@ import {
120
122
  extractCommandProtectionCommandText,
121
123
  extractErrorText,
122
124
  extractToolResultText,
125
+ fetchAiGatewayModels,
123
126
  findGitCommitSignals,
124
127
  findPrMergeSignals,
125
128
  getChatHistoryPageSenders,
@@ -186,13 +189,13 @@ import {
186
189
  serializeCanvasContentResponse,
187
190
  shellQuotePosix,
188
191
  stripAgentDiagnosticErrors
189
- } from "./chunk-L4RE37PJ.js";
192
+ } from "./chunk-3EPGH6IZ.js";
190
193
 
191
194
  // src/index.ts
192
195
  import { serve } from "@hono/node-server";
193
196
  import { Hono as Hono2 } from "hono";
194
197
  import { existsSync as existsSync11 } from "fs";
195
- import { randomUUID as randomUUID9 } from "crypto";
198
+ import { randomUUID as randomUUID10 } from "crypto";
196
199
  import { connect } from "net";
197
200
 
198
201
  // src/managers/github-token-manager.ts
@@ -1976,6 +1979,7 @@ function createDefaultDetails() {
1976
1979
  cursorAuthMethod: "none",
1977
1980
  deepseekAuthMethod: "none",
1978
1981
  fxAuthMethod: "none",
1982
+ kimiAuthMethod: "none",
1979
1983
  opencodeAuthMethod: "none",
1980
1984
  piAuthMethod: "none",
1981
1985
  lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -2017,6 +2021,7 @@ var EnvironmentDetailsService = class {
2017
2021
  details.cursorAuthMethod = detectCursorAuthMethod();
2018
2022
  details.deepseekAuthMethod = detectDeepseekAuthMethod();
2019
2023
  details.fxAuthMethod = detectFxAuthMethod();
2024
+ details.kimiAuthMethod = detectFxAuthMethod();
2020
2025
  details.opencodeAuthMethod = detectOpencodeAuthMethod();
2021
2026
  details.piAuthMethod = detectPiAuthMethod();
2022
2027
  details.credentialFallbacks = listCredentialFallbacks();
@@ -2772,10 +2777,10 @@ async function registerDesktopPreview() {
2772
2777
 
2773
2778
  // src/services/chat/chat-service.ts
2774
2779
  import { existsSync as existsSync8 } from "fs";
2775
- import { appendFile as appendFile4, copyFile, mkdir as mkdir17, readFile as readFile16, rename as rename3, rm as rm2 } from "fs/promises";
2780
+ import { appendFile as appendFile4, copyFile, mkdir as mkdir17, readFile as readFile17, rename as rename3, rm as rm2 } from "fs/promises";
2776
2781
  import { homedir as homedir15 } from "os";
2777
- import { join as join30 } from "path";
2778
- import { randomUUID as randomUUID7 } from "crypto";
2782
+ import { join as join29 } from "path";
2783
+ import { randomUUID as randomUUID8 } from "crypto";
2779
2784
 
2780
2785
  // src/managers/claude-manager.ts
2781
2786
  import {
@@ -6356,6 +6361,7 @@ var OPENCODE_HISTORY_DIR = join18(ENGINE_DIR2, "opencode-histories");
6356
6361
  var PI_HISTORY_DIR = join18(ENGINE_DIR2, "pi-histories");
6357
6362
  var DEEPSEEK_HISTORY_DIR = join18(ENGINE_DIR2, "deepseek-histories");
6358
6363
  var FX_HISTORY_DIR = join18(ENGINE_DIR2, "fx-histories");
6364
+ var KIMI_HISTORY_DIR = join18(ENGINE_DIR2, "kimi-histories");
6359
6365
  var HISTORY_DIR_BY_PROVIDER = {
6360
6366
  claude: CLAUDE_HISTORY_DIR,
6361
6367
  relay: RELAY_HISTORY_DIR,
@@ -6363,6 +6369,7 @@ var HISTORY_DIR_BY_PROVIDER = {
6363
6369
  cursor: CURSOR_HISTORY_DIR,
6364
6370
  deepseek: DEEPSEEK_HISTORY_DIR,
6365
6371
  fx: FX_HISTORY_DIR,
6372
+ kimi: KIMI_HISTORY_DIR,
6366
6373
  opencode: OPENCODE_HISTORY_DIR,
6367
6374
  pi: PI_HISTORY_DIR
6368
6375
  };
@@ -6482,15 +6489,9 @@ var CodexAspManager = class extends CodingAgentManager {
6482
6489
  this.initializeManager(this.processMessageInternal.bind(this));
6483
6490
  }
6484
6491
  async initialize() {
6485
- if (this.initialSessionId) {
6486
- this.currentThreadId = this.initialSessionId;
6487
- return;
6488
- }
6489
6492
  const replayed = await this.historyFile?.load();
6490
- if (replayed) {
6491
- if (replayed.transcript) {
6492
- this.mergeTranscriptSnapshot(replayed.transcript);
6493
- }
6493
+ if (replayed?.transcript && (!this.initialSessionId || replayed.transcript.threadId === this.initialSessionId)) {
6494
+ this.mergeTranscriptSnapshot(replayed.transcript);
6494
6495
  }
6495
6496
  this.currentThreadId = this.initialSessionId ?? replayed?.transcript?.threadId ?? null;
6496
6497
  }
@@ -6647,16 +6648,22 @@ var CodexAspManager = class extends CodingAgentManager {
6647
6648
  itemsView: "full"
6648
6649
  }
6649
6650
  );
6650
- const transcript = turnsToAspTranscript(
6651
+ const nativeTranscript = turnsToAspTranscript(
6651
6652
  this.currentThreadId,
6652
6653
  (/* @__PURE__ */ new Date()).toISOString(),
6653
6654
  response.data
6654
6655
  );
6655
- if (cursor === void 0) {
6656
- this.codexAspTranscript = transcript;
6657
- this.syncTranscriptSequence(transcript);
6658
- }
6659
- return { transcript, before: response.nextCursor };
6656
+ const mergedTranscript = mergeCodexAspTranscripts(this.codexAspTranscript, nativeTranscript) ?? nativeTranscript;
6657
+ this.codexAspTranscript = mergedTranscript;
6658
+ this.syncTranscriptSequence(mergedTranscript);
6659
+ const turnIds = new Set(response.data.map((turn) => turn.id));
6660
+ return {
6661
+ transcript: {
6662
+ ...mergedTranscript,
6663
+ turns: mergedTranscript.turns.filter((turn) => turnIds.has(turn.id))
6664
+ },
6665
+ before: response.nextCursor
6666
+ };
6660
6667
  }
6661
6668
  getGoal() {
6662
6669
  return this.currentGoal;
@@ -6723,6 +6730,7 @@ var CodexAspManager = class extends CodingAgentManager {
6723
6730
  this.recordCodexHistoryEvent("event_msg", {
6724
6731
  type: "user_message",
6725
6732
  message: request.message,
6733
+ ...request.messageId ? { [USER_MESSAGE_ID_PAYLOAD_KEY]: request.messageId } : {},
6726
6734
  ...images ? { images } : {},
6727
6735
  ...extraPayload
6728
6736
  });
@@ -8551,9 +8559,14 @@ ${request.message}` : request.message;
8551
8559
  };
8552
8560
 
8553
8561
  // src/managers/fx-manager.ts
8562
+ import { readFile as readFile12 } from "fs/promises";
8563
+ import { spawn as spawn5 } from "child_process";
8564
+
8565
+ // src/managers/acp-manager.ts
8554
8566
  import { spawn as spawn4 } from "child_process";
8555
- import { mkdir as mkdir13, readFile as readFile11 } from "fs/promises";
8556
- import { dirname as dirname7, join as join22 } from "path";
8567
+ import { randomUUID as randomUUID6 } from "crypto";
8568
+ import { mkdir as mkdir13, readFile as readFile11, writeFile as writeFile6 } from "fs/promises";
8569
+ import { dirname as dirname7 } from "path";
8557
8570
  import { Writable } from "stream";
8558
8571
  import {
8559
8572
  PROTOCOL_VERSION,
@@ -8561,21 +8574,12 @@ import {
8561
8574
  methods,
8562
8575
  ndJsonStream
8563
8576
  } from "@agentclientprotocol/sdk";
8564
- async function ensureResolvableDnsConfig() {
8565
- const contents = await readFile11("/etc/resolv.conf");
8566
- if (contents.length === 0 || contents.at(-1) === 10) return;
8567
- await new Promise((resolve5, reject) => {
8568
- const child = spawn4("sudo", ["tee", "-a", "/etc/resolv.conf"], { stdio: ["pipe", "ignore", "pipe"] });
8569
- let stderr = "";
8570
- child.stderr.on("data", (chunk) => {
8571
- stderr += chunk;
8572
- });
8573
- child.on("error", reject);
8574
- child.on("exit", (code) => code === 0 ? resolve5() : reject(new Error(`Failed to normalize /etc/resolv.conf: ${stderr.trim()}`)));
8575
- child.stdin.end("\n");
8576
- });
8577
+ function selectOptions(option) {
8578
+ if (option.type !== "select") return [];
8579
+ return option.options.flatMap((entry) => "group" in entry ? entry.options : [entry]);
8577
8580
  }
8578
- var FxManager = class extends CodingAgentManager {
8581
+ var AcpManager = class extends CodingAgentManager {
8582
+ acp;
8579
8583
  historyFile;
8580
8584
  historyFilePath;
8581
8585
  process = null;
@@ -8583,42 +8587,90 @@ var FxManager = class extends CodingAgentManager {
8583
8587
  context = null;
8584
8588
  sessionId = null;
8585
8589
  activeModel = null;
8590
+ activeThinkingLevel;
8586
8591
  planMode = false;
8592
+ replaying = false;
8587
8593
  slashCommands = [];
8594
+ configOptions = [];
8595
+ modes = null;
8596
+ capabilities = {};
8597
+ interactiveTools = false;
8598
+ pendingPermissions = /* @__PURE__ */ new Map();
8599
+ terminals = /* @__PURE__ */ new Map();
8588
8600
  constructor(options) {
8589
- super(options);
8590
- this.historyFilePath = options.historyFilePath ?? join22(ENGINE_ENV.HOME_DIR, ".replicas", "fx", "history.jsonl");
8601
+ super({ ...options, provider: options.acp.provider });
8602
+ this.acp = options.acp;
8603
+ this.historyFilePath = options.historyFilePath;
8591
8604
  this.historyFile = new CodexHistoryFile(this.historyFilePath);
8592
8605
  this.initializeManager(this.processMessageInternal.bind(this));
8593
8606
  }
8594
8607
  async initialize() {
8595
8608
  await mkdir13(dirname7(this.historyFilePath), { recursive: true });
8596
- await ensureResolvableDnsConfig();
8609
+ await this.acp.prepare?.();
8597
8610
  }
8598
8611
  getHistorySink() {
8599
8612
  return this.historyFile;
8600
8613
  }
8601
- async start(model) {
8602
- if (this.context && this.activeModel === model) return;
8614
+ async start(model, thinkingLevel) {
8615
+ if (this.context && this.activeModel === model && (!this.acp.restartOnThinkingChange || this.activeThinkingLevel === thinkingLevel)) return;
8603
8616
  this.dispose();
8604
- const child = spawn4("fx", ["acp", "--model", model], {
8617
+ const agentEnv = await this.acp.env?.(model, thinkingLevel);
8618
+ const child = spawn4(this.acp.command, this.acp.args(model), {
8605
8619
  cwd: this.workingDirectory,
8606
- env: { ...process.env, FX_AUTO_UPGRADE: "0", NO_COLOR: "1" },
8620
+ env: { ...process.env, NO_COLOR: "1", ...agentEnv },
8607
8621
  stdio: ["pipe", "pipe", "pipe"]
8608
8622
  });
8609
8623
  this.process = child;
8610
- child.stderr.on("data", (chunk) => console.error("[FxManager]", chunk.toString().trimEnd()));
8624
+ child.stderr.on("data", (chunk) => console.error(`[${this.acp.provider} ACP]`, chunk.toString().trimEnd()));
8611
8625
  child.on("exit", (code, signal) => {
8612
8626
  if (this.process !== child) return;
8613
8627
  this.process = null;
8614
8628
  this.context = null;
8615
8629
  this.connection = null;
8616
8630
  if (code !== 0 && signal !== "SIGTERM") {
8617
- this.recordHistoryEvent("fx-error", { message: `fx ACP exited with code ${code ?? signal}` }, this.historyFile);
8631
+ this.recordHistoryEvent("acp-error", {
8632
+ provider: this.acp.provider,
8633
+ message: `${this.acp.command} ACP exited with code ${code ?? signal}`
8634
+ }, this.historyFile);
8618
8635
  }
8619
8636
  });
8620
- const app2 = client({ name: "Replicas" }).onNotification(methods.client.session.update, ({ params }) => this.recordUpdate(params)).onRequest(methods.client.session.requestPermission, ({ params }) => {
8621
- const option = params.options.find((candidate) => candidate.kind === (this.planMode ? "reject_once" : "allow_always")) ?? params.options.find((candidate) => candidate.kind === (this.planMode ? "reject_always" : "allow_once"));
8637
+ const app2 = client({ name: "Replicas" }).onNotification(methods.client.session.update, ({ params }) => this.recordUpdate(params)).onRequest(methods.client.fs.readTextFile, async ({ params }) => {
8638
+ this.assertSession(params.sessionId);
8639
+ const content = await readFile11(params.path, "utf8");
8640
+ if (params.line === void 0 && params.limit === void 0) return { content };
8641
+ const start = Math.max(0, (params.line ?? 1) - 1);
8642
+ const end = params.limit === null || params.limit === void 0 ? void 0 : start + params.limit;
8643
+ return { content: content.split("\n").slice(start, end).join("\n") };
8644
+ }).onRequest(methods.client.fs.writeTextFile, async ({ params }) => {
8645
+ this.assertSession(params.sessionId);
8646
+ if (this.planMode) throw new Error("ACP file writes are disabled in plan mode.");
8647
+ await writeFile6(params.path, params.content, "utf8");
8648
+ return {};
8649
+ }).onRequest(methods.client.terminal.create, ({ params }) => this.createTerminal(params)).onRequest(methods.client.terminal.output, ({ params }) => {
8650
+ this.assertSession(params.sessionId);
8651
+ const terminal = this.getTerminal(params.terminalId);
8652
+ return {
8653
+ output: terminal.output.toString("utf8"),
8654
+ truncated: terminal.truncated,
8655
+ exitStatus: terminal.exitStatus
8656
+ };
8657
+ }).onRequest(methods.client.terminal.waitForExit, async ({ params }) => {
8658
+ this.assertSession(params.sessionId);
8659
+ return this.getTerminal(params.terminalId).exited;
8660
+ }).onRequest(methods.client.terminal.kill, ({ params }) => {
8661
+ this.assertSession(params.sessionId);
8662
+ this.getTerminal(params.terminalId).process.kill("SIGTERM");
8663
+ return {};
8664
+ }).onRequest(methods.client.terminal.release, ({ params }) => {
8665
+ this.assertSession(params.sessionId);
8666
+ const terminal = this.getTerminal(params.terminalId);
8667
+ if (terminal.exitStatus === null) terminal.process.kill("SIGTERM");
8668
+ this.terminals.delete(params.terminalId);
8669
+ return {};
8670
+ }).onRequest(methods.client.session.requestPermission, ({ params }) => {
8671
+ if (params.toolCall.title === "AskUserQuestion") return this.requestUserInput(params);
8672
+ const preferred = this.planMode ? ["reject_once", "reject_always"] : ["allow_always", "allow_once"];
8673
+ const option = preferred.flatMap((kind) => params.options.filter((candidate) => candidate.kind === kind))[0];
8622
8674
  return option ? { outcome: { outcome: "selected", optionId: option.optionId } } : { outcome: { outcome: "cancelled" } };
8623
8675
  });
8624
8676
  const stdout = new ReadableStream({
@@ -8631,26 +8683,34 @@ var FxManager = class extends CodingAgentManager {
8631
8683
  child.stdout.destroy();
8632
8684
  }
8633
8685
  });
8634
- const stream = ndJsonStream(
8635
- Writable.toWeb(child.stdin),
8636
- stdout
8637
- );
8638
- this.connection = app2.connect(stream);
8686
+ this.connection = app2.connect(ndJsonStream(Writable.toWeb(child.stdin), stdout));
8639
8687
  this.context = this.connection.agent;
8640
- await this.context.request(methods.agent.initialize, {
8688
+ const initialized = await this.context.request(methods.agent.initialize, {
8641
8689
  protocolVersion: PROTOCOL_VERSION,
8642
- clientCapabilities: { plan: {} },
8690
+ clientCapabilities: {
8691
+ fs: { readTextFile: true, writeTextFile: true },
8692
+ terminal: true,
8693
+ plan: {}
8694
+ },
8643
8695
  clientInfo: { name: "Replicas", version: "1" }
8644
8696
  });
8697
+ this.capabilities = initialized.agentCapabilities ?? {};
8645
8698
  const additionalDirectories = await getAgentAdditionalDirectories();
8646
8699
  const sessionId = this.sessionId ?? this.initialSessionId;
8647
- if (sessionId) {
8648
- await this.context.request(methods.agent.session.load, {
8649
- sessionId,
8650
- cwd: this.workingDirectory,
8651
- additionalDirectories,
8652
- mcpServers: []
8653
- });
8700
+ if (sessionId && this.capabilities.loadSession) {
8701
+ this.replaying = true;
8702
+ try {
8703
+ const session = await this.context.request(methods.agent.session.load, {
8704
+ sessionId,
8705
+ cwd: this.workingDirectory,
8706
+ additionalDirectories,
8707
+ mcpServers: []
8708
+ });
8709
+ this.configOptions = session.configOptions ?? [];
8710
+ this.modes = session.modes ?? null;
8711
+ } finally {
8712
+ this.replaying = false;
8713
+ }
8654
8714
  this.sessionId = sessionId;
8655
8715
  } else {
8656
8716
  const session = await this.context.request(methods.agent.session.new, {
@@ -8659,21 +8719,84 @@ var FxManager = class extends CodingAgentManager {
8659
8719
  mcpServers: []
8660
8720
  });
8661
8721
  this.sessionId = session.sessionId;
8722
+ this.configOptions = session.configOptions ?? [];
8723
+ this.modes = session.modes ?? null;
8662
8724
  await this.onSaveSessionId(session.sessionId);
8663
8725
  }
8664
8726
  this.activeModel = model;
8727
+ this.activeThinkingLevel = thinkingLevel;
8728
+ }
8729
+ assertSession(sessionId) {
8730
+ if (sessionId !== this.sessionId) throw new Error(`Unknown ACP session: ${sessionId}`);
8731
+ }
8732
+ getTerminal(terminalId) {
8733
+ const terminal = this.terminals.get(terminalId);
8734
+ if (!terminal) throw new Error(`Unknown ACP terminal: ${terminalId}`);
8735
+ return terminal;
8736
+ }
8737
+ createTerminal(params) {
8738
+ this.assertSession(params.sessionId);
8739
+ if (this.planMode) throw new Error("ACP terminals are disabled in plan mode.");
8740
+ const terminalId = randomUUID6();
8741
+ const outputByteLimit = Math.max(1, Math.min(params.outputByteLimit ?? 1048576, 10485760));
8742
+ const child = spawn4(params.command, params.args ?? [], {
8743
+ cwd: params.cwd ?? this.workingDirectory,
8744
+ env: {
8745
+ ...process.env,
8746
+ ...Object.fromEntries(params.env?.map(({ name, value }) => [name, value]) ?? [])
8747
+ },
8748
+ stdio: ["pipe", "pipe", "pipe"]
8749
+ });
8750
+ let resolveExit;
8751
+ const terminal = {
8752
+ process: child,
8753
+ output: Buffer.alloc(0),
8754
+ outputByteLimit,
8755
+ truncated: false,
8756
+ exitStatus: null,
8757
+ exited: new Promise((resolve5) => {
8758
+ resolveExit = resolve5;
8759
+ })
8760
+ };
8761
+ const append = (chunk) => {
8762
+ terminal.output = Buffer.concat([terminal.output, chunk]);
8763
+ if (terminal.output.length <= outputByteLimit) return;
8764
+ let start = terminal.output.length - outputByteLimit;
8765
+ while (start < terminal.output.length && (terminal.output[start] & 192) === 128) start += 1;
8766
+ terminal.output = terminal.output.subarray(start);
8767
+ terminal.truncated = true;
8768
+ };
8769
+ child.stdout.on("data", append);
8770
+ child.stderr.on("data", append);
8771
+ child.on("error", () => {
8772
+ const status = { exitCode: null, signal: null };
8773
+ terminal.exitStatus = status;
8774
+ resolveExit?.(status);
8775
+ });
8776
+ child.on("exit", (exitCode, signal) => {
8777
+ const status = { exitCode, signal };
8778
+ terminal.exitStatus = status;
8779
+ resolveExit?.(status);
8780
+ });
8781
+ this.terminals.set(terminalId, terminal);
8782
+ return { terminalId };
8665
8783
  }
8666
8784
  recordUpdate(notification) {
8667
- if (notification.sessionId !== this.sessionId) return;
8668
- if (notification.update.sessionUpdate === "available_commands_update") {
8669
- this.slashCommands = mergeSlashCommands(notification.update.availableCommands.flatMap((command) => {
8670
- const parsed = createProviderSlashCommand("fx", command.name, command.description, command.input?.hint);
8785
+ if (notification.sessionId !== this.sessionId && this.sessionId !== null) return;
8786
+ const update = notification.update;
8787
+ if (update.sessionUpdate === "available_commands_update") {
8788
+ this.slashCommands = mergeSlashCommands(update.availableCommands.flatMap((command) => {
8789
+ const parsed = createProviderSlashCommand(this.acp.provider, command.name, command.description, command.input?.hint);
8671
8790
  return parsed ? [parsed] : [];
8672
8791
  }));
8673
- } else if (notification.update.sessionUpdate === "usage_update") {
8674
- const { used, size } = notification.update;
8792
+ } else if (update.sessionUpdate === "config_option_update") {
8793
+ this.configOptions = update.configOptions;
8794
+ } else if (update.sessionUpdate === "current_mode_update" && this.modes) {
8795
+ this.modes = { ...this.modes, currentModeId: update.currentModeId };
8796
+ } else if (update.sessionUpdate === "usage_update" && !this.replaying) {
8797
+ const { used, size } = update;
8675
8798
  this.historyFile.append(this.emitContextUsage({
8676
- provider: "fx",
8799
+ provider: this.acp.provider,
8677
8800
  source: "provider_usage",
8678
8801
  model: this.activeModel,
8679
8802
  totalTokens: used,
@@ -8683,37 +8806,155 @@ var FxManager = class extends CodingAgentManager {
8683
8806
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
8684
8807
  }));
8685
8808
  }
8686
- this.recordHistoryEvent("fx-session-update", { update: notification.update }, this.historyFile);
8809
+ if (!this.replaying) {
8810
+ this.recordHistoryEvent("acp-session-update", {
8811
+ provider: this.acp.provider,
8812
+ update
8813
+ }, this.historyFile);
8814
+ }
8815
+ }
8816
+ async setConfig(category, value) {
8817
+ const context = this.context;
8818
+ const sessionId = this.sessionId;
8819
+ const option = this.configOptions.find((candidate) => candidate.category === category || candidate.id === category);
8820
+ if (!context || !sessionId || !option) return false;
8821
+ if (option.type === "select" && !selectOptions(option).some((candidate) => candidate.value === value)) return false;
8822
+ if (option.currentValue === value) return true;
8823
+ const response = typeof value === "boolean" ? await context.request(methods.agent.session.setConfigOption, {
8824
+ sessionId,
8825
+ configId: option.id,
8826
+ value,
8827
+ type: "boolean"
8828
+ }) : await context.request(methods.agent.session.setConfigOption, {
8829
+ sessionId,
8830
+ configId: option.id,
8831
+ value
8832
+ });
8833
+ this.configOptions = response.configOptions;
8834
+ return true;
8835
+ }
8836
+ async setMode(planMode) {
8837
+ const context = this.context;
8838
+ const sessionId = this.sessionId;
8839
+ if (!context || !sessionId) return;
8840
+ const candidates = planMode ? ["plan", "ask", "architect"] : ["code", "default", "auto", "yolo"];
8841
+ const mode = this.modes?.availableModes.find((candidate) => candidates.includes(candidate.id.toLowerCase())) ?? this.modes?.availableModes.find((candidate) => candidates.some((name) => candidate.name.toLowerCase().includes(name)));
8842
+ if (mode) {
8843
+ await context.request(methods.agent.session.setMode, { sessionId, modeId: mode.id });
8844
+ if (this.modes) this.modes = { ...this.modes, currentModeId: mode.id };
8845
+ return;
8846
+ }
8847
+ const option = this.configOptions.find((candidate) => candidate.category === "mode" || candidate.id === "mode");
8848
+ if (!option || option.type !== "select") return;
8849
+ const selected = selectOptions(option).find((candidate) => candidates.includes(candidate.value.toLowerCase())) ?? selectOptions(option).find((candidate) => candidates.some((name) => candidate.name.toLowerCase().includes(name)));
8850
+ if (selected) await this.setConfig("mode", selected.value);
8851
+ }
8852
+ async setThinking(level) {
8853
+ if (!level) return;
8854
+ const option = this.configOptions.find((candidate) => candidate.category === "thought_level" || candidate.id === "thinking");
8855
+ if (!option || option.type !== "select") return;
8856
+ const values = selectOptions(option).map((candidate) => candidate.value);
8857
+ const aliases = {
8858
+ low: ["low", "on"],
8859
+ medium: ["medium", "on"],
8860
+ high: ["high", "on"],
8861
+ xhigh: ["xhigh", "high", "on"],
8862
+ max: ["max", "xhigh", "high", "on"],
8863
+ ultra: ["ultra", "max", "xhigh", "high", "on"],
8864
+ ultracode: ["max", "xhigh", "high", "on"]
8865
+ };
8866
+ const value = aliases[level].find((candidate) => values.includes(candidate));
8867
+ if (value) await this.setConfig("thought_level", value);
8868
+ }
8869
+ async requestUserInput(params) {
8870
+ if (!this.interactiveTools) return { outcome: { outcome: "cancelled" } };
8871
+ const requestId = randomUUID6();
8872
+ const toolCallId = params.toolCall.toolCallId;
8873
+ return new Promise((resolve5) => {
8874
+ this.pendingPermissions.set(requestId, { toolCallId, options: params.options, resolve: resolve5 });
8875
+ this.recordHistoryEvent("acp-session-update", {
8876
+ provider: this.acp.provider,
8877
+ update: { ...params.toolCall, sessionUpdate: "tool_call", status: "pending" }
8878
+ }, this.historyFile);
8879
+ this.recordHistoryEvent("replicas-tool-input-request", {
8880
+ requestId,
8881
+ toolUseId: toolCallId,
8882
+ toolName: params.toolCall.title,
8883
+ options: params.options.map((option) => ({
8884
+ id: option.optionId,
8885
+ label: option.name,
8886
+ variant: option.kind.startsWith("reject") ? "secondary" : "primary"
8887
+ })),
8888
+ parent_tool_use_id: null
8889
+ }, this.historyFile);
8890
+ });
8891
+ }
8892
+ async respondToToolInput(requestId, selectionId) {
8893
+ const pending = this.pendingPermissions.get(requestId);
8894
+ if (!pending) return false;
8895
+ const option = pending.options.find((candidate) => candidate.optionId === selectionId);
8896
+ if (!option) return false;
8897
+ this.pendingPermissions.delete(requestId);
8898
+ pending.resolve({ outcome: { outcome: "selected", optionId: selectionId } });
8899
+ this.recordHistoryEvent("replicas-tool-input-resolved", {
8900
+ requestId,
8901
+ toolUseId: pending.toolCallId,
8902
+ toolName: "AskUserQuestion",
8903
+ selectionId,
8904
+ selectionSummary: option.name,
8905
+ parent_tool_use_id: null
8906
+ }, this.historyFile);
8907
+ return true;
8908
+ }
8909
+ isAwaitingInput() {
8910
+ return this.pendingPermissions.size > 0;
8911
+ }
8912
+ abortPendingPermissions() {
8913
+ for (const [requestId, pending] of this.pendingPermissions) {
8914
+ pending.resolve({ outcome: { outcome: "cancelled" } });
8915
+ this.recordHistoryEvent("replicas-tool-input-resolved", {
8916
+ requestId,
8917
+ toolUseId: pending.toolCallId,
8918
+ toolName: "AskUserQuestion",
8919
+ selectionId: "aborted",
8920
+ parent_tool_use_id: null
8921
+ }, this.historyFile);
8922
+ }
8923
+ this.pendingPermissions.clear();
8687
8924
  }
8688
8925
  async processMessageInternal(request) {
8689
8926
  try {
8690
- if (request.images?.length) throw new Error("fx does not support image prompts over ACP yet.");
8691
- const model = request.model ?? DEFAULT_FX_MODEL;
8692
- await this.start(model);
8927
+ const model = request.model ?? this.acp.defaultModel;
8928
+ await this.start(model, request.thinkingLevel);
8693
8929
  const context = this.context;
8694
8930
  const sessionId = this.sessionId;
8695
- if (!context || !sessionId) throw new Error("fx ACP failed to initialize.");
8931
+ if (!context || !sessionId) throw new Error(`${this.acp.command} ACP failed to initialize.`);
8696
8932
  this.recordHistoryEvent("event_msg", { type: "user_message", message: request.message }, this.historyFile);
8697
8933
  this.planMode = request.planMode ?? false;
8698
- await context.request(methods.agent.session.setMode, {
8699
- sessionId,
8700
- modeId: this.planMode ? "ask" : "code"
8701
- });
8702
- await context.request(methods.agent.session.prompt, {
8703
- sessionId,
8704
- prompt: [{
8705
- type: "text",
8706
- text: this.planMode ? `Plan the requested work without modifying files or executing mutating commands. Return a clear implementation plan only.
8707
-
8708
- ${request.message}` : request.message
8709
- }]
8710
- });
8711
- this.recordHistoryEvent("fx-turn-complete", {}, this.historyFile);
8934
+ this.interactiveTools = request.enableInteractiveTools ?? false;
8935
+ await this.setConfig("model", model);
8936
+ await this.setThinking(request.thinkingLevel);
8937
+ await this.setMode(this.planMode);
8938
+ const prompt = [{ type: "text", text: request.message }];
8939
+ for (const image of request.images ?? []) {
8940
+ if (!this.capabilities.promptCapabilities?.image) throw new Error(`${this.acp.provider} does not advertise ACP image support.`);
8941
+ if (image.source.type !== "base64") throw new Error(`${this.acp.provider} requires uploaded images to be base64 encoded.`);
8942
+ prompt.push({ type: "image", mimeType: image.source.media_type, data: image.source.data });
8943
+ }
8944
+ const response = await context.request(methods.agent.session.prompt, { sessionId, prompt });
8945
+ this.recordHistoryEvent("acp-turn-complete", {
8946
+ provider: this.acp.provider,
8947
+ stopReason: response.stopReason
8948
+ }, this.historyFile);
8712
8949
  } catch (error) {
8713
- this.recordHistoryEvent("fx-error", { message: error instanceof Error ? error.message : String(error) }, this.historyFile);
8950
+ this.recordHistoryEvent("acp-error", {
8951
+ provider: this.acp.provider,
8952
+ message: error instanceof Error ? error.message : String(error)
8953
+ }, this.historyFile);
8714
8954
  throw error;
8715
8955
  } finally {
8716
8956
  this.planMode = false;
8957
+ this.interactiveTools = false;
8717
8958
  try {
8718
8959
  await this.historyFile.flush();
8719
8960
  } finally {
@@ -8723,8 +8964,17 @@ ${request.message}` : request.message
8723
8964
  }
8724
8965
  async interruptActiveTurn() {
8725
8966
  if (!this.context || !this.sessionId) return;
8967
+ this.abortPendingPermissions();
8726
8968
  await this.context.notify(methods.agent.session.cancel, { sessionId: this.sessionId });
8727
8969
  }
8970
+ keepSteeredMessageQueued() {
8971
+ return true;
8972
+ }
8973
+ async steerRequest() {
8974
+ if (!this.isProcessing() || !this.context || !this.sessionId) return false;
8975
+ await this.interruptActiveTurn();
8976
+ return true;
8977
+ }
8728
8978
  async getHistory(page = {}) {
8729
8979
  await this.historyFile.flush();
8730
8980
  return {
@@ -8738,20 +8988,105 @@ ${request.message}` : request.message
8738
8988
  return this.slashCommands;
8739
8989
  }
8740
8990
  dispose() {
8991
+ for (const terminal of this.terminals.values()) {
8992
+ if (terminal.exitStatus === null) terminal.process.kill("SIGTERM");
8993
+ }
8994
+ this.terminals.clear();
8741
8995
  this.connection?.close();
8742
8996
  this.process?.kill("SIGTERM");
8743
8997
  this.connection = null;
8744
8998
  this.context = null;
8745
8999
  this.process = null;
8746
9000
  this.activeModel = null;
9001
+ this.activeThinkingLevel = void 0;
9002
+ this.configOptions = [];
9003
+ this.modes = null;
9004
+ this.capabilities = {};
8747
9005
  this.planMode = false;
9006
+ this.interactiveTools = false;
9007
+ this.abortPendingPermissions();
9008
+ }
9009
+ };
9010
+
9011
+ // src/managers/fx-manager.ts
9012
+ async function ensureResolvableDnsConfig() {
9013
+ const contents = await readFile12("/etc/resolv.conf");
9014
+ if (contents.length === 0 || contents.at(-1) === 10) return;
9015
+ await new Promise((resolve5, reject) => {
9016
+ const child = spawn5("sudo", ["tee", "-a", "/etc/resolv.conf"], { stdio: ["pipe", "ignore", "pipe"] });
9017
+ let stderr = "";
9018
+ child.stderr.on("data", (chunk) => {
9019
+ stderr += chunk;
9020
+ });
9021
+ child.on("error", reject);
9022
+ child.on("exit", (code) => code === 0 ? resolve5() : reject(new Error(`Failed to normalize /etc/resolv.conf: ${stderr.trim()}`)));
9023
+ child.stdin.end("\n");
9024
+ });
9025
+ }
9026
+ var FxManager = class extends AcpManager {
9027
+ constructor(options) {
9028
+ super({
9029
+ ...options,
9030
+ acp: {
9031
+ provider: "fx",
9032
+ command: "fx",
9033
+ args: (model) => ["acp", "--model", model],
9034
+ env: () => ({ FX_AUTO_UPGRADE: "0", AI_GATEWAY_API_KEY: ENGINE_ENV.AI_GATEWAY_API_KEY }),
9035
+ defaultModel: DEFAULT_FX_MODEL,
9036
+ prepare: ensureResolvableDnsConfig
9037
+ }
9038
+ });
9039
+ }
9040
+ };
9041
+
9042
+ // src/managers/kimi-manager.ts
9043
+ var KimiManager = class extends AcpManager {
9044
+ constructor(options) {
9045
+ super({
9046
+ ...options,
9047
+ acp: {
9048
+ provider: "kimi",
9049
+ command: "kimi",
9050
+ args: () => ["acp"],
9051
+ env: async (model, thinkingLevel) => {
9052
+ let contextSize = "262144";
9053
+ let capabilities = model.startsWith("moonshotai/kimi") ? "image_in,thinking,tool_use" : "tool_use";
9054
+ try {
9055
+ const entry = (await fetchAiGatewayModels()).find((candidate) => candidate.id === model);
9056
+ if (entry) {
9057
+ if (entry.contextWindow) contextSize = String(entry.contextWindow);
9058
+ capabilities = [
9059
+ entry.tags.includes("vision") ? "image_in" : null,
9060
+ entry.tags.includes("reasoning") ? "thinking" : null,
9061
+ entry.tags.includes("tool-use") ? "tool_use" : null
9062
+ ].filter((value) => value !== null).join(",") || capabilities;
9063
+ }
9064
+ } catch {
9065
+ }
9066
+ return {
9067
+ KIMI_DISABLE_TELEMETRY: "1",
9068
+ KIMI_MODEL_NAME: model,
9069
+ KIMI_MODEL_API_KEY: ENGINE_ENV.AI_GATEWAY_API_KEY,
9070
+ KIMI_MODEL_PROVIDER_TYPE: "openai",
9071
+ KIMI_MODEL_BASE_URL: AI_GATEWAY_BASE_URL,
9072
+ KIMI_MODEL_MAX_CONTEXT_SIZE: contextSize,
9073
+ KIMI_MODEL_CAPABILITIES: capabilities,
9074
+ ...thinkingLevel ? {
9075
+ KIMI_MODEL_THINKING_EFFORT: ["low", "medium", "high"].includes(thinkingLevel) ? thinkingLevel : "high"
9076
+ } : {}
9077
+ };
9078
+ },
9079
+ defaultModel: DEFAULT_KIMI_MODEL,
9080
+ restartOnThinkingChange: true
9081
+ }
9082
+ });
8748
9083
  }
8749
9084
  };
8750
9085
 
8751
9086
  // src/managers/opencode-manager.ts
8752
9087
  import { existsSync as existsSync7 } from "fs";
8753
- import { mkdir as mkdir14, readFile as readFile12 } from "fs/promises";
8754
- import { delimiter, dirname as dirname8, join as join23 } from "path";
9088
+ import { mkdir as mkdir14, readFile as readFile13 } from "fs/promises";
9089
+ import { delimiter, dirname as dirname8, join as join22 } from "path";
8755
9090
  import { randomBytes as randomBytes2 } from "crypto";
8756
9091
  import { fileURLToPath as fileURLToPath2 } from "url";
8757
9092
  import { Agent } from "undici";
@@ -8798,7 +9133,7 @@ async function getAllowedOpenRouterModels() {
8798
9133
 
8799
9134
  // src/managers/opencode-manager.ts
8800
9135
  var OPENCODE_SHIM_DIR = dirname8(fileURLToPath2(new URL("../../scripts/opencode", import.meta.url)));
8801
- var OPENCODE_CONFIG_PATH = join23(ENGINE_ENV.HOME_DIR, ".config", "opencode", "opencode.json");
9136
+ var OPENCODE_CONFIG_PATH = join22(ENGINE_ENV.HOME_DIR, ".config", "opencode", "opencode.json");
8802
9137
  var OPENCODE_FETCH_DISPATCHER = new Agent({ headersTimeout: 0, bodyTimeout: 0 });
8803
9138
  var OPENCODE_SERVER_STARTUP_TIMEOUT_MS = 3e4;
8804
9139
  var OPENCODE_WORKSPACE_PERMISSION = {
@@ -8835,7 +9170,7 @@ var opencodeAuthSchema = z4.record(z4.string(), z4.object({
8835
9170
  async function hasOpencodeCredentials(provider) {
8836
9171
  if (!existsSync7(OPENCODE_AUTH_PATH)) return false;
8837
9172
  try {
8838
- const auth = opencodeAuthSchema.safeParse(JSON.parse(await readFile12(OPENCODE_AUTH_PATH, "utf8")));
9173
+ const auth = opencodeAuthSchema.safeParse(JSON.parse(await readFile13(OPENCODE_AUTH_PATH, "utf8")));
8839
9174
  return auth.success && auth.data[provider]?.type === "api" && Boolean(auth.data[provider]?.key);
8840
9175
  } catch {
8841
9176
  return false;
@@ -8940,7 +9275,7 @@ function isOpencodeMcpEntry(value) {
8940
9275
  async function readProvisionedOpencodeMcpConfig() {
8941
9276
  let raw;
8942
9277
  try {
8943
- raw = await readFile12(OPENCODE_CONFIG_PATH, "utf8");
9278
+ raw = await readFile13(OPENCODE_CONFIG_PATH, "utf8");
8944
9279
  } catch (error) {
8945
9280
  if (isRecord2(error) && error.code === "ENOENT") return void 0;
8946
9281
  console.error("[OpencodeManager] Failed to read Opencode config:", error);
@@ -9048,7 +9383,7 @@ var OpencodeManager = class extends CodingAgentManager {
9048
9383
  constructor(options) {
9049
9384
  super(options);
9050
9385
  this.sessionId = options.initialSessionId;
9051
- this.historyFilePath = options.historyFilePath ?? join23(ENGINE_ENV.HOME_DIR, ".replicas", "opencode", "history.jsonl");
9386
+ this.historyFilePath = options.historyFilePath ?? join22(ENGINE_ENV.HOME_DIR, ".replicas", "opencode", "history.jsonl");
9052
9387
  this.historyFile = new CodexHistoryFile(this.historyFilePath);
9053
9388
  this.initializeManager(this.processMessageInternal.bind(this));
9054
9389
  }
@@ -9558,7 +9893,7 @@ var OpencodeManager = class extends CodingAgentManager {
9558
9893
 
9559
9894
  // src/managers/pi-manager.ts
9560
9895
  import { mkdir as mkdir15 } from "fs/promises";
9561
- import { dirname as dirname9, join as join24 } from "path";
9896
+ import { dirname as dirname9, join as join23 } from "path";
9562
9897
  import {
9563
9898
  AuthStorage,
9564
9899
  createAgentSession,
@@ -9641,7 +9976,7 @@ var PiManager = class extends CodingAgentManager {
9641
9976
  providerApiKey = null;
9642
9977
  constructor(options) {
9643
9978
  super(options);
9644
- this.historyFilePath = options.historyFilePath ?? join24(PI_HISTORY_DIR, `${Date.now()}.jsonl`);
9979
+ this.historyFilePath = options.historyFilePath ?? join23(PI_HISTORY_DIR, `${Date.now()}.jsonl`);
9645
9980
  this.historyFile = new CodexHistoryFile(this.historyFilePath);
9646
9981
  this.initializeManager(this.processMessageInternal.bind(this));
9647
9982
  }
@@ -9741,7 +10076,7 @@ var PiManager = class extends CodingAgentManager {
9741
10076
  const sessionManager = this.initialSessionId ? SessionManager.open(this.initialSessionId, PI_HISTORY_DIR, this.workingDirectory) : SessionManager.create(this.workingDirectory, PI_HISTORY_DIR);
9742
10077
  const resourceLoader = new DefaultResourceLoader({
9743
10078
  cwd: this.workingDirectory,
9744
- agentDir: join24(ENGINE_ENV.HOME_DIR, ".pi", "agent"),
10079
+ agentDir: join23(ENGINE_ENV.HOME_DIR, ".pi", "agent"),
9745
10080
  extensionFactories: [registerCommandProtection(
9746
10081
  this.workingDirectory,
9747
10082
  this.historyFile,
@@ -9818,6 +10153,7 @@ function getAvailableRelayProviders(availability) {
9818
10153
  const cursorAvailable = availability.cursorAvailable ?? false;
9819
10154
  const deepseekAvailable = availability.deepseekAvailable ?? false;
9820
10155
  const fxAvailable = availability.fxAvailable ?? false;
10156
+ const kimiAvailable = availability.kimiAvailable ?? false;
9821
10157
  const opencodeAvailable = availability.opencodeAvailable ?? false;
9822
10158
  const piAvailable = availability.piAvailable ?? false;
9823
10159
  const providers = ["claude"];
@@ -9825,6 +10161,7 @@ function getAvailableRelayProviders(availability) {
9825
10161
  if (cursorAvailable) providers.push("cursor");
9826
10162
  if (deepseekAvailable) providers.push("deepseek");
9827
10163
  if (fxAvailable) providers.push("fx");
10164
+ if (kimiAvailable) providers.push("kimi");
9828
10165
  if (opencodeAvailable) providers.push("opencode");
9829
10166
  if (piAvailable) providers.push("pi");
9830
10167
  providers.push("relay");
@@ -9914,7 +10251,7 @@ function extractFinalResponse(history) {
9914
10251
  const text = getDeepseekAssistantMessageText(payload.event, "text");
9915
10252
  if (text) return text;
9916
10253
  }
9917
- if (event.type === "fx-session-update") {
10254
+ if (event.type === "fx-session-update" || event.type === "acp-session-update") {
9918
10255
  const text = getFxTextChunk(payload.update, "agent_message_chunk");
9919
10256
  if (text) return text;
9920
10257
  }
@@ -9957,11 +10294,12 @@ You will also receive the chatId so you can send follow-up messages or clean up
9957
10294
  `Cursor: ${AGENT_MODELS.cursor.join(", ")}.`,
9958
10295
  `DeepSeek Harness: ${AGENT_MODELS.deepseek.join(", ")}.`,
9959
10296
  `fx: ${AGENT_MODELS.fx.join(", ")}.`,
10297
+ `Kimi Code: ${AGENT_MODELS.kimi.join(", ")}.`,
9960
10298
  `Opencode: ${AGENT_MODELS.opencode.join(", ")}.`,
9961
10299
  `Pi: ${AGENT_MODELS.pi.join(", ")}.`
9962
10300
  ].join(" ")),
9963
10301
  thinking_level: z5.enum(VALID_THINKING_LEVELS).optional().describe(
9964
- "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, fx, Opencode, and Pi = medium."
10302
+ "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, fx, Kimi Code, Opencode, and Pi = medium."
9965
10303
  ),
9966
10304
  title: z5.string().optional().describe("Optional title for the subagent chat (for identification)."),
9967
10305
  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.")
@@ -10213,14 +10551,15 @@ function getUsingToolsSection() {
10213
10551
  ];
10214
10552
  return [`# Using your tools`, ...prependBullets(items)].join("\n");
10215
10553
  }
10216
- function getDelegationSection(codexAvailable, cursorAvailable, deepseekAvailable, fxAvailable, opencodeAvailable, piAvailable) {
10217
- const providerList = getAvailableRelayProviders({ codexAvailable, cursorAvailable, deepseekAvailable, fxAvailable, opencodeAvailable, piAvailable }).join(", ");
10554
+ function getDelegationSection(codexAvailable, cursorAvailable, deepseekAvailable, fxAvailable, kimiAvailable, opencodeAvailable, piAvailable) {
10555
+ const providerList = getAvailableRelayProviders({ codexAvailable, cursorAvailable, deepseekAvailable, fxAvailable, kimiAvailable, opencodeAvailable, piAvailable }).join(", ");
10218
10556
  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).`;
10219
10557
  const claudeModelList = AGENT_MODELS.claude.join(", ");
10220
10558
  const extraAgentLines = [
10221
10559
  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,
10222
10560
  deepseekAvailable ? `Use provider 'deepseek' for tasks that benefit from the DeepSeek Harness tool loop. Suggested models: ${AGENT_MODELS.deepseek.join(", ")}.` : null,
10223
10561
  fxAvailable ? `Use provider 'fx' for tasks that benefit from fx's minimal native coding loop. Suggested models: ${AGENT_MODELS.fx.join(", ")}.` : null,
10562
+ kimiAvailable ? `Use provider 'kimi' for tasks that benefit from Kimi Code's ACP coding loop. Suggested models: ${AGENT_MODELS.kimi.join(", ")}.` : null,
10224
10563
  opencodeAvailable ? `Use provider 'opencode' for cheaper routine implementation tasks through OpenRouter-backed open source models. Suggested models: ${AGENT_MODELS.opencode.join(", ")}.` : null,
10225
10564
  piAvailable ? `Use provider 'pi' for coding tasks through Pi's OpenRouter-backed coding agent. Suggested models: ${AGENT_MODELS.pi.join(", ")}.` : null,
10226
10565
  cursorAvailable ? `Use provider 'cursor' for fast iteration on code changes. Suggested models: ${AGENT_MODELS.cursor.join(", ")}.` : null
@@ -10345,14 +10684,14 @@ function getEnvironmentSection() {
10345
10684
  ].join("\n");
10346
10685
  }
10347
10686
  function buildRelaySystemPrompt(options) {
10348
- const { customInstructions, codexAvailable, cursorAvailable, deepseekAvailable, fxAvailable, opencodeAvailable, piAvailable } = options ?? {};
10687
+ const { customInstructions, codexAvailable, cursorAvailable, deepseekAvailable, fxAvailable, kimiAvailable, opencodeAvailable, piAvailable } = options ?? {};
10349
10688
  const sections = [
10350
10689
  getIntroSection(),
10351
10690
  getSystemSection(),
10352
10691
  getDoingTasksSection(),
10353
10692
  getActionsSection(),
10354
10693
  getUsingToolsSection(),
10355
- getDelegationSection(codexAvailable ?? false, cursorAvailable ?? false, deepseekAvailable ?? false, fxAvailable ?? false, opencodeAvailable ?? false, piAvailable ?? false),
10694
+ getDelegationSection(codexAvailable ?? false, cursorAvailable ?? false, deepseekAvailable ?? false, fxAvailable ?? false, kimiAvailable ?? false, opencodeAvailable ?? false, piAvailable ?? false),
10356
10695
  getToneAndStyleSection(),
10357
10696
  getOutputEfficiencySection(),
10358
10697
  getEnvironmentSection(),
@@ -10386,9 +10725,10 @@ var RelayManager = class {
10386
10725
  const cursorAvailable = options.cursorAvailable ?? false;
10387
10726
  const deepseekAvailable = options.deepseekAvailable ?? false;
10388
10727
  const fxAvailable = options.fxAvailable ?? false;
10728
+ const kimiAvailable = options.kimiAvailable ?? false;
10389
10729
  const opencodeAvailable = options.opencodeAvailable ?? false;
10390
10730
  const piAvailable = options.piAvailable ?? false;
10391
- const availability = { codexAvailable, cursorAvailable, deepseekAvailable, fxAvailable, opencodeAvailable, piAvailable };
10731
+ const availability = { codexAvailable, cursorAvailable, deepseekAvailable, fxAvailable, kimiAvailable, opencodeAvailable, piAvailable };
10392
10732
  const getProviderAvailability = options.getProviderAvailability ?? (() => availability);
10393
10733
  this.inner = new ClaudeManager({
10394
10734
  ...options,
@@ -10453,16 +10793,16 @@ var RelayManager = class {
10453
10793
  import {
10454
10794
  appendFile as appendFile3,
10455
10795
  mkdir as mkdir16,
10456
- readFile as readFile13,
10796
+ readFile as readFile14,
10457
10797
  readdir as readdir6,
10458
10798
  rename as rename2,
10459
10799
  unlink as unlink3
10460
10800
  } from "fs/promises";
10461
- import { join as join25 } from "path";
10462
- import { randomUUID as randomUUID6 } from "crypto";
10801
+ import { join as join24 } from "path";
10802
+ import { randomUUID as randomUUID7 } from "crypto";
10463
10803
 
10464
10804
  // src/analytics/agent/activity/skill-mcp-call-extractor.ts
10465
- var NON_MCP_SERVERS = /* @__PURE__ */ new Set(["claude", "cursor", "deepseek", "fx", "opencode", "pi", "custom", "dynamic"]);
10805
+ var NON_MCP_SERVERS = /* @__PURE__ */ new Set(["claude", "cursor", "deepseek", "fx", "kimi", "opencode", "pi", "custom", "dynamic"]);
10466
10806
  function mcpNameFromToolCall(message) {
10467
10807
  const parsedName = parseMcpToolName(message.tool);
10468
10808
  if (parsedName) return parsedName.server;
@@ -10509,7 +10849,7 @@ var AgentChatActivityBuffer = class {
10509
10849
  options.storageName,
10510
10850
  ...options.legacyStorageNames ?? []
10511
10851
  ];
10512
- this.liveFile = join25(ENGINE_DIR2, `${options.storageName}.jsonl`);
10852
+ this.liveFile = join24(ENGINE_DIR2, `${options.storageName}.jsonl`);
10513
10853
  this.segmentFilePatterns = this.storageNames.map(
10514
10854
  (storageName) => new RegExp(`^${storageName}\\.(\\d+)\\.jsonl$`)
10515
10855
  );
@@ -10568,8 +10908,8 @@ var AgentChatActivityBuffer = class {
10568
10908
  await Promise.allSettled([...this.pendingAppends]);
10569
10909
  for (const storageName of this.storageNames) {
10570
10910
  await rename2(
10571
- join25(ENGINE_DIR2, `${storageName}.jsonl`),
10572
- join25(ENGINE_DIR2, `${storageName}.${Date.now()}.jsonl`)
10911
+ join24(ENGINE_DIR2, `${storageName}.jsonl`),
10912
+ join24(ENGINE_DIR2, `${storageName}.${Date.now()}.jsonl`)
10573
10913
  ).catch(() => {
10574
10914
  });
10575
10915
  }
@@ -10580,7 +10920,7 @@ var AgentChatActivityBuffer = class {
10580
10920
  if (!this.segmentFilePatterns.some((pattern) => pattern.test(entry)))
10581
10921
  continue;
10582
10922
  try {
10583
- await this.uploadSegment(join25(ENGINE_DIR2, entry));
10923
+ await this.uploadSegment(join24(ENGINE_DIR2, entry));
10584
10924
  flushed++;
10585
10925
  } catch (error) {
10586
10926
  failed++;
@@ -10612,13 +10952,13 @@ var AgentChatActivityBuffer = class {
10612
10952
  ) && entry.endsWith(UPLOADED_SUFFIX)
10613
10953
  ).sort();
10614
10954
  for (const entry of uploaded.slice(0, -MAX_UPLOADED_SEGMENTS)) {
10615
- await unlink3(join25(ENGINE_DIR2, entry)).catch(() => {
10955
+ await unlink3(join24(ENGINE_DIR2, entry)).catch(() => {
10616
10956
  });
10617
10957
  }
10618
10958
  return { flushed, failed };
10619
10959
  }
10620
10960
  async uploadSegment(filePath) {
10621
- const records = (await readFile13(filePath, "utf-8")).split("\n").flatMap((line) => {
10961
+ const records = (await readFile14(filePath, "utf-8")).split("\n").flatMap((line) => {
10622
10962
  try {
10623
10963
  const parsed = JSON.parse(line);
10624
10964
  return this.options.validate(parsed) ? [parsed] : [];
@@ -10680,7 +11020,7 @@ var AgentChatTurnActivityTracker = class {
10680
11020
  const credential = ENGINE_ENV.REPLICAS_AGENT_CREDENTIALS[provider];
10681
11021
  this.pendingMessages.delete(messageId);
10682
11022
  this.activeTurns.set(chatId, {
10683
- turnId: randomUUID6(),
11023
+ turnId: randomUUID7(),
10684
11024
  startedAtMs: Date.now(),
10685
11025
  provider,
10686
11026
  model: attributes.model ?? getDefaultAgentModel(provider),
@@ -10843,19 +11183,19 @@ var KeepAliveService = class _KeepAliveService {
10843
11183
  var keepAliveService = new KeepAliveService();
10844
11184
 
10845
11185
  // src/services/canvas-service.ts
10846
- import { readdir as readdir7, readFile as readFile14, stat as stat3 } from "fs/promises";
11186
+ import { readdir as readdir7, readFile as readFile15, stat as stat3 } from "fs/promises";
10847
11187
  import { homedir as homedir14 } from "os";
10848
- import { join as join26 } from "path";
11188
+ import { join as join25 } from "path";
10849
11189
  var GLOBAL_CANVAS_DIRECTORIES = [
10850
- join26(homedir14(), ".claude", "plans"),
10851
- join26(process.env.XDG_DATA_HOME ?? join26(homedir14(), ".local", "share"), "opencode", "plans"),
10852
- join26(homedir14(), ".replicas", "canvas")
11190
+ join25(homedir14(), ".claude", "plans"),
11191
+ join25(process.env.XDG_DATA_HOME ?? join25(homedir14(), ".local", "share"), "opencode", "plans"),
11192
+ join25(homedir14(), ".replicas", "canvas")
10853
11193
  ];
10854
11194
  async function canvasDirectories() {
10855
11195
  const repositories = await gitService.listRepositories().catch(() => []);
10856
11196
  return [
10857
11197
  ...GLOBAL_CANVAS_DIRECTORIES,
10858
- ...repositories.map((repository) => join26(repository.path, ".opencode", "plans"))
11198
+ ...repositories.map((repository) => join25(repository.path, ".opencode", "plans"))
10859
11199
  ];
10860
11200
  }
10861
11201
  var CanvasService = class {
@@ -10879,7 +11219,7 @@ var CanvasService = class {
10879
11219
  for (const entry of entries) {
10880
11220
  if (entry.name.startsWith(".")) continue;
10881
11221
  const filename = current.relativePath ? `${current.relativePath}/${entry.name}` : entry.name;
10882
- const filePath = join26(current.directory, entry.name);
11222
+ const filePath = join25(current.directory, entry.name);
10883
11223
  if (entry.isDirectory()) {
10884
11224
  pending.push({ directory: filePath, relativePath: filename });
10885
11225
  continue;
@@ -10904,7 +11244,7 @@ var CanvasService = class {
10904
11244
  if (!safe) return null;
10905
11245
  const { kind, mimeType } = classifyCanvasFilename(safe);
10906
11246
  for (const directory of await this.directories()) {
10907
- const filePath = join26(directory, safe);
11247
+ const filePath = join25(directory, safe);
10908
11248
  let sizeBytes = 0;
10909
11249
  let updatedAt = "";
10910
11250
  try {
@@ -10925,7 +11265,7 @@ var CanvasService = class {
10925
11265
  };
10926
11266
  }
10927
11267
  try {
10928
- const bytes = await readFile14(filePath);
11268
+ const bytes = await readFile15(filePath);
10929
11269
  return { filename: safe, kind, sizeBytes, mimeType, updatedAt, bytes };
10930
11270
  } catch {
10931
11271
  continue;
@@ -11075,14 +11415,14 @@ async function reconcileCanvasItems(filenames) {
11075
11415
  // src/services/upload-chat-transcripts.ts
11076
11416
  import { createReadStream } from "fs";
11077
11417
  import { createHash as createHash2 } from "crypto";
11078
- import { readFile as readFile15, readdir as readdir8, stat as stat4 } from "fs/promises";
11079
- import { basename as basename2, join as join28 } from "path";
11418
+ import { readFile as readFile16, readdir as readdir8, stat as stat4 } from "fs/promises";
11419
+ import { basename as basename2, join as join27 } from "path";
11080
11420
 
11081
11421
  // src/services/chat/chat-senders.ts
11082
- import { join as join27 } from "path";
11083
- var CHAT_SENDERS_DIR = join27(ENGINE_DIR2, "chat-senders");
11422
+ import { join as join26 } from "path";
11423
+ var CHAT_SENDERS_DIR = join26(ENGINE_DIR2, "chat-senders");
11084
11424
  function chatMessageSendersFilePath(chatId) {
11085
- return join27(CHAT_SENDERS_DIR, `${chatId}.jsonl`);
11425
+ return join26(CHAT_SENDERS_DIR, `${chatId}.jsonl`);
11086
11426
  }
11087
11427
  function parseChatMessageSendersJsonl(content) {
11088
11428
  return content.split("\n").flatMap((line) => {
@@ -11098,9 +11438,9 @@ function parseChatMessageSendersJsonl(content) {
11098
11438
 
11099
11439
  // src/services/upload-chat-transcripts.ts
11100
11440
  var HISTORY_DIRS = [
11101
- join28(ENGINE_DIR2, "claude-histories"),
11102
- join28(ENGINE_DIR2, "relay-histories"),
11103
- join28(ENGINE_DIR2, "codex-histories")
11441
+ join27(ENGINE_DIR2, "claude-histories"),
11442
+ join27(ENGINE_DIR2, "relay-histories"),
11443
+ join27(ENGINE_DIR2, "codex-histories")
11104
11444
  ];
11105
11445
  async function flushAllChatTranscripts(chatsById = /* @__PURE__ */ new Map(), capture) {
11106
11446
  let flushed = 0;
@@ -11118,7 +11458,7 @@ async function flushAllChatTranscripts(chatsById = /* @__PURE__ */ new Map(), ca
11118
11458
  if (!entry.endsWith(".jsonl")) continue;
11119
11459
  const chatId = basename2(entry, ".jsonl");
11120
11460
  tasks.push(
11121
- uploadChatTranscript(chatId, join28(dir, entry), chatsById.get(chatId), capture).then((artifact) => {
11461
+ uploadChatTranscript(chatId, join27(dir, entry), chatsById.get(chatId), capture).then((artifact) => {
11122
11462
  flushed++;
11123
11463
  if (artifact && capture) revisions.push(artifact);
11124
11464
  }).catch((err) => {
@@ -11134,7 +11474,7 @@ async function flushAllChatTranscripts(chatsById = /* @__PURE__ */ new Map(), ca
11134
11474
  async function uploadChatTranscript(chatId, filePath, chat, capture) {
11135
11475
  const { size } = await stat4(filePath);
11136
11476
  if (size === 0) return null;
11137
- const historyPages = createChatTranscriptPages(await readFile15(filePath));
11477
+ const historyPages = createChatTranscriptPages(await readFile16(filePath));
11138
11478
  const metadata = chat ? {
11139
11479
  provider: chat.provider,
11140
11480
  credential: ENGINE_ENV.REPLICAS_AGENT_CREDENTIALS[chat.provider],
@@ -11153,7 +11493,7 @@ async function uploadChatTranscript(chatId, filePath, chat, capture) {
11153
11493
  }
11154
11494
  try {
11155
11495
  metadata.senders = parseChatMessageSendersJsonl(
11156
- await readFile15(chatMessageSendersFilePath(chatId), "utf-8")
11496
+ await readFile16(chatMessageSendersFilePath(chatId), "utf-8")
11157
11497
  );
11158
11498
  } catch (error) {
11159
11499
  if (!(error && typeof error === "object" && "code" in error && error.code === "ENOENT")) throw error;
@@ -11236,7 +11576,7 @@ async function flushRepoState() {
11236
11576
  // src/services/upload-engine-logs.ts
11237
11577
  import { createReadStream as createReadStream2 } from "fs";
11238
11578
  import { readdir as readdir9, stat as stat5 } from "fs/promises";
11239
- import { join as join29 } from "path";
11579
+ import { join as join28 } from "path";
11240
11580
  var MAX_ENGINE_LOG_FLUSH_SESSIONS = 10;
11241
11581
  var MAX_ENGINE_LOG_FLUSH_BYTES = 5 * 1024 * 1024;
11242
11582
  var ENGINE_LOG_FLUSH_TIMEOUT_MS = 2e4;
@@ -11265,7 +11605,7 @@ async function flushAllEngineLogs() {
11265
11605
  const candidates = (await Promise.all(filenames.slice(0, MAX_ENGINE_LOG_FLUSH_SESSIONS).map(async (filename) => {
11266
11606
  try {
11267
11607
  const sessionId = filename.slice(0, -".log".length);
11268
- const filePath = join29(LOG_DIR, filename);
11608
+ const filePath = join28(LOG_DIR, filename);
11269
11609
  const fileStat = await runBeforeDeadline(() => stat5(filePath), deadline);
11270
11610
  if (!fileStat.isFile()) {
11271
11611
  skipped++;
@@ -11361,7 +11701,7 @@ async function uploadEngineLog(input, timeoutMs) {
11361
11701
  }
11362
11702
 
11363
11703
  // src/services/chat/chat-service.ts
11364
- var CODEX_AUTH_PATH2 = join30(homedir15(), ".codex", "auth.json");
11704
+ var CODEX_AUTH_PATH2 = join29(homedir15(), ".codex", "auth.json");
11365
11705
  var CHATS_BACKUP_FILE = `${CHATS_FILE}.bak`;
11366
11706
  function isCodexAvailable() {
11367
11707
  return existsSync8(CODEX_AUTH_PATH2) || Boolean(ENGINE_ENV.OPENAI_API_KEY);
@@ -11375,6 +11715,9 @@ function isDeepseekAvailable() {
11375
11715
  function isFxAvailable() {
11376
11716
  return Boolean(ENGINE_ENV.AI_GATEWAY_API_KEY);
11377
11717
  }
11718
+ function isKimiAvailable() {
11719
+ return Boolean(ENGINE_ENV.AI_GATEWAY_API_KEY);
11720
+ }
11378
11721
  function isPiAvailable() {
11379
11722
  return existsSync8(PI_AUTH_PATH);
11380
11723
  }
@@ -11567,7 +11910,7 @@ var ChatService = class {
11567
11910
  throw new ChatNotFoundError(parentChatId);
11568
11911
  }
11569
11912
  const persisted = {
11570
- id: request.id ?? randomUUID7(),
11913
+ id: request.id ?? randomUUID8(),
11571
11914
  provider: request.provider,
11572
11915
  title,
11573
11916
  createdAt: now,
@@ -11658,7 +12001,7 @@ var ChatService = class {
11658
12001
  }
11659
12002
  }
11660
12003
  async readSenders(chatId) {
11661
- return readFile16(chatMessageSendersFilePath(chatId), "utf-8").then(parseChatMessageSendersJsonl).catch((error) => {
12004
+ return readFile17(chatMessageSendersFilePath(chatId), "utf-8").then(parseChatMessageSendersJsonl).catch((error) => {
11662
12005
  if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") return [];
11663
12006
  console.error("[ChatService] Failed to read sender records:", error);
11664
12007
  return [];
@@ -11884,7 +12227,7 @@ var ChatService = class {
11884
12227
  return descendants;
11885
12228
  }
11886
12229
  async deleteHistoryFile(persisted) {
11887
- const historyPath = join30(HISTORY_DIR_BY_PROVIDER[persisted.provider], `${persisted.id}.jsonl`);
12230
+ const historyPath = join29(HISTORY_DIR_BY_PROVIDER[persisted.provider], `${persisted.id}.jsonl`);
11888
12231
  await Promise.all([
11889
12232
  historyPath,
11890
12233
  `${historyPath}.pages.jsonl`,
@@ -12007,7 +12350,7 @@ var ChatService = class {
12007
12350
  if (persisted.provider === "claude") {
12008
12351
  provider = new ClaudeManager({
12009
12352
  workingDirectory: this.workingDirectory,
12010
- historyFilePath: join30(CLAUDE_HISTORY_DIR, `${persisted.id}.jsonl`),
12353
+ historyFilePath: join29(CLAUDE_HISTORY_DIR, `${persisted.id}.jsonl`),
12011
12354
  initialSessionId: persisted.providerSessionId,
12012
12355
  onSaveSessionId: saveSession,
12013
12356
  onTurnComplete: onProviderTurnComplete,
@@ -12021,11 +12364,12 @@ var ChatService = class {
12021
12364
  piAvailable: isPiAvailable(),
12022
12365
  cursorAvailable: isCursorAvailable(),
12023
12366
  deepseekAvailable: isDeepseekAvailable(),
12024
- fxAvailable: isFxAvailable()
12367
+ fxAvailable: isFxAvailable(),
12368
+ kimiAvailable: isKimiAvailable()
12025
12369
  });
12026
12370
  provider = new RelayManager({
12027
12371
  workingDirectory: this.workingDirectory,
12028
- historyFilePath: join30(RELAY_HISTORY_DIR, `${persisted.id}.jsonl`),
12372
+ historyFilePath: join29(RELAY_HISTORY_DIR, `${persisted.id}.jsonl`),
12029
12373
  initialSessionId: persisted.providerSessionId,
12030
12374
  onSaveSessionId: saveSession,
12031
12375
  onTurnComplete: onProviderTurnComplete,
@@ -12038,7 +12382,7 @@ var ChatService = class {
12038
12382
  } else if (persisted.provider === "cursor") {
12039
12383
  provider = new CursorManager({
12040
12384
  workingDirectory: this.workingDirectory,
12041
- historyFilePath: join30(CURSOR_HISTORY_DIR, `${persisted.id}.jsonl`),
12385
+ historyFilePath: join29(CURSOR_HISTORY_DIR, `${persisted.id}.jsonl`),
12042
12386
  initialSessionId: persisted.providerSessionId,
12043
12387
  onSaveSessionId: saveSession,
12044
12388
  onTurnComplete: onProviderTurnComplete,
@@ -12048,7 +12392,7 @@ var ChatService = class {
12048
12392
  } else if (persisted.provider === "deepseek") {
12049
12393
  provider = new DeepseekManager({
12050
12394
  workingDirectory: this.workingDirectory,
12051
- historyFilePath: join30(DEEPSEEK_HISTORY_DIR, `${persisted.id}.jsonl`),
12395
+ historyFilePath: join29(DEEPSEEK_HISTORY_DIR, `${persisted.id}.jsonl`),
12052
12396
  initialSessionId: persisted.providerSessionId,
12053
12397
  onSaveSessionId: saveSession,
12054
12398
  onTurnComplete: onProviderTurnComplete,
@@ -12058,7 +12402,17 @@ var ChatService = class {
12058
12402
  } else if (persisted.provider === "fx") {
12059
12403
  provider = new FxManager({
12060
12404
  workingDirectory: this.workingDirectory,
12061
- historyFilePath: join30(FX_HISTORY_DIR, `${persisted.id}.jsonl`),
12405
+ historyFilePath: join29(FX_HISTORY_DIR, `${persisted.id}.jsonl`),
12406
+ initialSessionId: persisted.providerSessionId,
12407
+ onSaveSessionId: saveSession,
12408
+ onTurnComplete: onProviderTurnComplete,
12409
+ onEvent: onProviderEvent,
12410
+ onProcessingChanged
12411
+ });
12412
+ } else if (persisted.provider === "kimi") {
12413
+ provider = new KimiManager({
12414
+ workingDirectory: this.workingDirectory,
12415
+ historyFilePath: join29(KIMI_HISTORY_DIR, `${persisted.id}.jsonl`),
12062
12416
  initialSessionId: persisted.providerSessionId,
12063
12417
  onSaveSessionId: saveSession,
12064
12418
  onTurnComplete: onProviderTurnComplete,
@@ -12068,7 +12422,7 @@ var ChatService = class {
12068
12422
  } else if (persisted.provider === "opencode") {
12069
12423
  provider = new OpencodeManager({
12070
12424
  workingDirectory: this.workingDirectory,
12071
- historyFilePath: join30(OPENCODE_HISTORY_DIR, `${persisted.id}.jsonl`),
12425
+ historyFilePath: join29(OPENCODE_HISTORY_DIR, `${persisted.id}.jsonl`),
12072
12426
  initialSessionId: persisted.providerSessionId,
12073
12427
  onSaveSessionId: saveSession,
12074
12428
  onTurnComplete: onProviderTurnComplete,
@@ -12078,7 +12432,7 @@ var ChatService = class {
12078
12432
  } else if (persisted.provider === "pi") {
12079
12433
  provider = new PiManager({
12080
12434
  workingDirectory: this.workingDirectory,
12081
- historyFilePath: join30(PI_HISTORY_DIR, `${persisted.id}.jsonl`),
12435
+ historyFilePath: join29(PI_HISTORY_DIR, `${persisted.id}.jsonl`),
12082
12436
  initialSessionId: persisted.providerSessionId,
12083
12437
  onSaveSessionId: saveSession,
12084
12438
  onTurnComplete: onProviderTurnComplete,
@@ -12088,7 +12442,7 @@ var ChatService = class {
12088
12442
  } else {
12089
12443
  provider = new CodexAspManager({
12090
12444
  workingDirectory: this.workingDirectory,
12091
- historyFilePath: join30(CODEX_HISTORY_DIR, `${persisted.id}.jsonl`),
12445
+ historyFilePath: join29(CODEX_HISTORY_DIR, `${persisted.id}.jsonl`),
12092
12446
  initialSessionId: persisted.providerSessionId,
12093
12447
  onSaveSessionId: saveSession,
12094
12448
  onTurnComplete: onProviderTurnComplete,
@@ -12253,7 +12607,7 @@ var ChatService = class {
12253
12607
  });
12254
12608
  uploadChatTranscript(
12255
12609
  chatId,
12256
- join30(HISTORY_DIR_BY_PROVIDER[chat.persisted.provider], `${chatId}.jsonl`),
12610
+ join29(HISTORY_DIR_BY_PROVIDER[chat.persisted.provider], `${chatId}.jsonl`),
12257
12611
  this.toSummary(chat)
12258
12612
  ).catch((err) => {
12259
12613
  console.error("[ChatService] Failed to upload chat transcript:", { chatId, err });
@@ -12269,7 +12623,7 @@ var ChatService = class {
12269
12623
  }
12270
12624
  async loadChats() {
12271
12625
  try {
12272
- const content = await readFile16(CHATS_FILE, "utf-8");
12626
+ const content = await readFile17(CHATS_FILE, "utf-8");
12273
12627
  return parsePersistedChatsContent(content);
12274
12628
  } catch (error) {
12275
12629
  if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
@@ -12284,7 +12638,7 @@ var ChatService = class {
12284
12638
  console.error("[ChatService] Failed to quarantine corrupt chats file:", renameError);
12285
12639
  }
12286
12640
  try {
12287
- const backupContent = await readFile16(CHATS_BACKUP_FILE, "utf-8");
12641
+ const backupContent = await readFile17(CHATS_BACKUP_FILE, "utf-8");
12288
12642
  return parsePersistedChatsContent(backupContent);
12289
12643
  } catch (backupError) {
12290
12644
  if (backupError && typeof backupError === "object" && "code" in backupError && backupError.code === "ENOENT") {
@@ -12323,7 +12677,7 @@ var ChatService = class {
12323
12677
  }
12324
12678
  async publish(input) {
12325
12679
  const event = {
12326
- id: randomUUID7(),
12680
+ id: randomUUID8(),
12327
12681
  ts: (/* @__PURE__ */ new Date()).toISOString(),
12328
12682
  ...input
12329
12683
  };
@@ -12390,8 +12744,8 @@ var ChatService = class {
12390
12744
 
12391
12745
  // src/services/repo-file-service.ts
12392
12746
  import { execFile } from "child_process";
12393
- import { readFile as readFile17, realpath, stat as stat6 } from "fs/promises";
12394
- import { join as join31, resolve as resolve2, extname as extname2 } from "path";
12747
+ import { readFile as readFile18, realpath, stat as stat6 } from "fs/promises";
12748
+ import { join as join30, resolve as resolve2, extname as extname2 } from "path";
12395
12749
  var CACHE_TTL_MS = 3e4;
12396
12750
  var SEARCH_TIMEOUT_MS = 15e3;
12397
12751
  var MAX_CONTENT_BYTES = 256 * 1024;
@@ -12543,7 +12897,7 @@ var RepoFileService = class {
12543
12897
  const repo = repos.find((r) => r.name === repoName);
12544
12898
  if (!repo) return null;
12545
12899
  try {
12546
- const fullPath = await realpath(resolve2(join31(repo.path, filePath)));
12900
+ const fullPath = await realpath(resolve2(join30(repo.path, filePath)));
12547
12901
  const repoRoot = await realpath(repo.path);
12548
12902
  const repoPrefix = repoRoot.endsWith("/") ? repoRoot : repoRoot + "/";
12549
12903
  if (!fullPath.startsWith(repoPrefix) && fullPath !== repoRoot) return null;
@@ -12574,7 +12928,7 @@ var RepoFileService = class {
12574
12928
  sizeBytes,
12575
12929
  binary: true,
12576
12930
  tooLarge: false,
12577
- base64: (await readFile17(fullPath)).toString("base64"),
12931
+ base64: (await readFile18(fullPath)).toString("base64"),
12578
12932
  mimeType
12579
12933
  };
12580
12934
  }
@@ -12599,7 +12953,7 @@ var RepoFileService = class {
12599
12953
  tooLarge: true
12600
12954
  };
12601
12955
  }
12602
- const content = await readFile17(fullPath, "utf-8");
12956
+ const content = await readFile18(fullPath, "utf-8");
12603
12957
  return {
12604
12958
  repoName,
12605
12959
  path: filePath,
@@ -12677,21 +13031,21 @@ var RepoFileService = class {
12677
13031
  // src/v1-routes.ts
12678
13032
  import { Hono } from "hono";
12679
13033
  import { z as z6 } from "zod";
12680
- import { readdir as readdir11, stat as stat7, readFile as readFile20 } from "fs/promises";
12681
- import { join as join34, resolve as resolve3 } from "path";
13034
+ import { readdir as readdir11, stat as stat7, readFile as readFile21 } from "fs/promises";
13035
+ import { join as join33, resolve as resolve3 } from "path";
12682
13036
 
12683
13037
  // src/services/warm-hooks-service.ts
12684
- import { spawn as spawn5 } from "child_process";
12685
- import { readFile as readFile19 } from "fs/promises";
13038
+ import { spawn as spawn6 } from "child_process";
13039
+ import { readFile as readFile20 } from "fs/promises";
12686
13040
  import { existsSync as existsSync9 } from "fs";
12687
- import { join as join33 } from "path";
13041
+ import { join as join32 } from "path";
12688
13042
 
12689
13043
  // src/services/warm-hook-logs-service.ts
12690
- import { mkdir as mkdir18, readFile as readFile18, writeFile as writeFile6, readdir as readdir10, appendFile as appendFile5, unlink as unlink4 } from "fs/promises";
13044
+ import { mkdir as mkdir18, readFile as readFile19, writeFile as writeFile7, readdir as readdir10, appendFile as appendFile5, unlink as unlink4 } from "fs/promises";
12691
13045
  import { homedir as homedir16 } from "os";
12692
- import { join as join32 } from "path";
12693
- var LOGS_DIR2 = join32(homedir16(), ".replicas", "warm-hook-logs");
12694
- var CURRENT_RUN_LOG = join32(LOGS_DIR2, "current-run.log");
13046
+ import { join as join31 } from "path";
13047
+ var LOGS_DIR2 = join31(homedir16(), ".replicas", "warm-hook-logs");
13048
+ var CURRENT_RUN_LOG = join31(LOGS_DIR2, "current-run.log");
12695
13049
  var GLOBAL_FILENAME = "global.json";
12696
13050
  function withPreview2(stored) {
12697
13051
  const preview = buildHookOutputPreview(stored.output);
@@ -12708,7 +13062,7 @@ var WarmHookLogsService = class {
12708
13062
  hookName: "organization",
12709
13063
  ...entry
12710
13064
  };
12711
- await writeFile6(join32(LOGS_DIR2, GLOBAL_FILENAME), `${JSON.stringify(log, null, 2)}
13065
+ await writeFile7(join31(LOGS_DIR2, GLOBAL_FILENAME), `${JSON.stringify(log, null, 2)}
12712
13066
  `, "utf-8");
12713
13067
  }
12714
13068
  async saveEnvironmentHookLog(entry) {
@@ -12718,7 +13072,7 @@ var WarmHookLogsService = class {
12718
13072
  hookName: "environment",
12719
13073
  ...entry
12720
13074
  };
12721
- await writeFile6(join32(LOGS_DIR2, ENVIRONMENT_HOOK_LOG_FILENAME), `${JSON.stringify(log, null, 2)}
13075
+ await writeFile7(join31(LOGS_DIR2, ENVIRONMENT_HOOK_LOG_FILENAME), `${JSON.stringify(log, null, 2)}
12722
13076
  `, "utf-8");
12723
13077
  }
12724
13078
  async saveRepoHookLog(repoName, entry) {
@@ -12728,7 +13082,7 @@ var WarmHookLogsService = class {
12728
13082
  hookName: repoName,
12729
13083
  ...entry
12730
13084
  };
12731
- await writeFile6(join32(LOGS_DIR2, repoHookLogFilename(repoName)), `${JSON.stringify(log, null, 2)}
13085
+ await writeFile7(join31(LOGS_DIR2, repoHookLogFilename(repoName)), `${JSON.stringify(log, null, 2)}
12732
13086
  `, "utf-8");
12733
13087
  }
12734
13088
  async getAllLogs() {
@@ -12747,7 +13101,7 @@ var WarmHookLogsService = class {
12747
13101
  continue;
12748
13102
  }
12749
13103
  try {
12750
- const raw = await readFile18(join32(LOGS_DIR2, file), "utf-8");
13104
+ const raw = await readFile19(join31(LOGS_DIR2, file), "utf-8");
12751
13105
  const stored = JSON.parse(raw);
12752
13106
  logs.push(withPreview2(stored));
12753
13107
  } catch {
@@ -12776,7 +13130,7 @@ var WarmHookLogsService = class {
12776
13130
  }
12777
13131
  async getCurrentRunLog() {
12778
13132
  try {
12779
- return await readFile18(CURRENT_RUN_LOG, "utf-8");
13133
+ return await readFile19(CURRENT_RUN_LOG, "utf-8");
12780
13134
  } catch (err) {
12781
13135
  if (err.code === "ENOENT") return null;
12782
13136
  throw err;
@@ -12785,7 +13139,7 @@ var WarmHookLogsService = class {
12785
13139
  async getFullOutput(hookType, hookName) {
12786
13140
  const filename = hookType === "global" ? GLOBAL_FILENAME : hookType === "environment" ? ENVIRONMENT_HOOK_LOG_FILENAME : repoHookLogFilename(hookName);
12787
13141
  try {
12788
- const raw = await readFile18(join32(LOGS_DIR2, filename), "utf-8");
13142
+ const raw = await readFile19(join31(LOGS_DIR2, filename), "utf-8");
12789
13143
  const stored = JSON.parse(raw);
12790
13144
  if (stored.hookType !== hookType || stored.hookName !== hookName) {
12791
13145
  return null;
@@ -12804,12 +13158,12 @@ var warmHookLogsService = new WarmHookLogsService();
12804
13158
  // src/services/warm-hooks-service.ts
12805
13159
  async function readRepoWarmHook(repoPath) {
12806
13160
  for (const filename of REPLICAS_CONFIG_FILENAMES) {
12807
- const configPath = join33(repoPath, filename);
13161
+ const configPath = join32(repoPath, filename);
12808
13162
  if (!existsSync9(configPath)) {
12809
13163
  continue;
12810
13164
  }
12811
13165
  try {
12812
- const raw = await readFile19(configPath, "utf-8");
13166
+ const raw = await readFile20(configPath, "utf-8");
12813
13167
  const config = parseReplicasConfigString(raw, filename);
12814
13168
  if (!config.warmHook) {
12815
13169
  return null;
@@ -12856,7 +13210,7 @@ async function executeHookScriptStreaming(params) {
12856
13210
  params.onChunk(`$ ${params.label}
12857
13211
  `);
12858
13212
  return new Promise((resolve5) => {
12859
- const proc = spawn5("bash", ["-lc", params.content], {
13213
+ const proc = spawn6("bash", ["-lc", params.content], {
12860
13214
  cwd: params.cwd,
12861
13215
  env: process.env,
12862
13216
  stdio: ["pipe", "pipe", "pipe"]
@@ -13065,9 +13419,9 @@ ${combinedScript}` : combinedScript;
13065
13419
  }
13066
13420
 
13067
13421
  // src/services/terminal-service.ts
13068
- import { randomUUID as randomUUID8 } from "crypto";
13422
+ import { randomUUID as randomUUID9 } from "crypto";
13069
13423
  import { existsSync as existsSync10 } from "fs";
13070
- import { spawn as spawn6 } from "node-pty";
13424
+ import { spawn as spawn7 } from "node-pty";
13071
13425
  var MAX_REPLAY_CHARS = 1024 * 1024;
13072
13426
  var MAX_TERMINAL_SESSIONS = 8;
13073
13427
  var MAX_PENDING_INPUT = 64;
@@ -13084,9 +13438,9 @@ var TerminalService = class {
13084
13438
  code: "limit"
13085
13439
  });
13086
13440
  }
13087
- const id = randomUUID8();
13441
+ const id = randomUUID9();
13088
13442
  const shell = process.env.SHELL && existsSync10(process.env.SHELL) ? process.env.SHELL : "/bin/bash";
13089
- const pty = spawn6(shell, ["-l"], {
13443
+ const pty = spawn7(shell, ["-l"], {
13090
13444
  name: "xterm-256color",
13091
13445
  cols,
13092
13446
  rows,
@@ -14088,7 +14442,7 @@ data: ${JSON.stringify("Terminal session not found")}
14088
14442
  const logFiles = files.filter((f) => f.endsWith(".log"));
14089
14443
  const sessions = await Promise.all(
14090
14444
  logFiles.map(async (filename) => {
14091
- const filePath = join34(LOG_DIR, filename);
14445
+ const filePath = join33(LOG_DIR, filename);
14092
14446
  const fileStat = await stat7(filePath);
14093
14447
  const sessionId = filename.replace(/\.log$/, "");
14094
14448
  return {
@@ -14123,7 +14477,7 @@ data: ${JSON.stringify("Terminal session not found")}
14123
14477
  }
14124
14478
  let content;
14125
14479
  try {
14126
- content = await readFile20(filePath, "utf-8");
14480
+ content = await readFile21(filePath, "utf-8");
14127
14481
  } catch {
14128
14482
  return c.json(jsonError("Log session not found"), 404);
14129
14483
  }
@@ -14399,7 +14753,7 @@ function startStatusBroadcaster() {
14399
14753
  if (serialized !== previousRepoStatus) {
14400
14754
  previousRepoStatus = serialized;
14401
14755
  eventService.publish({
14402
- id: randomUUID9(),
14756
+ id: randomUUID10(),
14403
14757
  ts: (/* @__PURE__ */ new Date()).toISOString(),
14404
14758
  type: "repo.status.changed",
14405
14759
  payload: { repos }
@@ -14420,7 +14774,7 @@ function startStatusBroadcaster() {
14420
14774
  if (engineStatusJson !== previousEngineStatus) {
14421
14775
  previousEngineStatus = engineStatusJson;
14422
14776
  eventService.publish({
14423
- id: randomUUID9(),
14777
+ id: randomUUID10(),
14424
14778
  ts: (/* @__PURE__ */ new Date()).toISOString(),
14425
14779
  type: "engine.status.changed",
14426
14780
  payload: { status: engineStatus }
@@ -14439,7 +14793,7 @@ function startStatusBroadcaster() {
14439
14793
  previousHookStatus = hookSnapshot;
14440
14794
  if (!lastHooksRunning && hooksRunning) {
14441
14795
  eventService.publish({
14442
- id: randomUUID9(),
14796
+ id: randomUUID10(),
14443
14797
  ts: (/* @__PURE__ */ new Date()).toISOString(),
14444
14798
  type: "hooks.started",
14445
14799
  payload: { running: true, completed: false }
@@ -14448,7 +14802,7 @@ function startStatusBroadcaster() {
14448
14802
  }
14449
14803
  if (hooksRunning) {
14450
14804
  eventService.publish({
14451
- id: randomUUID9(),
14805
+ id: randomUUID10(),
14452
14806
  ts: (/* @__PURE__ */ new Date()).toISOString(),
14453
14807
  type: "hooks.progress",
14454
14808
  payload: { running: true, completed: false }
@@ -14457,7 +14811,7 @@ function startStatusBroadcaster() {
14457
14811
  }
14458
14812
  if (lastHooksRunning && !hooksRunning && hooksCompleted && !hooksFailed) {
14459
14813
  eventService.publish({
14460
- id: randomUUID9(),
14814
+ id: randomUUID10(),
14461
14815
  ts: (/* @__PURE__ */ new Date()).toISOString(),
14462
14816
  type: "hooks.completed",
14463
14817
  payload: { running: false, completed: true }
@@ -14466,7 +14820,7 @@ function startStatusBroadcaster() {
14466
14820
  }
14467
14821
  if (lastHooksRunning && !hooksRunning && hooksFailed) {
14468
14822
  eventService.publish({
14469
- id: randomUUID9(),
14823
+ id: randomUUID10(),
14470
14824
  ts: (/* @__PURE__ */ new Date()).toISOString(),
14471
14825
  type: "hooks.failed",
14472
14826
  payload: { running: false, completed: hooksCompleted }
@@ -14474,7 +14828,7 @@ function startStatusBroadcaster() {
14474
14828
  });
14475
14829
  }
14476
14830
  eventService.publish({
14477
- id: randomUUID9(),
14831
+ id: randomUUID10(),
14478
14832
  ts: (/* @__PURE__ */ new Date()).toISOString(),
14479
14833
  type: "hooks.status",
14480
14834
  payload: {
@@ -14529,20 +14883,20 @@ serve(
14529
14883
  }
14530
14884
  const repos = await gitService.listRepos();
14531
14885
  await eventService.publish({
14532
- id: randomUUID9(),
14886
+ id: randomUUID10(),
14533
14887
  ts: (/* @__PURE__ */ new Date()).toISOString(),
14534
14888
  type: "repo.discovered",
14535
14889
  payload: { repos }
14536
14890
  });
14537
14891
  const repoStatuses = await gitService.listRepos();
14538
14892
  await eventService.publish({
14539
- id: randomUUID9(),
14893
+ id: randomUUID10(),
14540
14894
  ts: (/* @__PURE__ */ new Date()).toISOString(),
14541
14895
  type: "repo.status.changed",
14542
14896
  payload: { repos: repoStatuses }
14543
14897
  });
14544
14898
  await eventService.publish({
14545
- id: randomUUID9(),
14899
+ id: randomUUID10(),
14546
14900
  ts: (/* @__PURE__ */ new Date()).toISOString(),
14547
14901
  type: "engine.ready",
14548
14902
  payload: { version: "v1" }