dskcode 0.1.31 → 0.1.33

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/index.js CHANGED
@@ -1175,7 +1175,7 @@ function formatArgsSummary(args) {
1175
1175
  return ` ${truncated}`;
1176
1176
  }
1177
1177
  }
1178
- function ToolCallBlock({ call, showPendingHint = true }) {
1178
+ function ToolCallBlock({ call }) {
1179
1179
  const argsDisplay = formatArgsSummary(call.arguments);
1180
1180
  return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginLeft: 3, marginTop: 1, children: [
1181
1181
  /* @__PURE__ */ jsxs3(Box3, { children: [
@@ -1188,8 +1188,7 @@ function ToolCallBlock({ call, showPendingHint = true }) {
1188
1188
  "\u2500".repeat(Math.max(1, 30 - call.name.length))
1189
1189
  ] })
1190
1190
  ] }),
1191
- /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: /* @__PURE__ */ jsx3(Text4, { dimColor: true, children: argsDisplay }) }),
1192
- showPendingHint && /* @__PURE__ */ jsx3(Box3, { children: /* @__PURE__ */ jsx3(Text4, { dimColor: true, children: "\u23F3 \u7B49\u5F85\u6267\u884C" }) })
1191
+ /* @__PURE__ */ jsx3(Box3, { flexDirection: "column", children: /* @__PURE__ */ jsx3(Text4, { dimColor: true, children: argsDisplay }) })
1193
1192
  ] });
1194
1193
  }
1195
1194
 
@@ -2658,7 +2657,7 @@ var Reflector = class {
2658
2657
  const reflections = [];
2659
2658
  for (const item of items) {
2660
2659
  if (item.result.success) continue;
2661
- const r = this.#ruleRepeatedFailure(item) ?? this.#ruleFileNotFound(item) ?? this.#rulePermissionDenied(item) ?? this.#ruleOutOfWriteRoot(item, ctx.writeRoots);
2660
+ const r = this.#ruleRepeatedFailure(item) ?? this.#ruleFileNotFound(item) ?? this.#ruleTextNotFound(item) ?? this.#rulePermissionDenied(item) ?? this.#ruleOutOfWriteRoot(item, ctx.writeRoots);
2662
2661
  if (r) reflections.push(r);
2663
2662
  if (reflections.length >= this.#maxReflections) break;
2664
2663
  }
@@ -2734,6 +2733,26 @@ var Reflector = class {
2734
2733
  hint: `\`${item.name}\` \u5931\u8D25\uFF1A\u6587\u4EF6\u4E0D\u5B58\u5728\u3002\u8BF7\u5148\u7528 \`ls\` / \`glob\` \u770B\u4E00\u4E0B\u76EE\u5F55\u7ED3\u6784\uFF0C\u6216\u786E\u8BA4\u8DEF\u5F84\u62FC\u5199\uFF08\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u89C1 system prompt\uFF09\u3002`
2735
2734
  };
2736
2735
  }
2736
+ /**
2737
+ * R2.5 文本未找到:edit_file / multi_edit 报 TEXT_NOT_FOUND 或 TEXT_MULTIPLE_MATCHES。
2738
+ *
2739
+ * 这是编辑工具最高频的失败。工具内部已对 CRLF 做归一化,仍报此错通常是
2740
+ * old_text 与当前文件内容不一致(缩进 / 已被改过 / 多次出现)。给一条明确
2741
+ * 的「重新读文件、逐字复制」指引,避免模型用同一份旧 old_text 反复重试。
2742
+ *
2743
+ * @pure 仅读入参
2744
+ */
2745
+ #ruleTextNotFound(item) {
2746
+ const isTextErr = item.result.error === "TEXT_NOT_FOUND" || item.result.error === "TEXT_MULTIPLE_MATCHES";
2747
+ if (!isTextErr) return null;
2748
+ const multiple = item.result.error === "TEXT_MULTIPLE_MATCHES";
2749
+ const hint = multiple ? `\`${item.name}\`\uFF1Aold_text \u5728\u6587\u4EF6\u4E2D\u51FA\u73B0\u591A\u6B21\u3002\u8BF7\u8865\u5145\u4E0A\u4E0B\u6587\u8BA9 old_text \u552F\u4E00\uFF0C\u6216\u6539\u7528 multi_edit \u7684 replaceAll\u3002` : `\`${item.name}\`\uFF1A\u672A\u627E\u5230 old_text\u3002\u8BF7\u91CD\u65B0 \`read_file\` \u770B\u5F53\u524D\u5185\u5BB9\uFF0C\u9010\u5B57\u590D\u5236\u8981\u66FF\u6362\u7684\u7247\u6BB5\uFF08\u6CE8\u610F\u7F29\u8FDB\u4E0E\u7A7A\u683C\uFF09\uFF1B\u884C\u5C3E\u5DF2\u505A CRLF \u5F52\u4E00\u5316\uFF0C\u65E0\u9700\u624B\u5199 \\r\\n\u3002`;
2750
+ return {
2751
+ category: "text_not_found",
2752
+ toolName: item.name,
2753
+ hint
2754
+ };
2755
+ }
2737
2756
  /**
2738
2757
  * R3 权限拒绝:GATE_DENIED 错误码 或 data 含 EACCES / permission / denied(不区分大小写)。
2739
2758
  *
@@ -2751,13 +2770,19 @@ var Reflector = class {
2751
2770
  };
2752
2771
  }
2753
2772
  /**
2754
- * R4 写根外:kind ∈ Edit/Delete/Move 且失败。
2773
+ * R4 写根外:kind ∈ Edit/Delete/Move 且错误码为 OUTSIDE_WRITE_ROOTS。
2774
+ *
2775
+ * 仅在工具本身明确报「路径超出写根」时才命中。旧实现只要写类工具失败就
2776
+ * 触发,会把 TEXT_NOT_FOUND / EXECUTION_ERROR 等误报为「写根外」,注入
2777
+ * 误导性提示(见会话日志 e65f0205 round 13:edit_file 因 CRLF 失配返回
2778
+ * TEXT_NOT_FOUND,却被反射为 out_of_write_root)。
2755
2779
  *
2756
2780
  * @pure 仅读入参
2757
2781
  */
2758
2782
  #ruleOutOfWriteRoot(item, writeRoots) {
2759
2783
  const isWriteKind = item.kind === "edit" /* Edit */ || item.kind === "delete" /* Delete */ || item.kind === "move" /* Move */;
2760
2784
  if (!isWriteKind) return null;
2785
+ if (item.result.error !== "OUTSIDE_WRITE_ROOTS") return null;
2761
2786
  const primary = writeRoots[0] ?? "<\u672A\u914D\u7F6E>";
2762
2787
  return {
2763
2788
  category: "out_of_write_root",
@@ -4335,8 +4360,13 @@ var DEFAULT_TIMEOUT_MS = 3e4;
4335
4360
  var DEFAULT_MAX_OUTPUT_LENGTH = 5e4;
4336
4361
  var DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024;
4337
4362
  var isWindows = process2.platform === "win32";
4363
+ function stripMentionPrefix(inputPath) {
4364
+ if (inputPath.startsWith("@")) return inputPath.slice(1);
4365
+ return inputPath;
4366
+ }
4338
4367
  function resolvePath(inputPath, cwd) {
4339
- const resolved = isAbsolute(inputPath) ? inputPath : resolve(cwd, inputPath);
4368
+ const stripped = stripMentionPrefix(inputPath);
4369
+ const resolved = isAbsolute(stripped) ? stripped : resolve(cwd, stripped);
4340
4370
  return resolve(resolved);
4341
4371
  }
4342
4372
  async function realPath(target) {
@@ -4732,6 +4762,10 @@ function detectEol(text) {
4732
4762
  function hasTrailingNewline(text) {
4733
4763
  return text.endsWith("\n") || text.endsWith("\r\n");
4734
4764
  }
4765
+ function toLf(text) {
4766
+ if (!text.includes("\r")) return text;
4767
+ return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
4768
+ }
4735
4769
  function normalizeEol(originalContent, newContent) {
4736
4770
  const targetEol = detectEol(originalContent);
4737
4771
  const lines = newContent.replace(/\r\n/g, "\n").split("\n");
@@ -4823,7 +4857,7 @@ var readFileTool = {
4823
4857
  }
4824
4858
  }
4825
4859
  const content = await readFile5(filePath, "utf-8");
4826
- const lines = content.split("\n");
4860
+ const lines = toLf(content).split("\n");
4827
4861
  if (lines.length > 0 && lines[lines.length - 1] === "" && content.endsWith("\n")) {
4828
4862
  lines.pop();
4829
4863
  }
@@ -4931,6 +4965,7 @@ var writeFileTool = {
4931
4965
 
4932
4966
  // src/tool/builtins/edit-file.ts
4933
4967
  import { readFile as readFile7 } from "fs/promises";
4968
+ import { writeFile as writeFile5 } from "fs/promises";
4934
4969
  import { basename as basename3 } from "path";
4935
4970
  var editFileTool = {
4936
4971
  name: "edit_file",
@@ -4974,7 +5009,9 @@ var editFileTool = {
4974
5009
  }
4975
5010
  try {
4976
5011
  const content = await readFile7(filePath, "utf-8");
4977
- const firstIndex = content.indexOf(args.old_text);
5012
+ const contentN = toLf(content);
5013
+ const oldTextN = toLf(args.old_text);
5014
+ const firstIndex = contentN.indexOf(oldTextN);
4978
5015
  if (firstIndex === -1) {
4979
5016
  return {
4980
5017
  success: false,
@@ -4982,7 +5019,7 @@ var editFileTool = {
4982
5019
  error: "TEXT_NOT_FOUND"
4983
5020
  };
4984
5021
  }
4985
- const secondIndex = content.indexOf(args.old_text, firstIndex + 1);
5022
+ const secondIndex = contentN.indexOf(oldTextN, firstIndex + 1);
4986
5023
  if (secondIndex !== -1) {
4987
5024
  return {
4988
5025
  success: false,
@@ -4990,14 +5027,15 @@ var editFileTool = {
4990
5027
  error: "TEXT_MULTIPLE_MATCHES"
4991
5028
  };
4992
5029
  }
4993
- const newContent = content.replace(args.old_text, args.new_text);
4994
- await writeFileWithEol(filePath, content, newContent);
4995
- const diff = computeFileDiff(content, newContent, filePath);
5030
+ const newContentN = contentN.slice(0, firstIndex) + toLf(args.new_text) + contentN.slice(firstIndex + oldTextN.length);
5031
+ const writtenContent = normalizeEol(content, newContentN);
5032
+ await writeFile5(filePath, writtenContent, "utf-8");
5033
+ const diff = computeFileDiff(content, writtenContent, filePath);
4996
5034
  diff.existedBefore = true;
4997
- const beforeText = content.slice(0, firstIndex);
5035
+ const beforeText = contentN.slice(0, firstIndex);
4998
5036
  const startLine = beforeText.split("\n").length;
4999
- const oldLines = args.old_text.split("\n").length;
5000
- const newLines = args.new_text.split("\n").length;
5037
+ const oldLines = oldTextN.split("\n").length;
5038
+ const newLines = toLf(args.new_text).split("\n").length;
5001
5039
  const diffSummary = `+${diff.additions} -${diff.deletions}`;
5002
5040
  const summary = `\u{1F4DD} \u4FEE\u6539: ${basename3(filePath)} (+${diff.additions} -${diff.deletions})`;
5003
5041
  return {
@@ -5022,6 +5060,7 @@ ${oldLines} \u884C \u2192 ${newLines} \u884C
5022
5060
 
5023
5061
  // src/tool/builtins/multi-edit.ts
5024
5062
  import { readFile as readFile8 } from "fs/promises";
5063
+ import { writeFile as writeFile6 } from "fs/promises";
5025
5064
  import { basename as basename4 } from "path";
5026
5065
  var multiEditTool = {
5027
5066
  name: "multi_edit",
@@ -5068,7 +5107,7 @@ var multiEditTool = {
5068
5107
  }
5069
5108
  try {
5070
5109
  const originalContent = await readFile8(filePath, "utf-8");
5071
- let currentContent = originalContent;
5110
+ let currentContent = toLf(originalContent);
5072
5111
  for (let idx = 0; idx < args.edits.length; idx++) {
5073
5112
  const step = args.edits[idx];
5074
5113
  if (typeof step.oldText !== "string") {
@@ -5078,10 +5117,19 @@ var multiEditTool = {
5078
5117
  error: "INVALID_STEP_ARGS"
5079
5118
  };
5080
5119
  }
5120
+ if (typeof step.newText !== "string") {
5121
+ return {
5122
+ success: false,
5123
+ data: `\u7B2C ${idx + 1} \u6B65\uFF1A\u7F3A\u5C11\u6709\u6548\u7684 newText`,
5124
+ error: "INVALID_STEP_ARGS"
5125
+ };
5126
+ }
5127
+ const oldTextN = toLf(step.oldText);
5128
+ const newTextN = toLf(step.newText);
5081
5129
  if (step.replaceAll) {
5082
5130
  let count = 0;
5083
5131
  let pos = -1;
5084
- while ((pos = currentContent.indexOf(step.oldText, pos + 1)) !== -1) {
5132
+ while ((pos = currentContent.indexOf(oldTextN, pos + 1)) !== -1) {
5085
5133
  count++;
5086
5134
  }
5087
5135
  if (count === 0) {
@@ -5091,9 +5139,9 @@ var multiEditTool = {
5091
5139
  error: "TEXT_NOT_FOUND"
5092
5140
  };
5093
5141
  }
5094
- currentContent = currentContent.split(step.oldText).join(step.newText);
5142
+ currentContent = currentContent.split(oldTextN).join(newTextN);
5095
5143
  } else {
5096
- const firstIdx = currentContent.indexOf(step.oldText);
5144
+ const firstIdx = currentContent.indexOf(oldTextN);
5097
5145
  if (firstIdx === -1) {
5098
5146
  return {
5099
5147
  success: false,
@@ -5101,7 +5149,7 @@ var multiEditTool = {
5101
5149
  error: "TEXT_NOT_FOUND"
5102
5150
  };
5103
5151
  }
5104
- const secondIdx = currentContent.indexOf(step.oldText, firstIdx + 1);
5152
+ const secondIdx = currentContent.indexOf(oldTextN, firstIdx + 1);
5105
5153
  if (secondIdx !== -1) {
5106
5154
  return {
5107
5155
  success: false,
@@ -5109,11 +5157,12 @@ var multiEditTool = {
5109
5157
  error: "TEXT_MULTIPLE_MATCHES"
5110
5158
  };
5111
5159
  }
5112
- currentContent = currentContent.replace(step.oldText, step.newText);
5160
+ currentContent = currentContent.slice(0, firstIdx) + newTextN + currentContent.slice(firstIdx + oldTextN.length);
5113
5161
  }
5114
5162
  }
5115
- await writeFileWithEol(filePath, originalContent, currentContent);
5116
- const diff = computeFileDiff(originalContent, currentContent, filePath);
5163
+ const writtenContent = normalizeEol(originalContent, currentContent);
5164
+ await writeFile6(filePath, writtenContent, "utf-8");
5165
+ const diff = computeFileDiff(originalContent, writtenContent, filePath);
5117
5166
  diff.existedBefore = true;
5118
5167
  return {
5119
5168
  success: true,
@@ -5136,11 +5185,13 @@ var multiEditTool = {
5136
5185
 
5137
5186
  // src/tool/builtins/delete-range.ts
5138
5187
  import { readFile as readFile9 } from "fs/promises";
5188
+ import { writeFile as writeFile7 } from "fs/promises";
5139
5189
  import { basename as basename5 } from "path";
5140
5190
  function findUniqueLine(lines, anchor, label) {
5191
+ const anchorN = toLf(anchor);
5141
5192
  const matches = [];
5142
5193
  for (let i = 0; i < lines.length; i++) {
5143
- if (lines[i] === anchor) {
5194
+ if (toLf(lines[i]) === anchorN) {
5144
5195
  matches.push(i);
5145
5196
  }
5146
5197
  }
@@ -5199,7 +5250,7 @@ var deleteRangeTool = {
5199
5250
  }
5200
5251
  try {
5201
5252
  const content = await readFile9(filePath, "utf-8");
5202
- const lines = content.split("\n");
5253
+ const lines = toLf(content).split("\n");
5203
5254
  const startResult = findUniqueLine(lines, args.startAnchor, "start_anchor");
5204
5255
  if ("error" in startResult) {
5205
5256
  return { success: false, data: startResult.error, error: "ANCHOR_NOT_FOUND" };
@@ -5227,9 +5278,10 @@ var deleteRangeTool = {
5227
5278
  };
5228
5279
  }
5229
5280
  const newLines = [...lines.slice(0, rangeStart), ...lines.slice(rangeEnd + 1)];
5230
- const newContent = newLines.join("\n");
5231
- await writeFileWithEol(filePath, content, newContent);
5232
- const diff = computeFileDiff(content, newContent, filePath);
5281
+ const newContentN = newLines.join("\n");
5282
+ const writtenContent = normalizeEol(content, newContentN);
5283
+ await writeFile7(filePath, writtenContent, "utf-8");
5284
+ const diff = computeFileDiff(content, writtenContent, filePath);
5233
5285
  diff.existedBefore = true;
5234
5286
  const deletedLines = rangeEnd - rangeStart + 1;
5235
5287
  const summary = `\u{1F4DD} \u4FEE\u6539: ${basename5(filePath)} (\u5220 ${deletedLines} \u884C, +${diff.additions} -${diff.deletions})`;
@@ -5425,7 +5477,8 @@ var globTool = {
5425
5477
  if (!args?.pattern || typeof args.pattern !== "string") {
5426
5478
  return { success: false, data: "\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570 pattern", error: "INVALID_ARGS" };
5427
5479
  }
5428
- const searchDir = args.directory ? isAbsolute2(args.directory) ? args.directory : join7(ctx.cwd, args.directory) : ctx.cwd;
5480
+ const dir = args.directory ? stripMentionPrefix(args.directory) : void 0;
5481
+ const searchDir = dir ? isAbsolute2(dir) ? dir : join7(ctx.cwd, dir) : ctx.cwd;
5429
5482
  const regex = globToRegex(args.pattern);
5430
5483
  try {
5431
5484
  const dirStat = await stat2(searchDir);
@@ -5526,7 +5579,8 @@ var grepTool = {
5526
5579
  if (!args?.pattern || typeof args.pattern !== "string") {
5527
5580
  return { success: false, data: "\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570 pattern", error: "INVALID_ARGS" };
5528
5581
  }
5529
- const searchDir = args.directory ? isAbsolute3(args.directory) ? args.directory : join8(ctx.cwd, args.directory) : ctx.cwd;
5582
+ const dir = args.directory ? stripMentionPrefix(args.directory) : void 0;
5583
+ const searchDir = dir ? isAbsolute3(dir) ? dir : join8(ctx.cwd, dir) : ctx.cwd;
5530
5584
  const maxFiles = args.max_files ?? 200;
5531
5585
  try {
5532
5586
  const flags = args.case_sensitive ? "g" : "gi";
@@ -5836,6 +5890,11 @@ var PHASE_CONFIG = {
5836
5890
  function isFileMutatingTool(name) {
5837
5891
  return name === "edit_file" || name === "write_file" || name === "multi_edit" || name === "delete_range";
5838
5892
  }
5893
+ function filterTodoToolCalls(calls) {
5894
+ if (!calls) return void 0;
5895
+ const filtered = calls.filter((tc) => !tc.name.startsWith("todo_"));
5896
+ return filtered.length > 0 ? filtered : void 0;
5897
+ }
5839
5898
  var commandRegistry = /* @__PURE__ */ new Map();
5840
5899
  function registerCommand(name, cmd) {
5841
5900
  commandRegistry.set(name, cmd);
@@ -5957,6 +6016,8 @@ function ChatSession({
5957
6016
  const [_streamingModel, setStreamingModel] = useState4(void 0);
5958
6017
  const [streamError, setStreamError] = useState4(void 0);
5959
6018
  const [todoSnapshot, setTodoSnapshot] = useState4([]);
6019
+ const [todoPanelVisible, setTodoPanelVisible] = useState4(true);
6020
+ const todoHideTimerRef = useRef3(null);
5960
6021
  const [sessionMode, setSessionMode] = useState4("code");
5961
6022
  const [thinkingEnabled, setThinkingEnabled] = useState4(true);
5962
6023
  const [thinkingEffort, setThinkingEffort] = useState4("high");
@@ -6014,6 +6075,20 @@ function ChatSession({
6014
6075
  }
6015
6076
  };
6016
6077
  }, []);
6078
+ useEffect4(() => {
6079
+ if (todoHideTimerRef.current) {
6080
+ clearTimeout(todoHideTimerRef.current);
6081
+ todoHideTimerRef.current = null;
6082
+ }
6083
+ if (todoSnapshot.length === 0) {
6084
+ setTodoPanelVisible(false);
6085
+ return;
6086
+ }
6087
+ const hasUnfinished = todoSnapshot.some(
6088
+ (it) => it.status === "pending" || it.status === "running" || it.status === "failed"
6089
+ );
6090
+ setTodoPanelVisible(hasUnfinished);
6091
+ }, [todoSnapshot]);
6017
6092
  const getFilteredSkills = useCallback2(
6018
6093
  (value) => {
6019
6094
  const match = value.match(/(?:^|\s)\/([^/]*)$/);
@@ -6055,7 +6130,7 @@ function ChatSession({
6055
6130
  content: m.content,
6056
6131
  assistantDetail: {
6057
6132
  content: m.content,
6058
- toolCalls: m.toolCalls
6133
+ toolCalls: filterTodoToolCalls(m.toolCalls)
6059
6134
  }
6060
6135
  });
6061
6136
  } else if (m.role === "tool") {
@@ -6721,7 +6796,14 @@ function ChatSession({
6721
6796
  currentToolCallsRef.current = [];
6722
6797
  const r = event.result;
6723
6798
  if (event.name.startsWith("todo_") && event.todoSnapshot) {
6724
- setTodoSnapshot(event.todoSnapshot);
6799
+ const allTerminated = event.todoSnapshot.length > 0 && event.todoSnapshot.every(
6800
+ (it) => it.status === "done" || it.status === "failed" || it.status === "skipped"
6801
+ );
6802
+ if (allTerminated) {
6803
+ setTodoSnapshot([]);
6804
+ } else {
6805
+ setTodoSnapshot(event.todoSnapshot);
6806
+ }
6725
6807
  } else {
6726
6808
  const line = r.success ? r.summary ?? `\u2705 ${event.name}: ${r.data.slice(0, 500)}${r.data.length > 500 ? "..." : ""}` : `\u274C ${event.name}: ${r.error ?? "\u6267\u884C\u5931\u8D25"}`;
6727
6809
  setDisplayMessages((prev) => [
@@ -6769,7 +6851,7 @@ function ChatSession({
6769
6851
  abortRef.current = null;
6770
6852
  const finContent = currentContentRef.current;
6771
6853
  const finReasoning = currentReasoningRef.current.map((s) => s.trim()).filter((s) => s.length > 0);
6772
- const finToolCalls = currentToolCallsRef.current.length > 0 ? currentToolCallsRef.current : void 0;
6854
+ const finToolCalls = filterTodoToolCalls(currentToolCallsRef.current);
6773
6855
  const finStreamError = streamErrorRef.current;
6774
6856
  if (finContent || finToolCalls || finStreamError) {
6775
6857
  const completed = {
@@ -6944,7 +7026,7 @@ function ChatSession({
6944
7026
  streamError
6945
7027
  ] }) })
6946
7028
  ] }),
6947
- todoSnapshot.length > 0 && /* @__PURE__ */ jsx10(TodoListPanel, { items: todoSnapshot }),
7029
+ todoPanelVisible && todoSnapshot.length > 0 && /* @__PURE__ */ jsx10(TodoListPanel, { items: todoSnapshot }),
6948
7030
  selectingModel ? /* @__PURE__ */ jsxs10(Box10, { marginTop: 1, flexDirection: "column", children: [
6949
7031
  /* @__PURE__ */ jsx10(Text11, { color: "#00ffff", dimColor: true, children: "\u2500".repeat(dividerWidth) }),
6950
7032
  /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", marginTop: 1, children: [