@rdmind/rdmind 0.2.3-alpha.5 → 0.2.3-alpha.6

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/cli.js CHANGED
@@ -158441,7 +158441,7 @@ __export(geminiContentGenerator_exports2, {
158441
158441
  createGeminiContentGenerator: () => createGeminiContentGenerator
158442
158442
  });
158443
158443
  function createGeminiContentGenerator(config2, gcConfig) {
158444
- const version2 = "0.2.3-alpha.5";
158444
+ const version2 = "0.2.3-alpha.6";
158445
158445
  const userAgent2 = config2.userAgent || `QwenCode/${version2} (${process.platform}; ${process.arch})`;
158446
158446
  const baseHeaders = {
158447
158447
  "User-Agent": userAgent2
@@ -174616,13 +174616,208 @@ function stripShellWrapper(command2) {
174616
174616
  return command2.trim();
174617
174617
  }
174618
174618
  function detectCommandSubstitution(command2) {
174619
+ const isCommentStart = /* @__PURE__ */ __name((index) => {
174620
+ if (command2[index] !== "#") return false;
174621
+ if (index === 0) return true;
174622
+ const prev = command2[index - 1];
174623
+ if (prev === " " || prev === " " || prev === "\n" || prev === "\r") {
174624
+ return true;
174625
+ }
174626
+ return [";", "&", "|", "(", ")", "<", ">"].includes(prev);
174627
+ }, "isCommentStart");
174628
+ const isWordBoundary = /* @__PURE__ */ __name((char) => {
174629
+ if (char === " " || char === " " || char === "\n" || char === "\r") {
174630
+ return true;
174631
+ }
174632
+ return [";", "&", "|", "<", ">", "(", ")"].includes(char);
174633
+ }, "isWordBoundary");
174634
+ const parseHeredocOperator = /* @__PURE__ */ __name((startIndex) => {
174635
+ if (command2[startIndex] !== "<" || command2[startIndex + 1] !== "<") {
174636
+ return null;
174637
+ }
174638
+ let i4 = startIndex + 2;
174639
+ const stripLeadingTabs = command2[i4] === "-";
174640
+ if (stripLeadingTabs) i4++;
174641
+ while (i4 < command2.length && (command2[i4] === " " || command2[i4] === " ")) {
174642
+ i4++;
174643
+ }
174644
+ let delimiter2 = "";
174645
+ let isQuotedDelimiter = false;
174646
+ let inSingleQuotes2 = false;
174647
+ let inDoubleQuotes2 = false;
174648
+ while (i4 < command2.length) {
174649
+ const char = command2[i4];
174650
+ if (!inSingleQuotes2 && !inDoubleQuotes2 && isWordBoundary(char)) {
174651
+ break;
174652
+ }
174653
+ if (!inSingleQuotes2 && !inDoubleQuotes2) {
174654
+ if (char === "'") {
174655
+ isQuotedDelimiter = true;
174656
+ inSingleQuotes2 = true;
174657
+ i4++;
174658
+ continue;
174659
+ }
174660
+ if (char === '"') {
174661
+ isQuotedDelimiter = true;
174662
+ inDoubleQuotes2 = true;
174663
+ i4++;
174664
+ continue;
174665
+ }
174666
+ if (char === "\\") {
174667
+ isQuotedDelimiter = true;
174668
+ i4++;
174669
+ if (i4 >= command2.length) break;
174670
+ delimiter2 += command2[i4];
174671
+ i4++;
174672
+ continue;
174673
+ }
174674
+ delimiter2 += char;
174675
+ i4++;
174676
+ continue;
174677
+ }
174678
+ if (inSingleQuotes2) {
174679
+ if (char === "'") {
174680
+ inSingleQuotes2 = false;
174681
+ i4++;
174682
+ continue;
174683
+ }
174684
+ delimiter2 += char;
174685
+ i4++;
174686
+ continue;
174687
+ }
174688
+ if (char === '"') {
174689
+ inDoubleQuotes2 = false;
174690
+ i4++;
174691
+ continue;
174692
+ }
174693
+ if (char === "\\") {
174694
+ isQuotedDelimiter = true;
174695
+ i4++;
174696
+ if (i4 >= command2.length) break;
174697
+ delimiter2 += command2[i4];
174698
+ i4++;
174699
+ continue;
174700
+ }
174701
+ delimiter2 += char;
174702
+ i4++;
174703
+ }
174704
+ if (delimiter2.length === 0) {
174705
+ return null;
174706
+ }
174707
+ return {
174708
+ nextIndex: i4,
174709
+ heredoc: {
174710
+ delimiter: delimiter2,
174711
+ isQuotedDelimiter,
174712
+ stripLeadingTabs
174713
+ }
174714
+ };
174715
+ }, "parseHeredocOperator");
174716
+ const lineHasCommandSubstitution = /* @__PURE__ */ __name((line) => {
174717
+ for (let i4 = 0; i4 < line.length; i4++) {
174718
+ const char = line[i4];
174719
+ const nextChar = line[i4 + 1];
174720
+ if (char === "\\") {
174721
+ i4++;
174722
+ continue;
174723
+ }
174724
+ if (char === "$" && nextChar === "(") {
174725
+ return true;
174726
+ }
174727
+ if (char === "`") {
174728
+ return true;
174729
+ }
174730
+ }
174731
+ return false;
174732
+ }, "lineHasCommandSubstitution");
174733
+ const consumeHeredocBodies = /* @__PURE__ */ __name((startIndex, pending) => {
174734
+ let i4 = startIndex;
174735
+ for (const heredoc of pending) {
174736
+ let pendingDollarLineContinuation = false;
174737
+ while (i4 <= command2.length) {
174738
+ const lineStart = i4;
174739
+ while (i4 < command2.length && command2[i4] !== "\n" && command2[i4] !== "\r") {
174740
+ i4++;
174741
+ }
174742
+ const lineEnd = i4;
174743
+ let newlineLength = 0;
174744
+ if (i4 < command2.length && command2[i4] === "\r" && command2[i4 + 1] === "\n") {
174745
+ newlineLength = 2;
174746
+ } else if (i4 < command2.length && (command2[i4] === "\n" || command2[i4] === "\r")) {
174747
+ newlineLength = 1;
174748
+ }
174749
+ const rawLine = command2.slice(lineStart, lineEnd);
174750
+ const effectiveLine = heredoc.stripLeadingTabs ? rawLine.replace(/^\t+/, "") : rawLine;
174751
+ if (effectiveLine === heredoc.delimiter) {
174752
+ i4 = lineEnd + newlineLength;
174753
+ break;
174754
+ }
174755
+ if (!heredoc.isQuotedDelimiter) {
174756
+ if (pendingDollarLineContinuation && effectiveLine.startsWith("(")) {
174757
+ return { nextIndex: i4, hasSubstitution: true };
174758
+ }
174759
+ if (lineHasCommandSubstitution(effectiveLine)) {
174760
+ return { nextIndex: i4, hasSubstitution: true };
174761
+ }
174762
+ pendingDollarLineContinuation = false;
174763
+ if (newlineLength > 0 && rawLine.length >= 2 && rawLine.endsWith("\\") && rawLine[rawLine.length - 2] === "$") {
174764
+ let backslashCount = 0;
174765
+ for (let j2 = rawLine.length - 3; j2 >= 0 && rawLine[j2] === "\\"; j2--) {
174766
+ backslashCount++;
174767
+ }
174768
+ const isEscapedDollar = backslashCount % 2 === 1;
174769
+ pendingDollarLineContinuation = !isEscapedDollar;
174770
+ }
174771
+ }
174772
+ i4 = lineEnd + newlineLength;
174773
+ if (newlineLength === 0) {
174774
+ break;
174775
+ }
174776
+ }
174777
+ }
174778
+ return { nextIndex: i4, hasSubstitution: false };
174779
+ }, "consumeHeredocBodies");
174619
174780
  let inSingleQuotes = false;
174620
174781
  let inDoubleQuotes = false;
174621
174782
  let inBackticks = false;
174783
+ let inComment = false;
174784
+ const pendingHeredocs = [];
174622
174785
  let i3 = 0;
174623
174786
  while (i3 < command2.length) {
174624
174787
  const char = command2[i3];
174625
174788
  const nextChar = command2[i3 + 1];
174789
+ if (!inSingleQuotes && !inDoubleQuotes && !inBackticks) {
174790
+ if (char === "\r" && nextChar === "\n") {
174791
+ inComment = false;
174792
+ if (pendingHeredocs.length > 0) {
174793
+ const result = consumeHeredocBodies(i3 + 2, pendingHeredocs);
174794
+ if (result.hasSubstitution) return true;
174795
+ pendingHeredocs.length = 0;
174796
+ i3 = result.nextIndex;
174797
+ continue;
174798
+ }
174799
+ } else if (char === "\n" || char === "\r") {
174800
+ inComment = false;
174801
+ if (pendingHeredocs.length > 0) {
174802
+ const result = consumeHeredocBodies(i3 + 1, pendingHeredocs);
174803
+ if (result.hasSubstitution) return true;
174804
+ pendingHeredocs.length = 0;
174805
+ i3 = result.nextIndex;
174806
+ continue;
174807
+ }
174808
+ }
174809
+ }
174810
+ if (!inSingleQuotes && !inDoubleQuotes && !inBackticks) {
174811
+ if (!inComment && isCommentStart(i3)) {
174812
+ inComment = true;
174813
+ i3++;
174814
+ continue;
174815
+ }
174816
+ if (inComment) {
174817
+ i3++;
174818
+ continue;
174819
+ }
174820
+ }
174626
174821
  if (char === "\\" && !inSingleQuotes) {
174627
174822
  i3 += 2;
174628
174823
  continue;
@@ -174634,6 +174829,14 @@ function detectCommandSubstitution(command2) {
174634
174829
  } else if (char === "`" && !inSingleQuotes) {
174635
174830
  inBackticks = !inBackticks;
174636
174831
  }
174832
+ if (!inSingleQuotes && !inDoubleQuotes && !inBackticks && char === "<" && nextChar === "<") {
174833
+ const parsed = parseHeredocOperator(i3);
174834
+ if (parsed) {
174835
+ pendingHeredocs.push(parsed.heredoc);
174836
+ i3 = parsed.nextIndex;
174837
+ continue;
174838
+ }
174839
+ }
174637
174840
  if (!inSingleQuotes) {
174638
174841
  if (char === "$" && nextChar === "(") {
174639
174842
  return true;
@@ -174644,7 +174847,7 @@ function detectCommandSubstitution(command2) {
174644
174847
  if (char === ">" && nextChar === "(" && !inDoubleQuotes && !inBackticks) {
174645
174848
  return true;
174646
174849
  }
174647
- if (char === "`" && !inBackticks) {
174850
+ if (char === "`") {
174648
174851
  return true;
174649
174852
  }
174650
174853
  }
@@ -176921,6 +177124,7 @@ var init_subagent = __esm({
176921
177124
  abortController,
176922
177125
  promptId,
176923
177126
  turnCounter,
177127
+ toolsList,
176924
177128
  currentResponseId
176925
177129
  );
176926
177130
  } else {
@@ -177006,8 +177210,49 @@ var init_subagent = __esm({
177006
177210
  * @returns {Promise<Content[]>} A promise that resolves to an array of `Content` parts representing the tool responses,
177007
177211
  * which are then used to update the chat history.
177008
177212
  */
177009
- async processFunctionCalls(functionCalls, abortController, promptId, currentRound, responseId) {
177213
+ async processFunctionCalls(functionCalls, abortController, promptId, currentRound, toolsList, responseId) {
177010
177214
  const toolResponseParts = [];
177215
+ const allowedToolNames = new Set(toolsList.map((t5) => t5.name));
177216
+ const authorizedCalls = [];
177217
+ for (const fc of functionCalls) {
177218
+ const callId = fc.id ?? `${fc.name}-${Date.now()}`;
177219
+ if (!allowedToolNames.has(fc.name)) {
177220
+ const toolName = String(fc.name);
177221
+ const errorMessage = `Tool "${toolName}" not found. Tools must use the exact names provided.`;
177222
+ this.eventEmitter?.emit("tool_call" /* TOOL_CALL */, {
177223
+ subagentId: this.subagentId,
177224
+ round: currentRound,
177225
+ callId,
177226
+ name: toolName,
177227
+ args: fc.args ?? {},
177228
+ description: `Tool "${toolName}" not found`,
177229
+ timestamp: Date.now()
177230
+ });
177231
+ const functionResponsePart = {
177232
+ functionResponse: {
177233
+ id: callId,
177234
+ name: toolName,
177235
+ response: { error: errorMessage }
177236
+ }
177237
+ };
177238
+ this.eventEmitter?.emit("tool_result" /* TOOL_RESULT */, {
177239
+ subagentId: this.subagentId,
177240
+ round: currentRound,
177241
+ callId,
177242
+ name: toolName,
177243
+ success: false,
177244
+ error: errorMessage,
177245
+ responseParts: [functionResponsePart],
177246
+ resultDisplay: errorMessage,
177247
+ durationMs: 0,
177248
+ timestamp: Date.now()
177249
+ });
177250
+ this.recordToolCallStats(toolName, false, 0, errorMessage);
177251
+ toolResponseParts.push(functionResponsePart);
177252
+ continue;
177253
+ }
177254
+ authorizedCalls.push(fc);
177255
+ }
177011
177256
  const responded = /* @__PURE__ */ new Set();
177012
177257
  let resolveBatch = null;
177013
177258
  const scheduler = new CoreToolScheduler({
@@ -177019,29 +177264,7 @@ var init_subagent = __esm({
177019
177264
  const duration3 = call.durationMs ?? 0;
177020
177265
  const success = call.status === "success";
177021
177266
  const errorMessage = call.status === "error" || call.status === "cancelled" ? call.response.error?.message : void 0;
177022
- this.executionStats.totalToolCalls += 1;
177023
- if (success) {
177024
- this.executionStats.successfulToolCalls += 1;
177025
- } else {
177026
- this.executionStats.failedToolCalls += 1;
177027
- }
177028
- const tu = this.toolUsage.get(toolName) || {
177029
- count: 0,
177030
- success: 0,
177031
- failure: 0,
177032
- totalDurationMs: 0,
177033
- averageDurationMs: 0
177034
- };
177035
- tu.count += 1;
177036
- if (success) {
177037
- tu.success += 1;
177038
- } else {
177039
- tu.failure += 1;
177040
- tu.lastError = errorMessage || "Unknown error";
177041
- }
177042
- tu.totalDurationMs = (tu.totalDurationMs || 0) + duration3;
177043
- tu.averageDurationMs = tu.count > 0 ? tu.totalDurationMs / tu.count : 0;
177044
- this.toolUsage.set(toolName, tu);
177267
+ this.recordToolCallStats(toolName, success, duration3, errorMessage);
177045
177268
  this.eventEmitter?.emit("tool_result" /* TOOL_RESULT */, {
177046
177269
  subagentId: this.subagentId,
177047
177270
  round: currentRound,
@@ -177050,22 +177273,10 @@ var init_subagent = __esm({
177050
177273
  success,
177051
177274
  error: errorMessage,
177052
177275
  responseParts: call.response.responseParts,
177053
- /**
177054
- * Tools like todoWrite will add some extra contents to the result,
177055
- * making it unable to deserialize the `responseParts` to a JSON object.
177056
- * While `resultDisplay` is normally a string, if not we stringify it,
177057
- * so that we can deserialize it to a JSON object when needed.
177058
- */
177059
177276
  resultDisplay: call.response.resultDisplay ? typeof call.response.resultDisplay === "string" ? call.response.resultDisplay : JSON.stringify(call.response.resultDisplay) : void 0,
177060
177277
  durationMs: duration3,
177061
177278
  timestamp: Date.now()
177062
177279
  });
177063
- this.stats.recordToolCall(
177064
- toolName,
177065
- success,
177066
- duration3,
177067
- this.toolUsage.get(toolName)?.lastError
177068
- );
177069
177280
  await this.hooks?.postToolUse?.({
177070
177281
  subagentId: this.subagentId,
177071
177282
  name: this.name,
@@ -177122,7 +177333,7 @@ var init_subagent = __esm({
177122
177333
  onEditorClose: /* @__PURE__ */ __name(() => {
177123
177334
  }, "onEditorClose")
177124
177335
  });
177125
- const requests = functionCalls.map((fc) => {
177336
+ const requests = authorizedCalls.map((fc) => {
177126
177337
  const toolName = String(fc.name || "unknown");
177127
177338
  const callId = fc.id ?? `${fc.name}-${Date.now()}`;
177128
177339
  const args = fc.args ?? {};
@@ -177255,6 +177466,41 @@ var init_subagent = __esm({
177255
177466
  return "";
177256
177467
  }
177257
177468
  }
177469
+ /**
177470
+ * Records tool call statistics for both successful and failed tool calls.
177471
+ * This includes updating aggregate stats, per-tool usage, and the statistics service.
177472
+ */
177473
+ recordToolCallStats(toolName, success, durationMs, errorMessage) {
177474
+ this.executionStats.totalToolCalls += 1;
177475
+ if (success) {
177476
+ this.executionStats.successfulToolCalls += 1;
177477
+ } else {
177478
+ this.executionStats.failedToolCalls += 1;
177479
+ }
177480
+ const tu = this.toolUsage.get(toolName) || {
177481
+ count: 0,
177482
+ success: 0,
177483
+ failure: 0,
177484
+ totalDurationMs: 0,
177485
+ averageDurationMs: 0
177486
+ };
177487
+ tu.count += 1;
177488
+ if (success) {
177489
+ tu.success += 1;
177490
+ } else {
177491
+ tu.failure += 1;
177492
+ tu.lastError = errorMessage || "Unknown error";
177493
+ }
177494
+ tu.totalDurationMs = (tu.totalDurationMs || 0) + durationMs;
177495
+ tu.averageDurationMs = tu.count > 0 ? tu.totalDurationMs / tu.count : 0;
177496
+ this.toolUsage.set(toolName, tu);
177497
+ this.stats.recordToolCall(
177498
+ toolName,
177499
+ success,
177500
+ durationMs,
177501
+ this.toolUsage.get(toolName)?.lastError
177502
+ );
177503
+ }
177258
177504
  buildChatSystemPrompt(context2) {
177259
177505
  if (!this.promptConfig.systemPrompt) {
177260
177506
  return "";
@@ -191309,21 +191555,53 @@ var init_esm11 = __esm({
191309
191555
  // packages/core/src/services/fileSystemService.ts
191310
191556
  import fs31 from "node:fs/promises";
191311
191557
  import * as path27 from "node:path";
191312
- var StandardFileSystemService;
191558
+ function hasUTF8BOM(buffer) {
191559
+ return buffer.length >= 3 && buffer[0] === 239 && buffer[1] === 187 && buffer[2] === 191;
191560
+ }
191561
+ var FileEncoding, StandardFileSystemService;
191313
191562
  var init_fileSystemService = __esm({
191314
191563
  "packages/core/src/services/fileSystemService.ts"() {
191315
191564
  "use strict";
191316
191565
  init_esbuild_shims();
191317
191566
  init_esm11();
191567
+ FileEncoding = {
191568
+ UTF8: "utf-8",
191569
+ UTF8_BOM: "utf-8-bom"
191570
+ };
191571
+ __name(hasUTF8BOM, "hasUTF8BOM");
191318
191572
  StandardFileSystemService = class {
191319
191573
  static {
191320
191574
  __name(this, "StandardFileSystemService");
191321
191575
  }
191322
191576
  async readTextFile(filePath) {
191323
- return fs31.readFile(filePath, "utf-8");
191577
+ return fs31.readFile(filePath, FileEncoding.UTF8);
191578
+ }
191579
+ async writeTextFile(filePath, content, options2) {
191580
+ const bom = options2?.bom ?? false;
191581
+ if (bom) {
191582
+ const normalizedContent = content.charCodeAt(0) === 65279 ? content.slice(1) : content;
191583
+ const bomBuffer = Buffer.from([239, 187, 191]);
191584
+ const contentBuffer = Buffer.from(normalizedContent, "utf-8");
191585
+ await fs31.writeFile(filePath, Buffer.concat([bomBuffer, contentBuffer]));
191586
+ } else {
191587
+ await fs31.writeFile(filePath, content, "utf-8");
191588
+ }
191324
191589
  }
191325
- async writeTextFile(filePath, content) {
191326
- await fs31.writeFile(filePath, content, "utf-8");
191590
+ async detectFileBOM(filePath) {
191591
+ let fd;
191592
+ try {
191593
+ fd = await fs31.open(filePath, "r");
191594
+ const buffer = Buffer.alloc(3);
191595
+ const { bytesRead } = await fd.read(buffer, 0, 3, 0);
191596
+ if (bytesRead < 3) {
191597
+ return false;
191598
+ }
191599
+ return hasUTF8BOM(buffer);
191600
+ } catch {
191601
+ return false;
191602
+ } finally {
191603
+ await fd?.close();
191604
+ }
191327
191605
  }
191328
191606
  findFiles(fileName, searchPaths) {
191329
191607
  return searchPaths.flatMap((searchPath) => {
@@ -208889,22 +209167,6 @@ function normalizeBasicCharacters(text) {
208889
209167
  }
208890
209168
  return normalized2;
208891
209169
  }
208892
- function stripTrailingWhitespacePreserveNewlines(text) {
208893
- const pieces = text.split(/(\r\n|\n|\r)/);
208894
- let result = "";
208895
- for (let i3 = 0; i3 < pieces.length; i3++) {
208896
- const segment = pieces[i3];
208897
- if (segment === void 0) {
208898
- continue;
208899
- }
208900
- if (i3 % 2 === 0) {
208901
- result += segment.trimEnd();
208902
- } else {
208903
- result += segment;
208904
- }
208905
- }
208906
- return result;
208907
- }
208908
209170
  function normalizeLineForComparison(value) {
208909
209171
  return normalizeBasicCharacters(value).trimEnd();
208910
209172
  }
@@ -209048,11 +209310,10 @@ function adjustNewStringForTrailingLine(newString, removedTrailingLine) {
209048
209310
  return removedTrailingLine ? removeTrailingNewline(newString) : newString;
209049
209311
  }
209050
209312
  function normalizeEditStrings(fileContent, oldString, newString) {
209051
- const trimmedNewString = stripTrailingWhitespacePreserveNewlines(newString);
209052
209313
  if (fileContent === null || oldString === "") {
209053
209314
  return {
209054
209315
  oldString,
209055
- newString: trimmedNewString
209316
+ newString
209056
209317
  };
209057
209318
  }
209058
209319
  const canonicalOriginal = findMatchedSlice(fileContent, oldString);
@@ -209060,14 +209321,14 @@ function normalizeEditStrings(fileContent, oldString, newString) {
209060
209321
  return {
209061
209322
  oldString: canonicalOriginal.slice,
209062
209323
  newString: adjustNewStringForTrailingLine(
209063
- trimmedNewString,
209324
+ newString,
209064
209325
  canonicalOriginal.removedTrailingFinalEmptyLine
209065
209326
  )
209066
209327
  };
209067
209328
  }
209068
209329
  return {
209069
209330
  oldString,
209070
- newString: trimmedNewString
209331
+ newString
209071
209332
  };
209072
209333
  }
209073
209334
  function maybeAugmentOldStringForDeletion(fileContent, oldString, newString) {
@@ -209177,7 +209438,6 @@ var init_editHelper = __esm({
209177
209438
  "\u3000": " "
209178
209439
  };
209179
209440
  __name(normalizeBasicCharacters, "normalizeBasicCharacters");
209180
- __name(stripTrailingWhitespacePreserveNewlines, "stripTrailingWhitespacePreserveNewlines");
209181
209441
  LINE_COMPARISON_PASSES = [
209182
209442
  (value) => value,
209183
209443
  (value) => value.trimEnd()
@@ -209225,6 +209485,7 @@ var init_edit = __esm({
209225
209485
  init_paths();
209226
209486
  init_errors();
209227
209487
  init_config3();
209488
+ init_fileSystemService();
209228
209489
  init_diffOptions();
209229
209490
  init_read_file();
209230
209491
  init_tool_names();
@@ -209456,7 +209717,14 @@ var init_edit = __esm({
209456
209717
  }
209457
209718
  try {
209458
209719
  this.ensureParentDirectoriesExist(this.params.file_path);
209459
- await this.config.getFileSystemService().writeTextFile(this.params.file_path, editData.newContent);
209720
+ if (editData.isNewFile) {
209721
+ const useBOM = this.config.getDefaultFileEncoding() === FileEncoding.UTF8_BOM;
209722
+ await this.config.getFileSystemService().writeTextFile(this.params.file_path, editData.newContent, {
209723
+ bom: useBOM
209724
+ });
209725
+ } else {
209726
+ await this.config.getFileSystemService().writeTextFile(this.params.file_path, editData.newContent);
209727
+ }
209460
209728
  const fileName = path35.basename(this.params.file_path);
209461
209729
  const originallyProposedContent = this.params.ai_proposed_content || editData.newContent;
209462
209730
  const diffStat = getDiffStat(
@@ -224253,6 +224521,7 @@ var init_write_file = __esm({
224253
224521
  init_config3();
224254
224522
  init_tools();
224255
224523
  init_tool_error();
224524
+ init_fileSystemService();
224256
224525
  init_paths();
224257
224526
  init_errors();
224258
224527
  init_diffOptions();
@@ -224365,7 +224634,13 @@ var init_write_file = __esm({
224365
224634
  if (!fs46.existsSync(dirName)) {
224366
224635
  fs46.mkdirSync(dirName, { recursive: true });
224367
224636
  }
224368
- await this.config.getFileSystemService().writeTextFile(file_path, fileContent);
224637
+ let useBOM = false;
224638
+ if (!isNewFile) {
224639
+ useBOM = await this.config.getFileSystemService().detectFileBOM(file_path);
224640
+ } else {
224641
+ useBOM = this.config.getDefaultFileEncoding() === FileEncoding.UTF8_BOM;
224642
+ }
224643
+ await this.config.getFileSystemService().writeTextFile(file_path, fileContent, { bom: useBOM });
224369
224644
  const fileName = path49.basename(file_path);
224370
224645
  const currentContentForDiff = correctedContentResult.error ? "" : originalContent;
224371
224646
  const fileDiff = createPatch(
@@ -249910,6 +250185,7 @@ var init_config3 = __esm({
249910
250185
  eventEmitter;
249911
250186
  useSmartEdit;
249912
250187
  channel;
250188
+ defaultFileEncoding;
249913
250189
  constructor(params) {
249914
250190
  this.sessionId = params.sessionId ?? randomUUID6();
249915
250191
  this.sessionData = params.sessionData;
@@ -250011,6 +250287,7 @@ var init_config3 = __esm({
250011
250287
  this.enableToolOutputTruncation = params.enableToolOutputTruncation ?? true;
250012
250288
  this.useSmartEdit = params.useSmartEdit ?? false;
250013
250289
  this.channel = params.channel;
250290
+ this.defaultFileEncoding = params.defaultFileEncoding ?? FileEncoding.UTF8;
250014
250291
  this.storage = new Storage(this.targetDir);
250015
250292
  this.vlmSwitchMode = params.vlmSwitchMode;
250016
250293
  this.inputFormat = params.inputFormat ?? "text" /* TEXT */;
@@ -250621,6 +250898,13 @@ var init_config3 = __esm({
250621
250898
  getChannel() {
250622
250899
  return this.channel;
250623
250900
  }
250901
+ /**
250902
+ * Get the default file encoding for new files.
250903
+ * @returns FileEncodingType
250904
+ */
250905
+ getDefaultFileEncoding() {
250906
+ return this.defaultFileEncoding;
250907
+ }
250624
250908
  /**
250625
250909
  * Get the current FileSystemService
250626
250910
  */
@@ -254958,8 +255242,8 @@ var init_git_commit = __esm({
254958
255242
  "packages/core/src/generated/git-commit.ts"() {
254959
255243
  "use strict";
254960
255244
  init_esbuild_shims();
254961
- GIT_COMMIT_INFO = "3234c50a0";
254962
- CLI_VERSION = "0.2.3-alpha.5";
255245
+ GIT_COMMIT_INFO = "250a5d631";
255246
+ CLI_VERSION = "0.2.3-alpha.6";
254963
255247
  }
254964
255248
  });
254965
255249
 
@@ -261029,6 +261313,7 @@ __export(core_exports5, {
261029
261313
  FatalTurnLimitedError: () => FatalTurnLimitedError,
261030
261314
  FetchError: () => FetchError,
261031
261315
  FileDiscoveryService: () => FileDiscoveryService,
261316
+ FileEncoding: () => FileEncoding,
261032
261317
  FileExclusions: () => FileExclusions,
261033
261318
  FileOperation: () => FileOperation,
261034
261319
  FileSchema: () => FileSchema,
@@ -299797,6 +300082,19 @@ var init_settingsSchema = __esm({
299797
300082
  default: true,
299798
300083
  description: "Enable saving chat history to disk. Disabling this will also prevent --continue and --resume from working.",
299799
300084
  showInDialog: false
300085
+ },
300086
+ defaultFileEncoding: {
300087
+ type: "enum",
300088
+ label: "Default File Encoding",
300089
+ category: "General",
300090
+ requiresRestart: false,
300091
+ default: "utf-8",
300092
+ description: 'Default encoding for new files. Use "utf-8" (default) for UTF-8 without BOM, or "utf-8-bom" for UTF-8 with BOM. Only change this if your project specifically requires BOM.',
300093
+ showInDialog: false,
300094
+ options: [
300095
+ { value: "utf-8", label: "UTF-8 (without BOM)" },
300096
+ { value: "utf-8-bom", label: "UTF-8 with BOM" }
300097
+ ]
299800
300098
  }
299801
300099
  }
299802
300100
  },
@@ -309614,6 +309912,7 @@ var init_en3 = __esm({
309614
309912
  "auto-accept edits": "auto-accept edits",
309615
309913
  "Accepting edits": "Accepting edits",
309616
309914
  "(shift + tab to cycle)": "(shift + tab to cycle)",
309915
+ "(tab to cycle)": "(tab to cycle)",
309617
309916
  "Execute shell commands via {{symbol}} (e.g., {{example1}}) or use natural language (e.g., {{example2}}).": "Execute shell commands via {{symbol}} (e.g., {{example1}}) or use natural language (e.g., {{example2}}).",
309618
309917
  "!": "!",
309619
309918
  "!npm run start": "!npm run start",
@@ -310423,6 +310722,7 @@ var init_en3 = __esm({
310423
310722
  "Type / to open the command popup; Tab autocompletes slash commands and saved prompts.": "Type / to open the command popup; Tab autocompletes slash commands and saved prompts.",
310424
310723
  "You can resume a previous conversation by running rdmind --continue or rdmind --resume.": "You can resume a previous conversation by running rdmind --continue or rdmind --resume.",
310425
310724
  "You can switch permission mode quickly with Shift+Tab or /approval-mode.": "You can switch permission mode quickly with Shift+Tab or /approval-mode.",
310725
+ "You can switch permission mode quickly with Tab or /approval-mode.": "You can switch permission mode quickly with Tab or /approval-mode.",
310426
310726
  "RDMind now supports custom commands, sub-agents, skills and other features. Check the documentation for usage help.": "RDMind now supports custom commands, sub-agents, skills and other features. Check the documentation for usage help.",
310427
310727
  "Try asking RDMind to help you commit code.": "Try asking RDMind to help you commit code.",
310428
310728
  "RDMind can read REDoc. Please do not upload documents involving user privacy and core algorithms.": "RDMind can read REDoc. Please do not upload documents involving user privacy and core algorithms.",
@@ -310697,6 +310997,7 @@ var init_zh = __esm({
310697
310997
  "auto-accept edits": "\u81EA\u52A8\u63A5\u53D7\u7F16\u8F91",
310698
310998
  "Accepting edits": "\u63A5\u53D7\u7F16\u8F91",
310699
310999
  "(shift + tab to cycle)": "(shift + tab \u5207\u6362)",
311000
+ "(tab to cycle)": "(\u6309 tab \u5207\u6362)",
310700
311001
  "Execute shell commands via {{symbol}} (e.g., {{example1}}) or use natural language (e.g., {{example2}}).": "\u901A\u8FC7 {{symbol}} \u6267\u884C shell \u547D\u4EE4\uFF08\u4F8B\u5982\uFF0C{{example1}}\uFF09\u6216\u4F7F\u7528\u81EA\u7136\u8BED\u8A00\uFF08\u4F8B\u5982\uFF0C{{example2}}\uFF09",
310701
311002
  "!": "!",
310702
311003
  "!npm run start": "!npm run start",
@@ -311506,6 +311807,7 @@ var init_zh = __esm({
311506
311807
  "Type / to open the command popup; Tab autocompletes slash commands and saved prompts.": "\u8F93\u5165 / \u6253\u5F00\u547D\u4EE4\u5F39\u7A97\uFF1B\u6309 Tab \u81EA\u52A8\u8865\u5168\u659C\u6760\u547D\u4EE4\u548C\u4FDD\u5B58\u7684\u63D0\u793A\u8BCD\u3002",
311507
311808
  "You can resume a previous conversation by running rdmind --continue or rdmind --resume.": "\u8FD0\u884C rdmind --continue \u6216 rdmind --resume \u53EF\u7EE7\u7EED\u4E4B\u524D\u7684\u4F1A\u8BDD\u3002",
311508
311809
  "You can switch permission mode quickly with Shift+Tab or /approval-mode.": "\u6309 Shift+Tab \u6216\u8F93\u5165 /approval-mode \u53EF\u5FEB\u901F\u5207\u6362\u6743\u9650\u6A21\u5F0F\u3002",
311810
+ "You can switch permission mode quickly with Tab or /approval-mode.": "\u6309 Tab \u6216\u8F93\u5165 /approval-mode \u53EF\u5FEB\u901F\u5207\u6362\u6743\u9650\u6A21\u5F0F\u3002",
311509
311811
  "RDMind now supports custom commands, sub-agents, skills and other features. Check the documentation for usage help.": "RDMind \u73B0\u5DF2\u652F\u6301\u81EA\u5B9A\u4E49\u547D\u4EE4\u3001\u5B50\u4EE3\u7406\u3001\u6280\u80FD\u7B49\u7279\u6027\uFF0C\u67E5\u9605\u6587\u6863\u83B7\u5F97\u4F7F\u7528\u5E2E\u52A9\u3002",
311510
311812
  "Try asking RDMind to help you commit code.": "\u8BD5\u8BD5\u8BA9 RDMind \u5E2E\u4F60\u63D0\u4EA4\u4EE3\u7801\u3002",
311511
311813
  "RDMind can read REDoc. Please do not upload documents involving user privacy and core algorithms.": "RDMind \u53EF\u4EE5\u8BFB\u61C2 REDoc\uFF0C\u6CE8\u610F\u4E0D\u8981\u4E0A\u4F20\u6D89\u53CA\u7528\u6237\u9690\u79C1\u548C\u6838\u5FC3\u7B97\u6CD5\u7684\u6587\u6863\u3002",
@@ -360858,7 +361160,7 @@ __name(getPackageJson, "getPackageJson");
360858
361160
  // packages/cli/src/utils/version.ts
360859
361161
  async function getCliVersion() {
360860
361162
  const pkgJson = await getPackageJson();
360861
- return "0.2.3-alpha.5";
361163
+ return "0.2.3-alpha.6";
360862
361164
  }
360863
361165
  __name(getCliVersion, "getCliVersion");
360864
361166
 
@@ -365468,6 +365770,7 @@ async function loadCliConfig(settings, argv, cwd7 = process.cwd(), overrideExten
365468
365770
  // NOTE: do NOT set a yargs default for `chat-recording`, otherwise argv will
365469
365771
  // always be true and the settings file can never disable recording.
365470
365772
  chatRecording: argv.chatRecording ?? settings.general?.chatRecording ?? true,
365773
+ defaultFileEncoding: settings.general?.defaultFileEncoding ?? FileEncoding.UTF8,
365471
365774
  lsp: {
365472
365775
  enabled: lspEnabled
365473
365776
  }
@@ -368588,7 +368891,7 @@ var formatDuration = /* @__PURE__ */ __name((milliseconds) => {
368588
368891
 
368589
368892
  // packages/cli/src/generated/git-commit.ts
368590
368893
  init_esbuild_shims();
368591
- var GIT_COMMIT_INFO2 = "3234c50a0";
368894
+ var GIT_COMMIT_INFO2 = "250a5d631";
368592
368895
 
368593
368896
  // packages/cli/src/utils/systemInfo.ts
368594
368897
  async function getNpmVersion() {
@@ -404549,7 +404852,7 @@ var Help = /* @__PURE__ */ __name(({ commands, width }) => /* @__PURE__ */ (0, i
404549
404852
  t4("Cancel operation / Clear input (double press)")
404550
404853
  ] }),
404551
404854
  /* @__PURE__ */ (0, import_jsx_runtime51.jsxs)(Text3, { color: theme.text.primary, children: [
404552
- /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(Text3, { bold: true, color: theme.text.accent, children: "Shift+Tab" }),
404855
+ /* @__PURE__ */ (0, import_jsx_runtime51.jsx)(Text3, { bold: true, color: theme.text.accent, children: process.platform === "win32" ? "Tab" : "Shift+Tab" }),
404553
404856
  " ",
404554
404857
  "- ",
404555
404858
  t4("Cycle approval modes")
@@ -405324,6 +405627,8 @@ var startupTips = [
405324
405627
  "You can run any shell commands from RDMind using ! (e.g. !ls).",
405325
405628
  "Type / to open the command popup; Tab autocompletes slash commands and saved prompts.",
405326
405629
  "You can resume a previous conversation by running rdmind --continue or rdmind --resume.",
405630
+ process.platform === "win32" ? "You can switch permission mode quickly with Tab or /approval-mode." : "You can switch permission mode quickly with Shift+Tab or /approval-mode.",
405631
+ "You can resume a previous conversation by running rdmind --continue or rdmind --resume.",
405327
405632
  "You can switch permission mode quickly with Shift+Tab or /approval-mode.",
405328
405633
  "RDMind now supports custom commands, sub-agents, skills and other features. Check the documentation for usage help.",
405329
405634
  "Try asking RDMind to help you commit code.",
@@ -412343,21 +412648,22 @@ var AutoAcceptIndicator = /* @__PURE__ */ __name(({
412343
412648
  let textColor = "";
412344
412649
  let textContent2 = "";
412345
412650
  let subText = "";
412651
+ const cycleText = process.platform === "win32" ? ` ${t4("(tab to cycle)")}` : ` ${t4("(shift + tab to cycle)")}`;
412346
412652
  switch (approvalMode) {
412347
412653
  case "plan" /* PLAN */:
412348
412654
  textColor = theme.status.success;
412349
412655
  textContent2 = t4("plan mode");
412350
- subText = ` ${t4("(shift + tab to cycle)")}`;
412656
+ subText = cycleText;
412351
412657
  break;
412352
412658
  case "auto-edit" /* AUTO_EDIT */:
412353
412659
  textColor = theme.status.warning;
412354
412660
  textContent2 = t4("auto-accept edits");
412355
- subText = ` ${t4("(shift + tab to cycle)")}`;
412661
+ subText = cycleText;
412356
412662
  break;
412357
412663
  case "yolo" /* YOLO */:
412358
412664
  textColor = theme.status.error;
412359
412665
  textContent2 = t4("YOLO mode");
412360
- subText = ` ${t4("(shift + tab to cycle)")}`;
412666
+ subText = cycleText;
412361
412667
  break;
412362
412668
  case "default" /* DEFAULT */:
412363
412669
  default:
@@ -412499,7 +412805,10 @@ var getShortcuts = /* @__PURE__ */ __name(() => [
412499
412805
  { key: "/", description: t4("for commands") },
412500
412806
  { key: "@", description: t4("for file paths") },
412501
412807
  { key: "esc esc", description: t4("to clear input") },
412502
- { key: "shift+tab", description: t4("to cycle approvals") },
412808
+ {
412809
+ key: process.platform === "win32" ? "tab" : "shift+tab",
412810
+ description: t4("to cycle approvals")
412811
+ },
412503
412812
  { key: "ctrl+c", description: t4("to quit") },
412504
412813
  { key: getNewlineKey(), description: t4("for newline") + " \u23CE" },
412505
412814
  { key: "ctrl+l", description: t4("to clear screen") },
@@ -421034,7 +421343,9 @@ function useAutoAcceptIndicator({
421034
421343
  }, [currentConfigValue]);
421035
421344
  useKeypress(
421036
421345
  (key) => {
421037
- if (key.shift && key.name === "tab") {
421346
+ const isShiftTab = key.shift && key.name === "tab";
421347
+ const isWindowsTab = process.platform === "win32" && key.name === "tab" && !key.ctrl && !key.meta;
421348
+ if (isShiftTab || isWindowsTab) {
421038
421349
  const currentMode = config2.getApprovalMode();
421039
421350
  const currentIndex = APPROVAL_MODES.indexOf(currentMode);
421040
421351
  const nextIndex = currentIndex === -1 ? 0 : (currentIndex + 1) % APPROVAL_MODES.length;
@@ -425567,16 +425878,32 @@ var AcpFileSystemService = class {
425567
425878
  }
425568
425879
  return response.content;
425569
425880
  }
425570
- async writeTextFile(filePath, content) {
425881
+ async writeTextFile(filePath, content, options2) {
425571
425882
  if (!this.capabilities.writeTextFile) {
425572
- return this.fallback.writeTextFile(filePath, content);
425883
+ return this.fallback.writeTextFile(filePath, content, options2);
425573
425884
  }
425885
+ const finalContent = options2?.bom ? "\uFEFF" + content : content;
425574
425886
  await this.client.writeTextFile({
425575
425887
  path: filePath,
425576
- content,
425888
+ content: finalContent,
425577
425889
  sessionId: this.sessionId
425578
425890
  });
425579
425891
  }
425892
+ async detectFileBOM(filePath) {
425893
+ if (this.capabilities.readTextFile) {
425894
+ try {
425895
+ const response = await this.client.readTextFile({
425896
+ path: filePath,
425897
+ sessionId: this.sessionId,
425898
+ line: null,
425899
+ limit: 1
425900
+ });
425901
+ return response.content.charCodeAt(0) === 65279;
425902
+ } catch {
425903
+ }
425904
+ }
425905
+ return this.fallback.detectFileBOM(filePath);
425906
+ }
425580
425907
  findFiles(fileName, searchPaths) {
425581
425908
  return this.fallback.findFiles(fileName, searchPaths);
425582
425909
  }
@@ -427248,7 +427575,7 @@ var GeminiAgent = class {
427248
427575
  name: APPROVAL_MODE_INFO[mode].name,
427249
427576
  description: APPROVAL_MODE_INFO[mode].description
427250
427577
  }));
427251
- const version2 = "0.2.3-alpha.5";
427578
+ const version2 = "0.2.3-alpha.6";
427252
427579
  return {
427253
427580
  protocolVersion: PROTOCOL_VERSION,
427254
427581
  agentInfo: {
package/locales/en.js CHANGED
@@ -23,6 +23,7 @@ export default {
23
23
  'auto-accept edits': 'auto-accept edits',
24
24
  'Accepting edits': 'Accepting edits',
25
25
  '(shift + tab to cycle)': '(shift + tab to cycle)',
26
+ '(tab to cycle)': '(tab to cycle)',
26
27
  'Execute shell commands via {{symbol}} (e.g., {{example1}}) or use natural language (e.g., {{example2}}).':
27
28
  'Execute shell commands via {{symbol}} (e.g., {{example1}}) or use natural language (e.g., {{example2}}).',
28
29
  '!': '!',
@@ -1142,6 +1143,8 @@ export default {
1142
1143
  'You can resume a previous conversation by running rdmind --continue or rdmind --resume.',
1143
1144
  'You can switch permission mode quickly with Shift+Tab or /approval-mode.':
1144
1145
  'You can switch permission mode quickly with Shift+Tab or /approval-mode.',
1146
+ 'You can switch permission mode quickly with Tab or /approval-mode.':
1147
+ 'You can switch permission mode quickly with Tab or /approval-mode.',
1145
1148
  'RDMind now supports custom commands, sub-agents, skills and other features. Check the documentation for usage help.':
1146
1149
  'RDMind now supports custom commands, sub-agents, skills and other features. Check the documentation for usage help.',
1147
1150
  'Try asking RDMind to help you commit code.':
package/locales/zh.js CHANGED
@@ -22,6 +22,7 @@ export default {
22
22
  'auto-accept edits': '自动接受编辑',
23
23
  'Accepting edits': '接受编辑',
24
24
  '(shift + tab to cycle)': '(shift + tab 切换)',
25
+ '(tab to cycle)': '(按 tab 切换)',
25
26
  'Execute shell commands via {{symbol}} (e.g., {{example1}}) or use natural language (e.g., {{example2}}).':
26
27
  '通过 {{symbol}} 执行 shell 命令(例如,{{example1}})或使用自然语言(例如,{{example2}})',
27
28
  '!': '!',
@@ -1078,6 +1079,8 @@ export default {
1078
1079
  '运行 rdmind --continue 或 rdmind --resume 可继续之前的会话。',
1079
1080
  'You can switch permission mode quickly with Shift+Tab or /approval-mode.':
1080
1081
  '按 Shift+Tab 或输入 /approval-mode 可快速切换权限模式。',
1082
+ 'You can switch permission mode quickly with Tab or /approval-mode.':
1083
+ '按 Tab 或输入 /approval-mode 可快速切换权限模式。',
1081
1084
  'RDMind now supports custom commands, sub-agents, skills and other features. Check the documentation for usage help.':
1082
1085
  'RDMind 现已支持自定义命令、子代理、技能等特性,查阅文档获得使用帮助。',
1083
1086
  'Try asking RDMind to help you commit code.': '试试让 RDMind 帮你提交代码。',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rdmind/rdmind",
3
- "version": "0.2.3-alpha.5",
3
+ "version": "0.2.3-alpha.6",
4
4
  "description": "RDMind - AI-powered coding assistant",
5
5
  "type": "module",
6
6
  "main": "cli.js",
@@ -20,7 +20,7 @@
20
20
  "locales"
21
21
  ],
22
22
  "config": {
23
- "sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.2.3-alpha.5"
23
+ "sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.2.3-alpha.6"
24
24
  },
25
25
  "publishConfig": {
26
26
  "access": "public"