replicas-engine 0.1.665 → 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-SBJ4HUPK.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
  };
@@ -8552,9 +8559,14 @@ ${request.message}` : request.message;
8552
8559
  };
8553
8560
 
8554
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
8555
8566
  import { spawn as spawn4 } from "child_process";
8556
- import { mkdir as mkdir13, readFile as readFile11 } from "fs/promises";
8557
- 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";
8558
8570
  import { Writable } from "stream";
8559
8571
  import {
8560
8572
  PROTOCOL_VERSION,
@@ -8562,21 +8574,12 @@ import {
8562
8574
  methods,
8563
8575
  ndJsonStream
8564
8576
  } from "@agentclientprotocol/sdk";
8565
- async function ensureResolvableDnsConfig() {
8566
- const contents = await readFile11("/etc/resolv.conf");
8567
- if (contents.length === 0 || contents.at(-1) === 10) return;
8568
- await new Promise((resolve5, reject) => {
8569
- const child = spawn4("sudo", ["tee", "-a", "/etc/resolv.conf"], { stdio: ["pipe", "ignore", "pipe"] });
8570
- let stderr = "";
8571
- child.stderr.on("data", (chunk) => {
8572
- stderr += chunk;
8573
- });
8574
- child.on("error", reject);
8575
- child.on("exit", (code) => code === 0 ? resolve5() : reject(new Error(`Failed to normalize /etc/resolv.conf: ${stderr.trim()}`)));
8576
- child.stdin.end("\n");
8577
- });
8577
+ function selectOptions(option) {
8578
+ if (option.type !== "select") return [];
8579
+ return option.options.flatMap((entry) => "group" in entry ? entry.options : [entry]);
8578
8580
  }
8579
- var FxManager = class extends CodingAgentManager {
8581
+ var AcpManager = class extends CodingAgentManager {
8582
+ acp;
8580
8583
  historyFile;
8581
8584
  historyFilePath;
8582
8585
  process = null;
@@ -8584,42 +8587,90 @@ var FxManager = class extends CodingAgentManager {
8584
8587
  context = null;
8585
8588
  sessionId = null;
8586
8589
  activeModel = null;
8590
+ activeThinkingLevel;
8587
8591
  planMode = false;
8592
+ replaying = false;
8588
8593
  slashCommands = [];
8594
+ configOptions = [];
8595
+ modes = null;
8596
+ capabilities = {};
8597
+ interactiveTools = false;
8598
+ pendingPermissions = /* @__PURE__ */ new Map();
8599
+ terminals = /* @__PURE__ */ new Map();
8589
8600
  constructor(options) {
8590
- super(options);
8591
- 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;
8592
8604
  this.historyFile = new CodexHistoryFile(this.historyFilePath);
8593
8605
  this.initializeManager(this.processMessageInternal.bind(this));
8594
8606
  }
8595
8607
  async initialize() {
8596
8608
  await mkdir13(dirname7(this.historyFilePath), { recursive: true });
8597
- await ensureResolvableDnsConfig();
8609
+ await this.acp.prepare?.();
8598
8610
  }
8599
8611
  getHistorySink() {
8600
8612
  return this.historyFile;
8601
8613
  }
8602
- async start(model) {
8603
- 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;
8604
8616
  this.dispose();
8605
- 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), {
8606
8619
  cwd: this.workingDirectory,
8607
- env: { ...process.env, FX_AUTO_UPGRADE: "0", NO_COLOR: "1" },
8620
+ env: { ...process.env, NO_COLOR: "1", ...agentEnv },
8608
8621
  stdio: ["pipe", "pipe", "pipe"]
8609
8622
  });
8610
8623
  this.process = child;
8611
- 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()));
8612
8625
  child.on("exit", (code, signal) => {
8613
8626
  if (this.process !== child) return;
8614
8627
  this.process = null;
8615
8628
  this.context = null;
8616
8629
  this.connection = null;
8617
8630
  if (code !== 0 && signal !== "SIGTERM") {
8618
- 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);
8619
8635
  }
8620
8636
  });
8621
- const app2 = client({ name: "Replicas" }).onNotification(methods.client.session.update, ({ params }) => this.recordUpdate(params)).onRequest(methods.client.session.requestPermission, ({ params }) => {
8622
- 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];
8623
8674
  return option ? { outcome: { outcome: "selected", optionId: option.optionId } } : { outcome: { outcome: "cancelled" } };
8624
8675
  });
8625
8676
  const stdout = new ReadableStream({
@@ -8632,26 +8683,34 @@ var FxManager = class extends CodingAgentManager {
8632
8683
  child.stdout.destroy();
8633
8684
  }
8634
8685
  });
8635
- const stream = ndJsonStream(
8636
- Writable.toWeb(child.stdin),
8637
- stdout
8638
- );
8639
- this.connection = app2.connect(stream);
8686
+ this.connection = app2.connect(ndJsonStream(Writable.toWeb(child.stdin), stdout));
8640
8687
  this.context = this.connection.agent;
8641
- await this.context.request(methods.agent.initialize, {
8688
+ const initialized = await this.context.request(methods.agent.initialize, {
8642
8689
  protocolVersion: PROTOCOL_VERSION,
8643
- clientCapabilities: { plan: {} },
8690
+ clientCapabilities: {
8691
+ fs: { readTextFile: true, writeTextFile: true },
8692
+ terminal: true,
8693
+ plan: {}
8694
+ },
8644
8695
  clientInfo: { name: "Replicas", version: "1" }
8645
8696
  });
8697
+ this.capabilities = initialized.agentCapabilities ?? {};
8646
8698
  const additionalDirectories = await getAgentAdditionalDirectories();
8647
8699
  const sessionId = this.sessionId ?? this.initialSessionId;
8648
- if (sessionId) {
8649
- await this.context.request(methods.agent.session.load, {
8650
- sessionId,
8651
- cwd: this.workingDirectory,
8652
- additionalDirectories,
8653
- mcpServers: []
8654
- });
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
+ }
8655
8714
  this.sessionId = sessionId;
8656
8715
  } else {
8657
8716
  const session = await this.context.request(methods.agent.session.new, {
@@ -8660,21 +8719,84 @@ var FxManager = class extends CodingAgentManager {
8660
8719
  mcpServers: []
8661
8720
  });
8662
8721
  this.sessionId = session.sessionId;
8722
+ this.configOptions = session.configOptions ?? [];
8723
+ this.modes = session.modes ?? null;
8663
8724
  await this.onSaveSessionId(session.sessionId);
8664
8725
  }
8665
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 };
8666
8783
  }
8667
8784
  recordUpdate(notification) {
8668
- if (notification.sessionId !== this.sessionId) return;
8669
- if (notification.update.sessionUpdate === "available_commands_update") {
8670
- this.slashCommands = mergeSlashCommands(notification.update.availableCommands.flatMap((command) => {
8671
- 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);
8672
8790
  return parsed ? [parsed] : [];
8673
8791
  }));
8674
- } else if (notification.update.sessionUpdate === "usage_update") {
8675
- 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;
8676
8798
  this.historyFile.append(this.emitContextUsage({
8677
- provider: "fx",
8799
+ provider: this.acp.provider,
8678
8800
  source: "provider_usage",
8679
8801
  model: this.activeModel,
8680
8802
  totalTokens: used,
@@ -8684,37 +8806,155 @@ var FxManager = class extends CodingAgentManager {
8684
8806
  updatedAt: (/* @__PURE__ */ new Date()).toISOString()
8685
8807
  }));
8686
8808
  }
8687
- 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();
8688
8924
  }
8689
8925
  async processMessageInternal(request) {
8690
8926
  try {
8691
- if (request.images?.length) throw new Error("fx does not support image prompts over ACP yet.");
8692
- const model = request.model ?? DEFAULT_FX_MODEL;
8693
- await this.start(model);
8927
+ const model = request.model ?? this.acp.defaultModel;
8928
+ await this.start(model, request.thinkingLevel);
8694
8929
  const context = this.context;
8695
8930
  const sessionId = this.sessionId;
8696
- 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.`);
8697
8932
  this.recordHistoryEvent("event_msg", { type: "user_message", message: request.message }, this.historyFile);
8698
8933
  this.planMode = request.planMode ?? false;
8699
- await context.request(methods.agent.session.setMode, {
8700
- sessionId,
8701
- modeId: this.planMode ? "ask" : "code"
8702
- });
8703
- await context.request(methods.agent.session.prompt, {
8704
- sessionId,
8705
- prompt: [{
8706
- type: "text",
8707
- text: this.planMode ? `Plan the requested work without modifying files or executing mutating commands. Return a clear implementation plan only.
8708
-
8709
- ${request.message}` : request.message
8710
- }]
8711
- });
8712
- 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);
8713
8949
  } catch (error) {
8714
- 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);
8715
8954
  throw error;
8716
8955
  } finally {
8717
8956
  this.planMode = false;
8957
+ this.interactiveTools = false;
8718
8958
  try {
8719
8959
  await this.historyFile.flush();
8720
8960
  } finally {
@@ -8724,8 +8964,17 @@ ${request.message}` : request.message
8724
8964
  }
8725
8965
  async interruptActiveTurn() {
8726
8966
  if (!this.context || !this.sessionId) return;
8967
+ this.abortPendingPermissions();
8727
8968
  await this.context.notify(methods.agent.session.cancel, { sessionId: this.sessionId });
8728
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
+ }
8729
8978
  async getHistory(page = {}) {
8730
8979
  await this.historyFile.flush();
8731
8980
  return {
@@ -8739,20 +8988,105 @@ ${request.message}` : request.message
8739
8988
  return this.slashCommands;
8740
8989
  }
8741
8990
  dispose() {
8991
+ for (const terminal of this.terminals.values()) {
8992
+ if (terminal.exitStatus === null) terminal.process.kill("SIGTERM");
8993
+ }
8994
+ this.terminals.clear();
8742
8995
  this.connection?.close();
8743
8996
  this.process?.kill("SIGTERM");
8744
8997
  this.connection = null;
8745
8998
  this.context = null;
8746
8999
  this.process = null;
8747
9000
  this.activeModel = null;
9001
+ this.activeThinkingLevel = void 0;
9002
+ this.configOptions = [];
9003
+ this.modes = null;
9004
+ this.capabilities = {};
8748
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
+ });
8749
9083
  }
8750
9084
  };
8751
9085
 
8752
9086
  // src/managers/opencode-manager.ts
8753
9087
  import { existsSync as existsSync7 } from "fs";
8754
- import { mkdir as mkdir14, readFile as readFile12 } from "fs/promises";
8755
- 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";
8756
9090
  import { randomBytes as randomBytes2 } from "crypto";
8757
9091
  import { fileURLToPath as fileURLToPath2 } from "url";
8758
9092
  import { Agent } from "undici";
@@ -8799,7 +9133,7 @@ async function getAllowedOpenRouterModels() {
8799
9133
 
8800
9134
  // src/managers/opencode-manager.ts
8801
9135
  var OPENCODE_SHIM_DIR = dirname8(fileURLToPath2(new URL("../../scripts/opencode", import.meta.url)));
8802
- 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");
8803
9137
  var OPENCODE_FETCH_DISPATCHER = new Agent({ headersTimeout: 0, bodyTimeout: 0 });
8804
9138
  var OPENCODE_SERVER_STARTUP_TIMEOUT_MS = 3e4;
8805
9139
  var OPENCODE_WORKSPACE_PERMISSION = {
@@ -8836,7 +9170,7 @@ var opencodeAuthSchema = z4.record(z4.string(), z4.object({
8836
9170
  async function hasOpencodeCredentials(provider) {
8837
9171
  if (!existsSync7(OPENCODE_AUTH_PATH)) return false;
8838
9172
  try {
8839
- 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")));
8840
9174
  return auth.success && auth.data[provider]?.type === "api" && Boolean(auth.data[provider]?.key);
8841
9175
  } catch {
8842
9176
  return false;
@@ -8941,7 +9275,7 @@ function isOpencodeMcpEntry(value) {
8941
9275
  async function readProvisionedOpencodeMcpConfig() {
8942
9276
  let raw;
8943
9277
  try {
8944
- raw = await readFile12(OPENCODE_CONFIG_PATH, "utf8");
9278
+ raw = await readFile13(OPENCODE_CONFIG_PATH, "utf8");
8945
9279
  } catch (error) {
8946
9280
  if (isRecord2(error) && error.code === "ENOENT") return void 0;
8947
9281
  console.error("[OpencodeManager] Failed to read Opencode config:", error);
@@ -9049,7 +9383,7 @@ var OpencodeManager = class extends CodingAgentManager {
9049
9383
  constructor(options) {
9050
9384
  super(options);
9051
9385
  this.sessionId = options.initialSessionId;
9052
- 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");
9053
9387
  this.historyFile = new CodexHistoryFile(this.historyFilePath);
9054
9388
  this.initializeManager(this.processMessageInternal.bind(this));
9055
9389
  }
@@ -9559,7 +9893,7 @@ var OpencodeManager = class extends CodingAgentManager {
9559
9893
 
9560
9894
  // src/managers/pi-manager.ts
9561
9895
  import { mkdir as mkdir15 } from "fs/promises";
9562
- import { dirname as dirname9, join as join24 } from "path";
9896
+ import { dirname as dirname9, join as join23 } from "path";
9563
9897
  import {
9564
9898
  AuthStorage,
9565
9899
  createAgentSession,
@@ -9642,7 +9976,7 @@ var PiManager = class extends CodingAgentManager {
9642
9976
  providerApiKey = null;
9643
9977
  constructor(options) {
9644
9978
  super(options);
9645
- this.historyFilePath = options.historyFilePath ?? join24(PI_HISTORY_DIR, `${Date.now()}.jsonl`);
9979
+ this.historyFilePath = options.historyFilePath ?? join23(PI_HISTORY_DIR, `${Date.now()}.jsonl`);
9646
9980
  this.historyFile = new CodexHistoryFile(this.historyFilePath);
9647
9981
  this.initializeManager(this.processMessageInternal.bind(this));
9648
9982
  }
@@ -9742,7 +10076,7 @@ var PiManager = class extends CodingAgentManager {
9742
10076
  const sessionManager = this.initialSessionId ? SessionManager.open(this.initialSessionId, PI_HISTORY_DIR, this.workingDirectory) : SessionManager.create(this.workingDirectory, PI_HISTORY_DIR);
9743
10077
  const resourceLoader = new DefaultResourceLoader({
9744
10078
  cwd: this.workingDirectory,
9745
- agentDir: join24(ENGINE_ENV.HOME_DIR, ".pi", "agent"),
10079
+ agentDir: join23(ENGINE_ENV.HOME_DIR, ".pi", "agent"),
9746
10080
  extensionFactories: [registerCommandProtection(
9747
10081
  this.workingDirectory,
9748
10082
  this.historyFile,
@@ -9819,6 +10153,7 @@ function getAvailableRelayProviders(availability) {
9819
10153
  const cursorAvailable = availability.cursorAvailable ?? false;
9820
10154
  const deepseekAvailable = availability.deepseekAvailable ?? false;
9821
10155
  const fxAvailable = availability.fxAvailable ?? false;
10156
+ const kimiAvailable = availability.kimiAvailable ?? false;
9822
10157
  const opencodeAvailable = availability.opencodeAvailable ?? false;
9823
10158
  const piAvailable = availability.piAvailable ?? false;
9824
10159
  const providers = ["claude"];
@@ -9826,6 +10161,7 @@ function getAvailableRelayProviders(availability) {
9826
10161
  if (cursorAvailable) providers.push("cursor");
9827
10162
  if (deepseekAvailable) providers.push("deepseek");
9828
10163
  if (fxAvailable) providers.push("fx");
10164
+ if (kimiAvailable) providers.push("kimi");
9829
10165
  if (opencodeAvailable) providers.push("opencode");
9830
10166
  if (piAvailable) providers.push("pi");
9831
10167
  providers.push("relay");
@@ -9915,7 +10251,7 @@ function extractFinalResponse(history) {
9915
10251
  const text = getDeepseekAssistantMessageText(payload.event, "text");
9916
10252
  if (text) return text;
9917
10253
  }
9918
- if (event.type === "fx-session-update") {
10254
+ if (event.type === "fx-session-update" || event.type === "acp-session-update") {
9919
10255
  const text = getFxTextChunk(payload.update, "agent_message_chunk");
9920
10256
  if (text) return text;
9921
10257
  }
@@ -9958,11 +10294,12 @@ You will also receive the chatId so you can send follow-up messages or clean up
9958
10294
  `Cursor: ${AGENT_MODELS.cursor.join(", ")}.`,
9959
10295
  `DeepSeek Harness: ${AGENT_MODELS.deepseek.join(", ")}.`,
9960
10296
  `fx: ${AGENT_MODELS.fx.join(", ")}.`,
10297
+ `Kimi Code: ${AGENT_MODELS.kimi.join(", ")}.`,
9961
10298
  `Opencode: ${AGENT_MODELS.opencode.join(", ")}.`,
9962
10299
  `Pi: ${AGENT_MODELS.pi.join(", ")}.`
9963
10300
  ].join(" ")),
9964
10301
  thinking_level: z5.enum(VALID_THINKING_LEVELS).optional().describe(
9965
- "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."
9966
10303
  ),
9967
10304
  title: z5.string().optional().describe("Optional title for the subagent chat (for identification)."),
9968
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.")
@@ -10214,14 +10551,15 @@ function getUsingToolsSection() {
10214
10551
  ];
10215
10552
  return [`# Using your tools`, ...prependBullets(items)].join("\n");
10216
10553
  }
10217
- function getDelegationSection(codexAvailable, cursorAvailable, deepseekAvailable, fxAvailable, opencodeAvailable, piAvailable) {
10218
- 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(", ");
10219
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).`;
10220
10557
  const claudeModelList = AGENT_MODELS.claude.join(", ");
10221
10558
  const extraAgentLines = [
10222
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,
10223
10560
  deepseekAvailable ? `Use provider 'deepseek' for tasks that benefit from the DeepSeek Harness tool loop. Suggested models: ${AGENT_MODELS.deepseek.join(", ")}.` : null,
10224
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,
10225
10563
  opencodeAvailable ? `Use provider 'opencode' for cheaper routine implementation tasks through OpenRouter-backed open source models. Suggested models: ${AGENT_MODELS.opencode.join(", ")}.` : null,
10226
10564
  piAvailable ? `Use provider 'pi' for coding tasks through Pi's OpenRouter-backed coding agent. Suggested models: ${AGENT_MODELS.pi.join(", ")}.` : null,
10227
10565
  cursorAvailable ? `Use provider 'cursor' for fast iteration on code changes. Suggested models: ${AGENT_MODELS.cursor.join(", ")}.` : null
@@ -10346,14 +10684,14 @@ function getEnvironmentSection() {
10346
10684
  ].join("\n");
10347
10685
  }
10348
10686
  function buildRelaySystemPrompt(options) {
10349
- const { customInstructions, codexAvailable, cursorAvailable, deepseekAvailable, fxAvailable, opencodeAvailable, piAvailable } = options ?? {};
10687
+ const { customInstructions, codexAvailable, cursorAvailable, deepseekAvailable, fxAvailable, kimiAvailable, opencodeAvailable, piAvailable } = options ?? {};
10350
10688
  const sections = [
10351
10689
  getIntroSection(),
10352
10690
  getSystemSection(),
10353
10691
  getDoingTasksSection(),
10354
10692
  getActionsSection(),
10355
10693
  getUsingToolsSection(),
10356
- 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),
10357
10695
  getToneAndStyleSection(),
10358
10696
  getOutputEfficiencySection(),
10359
10697
  getEnvironmentSection(),
@@ -10387,9 +10725,10 @@ var RelayManager = class {
10387
10725
  const cursorAvailable = options.cursorAvailable ?? false;
10388
10726
  const deepseekAvailable = options.deepseekAvailable ?? false;
10389
10727
  const fxAvailable = options.fxAvailable ?? false;
10728
+ const kimiAvailable = options.kimiAvailable ?? false;
10390
10729
  const opencodeAvailable = options.opencodeAvailable ?? false;
10391
10730
  const piAvailable = options.piAvailable ?? false;
10392
- const availability = { codexAvailable, cursorAvailable, deepseekAvailable, fxAvailable, opencodeAvailable, piAvailable };
10731
+ const availability = { codexAvailable, cursorAvailable, deepseekAvailable, fxAvailable, kimiAvailable, opencodeAvailable, piAvailable };
10393
10732
  const getProviderAvailability = options.getProviderAvailability ?? (() => availability);
10394
10733
  this.inner = new ClaudeManager({
10395
10734
  ...options,
@@ -10454,16 +10793,16 @@ var RelayManager = class {
10454
10793
  import {
10455
10794
  appendFile as appendFile3,
10456
10795
  mkdir as mkdir16,
10457
- readFile as readFile13,
10796
+ readFile as readFile14,
10458
10797
  readdir as readdir6,
10459
10798
  rename as rename2,
10460
10799
  unlink as unlink3
10461
10800
  } from "fs/promises";
10462
- import { join as join25 } from "path";
10463
- import { randomUUID as randomUUID6 } from "crypto";
10801
+ import { join as join24 } from "path";
10802
+ import { randomUUID as randomUUID7 } from "crypto";
10464
10803
 
10465
10804
  // src/analytics/agent/activity/skill-mcp-call-extractor.ts
10466
- 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"]);
10467
10806
  function mcpNameFromToolCall(message) {
10468
10807
  const parsedName = parseMcpToolName(message.tool);
10469
10808
  if (parsedName) return parsedName.server;
@@ -10510,7 +10849,7 @@ var AgentChatActivityBuffer = class {
10510
10849
  options.storageName,
10511
10850
  ...options.legacyStorageNames ?? []
10512
10851
  ];
10513
- this.liveFile = join25(ENGINE_DIR2, `${options.storageName}.jsonl`);
10852
+ this.liveFile = join24(ENGINE_DIR2, `${options.storageName}.jsonl`);
10514
10853
  this.segmentFilePatterns = this.storageNames.map(
10515
10854
  (storageName) => new RegExp(`^${storageName}\\.(\\d+)\\.jsonl$`)
10516
10855
  );
@@ -10569,8 +10908,8 @@ var AgentChatActivityBuffer = class {
10569
10908
  await Promise.allSettled([...this.pendingAppends]);
10570
10909
  for (const storageName of this.storageNames) {
10571
10910
  await rename2(
10572
- join25(ENGINE_DIR2, `${storageName}.jsonl`),
10573
- join25(ENGINE_DIR2, `${storageName}.${Date.now()}.jsonl`)
10911
+ join24(ENGINE_DIR2, `${storageName}.jsonl`),
10912
+ join24(ENGINE_DIR2, `${storageName}.${Date.now()}.jsonl`)
10574
10913
  ).catch(() => {
10575
10914
  });
10576
10915
  }
@@ -10581,7 +10920,7 @@ var AgentChatActivityBuffer = class {
10581
10920
  if (!this.segmentFilePatterns.some((pattern) => pattern.test(entry)))
10582
10921
  continue;
10583
10922
  try {
10584
- await this.uploadSegment(join25(ENGINE_DIR2, entry));
10923
+ await this.uploadSegment(join24(ENGINE_DIR2, entry));
10585
10924
  flushed++;
10586
10925
  } catch (error) {
10587
10926
  failed++;
@@ -10613,13 +10952,13 @@ var AgentChatActivityBuffer = class {
10613
10952
  ) && entry.endsWith(UPLOADED_SUFFIX)
10614
10953
  ).sort();
10615
10954
  for (const entry of uploaded.slice(0, -MAX_UPLOADED_SEGMENTS)) {
10616
- await unlink3(join25(ENGINE_DIR2, entry)).catch(() => {
10955
+ await unlink3(join24(ENGINE_DIR2, entry)).catch(() => {
10617
10956
  });
10618
10957
  }
10619
10958
  return { flushed, failed };
10620
10959
  }
10621
10960
  async uploadSegment(filePath) {
10622
- const records = (await readFile13(filePath, "utf-8")).split("\n").flatMap((line) => {
10961
+ const records = (await readFile14(filePath, "utf-8")).split("\n").flatMap((line) => {
10623
10962
  try {
10624
10963
  const parsed = JSON.parse(line);
10625
10964
  return this.options.validate(parsed) ? [parsed] : [];
@@ -10681,7 +11020,7 @@ var AgentChatTurnActivityTracker = class {
10681
11020
  const credential = ENGINE_ENV.REPLICAS_AGENT_CREDENTIALS[provider];
10682
11021
  this.pendingMessages.delete(messageId);
10683
11022
  this.activeTurns.set(chatId, {
10684
- turnId: randomUUID6(),
11023
+ turnId: randomUUID7(),
10685
11024
  startedAtMs: Date.now(),
10686
11025
  provider,
10687
11026
  model: attributes.model ?? getDefaultAgentModel(provider),
@@ -10844,19 +11183,19 @@ var KeepAliveService = class _KeepAliveService {
10844
11183
  var keepAliveService = new KeepAliveService();
10845
11184
 
10846
11185
  // src/services/canvas-service.ts
10847
- 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";
10848
11187
  import { homedir as homedir14 } from "os";
10849
- import { join as join26 } from "path";
11188
+ import { join as join25 } from "path";
10850
11189
  var GLOBAL_CANVAS_DIRECTORIES = [
10851
- join26(homedir14(), ".claude", "plans"),
10852
- join26(process.env.XDG_DATA_HOME ?? join26(homedir14(), ".local", "share"), "opencode", "plans"),
10853
- 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")
10854
11193
  ];
10855
11194
  async function canvasDirectories() {
10856
11195
  const repositories = await gitService.listRepositories().catch(() => []);
10857
11196
  return [
10858
11197
  ...GLOBAL_CANVAS_DIRECTORIES,
10859
- ...repositories.map((repository) => join26(repository.path, ".opencode", "plans"))
11198
+ ...repositories.map((repository) => join25(repository.path, ".opencode", "plans"))
10860
11199
  ];
10861
11200
  }
10862
11201
  var CanvasService = class {
@@ -10880,7 +11219,7 @@ var CanvasService = class {
10880
11219
  for (const entry of entries) {
10881
11220
  if (entry.name.startsWith(".")) continue;
10882
11221
  const filename = current.relativePath ? `${current.relativePath}/${entry.name}` : entry.name;
10883
- const filePath = join26(current.directory, entry.name);
11222
+ const filePath = join25(current.directory, entry.name);
10884
11223
  if (entry.isDirectory()) {
10885
11224
  pending.push({ directory: filePath, relativePath: filename });
10886
11225
  continue;
@@ -10905,7 +11244,7 @@ var CanvasService = class {
10905
11244
  if (!safe) return null;
10906
11245
  const { kind, mimeType } = classifyCanvasFilename(safe);
10907
11246
  for (const directory of await this.directories()) {
10908
- const filePath = join26(directory, safe);
11247
+ const filePath = join25(directory, safe);
10909
11248
  let sizeBytes = 0;
10910
11249
  let updatedAt = "";
10911
11250
  try {
@@ -10926,7 +11265,7 @@ var CanvasService = class {
10926
11265
  };
10927
11266
  }
10928
11267
  try {
10929
- const bytes = await readFile14(filePath);
11268
+ const bytes = await readFile15(filePath);
10930
11269
  return { filename: safe, kind, sizeBytes, mimeType, updatedAt, bytes };
10931
11270
  } catch {
10932
11271
  continue;
@@ -11076,14 +11415,14 @@ async function reconcileCanvasItems(filenames) {
11076
11415
  // src/services/upload-chat-transcripts.ts
11077
11416
  import { createReadStream } from "fs";
11078
11417
  import { createHash as createHash2 } from "crypto";
11079
- import { readFile as readFile15, readdir as readdir8, stat as stat4 } from "fs/promises";
11080
- 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";
11081
11420
 
11082
11421
  // src/services/chat/chat-senders.ts
11083
- import { join as join27 } from "path";
11084
- 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");
11085
11424
  function chatMessageSendersFilePath(chatId) {
11086
- return join27(CHAT_SENDERS_DIR, `${chatId}.jsonl`);
11425
+ return join26(CHAT_SENDERS_DIR, `${chatId}.jsonl`);
11087
11426
  }
11088
11427
  function parseChatMessageSendersJsonl(content) {
11089
11428
  return content.split("\n").flatMap((line) => {
@@ -11099,9 +11438,9 @@ function parseChatMessageSendersJsonl(content) {
11099
11438
 
11100
11439
  // src/services/upload-chat-transcripts.ts
11101
11440
  var HISTORY_DIRS = [
11102
- join28(ENGINE_DIR2, "claude-histories"),
11103
- join28(ENGINE_DIR2, "relay-histories"),
11104
- join28(ENGINE_DIR2, "codex-histories")
11441
+ join27(ENGINE_DIR2, "claude-histories"),
11442
+ join27(ENGINE_DIR2, "relay-histories"),
11443
+ join27(ENGINE_DIR2, "codex-histories")
11105
11444
  ];
11106
11445
  async function flushAllChatTranscripts(chatsById = /* @__PURE__ */ new Map(), capture) {
11107
11446
  let flushed = 0;
@@ -11119,7 +11458,7 @@ async function flushAllChatTranscripts(chatsById = /* @__PURE__ */ new Map(), ca
11119
11458
  if (!entry.endsWith(".jsonl")) continue;
11120
11459
  const chatId = basename2(entry, ".jsonl");
11121
11460
  tasks.push(
11122
- uploadChatTranscript(chatId, join28(dir, entry), chatsById.get(chatId), capture).then((artifact) => {
11461
+ uploadChatTranscript(chatId, join27(dir, entry), chatsById.get(chatId), capture).then((artifact) => {
11123
11462
  flushed++;
11124
11463
  if (artifact && capture) revisions.push(artifact);
11125
11464
  }).catch((err) => {
@@ -11135,7 +11474,7 @@ async function flushAllChatTranscripts(chatsById = /* @__PURE__ */ new Map(), ca
11135
11474
  async function uploadChatTranscript(chatId, filePath, chat, capture) {
11136
11475
  const { size } = await stat4(filePath);
11137
11476
  if (size === 0) return null;
11138
- const historyPages = createChatTranscriptPages(await readFile15(filePath));
11477
+ const historyPages = createChatTranscriptPages(await readFile16(filePath));
11139
11478
  const metadata = chat ? {
11140
11479
  provider: chat.provider,
11141
11480
  credential: ENGINE_ENV.REPLICAS_AGENT_CREDENTIALS[chat.provider],
@@ -11154,7 +11493,7 @@ async function uploadChatTranscript(chatId, filePath, chat, capture) {
11154
11493
  }
11155
11494
  try {
11156
11495
  metadata.senders = parseChatMessageSendersJsonl(
11157
- await readFile15(chatMessageSendersFilePath(chatId), "utf-8")
11496
+ await readFile16(chatMessageSendersFilePath(chatId), "utf-8")
11158
11497
  );
11159
11498
  } catch (error) {
11160
11499
  if (!(error && typeof error === "object" && "code" in error && error.code === "ENOENT")) throw error;
@@ -11237,7 +11576,7 @@ async function flushRepoState() {
11237
11576
  // src/services/upload-engine-logs.ts
11238
11577
  import { createReadStream as createReadStream2 } from "fs";
11239
11578
  import { readdir as readdir9, stat as stat5 } from "fs/promises";
11240
- import { join as join29 } from "path";
11579
+ import { join as join28 } from "path";
11241
11580
  var MAX_ENGINE_LOG_FLUSH_SESSIONS = 10;
11242
11581
  var MAX_ENGINE_LOG_FLUSH_BYTES = 5 * 1024 * 1024;
11243
11582
  var ENGINE_LOG_FLUSH_TIMEOUT_MS = 2e4;
@@ -11266,7 +11605,7 @@ async function flushAllEngineLogs() {
11266
11605
  const candidates = (await Promise.all(filenames.slice(0, MAX_ENGINE_LOG_FLUSH_SESSIONS).map(async (filename) => {
11267
11606
  try {
11268
11607
  const sessionId = filename.slice(0, -".log".length);
11269
- const filePath = join29(LOG_DIR, filename);
11608
+ const filePath = join28(LOG_DIR, filename);
11270
11609
  const fileStat = await runBeforeDeadline(() => stat5(filePath), deadline);
11271
11610
  if (!fileStat.isFile()) {
11272
11611
  skipped++;
@@ -11362,7 +11701,7 @@ async function uploadEngineLog(input, timeoutMs) {
11362
11701
  }
11363
11702
 
11364
11703
  // src/services/chat/chat-service.ts
11365
- var CODEX_AUTH_PATH2 = join30(homedir15(), ".codex", "auth.json");
11704
+ var CODEX_AUTH_PATH2 = join29(homedir15(), ".codex", "auth.json");
11366
11705
  var CHATS_BACKUP_FILE = `${CHATS_FILE}.bak`;
11367
11706
  function isCodexAvailable() {
11368
11707
  return existsSync8(CODEX_AUTH_PATH2) || Boolean(ENGINE_ENV.OPENAI_API_KEY);
@@ -11376,6 +11715,9 @@ function isDeepseekAvailable() {
11376
11715
  function isFxAvailable() {
11377
11716
  return Boolean(ENGINE_ENV.AI_GATEWAY_API_KEY);
11378
11717
  }
11718
+ function isKimiAvailable() {
11719
+ return Boolean(ENGINE_ENV.AI_GATEWAY_API_KEY);
11720
+ }
11379
11721
  function isPiAvailable() {
11380
11722
  return existsSync8(PI_AUTH_PATH);
11381
11723
  }
@@ -11568,7 +11910,7 @@ var ChatService = class {
11568
11910
  throw new ChatNotFoundError(parentChatId);
11569
11911
  }
11570
11912
  const persisted = {
11571
- id: request.id ?? randomUUID7(),
11913
+ id: request.id ?? randomUUID8(),
11572
11914
  provider: request.provider,
11573
11915
  title,
11574
11916
  createdAt: now,
@@ -11659,7 +12001,7 @@ var ChatService = class {
11659
12001
  }
11660
12002
  }
11661
12003
  async readSenders(chatId) {
11662
- return readFile16(chatMessageSendersFilePath(chatId), "utf-8").then(parseChatMessageSendersJsonl).catch((error) => {
12004
+ return readFile17(chatMessageSendersFilePath(chatId), "utf-8").then(parseChatMessageSendersJsonl).catch((error) => {
11663
12005
  if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") return [];
11664
12006
  console.error("[ChatService] Failed to read sender records:", error);
11665
12007
  return [];
@@ -11885,7 +12227,7 @@ var ChatService = class {
11885
12227
  return descendants;
11886
12228
  }
11887
12229
  async deleteHistoryFile(persisted) {
11888
- 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`);
11889
12231
  await Promise.all([
11890
12232
  historyPath,
11891
12233
  `${historyPath}.pages.jsonl`,
@@ -12008,7 +12350,7 @@ var ChatService = class {
12008
12350
  if (persisted.provider === "claude") {
12009
12351
  provider = new ClaudeManager({
12010
12352
  workingDirectory: this.workingDirectory,
12011
- historyFilePath: join30(CLAUDE_HISTORY_DIR, `${persisted.id}.jsonl`),
12353
+ historyFilePath: join29(CLAUDE_HISTORY_DIR, `${persisted.id}.jsonl`),
12012
12354
  initialSessionId: persisted.providerSessionId,
12013
12355
  onSaveSessionId: saveSession,
12014
12356
  onTurnComplete: onProviderTurnComplete,
@@ -12022,11 +12364,12 @@ var ChatService = class {
12022
12364
  piAvailable: isPiAvailable(),
12023
12365
  cursorAvailable: isCursorAvailable(),
12024
12366
  deepseekAvailable: isDeepseekAvailable(),
12025
- fxAvailable: isFxAvailable()
12367
+ fxAvailable: isFxAvailable(),
12368
+ kimiAvailable: isKimiAvailable()
12026
12369
  });
12027
12370
  provider = new RelayManager({
12028
12371
  workingDirectory: this.workingDirectory,
12029
- historyFilePath: join30(RELAY_HISTORY_DIR, `${persisted.id}.jsonl`),
12372
+ historyFilePath: join29(RELAY_HISTORY_DIR, `${persisted.id}.jsonl`),
12030
12373
  initialSessionId: persisted.providerSessionId,
12031
12374
  onSaveSessionId: saveSession,
12032
12375
  onTurnComplete: onProviderTurnComplete,
@@ -12039,7 +12382,7 @@ var ChatService = class {
12039
12382
  } else if (persisted.provider === "cursor") {
12040
12383
  provider = new CursorManager({
12041
12384
  workingDirectory: this.workingDirectory,
12042
- historyFilePath: join30(CURSOR_HISTORY_DIR, `${persisted.id}.jsonl`),
12385
+ historyFilePath: join29(CURSOR_HISTORY_DIR, `${persisted.id}.jsonl`),
12043
12386
  initialSessionId: persisted.providerSessionId,
12044
12387
  onSaveSessionId: saveSession,
12045
12388
  onTurnComplete: onProviderTurnComplete,
@@ -12049,7 +12392,7 @@ var ChatService = class {
12049
12392
  } else if (persisted.provider === "deepseek") {
12050
12393
  provider = new DeepseekManager({
12051
12394
  workingDirectory: this.workingDirectory,
12052
- historyFilePath: join30(DEEPSEEK_HISTORY_DIR, `${persisted.id}.jsonl`),
12395
+ historyFilePath: join29(DEEPSEEK_HISTORY_DIR, `${persisted.id}.jsonl`),
12053
12396
  initialSessionId: persisted.providerSessionId,
12054
12397
  onSaveSessionId: saveSession,
12055
12398
  onTurnComplete: onProviderTurnComplete,
@@ -12059,7 +12402,17 @@ var ChatService = class {
12059
12402
  } else if (persisted.provider === "fx") {
12060
12403
  provider = new FxManager({
12061
12404
  workingDirectory: this.workingDirectory,
12062
- 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`),
12063
12416
  initialSessionId: persisted.providerSessionId,
12064
12417
  onSaveSessionId: saveSession,
12065
12418
  onTurnComplete: onProviderTurnComplete,
@@ -12069,7 +12422,7 @@ var ChatService = class {
12069
12422
  } else if (persisted.provider === "opencode") {
12070
12423
  provider = new OpencodeManager({
12071
12424
  workingDirectory: this.workingDirectory,
12072
- historyFilePath: join30(OPENCODE_HISTORY_DIR, `${persisted.id}.jsonl`),
12425
+ historyFilePath: join29(OPENCODE_HISTORY_DIR, `${persisted.id}.jsonl`),
12073
12426
  initialSessionId: persisted.providerSessionId,
12074
12427
  onSaveSessionId: saveSession,
12075
12428
  onTurnComplete: onProviderTurnComplete,
@@ -12079,7 +12432,7 @@ var ChatService = class {
12079
12432
  } else if (persisted.provider === "pi") {
12080
12433
  provider = new PiManager({
12081
12434
  workingDirectory: this.workingDirectory,
12082
- historyFilePath: join30(PI_HISTORY_DIR, `${persisted.id}.jsonl`),
12435
+ historyFilePath: join29(PI_HISTORY_DIR, `${persisted.id}.jsonl`),
12083
12436
  initialSessionId: persisted.providerSessionId,
12084
12437
  onSaveSessionId: saveSession,
12085
12438
  onTurnComplete: onProviderTurnComplete,
@@ -12089,7 +12442,7 @@ var ChatService = class {
12089
12442
  } else {
12090
12443
  provider = new CodexAspManager({
12091
12444
  workingDirectory: this.workingDirectory,
12092
- historyFilePath: join30(CODEX_HISTORY_DIR, `${persisted.id}.jsonl`),
12445
+ historyFilePath: join29(CODEX_HISTORY_DIR, `${persisted.id}.jsonl`),
12093
12446
  initialSessionId: persisted.providerSessionId,
12094
12447
  onSaveSessionId: saveSession,
12095
12448
  onTurnComplete: onProviderTurnComplete,
@@ -12254,7 +12607,7 @@ var ChatService = class {
12254
12607
  });
12255
12608
  uploadChatTranscript(
12256
12609
  chatId,
12257
- join30(HISTORY_DIR_BY_PROVIDER[chat.persisted.provider], `${chatId}.jsonl`),
12610
+ join29(HISTORY_DIR_BY_PROVIDER[chat.persisted.provider], `${chatId}.jsonl`),
12258
12611
  this.toSummary(chat)
12259
12612
  ).catch((err) => {
12260
12613
  console.error("[ChatService] Failed to upload chat transcript:", { chatId, err });
@@ -12270,7 +12623,7 @@ var ChatService = class {
12270
12623
  }
12271
12624
  async loadChats() {
12272
12625
  try {
12273
- const content = await readFile16(CHATS_FILE, "utf-8");
12626
+ const content = await readFile17(CHATS_FILE, "utf-8");
12274
12627
  return parsePersistedChatsContent(content);
12275
12628
  } catch (error) {
12276
12629
  if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
@@ -12285,7 +12638,7 @@ var ChatService = class {
12285
12638
  console.error("[ChatService] Failed to quarantine corrupt chats file:", renameError);
12286
12639
  }
12287
12640
  try {
12288
- const backupContent = await readFile16(CHATS_BACKUP_FILE, "utf-8");
12641
+ const backupContent = await readFile17(CHATS_BACKUP_FILE, "utf-8");
12289
12642
  return parsePersistedChatsContent(backupContent);
12290
12643
  } catch (backupError) {
12291
12644
  if (backupError && typeof backupError === "object" && "code" in backupError && backupError.code === "ENOENT") {
@@ -12324,7 +12677,7 @@ var ChatService = class {
12324
12677
  }
12325
12678
  async publish(input) {
12326
12679
  const event = {
12327
- id: randomUUID7(),
12680
+ id: randomUUID8(),
12328
12681
  ts: (/* @__PURE__ */ new Date()).toISOString(),
12329
12682
  ...input
12330
12683
  };
@@ -12391,8 +12744,8 @@ var ChatService = class {
12391
12744
 
12392
12745
  // src/services/repo-file-service.ts
12393
12746
  import { execFile } from "child_process";
12394
- import { readFile as readFile17, realpath, stat as stat6 } from "fs/promises";
12395
- 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";
12396
12749
  var CACHE_TTL_MS = 3e4;
12397
12750
  var SEARCH_TIMEOUT_MS = 15e3;
12398
12751
  var MAX_CONTENT_BYTES = 256 * 1024;
@@ -12544,7 +12897,7 @@ var RepoFileService = class {
12544
12897
  const repo = repos.find((r) => r.name === repoName);
12545
12898
  if (!repo) return null;
12546
12899
  try {
12547
- const fullPath = await realpath(resolve2(join31(repo.path, filePath)));
12900
+ const fullPath = await realpath(resolve2(join30(repo.path, filePath)));
12548
12901
  const repoRoot = await realpath(repo.path);
12549
12902
  const repoPrefix = repoRoot.endsWith("/") ? repoRoot : repoRoot + "/";
12550
12903
  if (!fullPath.startsWith(repoPrefix) && fullPath !== repoRoot) return null;
@@ -12575,7 +12928,7 @@ var RepoFileService = class {
12575
12928
  sizeBytes,
12576
12929
  binary: true,
12577
12930
  tooLarge: false,
12578
- base64: (await readFile17(fullPath)).toString("base64"),
12931
+ base64: (await readFile18(fullPath)).toString("base64"),
12579
12932
  mimeType
12580
12933
  };
12581
12934
  }
@@ -12600,7 +12953,7 @@ var RepoFileService = class {
12600
12953
  tooLarge: true
12601
12954
  };
12602
12955
  }
12603
- const content = await readFile17(fullPath, "utf-8");
12956
+ const content = await readFile18(fullPath, "utf-8");
12604
12957
  return {
12605
12958
  repoName,
12606
12959
  path: filePath,
@@ -12678,21 +13031,21 @@ var RepoFileService = class {
12678
13031
  // src/v1-routes.ts
12679
13032
  import { Hono } from "hono";
12680
13033
  import { z as z6 } from "zod";
12681
- import { readdir as readdir11, stat as stat7, readFile as readFile20 } from "fs/promises";
12682
- 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";
12683
13036
 
12684
13037
  // src/services/warm-hooks-service.ts
12685
- import { spawn as spawn5 } from "child_process";
12686
- import { readFile as readFile19 } from "fs/promises";
13038
+ import { spawn as spawn6 } from "child_process";
13039
+ import { readFile as readFile20 } from "fs/promises";
12687
13040
  import { existsSync as existsSync9 } from "fs";
12688
- import { join as join33 } from "path";
13041
+ import { join as join32 } from "path";
12689
13042
 
12690
13043
  // src/services/warm-hook-logs-service.ts
12691
- 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";
12692
13045
  import { homedir as homedir16 } from "os";
12693
- import { join as join32 } from "path";
12694
- var LOGS_DIR2 = join32(homedir16(), ".replicas", "warm-hook-logs");
12695
- 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");
12696
13049
  var GLOBAL_FILENAME = "global.json";
12697
13050
  function withPreview2(stored) {
12698
13051
  const preview = buildHookOutputPreview(stored.output);
@@ -12709,7 +13062,7 @@ var WarmHookLogsService = class {
12709
13062
  hookName: "organization",
12710
13063
  ...entry
12711
13064
  };
12712
- 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)}
12713
13066
  `, "utf-8");
12714
13067
  }
12715
13068
  async saveEnvironmentHookLog(entry) {
@@ -12719,7 +13072,7 @@ var WarmHookLogsService = class {
12719
13072
  hookName: "environment",
12720
13073
  ...entry
12721
13074
  };
12722
- 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)}
12723
13076
  `, "utf-8");
12724
13077
  }
12725
13078
  async saveRepoHookLog(repoName, entry) {
@@ -12729,7 +13082,7 @@ var WarmHookLogsService = class {
12729
13082
  hookName: repoName,
12730
13083
  ...entry
12731
13084
  };
12732
- 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)}
12733
13086
  `, "utf-8");
12734
13087
  }
12735
13088
  async getAllLogs() {
@@ -12748,7 +13101,7 @@ var WarmHookLogsService = class {
12748
13101
  continue;
12749
13102
  }
12750
13103
  try {
12751
- const raw = await readFile18(join32(LOGS_DIR2, file), "utf-8");
13104
+ const raw = await readFile19(join31(LOGS_DIR2, file), "utf-8");
12752
13105
  const stored = JSON.parse(raw);
12753
13106
  logs.push(withPreview2(stored));
12754
13107
  } catch {
@@ -12777,7 +13130,7 @@ var WarmHookLogsService = class {
12777
13130
  }
12778
13131
  async getCurrentRunLog() {
12779
13132
  try {
12780
- return await readFile18(CURRENT_RUN_LOG, "utf-8");
13133
+ return await readFile19(CURRENT_RUN_LOG, "utf-8");
12781
13134
  } catch (err) {
12782
13135
  if (err.code === "ENOENT") return null;
12783
13136
  throw err;
@@ -12786,7 +13139,7 @@ var WarmHookLogsService = class {
12786
13139
  async getFullOutput(hookType, hookName) {
12787
13140
  const filename = hookType === "global" ? GLOBAL_FILENAME : hookType === "environment" ? ENVIRONMENT_HOOK_LOG_FILENAME : repoHookLogFilename(hookName);
12788
13141
  try {
12789
- const raw = await readFile18(join32(LOGS_DIR2, filename), "utf-8");
13142
+ const raw = await readFile19(join31(LOGS_DIR2, filename), "utf-8");
12790
13143
  const stored = JSON.parse(raw);
12791
13144
  if (stored.hookType !== hookType || stored.hookName !== hookName) {
12792
13145
  return null;
@@ -12805,12 +13158,12 @@ var warmHookLogsService = new WarmHookLogsService();
12805
13158
  // src/services/warm-hooks-service.ts
12806
13159
  async function readRepoWarmHook(repoPath) {
12807
13160
  for (const filename of REPLICAS_CONFIG_FILENAMES) {
12808
- const configPath = join33(repoPath, filename);
13161
+ const configPath = join32(repoPath, filename);
12809
13162
  if (!existsSync9(configPath)) {
12810
13163
  continue;
12811
13164
  }
12812
13165
  try {
12813
- const raw = await readFile19(configPath, "utf-8");
13166
+ const raw = await readFile20(configPath, "utf-8");
12814
13167
  const config = parseReplicasConfigString(raw, filename);
12815
13168
  if (!config.warmHook) {
12816
13169
  return null;
@@ -12857,7 +13210,7 @@ async function executeHookScriptStreaming(params) {
12857
13210
  params.onChunk(`$ ${params.label}
12858
13211
  `);
12859
13212
  return new Promise((resolve5) => {
12860
- const proc = spawn5("bash", ["-lc", params.content], {
13213
+ const proc = spawn6("bash", ["-lc", params.content], {
12861
13214
  cwd: params.cwd,
12862
13215
  env: process.env,
12863
13216
  stdio: ["pipe", "pipe", "pipe"]
@@ -13066,9 +13419,9 @@ ${combinedScript}` : combinedScript;
13066
13419
  }
13067
13420
 
13068
13421
  // src/services/terminal-service.ts
13069
- import { randomUUID as randomUUID8 } from "crypto";
13422
+ import { randomUUID as randomUUID9 } from "crypto";
13070
13423
  import { existsSync as existsSync10 } from "fs";
13071
- import { spawn as spawn6 } from "node-pty";
13424
+ import { spawn as spawn7 } from "node-pty";
13072
13425
  var MAX_REPLAY_CHARS = 1024 * 1024;
13073
13426
  var MAX_TERMINAL_SESSIONS = 8;
13074
13427
  var MAX_PENDING_INPUT = 64;
@@ -13085,9 +13438,9 @@ var TerminalService = class {
13085
13438
  code: "limit"
13086
13439
  });
13087
13440
  }
13088
- const id = randomUUID8();
13441
+ const id = randomUUID9();
13089
13442
  const shell = process.env.SHELL && existsSync10(process.env.SHELL) ? process.env.SHELL : "/bin/bash";
13090
- const pty = spawn6(shell, ["-l"], {
13443
+ const pty = spawn7(shell, ["-l"], {
13091
13444
  name: "xterm-256color",
13092
13445
  cols,
13093
13446
  rows,
@@ -14089,7 +14442,7 @@ data: ${JSON.stringify("Terminal session not found")}
14089
14442
  const logFiles = files.filter((f) => f.endsWith(".log"));
14090
14443
  const sessions = await Promise.all(
14091
14444
  logFiles.map(async (filename) => {
14092
- const filePath = join34(LOG_DIR, filename);
14445
+ const filePath = join33(LOG_DIR, filename);
14093
14446
  const fileStat = await stat7(filePath);
14094
14447
  const sessionId = filename.replace(/\.log$/, "");
14095
14448
  return {
@@ -14124,7 +14477,7 @@ data: ${JSON.stringify("Terminal session not found")}
14124
14477
  }
14125
14478
  let content;
14126
14479
  try {
14127
- content = await readFile20(filePath, "utf-8");
14480
+ content = await readFile21(filePath, "utf-8");
14128
14481
  } catch {
14129
14482
  return c.json(jsonError("Log session not found"), 404);
14130
14483
  }
@@ -14400,7 +14753,7 @@ function startStatusBroadcaster() {
14400
14753
  if (serialized !== previousRepoStatus) {
14401
14754
  previousRepoStatus = serialized;
14402
14755
  eventService.publish({
14403
- id: randomUUID9(),
14756
+ id: randomUUID10(),
14404
14757
  ts: (/* @__PURE__ */ new Date()).toISOString(),
14405
14758
  type: "repo.status.changed",
14406
14759
  payload: { repos }
@@ -14421,7 +14774,7 @@ function startStatusBroadcaster() {
14421
14774
  if (engineStatusJson !== previousEngineStatus) {
14422
14775
  previousEngineStatus = engineStatusJson;
14423
14776
  eventService.publish({
14424
- id: randomUUID9(),
14777
+ id: randomUUID10(),
14425
14778
  ts: (/* @__PURE__ */ new Date()).toISOString(),
14426
14779
  type: "engine.status.changed",
14427
14780
  payload: { status: engineStatus }
@@ -14440,7 +14793,7 @@ function startStatusBroadcaster() {
14440
14793
  previousHookStatus = hookSnapshot;
14441
14794
  if (!lastHooksRunning && hooksRunning) {
14442
14795
  eventService.publish({
14443
- id: randomUUID9(),
14796
+ id: randomUUID10(),
14444
14797
  ts: (/* @__PURE__ */ new Date()).toISOString(),
14445
14798
  type: "hooks.started",
14446
14799
  payload: { running: true, completed: false }
@@ -14449,7 +14802,7 @@ function startStatusBroadcaster() {
14449
14802
  }
14450
14803
  if (hooksRunning) {
14451
14804
  eventService.publish({
14452
- id: randomUUID9(),
14805
+ id: randomUUID10(),
14453
14806
  ts: (/* @__PURE__ */ new Date()).toISOString(),
14454
14807
  type: "hooks.progress",
14455
14808
  payload: { running: true, completed: false }
@@ -14458,7 +14811,7 @@ function startStatusBroadcaster() {
14458
14811
  }
14459
14812
  if (lastHooksRunning && !hooksRunning && hooksCompleted && !hooksFailed) {
14460
14813
  eventService.publish({
14461
- id: randomUUID9(),
14814
+ id: randomUUID10(),
14462
14815
  ts: (/* @__PURE__ */ new Date()).toISOString(),
14463
14816
  type: "hooks.completed",
14464
14817
  payload: { running: false, completed: true }
@@ -14467,7 +14820,7 @@ function startStatusBroadcaster() {
14467
14820
  }
14468
14821
  if (lastHooksRunning && !hooksRunning && hooksFailed) {
14469
14822
  eventService.publish({
14470
- id: randomUUID9(),
14823
+ id: randomUUID10(),
14471
14824
  ts: (/* @__PURE__ */ new Date()).toISOString(),
14472
14825
  type: "hooks.failed",
14473
14826
  payload: { running: false, completed: hooksCompleted }
@@ -14475,7 +14828,7 @@ function startStatusBroadcaster() {
14475
14828
  });
14476
14829
  }
14477
14830
  eventService.publish({
14478
- id: randomUUID9(),
14831
+ id: randomUUID10(),
14479
14832
  ts: (/* @__PURE__ */ new Date()).toISOString(),
14480
14833
  type: "hooks.status",
14481
14834
  payload: {
@@ -14530,20 +14883,20 @@ serve(
14530
14883
  }
14531
14884
  const repos = await gitService.listRepos();
14532
14885
  await eventService.publish({
14533
- id: randomUUID9(),
14886
+ id: randomUUID10(),
14534
14887
  ts: (/* @__PURE__ */ new Date()).toISOString(),
14535
14888
  type: "repo.discovered",
14536
14889
  payload: { repos }
14537
14890
  });
14538
14891
  const repoStatuses = await gitService.listRepos();
14539
14892
  await eventService.publish({
14540
- id: randomUUID9(),
14893
+ id: randomUUID10(),
14541
14894
  ts: (/* @__PURE__ */ new Date()).toISOString(),
14542
14895
  type: "repo.status.changed",
14543
14896
  payload: { repos: repoStatuses }
14544
14897
  });
14545
14898
  await eventService.publish({
14546
- id: randomUUID9(),
14899
+ id: randomUUID10(),
14547
14900
  ts: (/* @__PURE__ */ new Date()).toISOString(),
14548
14901
  type: "engine.ready",
14549
14902
  payload: { version: "v1" }