replicas-engine 0.1.362 → 0.1.364

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/README.md CHANGED
@@ -28,6 +28,7 @@ Chats:
28
28
  - `GET /chats/:chatId`
29
29
  - `DELETE /chats/:chatId`
30
30
  - `GET /chats/:chatId/history`
31
+ - `GET /chats/:chatId/slash-commands`
31
32
  - `POST /chats/:chatId/messages`
32
33
  - `POST /chats/:chatId/interrupt`
33
34
 
package/dist/src/index.js CHANGED
@@ -121,9 +121,9 @@ var EXT_TO_LANGUAGE = {
121
121
  function detectLanguageByPath(filePath) {
122
122
  const dot = filePath.lastIndexOf(".");
123
123
  if (dot === -1) {
124
- const basename2 = filePath.split("/").pop() ?? "";
125
- if (basename2 === "Dockerfile") return "dockerfile";
126
- if (basename2 === "Makefile") return "makefile";
124
+ const basename3 = filePath.split("/").pop() ?? "";
125
+ if (basename3 === "Dockerfile") return "dockerfile";
126
+ if (basename3 === "Makefile") return "makefile";
127
127
  return null;
128
128
  }
129
129
  const ext = filePath.slice(dot).toLowerCase();
@@ -295,7 +295,7 @@ var WORKSPACE_SIZES = ["small", "large"];
295
295
  var INVALID_WORKSPACE_SIZE_ERROR = `Invalid size: must be one of ${WORKSPACE_SIZES.join(", ")}`;
296
296
 
297
297
  // ../shared/src/e2b.ts
298
- var E2B_TEMPLATE_NAME = "replicas-sandbox-2026-06-27-v4";
298
+ var E2B_TEMPLATE_NAME = "replicas-sandbox-2026-06-28-v2";
299
299
 
300
300
  // ../shared/src/runtime-env.ts
301
301
  function parsePosixEnvFile(content) {
@@ -427,6 +427,56 @@ function normalizeRepositoryUrl(url, options = {}) {
427
427
  }
428
428
 
429
429
  // ../shared/src/slash-commands.ts
430
+ function normalizeSlashCommandName(name) {
431
+ const command = name.trim().replace(/^\/+/, "");
432
+ if (!command || /\s/.test(command)) return null;
433
+ return `/${command}`;
434
+ }
435
+ function createProviderSlashCommand(provider, name, description, argumentHint) {
436
+ const command = normalizeSlashCommandName(name);
437
+ if (!command) return null;
438
+ const trimmedDescription = description?.trim();
439
+ const trimmedArgumentHint = argumentHint?.trim();
440
+ return {
441
+ command,
442
+ description: trimmedDescription || `Run ${command}.`,
443
+ ...trimmedArgumentHint ? { argumentHint: trimmedArgumentHint } : {},
444
+ providers: [provider]
445
+ };
446
+ }
447
+ var SLASH_COMMANDS = [
448
+ {
449
+ command: "/plan",
450
+ description: "Switch to plan mode and optionally send a prompt.",
451
+ argumentHint: "[prompt]",
452
+ providers: ["claude", "codex", "cursor", "opencode", "relay"]
453
+ },
454
+ {
455
+ command: "/fast",
456
+ description: "Use the fast service tier for future turns.",
457
+ providers: ["claude", "codex"]
458
+ },
459
+ {
460
+ command: "/goal",
461
+ description: "Set or clear a task goal.",
462
+ argumentHint: "<objective | clear>",
463
+ providers: ["codex"]
464
+ }
465
+ ];
466
+ function mergeSlashCommands(...commandGroups) {
467
+ const seen = /* @__PURE__ */ new Set();
468
+ const commands = [];
469
+ for (const command of commandGroups.flat()) {
470
+ const key = command.command.toLowerCase();
471
+ if (seen.has(key)) continue;
472
+ seen.add(key);
473
+ commands.push(command);
474
+ }
475
+ return commands;
476
+ }
477
+ function getSlashCommandsForProvider(provider, commands = SLASH_COMMANDS) {
478
+ return commands.filter((command) => command.providers.includes(provider));
479
+ }
430
480
  var MAX_CODEX_GOAL_OBJECTIVE_CHARS = 4e3;
431
481
  function parseGoalCommand(message) {
432
482
  const match = message.trim().match(/^\/goal(?:\s+([\s\S]*))?$/i);
@@ -5257,7 +5307,7 @@ async function registerDesktopPreview() {
5257
5307
 
5258
5308
  // src/services/chat/chat-service.ts
5259
5309
  import { existsSync as existsSync7 } from "fs";
5260
- import { appendFile as appendFile3, copyFile, mkdir as mkdir13, readFile as readFile13, rename as rename2, rm } from "fs/promises";
5310
+ import { appendFile as appendFile3, copyFile, mkdir as mkdir13, readFile as readFile14, rename as rename2, rm } from "fs/promises";
5261
5311
  import { homedir as homedir14 } from "os";
5262
5312
  import { join as join19 } from "path";
5263
5313
  import { randomUUID as randomUUID5 } from "crypto";
@@ -6579,6 +6629,16 @@ var MAX_MIDTURN_CONTINUE_RETRIES = 2;
6579
6629
  var TRANSIENT_RETRY_DELAYS_MS = [1e3, 2500];
6580
6630
  var CLAUDE_TRANSIENT_HTTP_STATUSES = [408, 500, 502, 503, 504, 529];
6581
6631
  var CLAUDE_MIDTURN_CONTINUE_PROMPT = "Your previous turn was interrupted by a transient network error before it could finish. Continue from exactly where you left off. Do not repeat any tool calls, commits, messages, or other actions you have already completed \u2014 first check what is already done, then do only the remaining work.";
6632
+ function toSlashCommands(command) {
6633
+ const names = [command.name, ...command.aliases ?? []];
6634
+ return names.flatMap((name) => {
6635
+ const result = createProviderSlashCommand("claude", name, command.description, command.argumentHint);
6636
+ return result ? [result] : [];
6637
+ });
6638
+ }
6639
+ function normalizeClaudeSlashCommands(commands) {
6640
+ return mergeSlashCommands(...commands.map(toSlashCommands));
6641
+ }
6582
6642
  var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
6583
6643
  historyFilePath;
6584
6644
  historyFile;
@@ -6602,6 +6662,7 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
6602
6662
  disallowedToolsOverride;
6603
6663
  /** Active tool-input requests keyed by requestId; resolved when the user selects an option. */
6604
6664
  pendingToolInputs = /* @__PURE__ */ new Map();
6665
+ supportedSlashCommands = [];
6605
6666
  authRetrying = false;
6606
6667
  constructor(options) {
6607
6668
  super(options);
@@ -6640,6 +6701,21 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
6640
6701
  isAuthRetrying() {
6641
6702
  return this.authRetrying;
6642
6703
  }
6704
+ async listSlashCommands() {
6705
+ await this.initialized;
6706
+ if (!this.activeQuery || this.isProcessing()) {
6707
+ return this.supportedSlashCommands;
6708
+ }
6709
+ try {
6710
+ const commands = await this.activeQuery?.supportedCommands();
6711
+ if (commands) {
6712
+ this.supportedSlashCommands = normalizeClaudeSlashCommands(commands);
6713
+ }
6714
+ } catch (error) {
6715
+ console.warn("[ClaudeManager] Failed to load slash commands:", error);
6716
+ }
6717
+ return this.supportedSlashCommands;
6718
+ }
6643
6719
  setAuthRetrying(value) {
6644
6720
  if (this.authRetrying === value) return;
6645
6721
  this.authRetrying = value;
@@ -7390,6 +7466,9 @@ var ClaudeManager = class _ClaudeManager extends CodingAgentManager {
7390
7466
  this.handlePartialAssistantMessage(message);
7391
7467
  return;
7392
7468
  }
7469
+ if (message.type === "system" && message.subtype === "commands_changed") {
7470
+ this.supportedSlashCommands = normalizeClaudeSlashCommands(message.commands);
7471
+ }
7393
7472
  this.trackNativeCompaction(message);
7394
7473
  await this.recordEvent(message);
7395
7474
  }
@@ -7612,7 +7691,7 @@ var AspClient = class {
7612
7691
  // src/managers/codex-asp/app-server-process.ts
7613
7692
  var DEFAULT_CODEX_BINARY = "codex";
7614
7693
  var DEFAULT_CODEX_ARGS = ["app-server", "--listen", "stdio://"];
7615
- var ENGINE_PACKAGE_VERSION = "0.1.362";
7694
+ var ENGINE_PACKAGE_VERSION = "0.1.364";
7616
7695
  var INITIALIZE_METHOD = "initialize";
7617
7696
  var INITIALIZED_NOTIFICATION = "initialized";
7618
7697
  var ACCOUNT_LOGIN_START_METHOD = "account/login/start";
@@ -7867,6 +7946,7 @@ var TURN_START_METHOD = "turn/start";
7867
7946
  var TURN_INTERRUPT_METHOD = "turn/interrupt";
7868
7947
  var ACCOUNT_RATE_LIMITS_READ_METHOD = "account/rateLimits/read";
7869
7948
  var MODEL_LIST_METHOD = "model/list";
7949
+ var SKILLS_LIST_METHOD = "skills/list";
7870
7950
  var MAX_CODEX_ASP_TRANSCRIPT_OUTPUT_CHARS = DEFAULT_HOOK_OUTPUT_PREVIEW_CHARS;
7871
7951
  function codexApprovalPolicyOverrides() {
7872
7952
  if (!ENGINE_ENV.REPLICAS_DISABLE_GH_PR_MERGE) {
@@ -8420,6 +8500,20 @@ var TranscriptUpdateCoalescer = class {
8420
8500
  };
8421
8501
 
8422
8502
  // src/managers/codex-asp/codex-asp-manager.ts
8503
+ var CODEX_SLASH_COMMANDS_CACHE_MS = 3e4;
8504
+ function skillToSlashCommand(skill) {
8505
+ if (!skill.enabled) return null;
8506
+ const description = skill.interface?.shortDescription ?? skill.shortDescription ?? skill.description;
8507
+ return createProviderSlashCommand("codex", skill.name, description);
8508
+ }
8509
+ function skillsListToSlashCommands(response) {
8510
+ return mergeSlashCommands(
8511
+ ...response.data.map((entry) => entry.skills.flatMap((skill) => {
8512
+ const command = skillToSlashCommand(skill);
8513
+ return command ? [command] : [];
8514
+ }))
8515
+ );
8516
+ }
8423
8517
  var CodexAspManager = class extends CodingAgentManager {
8424
8518
  currentThreadId = null;
8425
8519
  activeTurnId = null;
@@ -8437,6 +8531,8 @@ var CodexAspManager = class extends CodingAgentManager {
8437
8531
  skillRegistriesApplied = false;
8438
8532
  modelServiceTierCache = null;
8439
8533
  activeServiceTier;
8534
+ slashCommandsCache = null;
8535
+ slashCommandsRequest = null;
8440
8536
  constructor(options) {
8441
8537
  super(options);
8442
8538
  this.historyFile = options.historyFilePath ? new CodexHistoryFile(options.historyFilePath) : null;
@@ -8515,6 +8611,34 @@ var CodexAspManager = class extends CodingAgentManager {
8515
8611
  getGoal() {
8516
8612
  return this.currentGoal;
8517
8613
  }
8614
+ async listSlashCommands() {
8615
+ await this.initialized;
8616
+ const now = Date.now();
8617
+ if (this.slashCommandsCache && this.slashCommandsCache.expiresAt > now) {
8618
+ return this.slashCommandsCache.commands;
8619
+ }
8620
+ if (!this.slashCommandsRequest) {
8621
+ this.slashCommandsRequest = (async () => {
8622
+ try {
8623
+ const host = await getCodexAspHost();
8624
+ await this.applySkillRegistries(host);
8625
+ const response = await host.client.request(
8626
+ SKILLS_LIST_METHOD,
8627
+ { cwds: [this.workingDirectory] }
8628
+ );
8629
+ const commands = skillsListToSlashCommands(response);
8630
+ this.slashCommandsCache = { commands, expiresAt: Date.now() + CODEX_SLASH_COMMANDS_CACHE_MS };
8631
+ return commands;
8632
+ } catch (error) {
8633
+ console.warn("[CodexAspManager] Failed to load slash commands:", error);
8634
+ return this.slashCommandsCache?.commands ?? [];
8635
+ } finally {
8636
+ this.slashCommandsRequest = null;
8637
+ }
8638
+ })();
8639
+ }
8640
+ return this.slashCommandsRequest;
8641
+ }
8518
8642
  async clearGoal() {
8519
8643
  await this.initialized;
8520
8644
  if (!this.currentThreadId) {
@@ -9381,9 +9505,11 @@ var CodexAspManager = class extends CodingAgentManager {
9381
9505
  };
9382
9506
 
9383
9507
  // src/managers/cursor-manager.ts
9384
- import { mkdir as mkdir11 } from "fs/promises";
9385
- import { dirname as dirname5, join as join15 } from "path";
9508
+ import { mkdir as mkdir11, readFile as readFile10, readdir as readdir4 } from "fs/promises";
9509
+ import { basename, dirname as dirname5, extname, join as join15 } from "path";
9510
+ import { parse as parseYaml2 } from "yaml";
9386
9511
  import { Agent as CursorAgent } from "@cursor/sdk";
9512
+ var CURSOR_SLASH_COMMANDS_CACHE_MS = 3e4;
9387
9513
  var CURSOR_COMPOSER_CONTEXT_WINDOW = 2e5;
9388
9514
  var CURSOR_CATEGORY_COLORS = {
9389
9515
  input: "#3eeba3",
@@ -9394,12 +9520,45 @@ var CURSOR_CATEGORY_COLORS = {
9394
9520
  function finiteNumber(value) {
9395
9521
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
9396
9522
  }
9523
+ function extractCursorCommandDescription(content) {
9524
+ const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
9525
+ if (!match) return void 0;
9526
+ try {
9527
+ const parsed = parseYaml2(match[1]);
9528
+ if (isRecord4(parsed) && typeof parsed.description === "string") return parsed.description;
9529
+ } catch {
9530
+ }
9531
+ return void 0;
9532
+ }
9533
+ async function listCursorCommandsInDirectory(directory) {
9534
+ let entries;
9535
+ try {
9536
+ entries = await readdir4(directory, { withFileTypes: true });
9537
+ } catch (error) {
9538
+ if (isRecord4(error) && error.code === "ENOENT") return [];
9539
+ console.warn("[CursorManager] Failed to read slash command directory:", error);
9540
+ return [];
9541
+ }
9542
+ const commands = await Promise.all(entries.filter((entry) => entry.isFile() && extname(entry.name) === ".md").map(async (entry) => {
9543
+ const name = basename(entry.name, ".md");
9544
+ let description;
9545
+ try {
9546
+ description = extractCursorCommandDescription(await readFile10(join15(directory, entry.name), "utf8"));
9547
+ } catch (error) {
9548
+ console.warn("[CursorManager] Failed to read slash command file:", error);
9549
+ }
9550
+ return createProviderSlashCommand("cursor", name, description);
9551
+ }));
9552
+ return commands.filter((command) => Boolean(command));
9553
+ }
9397
9554
  var CursorManager = class extends CodingAgentManager {
9398
9555
  agent = null;
9399
9556
  activeRun = null;
9400
9557
  activeModel = null;
9401
9558
  historyFilePath;
9402
9559
  historyFile;
9560
+ slashCommandsCache = null;
9561
+ slashCommandsRequest = null;
9403
9562
  constructor(options) {
9404
9563
  super(options);
9405
9564
  this.historyFilePath = options.historyFilePath ?? join15(ENGINE_ENV.HOME_DIR, ".replicas", "cursor", "history.jsonl");
@@ -9421,6 +9580,26 @@ var CursorManager = class extends CodingAgentManager {
9421
9580
  goal: null
9422
9581
  };
9423
9582
  }
9583
+ async listSlashCommands() {
9584
+ await this.initialized;
9585
+ const now = Date.now();
9586
+ if (this.slashCommandsCache && this.slashCommandsCache.expiresAt > now) {
9587
+ return this.slashCommandsCache.commands;
9588
+ }
9589
+ this.slashCommandsRequest ??= (async () => {
9590
+ try {
9591
+ const commands = mergeSlashCommands(
9592
+ await listCursorCommandsInDirectory(join15(this.workingDirectory, ".cursor", "commands")),
9593
+ await listCursorCommandsInDirectory(join15(ENGINE_ENV.HOME_DIR, ".cursor", "commands"))
9594
+ );
9595
+ this.slashCommandsCache = { commands, expiresAt: Date.now() + CURSOR_SLASH_COMMANDS_CACHE_MS };
9596
+ return commands;
9597
+ } finally {
9598
+ this.slashCommandsRequest = null;
9599
+ }
9600
+ })();
9601
+ return this.slashCommandsRequest;
9602
+ }
9424
9603
  async ensureAgent(request) {
9425
9604
  if (this.agent) return this.agent;
9426
9605
  const apiKey = ENGINE_ENV.CURSOR_API_KEY;
@@ -9560,7 +9739,7 @@ var CursorManager = class extends CodingAgentManager {
9560
9739
  };
9561
9740
 
9562
9741
  // src/managers/opencode-manager.ts
9563
- import { mkdir as mkdir12, readFile as readFile10 } from "fs/promises";
9742
+ import { mkdir as mkdir12, readFile as readFile11 } from "fs/promises";
9564
9743
  import { delimiter, dirname as dirname6, join as join16 } from "path";
9565
9744
  import { randomBytes as randomBytes2 } from "crypto";
9566
9745
  import { fileURLToPath } from "url";
@@ -9573,6 +9752,7 @@ var OPENCODE_SHIM_DIR = dirname6(fileURLToPath(new URL("../../scripts/opencode",
9573
9752
  var OPENCODE_CONFIG_PATH = join16(ENGINE_ENV.HOME_DIR, ".config", "opencode", "opencode.json");
9574
9753
  var OPENCODE_FETCH_DISPATCHER = new Agent({ headersTimeout: 0, bodyTimeout: 0 });
9575
9754
  var OPENCODE_WORKSPACE_PERMISSION = "allow";
9755
+ var OPENCODE_SLASH_COMMANDS_CACHE_MS = 3e4;
9576
9756
  var OPENCODE_VARIANT_CANDIDATES_BY_THINKING_LEVEL = {
9577
9757
  low: ["low"],
9578
9758
  medium: ["medium"],
@@ -9604,6 +9784,25 @@ async function opencodeConfig(model) {
9604
9784
  function getConfiguredOpencodeModels(model) {
9605
9785
  return [.../* @__PURE__ */ new Set([model, ...AGENT_MODELS.opencode])];
9606
9786
  }
9787
+ function opencodeCommandToSlashCommand(command) {
9788
+ return createProviderSlashCommand("opencode", command.name, command.description);
9789
+ }
9790
+ function opencodeSkillToSlashCommand(skill) {
9791
+ if (!skill.slash) return null;
9792
+ return createProviderSlashCommand("opencode", skill.name, skill.description);
9793
+ }
9794
+ function opencodeCommandListToSlashCommands(response) {
9795
+ return mergeSlashCommands(response.data.flatMap((command) => {
9796
+ const result = opencodeCommandToSlashCommand(command);
9797
+ return result ? [result] : [];
9798
+ }));
9799
+ }
9800
+ function opencodeSkillListToSlashCommands(response) {
9801
+ return mergeSlashCommands(response.data.flatMap((skill) => {
9802
+ const result = opencodeSkillToSlashCommand(skill);
9803
+ return result ? [result] : [];
9804
+ }));
9805
+ }
9607
9806
  function isOpencodePart(value) {
9608
9807
  return typeof value === "object" && value !== null && "type" in value && typeof value.type === "string";
9609
9808
  }
@@ -9627,7 +9826,7 @@ function isOpencodeMcpEntry(value) {
9627
9826
  async function readProvisionedOpencodeMcpConfig() {
9628
9827
  let raw;
9629
9828
  try {
9630
- raw = await readFile10(OPENCODE_CONFIG_PATH, "utf8");
9829
+ raw = await readFile11(OPENCODE_CONFIG_PATH, "utf8");
9631
9830
  } catch (error) {
9632
9831
  if (isRecord4(error) && error.code === "ENOENT") return void 0;
9633
9832
  console.error("[OpencodeManager] Failed to read Opencode config:", error);
@@ -9699,6 +9898,8 @@ var OpencodeManager = class extends CodingAgentManager {
9699
9898
  reasoningParts = /* @__PURE__ */ new Map();
9700
9899
  nonAssistantMessageIds = /* @__PURE__ */ new Set();
9701
9900
  modelVariants = /* @__PURE__ */ new Map();
9901
+ slashCommandsCache = null;
9902
+ slashCommandsRequest = null;
9702
9903
  constructor(options) {
9703
9904
  super(options);
9704
9905
  this.sessionId = options.initialSessionId;
@@ -9731,6 +9932,35 @@ var OpencodeManager = class extends CodingAgentManager {
9731
9932
  goal: null
9732
9933
  };
9733
9934
  }
9935
+ async listSlashCommands() {
9936
+ await this.initialized;
9937
+ const now = Date.now();
9938
+ if (this.slashCommandsCache && this.slashCommandsCache.expiresAt > now) {
9939
+ return this.slashCommandsCache.commands;
9940
+ }
9941
+ this.slashCommandsRequest ??= (async () => {
9942
+ try {
9943
+ const client = await this.ensureClient(DEFAULT_OPENCODE_MODEL);
9944
+ const location = { directory: this.workingDirectory };
9945
+ const [commandResponse, skillResponse] = await Promise.all([
9946
+ client.v2.command.list({ location }, { throwOnError: true }),
9947
+ client.v2.skill.list({ location }, { throwOnError: true })
9948
+ ]);
9949
+ const commands = mergeSlashCommands(
9950
+ opencodeCommandListToSlashCommands(commandResponse.data),
9951
+ opencodeSkillListToSlashCommands(skillResponse.data)
9952
+ );
9953
+ this.slashCommandsCache = { commands, expiresAt: Date.now() + OPENCODE_SLASH_COMMANDS_CACHE_MS };
9954
+ return commands;
9955
+ } catch (error) {
9956
+ console.warn("[OpencodeManager] Failed to load slash commands:", error);
9957
+ return this.slashCommandsCache?.commands ?? [];
9958
+ } finally {
9959
+ this.slashCommandsRequest = null;
9960
+ }
9961
+ })();
9962
+ return this.slashCommandsRequest;
9963
+ }
9734
9964
  async ensureClient(model) {
9735
9965
  if (this.client && this.configuredModels.has(model)) return this.client;
9736
9966
  if (!ENGINE_ENV.OPENROUTER_API_KEY) {
@@ -10545,6 +10775,9 @@ var RelayManager = class {
10545
10775
  async getHistory() {
10546
10776
  return this.inner.getHistory();
10547
10777
  }
10778
+ async listSlashCommands() {
10779
+ return this.inner.listSlashCommands?.() ?? [];
10780
+ }
10548
10781
  isProcessing() {
10549
10782
  return this.inner.isProcessing();
10550
10783
  }
@@ -10604,7 +10837,7 @@ var KeepAliveService = class _KeepAliveService {
10604
10837
  var keepAliveService = new KeepAliveService();
10605
10838
 
10606
10839
  // src/services/canvas-service.ts
10607
- import { readdir as readdir4, readFile as readFile11, stat as stat3 } from "fs/promises";
10840
+ import { readdir as readdir5, readFile as readFile12, stat as stat3 } from "fs/promises";
10608
10841
  import { homedir as homedir12 } from "os";
10609
10842
  import { join as join17 } from "path";
10610
10843
  var CANVAS_DIRECTORIES = [
@@ -10617,7 +10850,7 @@ var CanvasService = class {
10617
10850
  for (const directory of CANVAS_DIRECTORIES) {
10618
10851
  let entries;
10619
10852
  try {
10620
- entries = await readdir4(directory, { withFileTypes: true });
10853
+ entries = await readdir5(directory, { withFileTypes: true });
10621
10854
  } catch {
10622
10855
  continue;
10623
10856
  }
@@ -10664,7 +10897,7 @@ var CanvasService = class {
10664
10897
  };
10665
10898
  }
10666
10899
  try {
10667
- const bytes = await readFile11(filePath);
10900
+ const bytes = await readFile12(filePath);
10668
10901
  return { filename: safe, kind, sizeBytes, mimeType, updatedAt, bytes };
10669
10902
  } catch {
10670
10903
  continue;
@@ -10779,8 +11012,8 @@ async function reconcileCanvasItems(filenames) {
10779
11012
  }
10780
11013
 
10781
11014
  // src/services/upload-chat-transcripts.ts
10782
- import { readdir as readdir5, readFile as readFile12 } from "fs/promises";
10783
- import { basename, join as join18 } from "path";
11015
+ import { readdir as readdir6, readFile as readFile13 } from "fs/promises";
11016
+ import { basename as basename2, join as join18 } from "path";
10784
11017
  import { homedir as homedir13 } from "os";
10785
11018
  var ENGINE_DIR2 = join18(homedir13(), ".replicas", "engine");
10786
11019
  var HISTORY_DIRS = [
@@ -10795,13 +11028,13 @@ async function flushAllChatTranscripts(chatsById = /* @__PURE__ */ new Map()) {
10795
11028
  for (const dir of HISTORY_DIRS) {
10796
11029
  let entries;
10797
11030
  try {
10798
- entries = await readdir5(dir);
11031
+ entries = await readdir6(dir);
10799
11032
  } catch {
10800
11033
  continue;
10801
11034
  }
10802
11035
  for (const entry of entries) {
10803
11036
  if (!entry.endsWith(".jsonl")) continue;
10804
- const chatId = basename(entry, ".jsonl");
11037
+ const chatId = basename2(entry, ".jsonl");
10805
11038
  tasks.push(
10806
11039
  uploadChatTranscript(chatId, join18(dir, entry), chatsById.get(chatId)).then(() => {
10807
11040
  flushed++;
@@ -10816,7 +11049,7 @@ async function flushAllChatTranscripts(chatsById = /* @__PURE__ */ new Map()) {
10816
11049
  return { flushed, failed };
10817
11050
  }
10818
11051
  async function uploadChatTranscript(chatId, filePath, chat) {
10819
- const bytes = await readFile12(filePath);
11052
+ const bytes = await readFile13(filePath);
10820
11053
  if (bytes.byteLength === 0) return;
10821
11054
  const form = new FormData();
10822
11055
  form.append("chat_id", chatId);
@@ -11046,6 +11279,21 @@ var ChatService = class {
11046
11279
  const chat = this.chats.get(chatId);
11047
11280
  return chat ? this.toSummary(chat) : null;
11048
11281
  }
11282
+ async listSlashCommands(chatId) {
11283
+ const chat = this.requireChat(chatId);
11284
+ const provider = chat.persisted.provider;
11285
+ const discovered = await chat.provider.listSlashCommands?.() ?? [];
11286
+ const providerCommands = discovered.map((command) => ({
11287
+ ...command,
11288
+ providers: [provider]
11289
+ }));
11290
+ return {
11291
+ commands: mergeSlashCommands(
11292
+ getSlashCommandsForProvider(provider),
11293
+ providerCommands
11294
+ )
11295
+ };
11296
+ }
11049
11297
  async createChat(request) {
11050
11298
  const now = (/* @__PURE__ */ new Date()).toISOString();
11051
11299
  const title = request.title?.trim() || `${request.provider} chat`;
@@ -11121,7 +11369,7 @@ var ChatService = class {
11121
11369
  }
11122
11370
  async readSenders(chatId) {
11123
11371
  try {
11124
- const content = await readFile13(this.senderFilePath(chatId), "utf-8");
11372
+ const content = await readFile14(this.senderFilePath(chatId), "utf-8");
11125
11373
  const lines = content.split("\n").filter((line) => line.trim().length > 0);
11126
11374
  const senders = [];
11127
11375
  for (const line of lines) {
@@ -11531,7 +11779,7 @@ var ChatService = class {
11531
11779
  }
11532
11780
  async loadChats() {
11533
11781
  try {
11534
- const content = await readFile13(CHATS_FILE, "utf-8");
11782
+ const content = await readFile14(CHATS_FILE, "utf-8");
11535
11783
  return parsePersistedChatsContent(content);
11536
11784
  } catch (error) {
11537
11785
  if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
@@ -11546,7 +11794,7 @@ var ChatService = class {
11546
11794
  console.error("[ChatService] Failed to quarantine corrupt chats file:", renameError);
11547
11795
  }
11548
11796
  try {
11549
- const backupContent = await readFile13(CHATS_BACKUP_FILE, "utf-8");
11797
+ const backupContent = await readFile14(CHATS_BACKUP_FILE, "utf-8");
11550
11798
  return parsePersistedChatsContent(backupContent);
11551
11799
  } catch (backupError) {
11552
11800
  if (backupError && typeof backupError === "object" && "code" in backupError && backupError.code === "ENOENT") {
@@ -11633,8 +11881,8 @@ var ChatService = class {
11633
11881
 
11634
11882
  // src/services/repo-file-service.ts
11635
11883
  import { execFile as execFile2 } from "child_process";
11636
- import { readFile as readFile14, realpath, stat as stat4 } from "fs/promises";
11637
- import { join as join20, resolve as resolve2, extname } from "path";
11884
+ import { readFile as readFile15, realpath, stat as stat4 } from "fs/promises";
11885
+ import { join as join20, resolve as resolve2, extname as extname2 } from "path";
11638
11886
  var CACHE_TTL_MS = 3e4;
11639
11887
  var SEARCH_TIMEOUT_MS = 15e3;
11640
11888
  var MAX_CONTENT_BYTES = 256 * 1024;
@@ -11642,7 +11890,7 @@ var DEFAULT_LIMIT = 50;
11642
11890
  var MAX_LIMIT = 5e3;
11643
11891
  var EXCLUDED_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", ".next", "dist", "build", "out", "vendor", ".turbo", "__pycache__"]);
11644
11892
  function isBinaryExtension(filePath) {
11645
- const ext = extname(filePath).toLowerCase();
11893
+ const ext = extname2(filePath).toLowerCase();
11646
11894
  const binaryExts = /* @__PURE__ */ new Set([
11647
11895
  ".png",
11648
11896
  ".jpg",
@@ -11694,11 +11942,11 @@ function scoreMatch(query2, filePath) {
11694
11942
  const lowerQuery = query2.toLowerCase();
11695
11943
  const lowerPath = filePath.toLowerCase();
11696
11944
  const segments = lowerPath.split("/");
11697
- const basename2 = segments[segments.length - 1] ?? "";
11698
- if (basename2 === lowerQuery) return 100;
11699
- if (basename2.startsWith(lowerQuery)) return 90;
11945
+ const basename3 = segments[segments.length - 1] ?? "";
11946
+ if (basename3 === lowerQuery) return 100;
11947
+ if (basename3.startsWith(lowerQuery)) return 90;
11700
11948
  if (segments.some((seg) => seg === lowerQuery)) return 80;
11701
- if (basename2.includes(lowerQuery)) return 70;
11949
+ if (basename3.includes(lowerQuery)) return 70;
11702
11950
  if (segments.some((seg) => seg.includes(lowerQuery))) return 60;
11703
11951
  if (lowerPath.includes(lowerQuery)) return 50;
11704
11952
  return 0;
@@ -11823,7 +12071,7 @@ var RepoFileService = class {
11823
12071
  tooLarge: true
11824
12072
  };
11825
12073
  }
11826
- const content = await readFile14(fullPath, "utf-8");
12074
+ const content = await readFile15(fullPath, "utf-8");
11827
12075
  return {
11828
12076
  repoName,
11829
12077
  path: filePath,
@@ -11901,17 +12149,17 @@ var RepoFileService = class {
11901
12149
  // src/v1-routes.ts
11902
12150
  import { Hono } from "hono";
11903
12151
  import { z as z2 } from "zod";
11904
- import { readdir as readdir7, stat as stat5, readFile as readFile17 } from "fs/promises";
12152
+ import { readdir as readdir8, stat as stat5, readFile as readFile18 } from "fs/promises";
11905
12153
  import { join as join23, resolve as resolve3 } from "path";
11906
12154
 
11907
12155
  // src/services/warm-hooks-service.ts
11908
12156
  import { spawn as spawn4 } from "child_process";
11909
- import { readFile as readFile16 } from "fs/promises";
12157
+ import { readFile as readFile17 } from "fs/promises";
11910
12158
  import { existsSync as existsSync8 } from "fs";
11911
12159
  import { join as join22 } from "path";
11912
12160
 
11913
12161
  // src/services/warm-hook-logs-service.ts
11914
- import { mkdir as mkdir14, readFile as readFile15, writeFile as writeFile6, readdir as readdir6, appendFile as appendFile4, unlink as unlink3 } from "fs/promises";
12162
+ import { mkdir as mkdir14, readFile as readFile16, writeFile as writeFile6, readdir as readdir7, appendFile as appendFile4, unlink as unlink3 } from "fs/promises";
11915
12163
  import { homedir as homedir15 } from "os";
11916
12164
  import { join as join21 } from "path";
11917
12165
  var LOGS_DIR2 = join21(homedir15(), ".replicas", "warm-hook-logs");
@@ -11958,7 +12206,7 @@ var WarmHookLogsService = class {
11958
12206
  async getAllLogs() {
11959
12207
  let files;
11960
12208
  try {
11961
- files = await readdir6(LOGS_DIR2);
12209
+ files = await readdir7(LOGS_DIR2);
11962
12210
  } catch (err) {
11963
12211
  if (err.code === "ENOENT") {
11964
12212
  return [];
@@ -11971,7 +12219,7 @@ var WarmHookLogsService = class {
11971
12219
  continue;
11972
12220
  }
11973
12221
  try {
11974
- const raw = await readFile15(join21(LOGS_DIR2, file), "utf-8");
12222
+ const raw = await readFile16(join21(LOGS_DIR2, file), "utf-8");
11975
12223
  const stored = JSON.parse(raw);
11976
12224
  logs.push(withPreview2(stored));
11977
12225
  } catch {
@@ -12000,7 +12248,7 @@ var WarmHookLogsService = class {
12000
12248
  }
12001
12249
  async getCurrentRunLog() {
12002
12250
  try {
12003
- return await readFile15(CURRENT_RUN_LOG, "utf-8");
12251
+ return await readFile16(CURRENT_RUN_LOG, "utf-8");
12004
12252
  } catch (err) {
12005
12253
  if (err.code === "ENOENT") return null;
12006
12254
  throw err;
@@ -12009,7 +12257,7 @@ var WarmHookLogsService = class {
12009
12257
  async getFullOutput(hookType, hookName) {
12010
12258
  const filename = hookType === "global" ? GLOBAL_FILENAME : hookType === "environment" ? ENVIRONMENT_HOOK_LOG_FILENAME : repoHookLogFilename(hookName);
12011
12259
  try {
12012
- const raw = await readFile15(join21(LOGS_DIR2, filename), "utf-8");
12260
+ const raw = await readFile16(join21(LOGS_DIR2, filename), "utf-8");
12013
12261
  const stored = JSON.parse(raw);
12014
12262
  if (stored.hookType !== hookType || stored.hookName !== hookName) {
12015
12263
  return null;
@@ -12033,7 +12281,7 @@ async function readRepoWarmHook(repoPath) {
12033
12281
  continue;
12034
12282
  }
12035
12283
  try {
12036
- const raw = await readFile16(configPath, "utf-8");
12284
+ const raw = await readFile17(configPath, "utf-8");
12037
12285
  const config = parseReplicasConfigString(raw, filename);
12038
12286
  if (!config.warmHook) {
12039
12287
  return null;
@@ -12468,6 +12716,17 @@ function createV1Routes(deps) {
12468
12716
  return c.json(jsonError("Failed to load chat history", error instanceof Error ? error.message : "Unknown error"), 404);
12469
12717
  }
12470
12718
  });
12719
+ app2.get("/chats/:chatId/slash-commands", async (c) => {
12720
+ try {
12721
+ const response = await deps.chatService.listSlashCommands(c.req.param("chatId"));
12722
+ return c.json(response);
12723
+ } catch (error) {
12724
+ if (error instanceof ChatNotFoundError) {
12725
+ return c.json(jsonError("Failed to load slash commands", error.message), 404);
12726
+ }
12727
+ return c.json(jsonError("Failed to load slash commands", error instanceof Error ? error.message : "Unknown error"), 500);
12728
+ }
12729
+ });
12471
12730
  app2.post("/chats/:chatId/messages", async (c) => {
12472
12731
  try {
12473
12732
  const body = sendMessageSchema.parse(await c.req.json());
@@ -12979,7 +13238,7 @@ function createV1Routes(deps) {
12979
13238
  });
12980
13239
  app2.get("/logs", async (c) => {
12981
13240
  try {
12982
- const files = await readdir7(LOG_DIR).catch(() => []);
13241
+ const files = await readdir8(LOG_DIR).catch(() => []);
12983
13242
  const logFiles = files.filter((f) => f.endsWith(".log"));
12984
13243
  const sessions = await Promise.all(
12985
13244
  logFiles.map(async (filename) => {
@@ -13020,7 +13279,7 @@ function createV1Routes(deps) {
13020
13279
  const limit = Math.min(parseInt(c.req.query("limit") || "500", 10), 5e3);
13021
13280
  let content;
13022
13281
  try {
13023
- content = await readFile17(filePath, "utf-8");
13282
+ content = await readFile18(filePath, "utf-8");
13024
13283
  } catch {
13025
13284
  return c.json(jsonError("Log session not found"), 404);
13026
13285
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "replicas-engine",
3
- "version": "0.1.362",
3
+ "version": "0.1.364",
4
4
  "description": "Lightweight API server for Replicas workspaces",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",