dominds 1.27.6 → 1.28.0

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.
Files changed (34) hide show
  1. package/dist/dialog.js +1 -1
  2. package/dist/docs/team_mgmt-toolset.md +21 -20
  3. package/dist/docs/team_mgmt-toolset.zh.md +21 -20
  4. package/dist/docs/txt-editing-tools.md +196 -122
  5. package/dist/docs/txt-editing-tools.zh.md +171 -97
  6. package/dist/llm/gen/failure-classifier.js +60 -11
  7. package/dist/llm/kernel-driver/drive.js +1 -1
  8. package/dist/tool.d.ts +2 -0
  9. package/dist/tool.js +52 -0
  10. package/dist/tools/app-reminders.js +1 -1
  11. package/dist/tools/builtins.js +31 -12
  12. package/dist/tools/prompts/team_mgmt.en.md +13 -29
  13. package/dist/tools/prompts/team_mgmt.zh.md +13 -29
  14. package/dist/tools/prompts/ws_mod/en/errors.md +45 -42
  15. package/dist/tools/prompts/ws_mod/en/index.md +10 -10
  16. package/dist/tools/prompts/ws_mod/en/principles.md +27 -34
  17. package/dist/tools/prompts/ws_mod/en/scenarios.md +42 -29
  18. package/dist/tools/prompts/ws_mod/en/tools.md +43 -46
  19. package/dist/tools/prompts/ws_mod/zh/errors.md +45 -42
  20. package/dist/tools/prompts/ws_mod/zh/index.md +10 -10
  21. package/dist/tools/prompts/ws_mod/zh/principles.md +27 -34
  22. package/dist/tools/prompts/ws_mod/zh/scenarios.md +42 -29
  23. package/dist/tools/prompts/ws_mod/zh/tools.md +42 -45
  24. package/dist/tools/prompts/ws_mod.en.md +77 -47
  25. package/dist/tools/prompts/ws_mod.zh.md +77 -47
  26. package/dist/tools/team_mgmt.d.ts +7 -6
  27. package/dist/tools/team_mgmt.js +379 -220
  28. package/dist/tools/txt.d.ts +20 -9
  29. package/dist/tools/txt.js +2282 -1842
  30. package/dist/utils/taskdoc.js +2 -2
  31. package/package.json +5 -5
  32. package/webapp/dist/assets/{main-NXVX2KTO.js → main-YWP5PWOM.js} +62 -1
  33. package/webapp/dist/assets/{main-NXVX2KTO.js.map → main-YWP5PWOM.js.map} +4 -4
  34. package/webapp/dist/index.html +1 -1
@@ -13,7 +13,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
13
13
  return (mod && mod.__esModule) ? mod : { "default": mod };
14
14
  };
15
15
  Object.defineProperty(exports, "__esModule", { value: true });
16
- exports.teamMgmtTools = exports.teamMgmtClearProblemsTool = exports.teamMgmtListProblemsTool = exports.teamMgmtValidateMcpCfgTool = exports.teamMgmtValidateTeamCfgTool = exports.teamMgmtValidatePrimingScriptsTool = exports.teamMgmtUnlinkSkillTool = exports.teamMgmtLinkSkillTool = exports.teamMgmtRmSymlinkTool = exports.teamMgmtCreateSymlinkTool = exports.teamMgmtReadSymlinkTool = exports.teamMgmtRmDirTool = exports.teamMgmtRmFileTool = exports.teamMgmtRipgrepSearchTool = exports.teamMgmtRipgrepFixedTool = exports.teamMgmtRipgrepCountTool = exports.teamMgmtRipgrepSnippetsTool = exports.teamMgmtRipgrepFilesTool = exports.teamMgmtMoveDirTool = exports.teamMgmtMoveFileTool = exports.teamMgmtMkDirTool = exports.teamMgmtApplyFileModificationTool = exports.teamMgmtPrepareFileRangeEditTool = exports.teamMgmtPrepareBlockReplaceTool = exports.teamMgmtPrepareInsertBeforeTool = exports.teamMgmtPrepareInsertAfterTool = exports.teamMgmtPrepareFileAppendTool = exports.teamMgmtOverwriteEntireFileTool = exports.teamMgmtCreateNewFileTool = exports.teamMgmtReadFileTool = exports.teamMgmtListDirTool = exports.teamMgmtListModelsTool = exports.teamMgmtListProvidersTool = exports.teamMgmtCheckProviderTool = void 0;
16
+ exports.teamMgmtTools = exports.teamMgmtClearProblemsTool = exports.teamMgmtListProblemsTool = exports.teamMgmtValidateMcpCfgTool = exports.teamMgmtValidateTeamCfgTool = exports.teamMgmtValidatePrimingScriptsTool = exports.teamMgmtUnlinkSkillTool = exports.teamMgmtLinkSkillTool = exports.teamMgmtRmSymlinkTool = exports.teamMgmtCreateSymlinkTool = exports.teamMgmtReadSymlinkTool = exports.teamMgmtRmDirTool = exports.teamMgmtRmFileTool = exports.teamMgmtRipgrepSearchTool = exports.teamMgmtRipgrepFixedTool = exports.teamMgmtRipgrepCountTool = exports.teamMgmtRipgrepSnippetsTool = exports.teamMgmtRipgrepFilesTool = exports.teamMgmtMoveDirTool = exports.teamMgmtMoveFileTool = exports.teamMgmtMkDirTool = exports.teamMgmtApplyOccurrenceReplaceTool = exports.teamMgmtPrepareOccurrenceReplaceTool = exports.teamMgmtFileRangeEditTool = exports.teamMgmtFileBlockReplaceTool = exports.teamMgmtFileInsertBeforeTool = exports.teamMgmtFileInsertAfterTool = exports.teamMgmtFileAppendTool = exports.teamMgmtOverwriteEntireFileTool = exports.teamMgmtCreateNewFileTool = exports.teamMgmtReadFileTool = exports.teamMgmtListDirTool = exports.teamMgmtListModelsTool = exports.teamMgmtListProvidersTool = exports.teamMgmtCheckProviderTool = void 0;
17
17
  exports.splitCommandArgs = splitCommandArgs;
18
18
  exports.renderMemberProperties = renderMemberProperties;
19
19
  exports.renderTeamManual = renderTeamManual;
@@ -99,12 +99,6 @@ function yamlQuote(value) {
99
99
  function formatYamlCodeBlock(yaml) {
100
100
  return `\`\`\`yaml\n${yaml}\n\`\`\``;
101
101
  }
102
- function normalizeFileWriteBody(inputBody) {
103
- if (inputBody === '' || inputBody.endsWith('\n')) {
104
- return { normalizedBody: inputBody, addedTrailingNewlineToContent: false };
105
- }
106
- return { normalizedBody: `${inputBody}\n`, addedTrailingNewlineToContent: true };
107
- }
108
102
  function isEmptyLine(line) {
109
103
  return line.trim() === '';
110
104
  }
@@ -197,15 +191,6 @@ async function lintTeamYamlStyleProblems() {
197
191
  },
198
192
  ]);
199
193
  }
200
- function countLogicalLines(text) {
201
- if (text === '')
202
- return 0;
203
- const parts = text.split('\n');
204
- if (parts.length > 0 && parts[parts.length - 1] === '') {
205
- parts.pop();
206
- }
207
- return parts.length;
208
- }
209
194
  function normalizePathToken(raw) {
210
195
  return raw.trim().replace(/\\/g, '/').replace(/^\/+/, '');
211
196
  }
@@ -1523,10 +1508,10 @@ exports.teamMgmtReadFileTool = {
1523
1508
  exports.teamMgmtCreateNewFileTool = {
1524
1509
  type: 'func',
1525
1510
  name: 'team_mgmt_create_new_file',
1526
- description: `Create a new file under ${MINDS_DIR}/ (no prepare/apply). Refuses to overwrite existing files.`,
1511
+ description: `Create a new file under ${MINDS_DIR}/ from inline content or a ws_mod pad source. Refuses to overwrite existing files.`,
1527
1512
  descriptionI18n: {
1528
- en: `Create a new file under ${MINDS_DIR}/ (no prepare/apply). Refuses to overwrite existing files.`,
1529
- zh: `在 ${MINDS_DIR}/ 下创建一个新文件(不走 prepare/apply)。若文件已存在则拒绝覆写。`,
1513
+ en: `Create a new file under ${MINDS_DIR}/ from inline content or a ws_mod pad source. Refuses to overwrite existing files.`,
1514
+ zh: `用内联 content 或 ws_mod pad 来源在 ${MINDS_DIR}/ 下创建一个新文件。若文件已存在则拒绝覆写。`,
1530
1515
  },
1531
1516
  parameters: {
1532
1517
  type: 'object',
@@ -1535,26 +1520,13 @@ exports.teamMgmtCreateNewFileTool = {
1535
1520
  properties: {
1536
1521
  path: { type: 'string' },
1537
1522
  content: { type: 'string' },
1523
+ pad_id: { type: 'string' },
1524
+ pad_range: { type: 'string' },
1538
1525
  },
1539
1526
  },
1540
1527
  argsValidation: 'dominds',
1541
- async call(dlg, _caller, args) {
1528
+ async call(dlg, caller, args) {
1542
1529
  const language = getUserLang(dlg);
1543
- const t = language === 'zh'
1544
- ? {
1545
- invalidArgs: (msg) => `参数不正确:${msg}`,
1546
- fileExists: '文件已存在,拒绝创建。',
1547
- notAFile: '路径已存在但不是文件(可能是目录),拒绝创建。',
1548
- nextOverwrite: '下一步:先用 team_mgmt_read_file 获取 total_lines/size_bytes,然后再调用 team_mgmt_overwrite_entire_file 覆盖写入。',
1549
- ok: '已创建新文件。',
1550
- }
1551
- : {
1552
- invalidArgs: (msg) => `Invalid args: ${msg}`,
1553
- fileExists: 'File already exists; refusing to create.',
1554
- notAFile: 'Path exists but is not a file (e.g. a directory); refusing to create.',
1555
- nextOverwrite: 'Next: call team_mgmt_read_file to get total_lines/size_bytes, then use team_mgmt_overwrite_entire_file to overwrite.',
1556
- ok: 'Created new file.',
1557
- };
1558
1530
  try {
1559
1531
  const mindsState = await getMindsDirState();
1560
1532
  if (mindsState.kind === 'not_directory') {
@@ -1563,114 +1535,66 @@ exports.teamMgmtCreateNewFileTool = {
1563
1535
  await ensureMindsRootDirExists();
1564
1536
  const pathValue = args['path'];
1565
1537
  const rawPath = typeof pathValue === 'string' ? pathValue.trim() : '';
1566
- if (!rawPath) {
1567
- const content = formatYamlCodeBlock([
1568
- `status: error`,
1569
- `mode: create_new_file`,
1570
- `error: INVALID_ARGS`,
1571
- `summary: ${yamlQuote(t.invalidArgs('Path required'))}`,
1572
- ].join('\n'));
1573
- return fail(content, [{ type: 'environment_msg', role: 'user', content }]);
1574
- }
1575
- const rel = toMindsRelativePath(rawPath);
1576
- const { abs } = ensureMindsScopedPath(rel);
1577
- if (rel === MINDS_DIR) {
1578
- const content = formatYamlCodeBlock([
1579
- `status: error`,
1580
- `mode: create_new_file`,
1581
- `path: ${yamlQuote(rel)}`,
1582
- `error: NOT_A_FILE`,
1583
- `summary: ${yamlQuote(t.notAFile)}`,
1584
- ].join('\n'));
1585
- return fail(content, [{ type: 'environment_msg', role: 'user', content }]);
1586
- }
1538
+ if (!rawPath)
1539
+ throw new Error('Path required');
1587
1540
  const contentValue = args['content'];
1588
1541
  if (contentValue !== undefined && typeof contentValue !== 'string') {
1589
1542
  throw new Error('Invalid content (expected string)');
1590
1543
  }
1591
- const initialContent = typeof contentValue === 'string' ? contentValue : '';
1592
- try {
1593
- const st = await promises_1.default.stat(abs);
1594
- if (!st.isFile()) {
1595
- const out = formatYamlCodeBlock([
1596
- `status: error`,
1597
- `mode: create_new_file`,
1598
- `path: ${yamlQuote(rel)}`,
1599
- `error: NOT_A_FILE`,
1600
- `summary: ${yamlQuote(t.notAFile)}`,
1601
- ].join('\n'));
1602
- return fail(out, [{ type: 'environment_msg', role: 'user', content: out }]);
1603
- }
1604
- const out = formatYamlCodeBlock([
1605
- `status: error`,
1606
- `mode: create_new_file`,
1607
- `path: ${yamlQuote(rel)}`,
1608
- `error: FILE_EXISTS`,
1609
- `summary: ${yamlQuote(t.fileExists)}`,
1610
- `next: ${yamlQuote(t.nextOverwrite)}`,
1611
- ].join('\n'));
1612
- return fail(out, [{ type: 'environment_msg', role: 'user', content: out }]);
1544
+ const padIdValue = args['pad_id'];
1545
+ if (padIdValue !== undefined && typeof padIdValue !== 'string') {
1546
+ throw new Error('Invalid pad_id (expected string)');
1613
1547
  }
1614
- catch (err) {
1615
- if (!isFsErrWithCode(err) || err.code !== 'ENOENT')
1616
- throw err;
1548
+ const padRangeValue = args['pad_range'];
1549
+ if (padRangeValue !== undefined && typeof padRangeValue !== 'string') {
1550
+ throw new Error('Invalid pad_range (expected string)');
1617
1551
  }
1618
- const { normalizedBody, addedTrailingNewlineToContent } = normalizeFileWriteBody(initialContent);
1619
- await promises_1.default.mkdir(path_1.default.dirname(abs), { recursive: true });
1620
- await promises_1.default.writeFile(abs, normalizedBody, 'utf8');
1621
- const newTotalBytes = Buffer.byteLength(normalizedBody, 'utf8');
1622
- const newTotalLines = countLogicalLines(normalizedBody);
1623
- const normalizedNewlineAdded = addedTrailingNewlineToContent && normalizedBody !== '';
1624
- const summary = language === 'zh'
1625
- ? `${t.ok} path=${rel}; new_total_lines=${newTotalLines}; new_total_bytes=${newTotalBytes}.`
1626
- : `${t.ok} path=${rel}; new_total_lines=${newTotalLines}; new_total_bytes=${newTotalBytes}.`;
1627
- const out = formatYamlCodeBlock([
1628
- `status: ok`,
1629
- `mode: create_new_file`,
1630
- `path: ${yamlQuote(rel)}`,
1631
- `new_total_lines: ${newTotalLines}`,
1632
- `new_total_bytes: ${newTotalBytes}`,
1633
- `normalized_trailing_newline_added: ${normalizedNewlineAdded}`,
1634
- `summary: ${yamlQuote(summary)}`,
1635
- ].join('\n'));
1636
- await refreshDerivedStateAfterTeamMgmtWrite({
1637
- relPaths: [rel],
1638
- trigger: 'create_new_file',
1552
+ const rel = toMindsRelativePath(rawPath);
1553
+ ensureMindsScopedPath(rel);
1554
+ const proxyCaller = makeMindsOnlyAccessMember(caller);
1555
+ const output = await txt_1.createNewFileTool.call(dlg, proxyCaller, {
1556
+ path: rel,
1557
+ ...(contentValue !== undefined ? { content: contentValue } : {}),
1558
+ ...(padIdValue !== undefined ? { pad_id: padIdValue } : {}),
1559
+ ...(padRangeValue !== undefined ? { pad_range: padRangeValue } : {}),
1639
1560
  });
1640
- return ok(out, [{ type: 'environment_msg', role: 'user', content: out }]);
1561
+ const content = toolCallOutputToString(output);
1562
+ if (isSuccessfulYamlToolResult(content, 'create_new_file')) {
1563
+ await refreshDerivedStateAfterTeamMgmtWrite({
1564
+ relPaths: [rel],
1565
+ trigger: 'create_new_file',
1566
+ });
1567
+ }
1568
+ return output;
1641
1569
  }
1642
1570
  catch (err) {
1643
1571
  const msg = language === 'zh'
1644
1572
  ? `错误:${err instanceof Error ? err.message : String(err)}`
1645
1573
  : `Error: ${err instanceof Error ? err.message : String(err)}`;
1646
- const out = formatYamlCodeBlock([
1647
- `status: error`,
1648
- `mode: create_new_file`,
1649
- `error: FAILED`,
1650
- `summary: ${yamlQuote(msg)}`,
1651
- ].join('\n'));
1652
- return fail(out, [{ type: 'environment_msg', role: 'user', content: out }]);
1574
+ return fail(msg, [{ type: 'environment_msg', role: 'user', content: msg }]);
1653
1575
  }
1654
1576
  },
1655
1577
  };
1656
1578
  exports.teamMgmtOverwriteEntireFileTool = {
1657
1579
  type: 'func',
1658
1580
  name: 'team_mgmt_overwrite_entire_file',
1659
- description: `Overwrite an existing file under ${MINDS_DIR}/ (writes immediately; guarded).`,
1581
+ description: `Overwrite an existing file under ${MINDS_DIR}/ from inline content or a ws_mod pad source (writes immediately; guarded).`,
1660
1582
  descriptionI18n: {
1661
- en: `Overwrite an existing file under ${MINDS_DIR}/ (writes immediately; guarded).`,
1662
- zh: `整体覆盖写入 ${MINDS_DIR}/ 下的已存在文件(直接写盘,带护栏)。`,
1583
+ en: `Overwrite an existing file under ${MINDS_DIR}/ from inline content or a ws_mod pad source (writes immediately; guarded).`,
1584
+ zh: `用内联 content 或 ws_mod pad 来源整体覆盖写入 ${MINDS_DIR}/ 下的已存在文件(直接写盘,带护栏)。`,
1663
1585
  },
1664
1586
  parameters: {
1665
1587
  type: 'object',
1666
1588
  additionalProperties: false,
1667
- required: ['path', 'known_old_total_lines', 'known_old_total_bytes', 'content'],
1589
+ required: ['path', 'known_old_total_lines', 'known_old_total_bytes'],
1668
1590
  properties: {
1669
1591
  path: { type: 'string' },
1670
1592
  known_old_total_lines: { type: 'integer' },
1671
1593
  known_old_total_bytes: { type: 'integer' },
1672
1594
  content_format: { type: 'string' },
1673
1595
  content: { type: 'string' },
1596
+ pad_id: { type: 'string' },
1597
+ pad_range: { type: 'string' },
1674
1598
  },
1675
1599
  },
1676
1600
  argsValidation: 'dominds',
@@ -1699,9 +1623,17 @@ exports.teamMgmtOverwriteEntireFileTool = {
1699
1623
  : 'known_old_total_bytes must be an integer.');
1700
1624
  }
1701
1625
  const contentValue = args['content'];
1702
- if (typeof contentValue !== 'string') {
1626
+ if (contentValue !== undefined && typeof contentValue !== 'string') {
1703
1627
  throw new Error(language === 'zh' ? 'content 需要为字符串。' : 'content must be a string.');
1704
1628
  }
1629
+ const padIdValue = args['pad_id'];
1630
+ if (padIdValue !== undefined && typeof padIdValue !== 'string') {
1631
+ throw new Error('Invalid pad_id (expected string)');
1632
+ }
1633
+ const padRangeValue = args['pad_range'];
1634
+ if (padRangeValue !== undefined && typeof padRangeValue !== 'string') {
1635
+ throw new Error('Invalid pad_range (expected string)');
1636
+ }
1705
1637
  const contentFormatValue = args['content_format'];
1706
1638
  const contentFormat = contentFormatValue === undefined
1707
1639
  ? undefined
@@ -1718,7 +1650,9 @@ exports.teamMgmtOverwriteEntireFileTool = {
1718
1650
  path: rel,
1719
1651
  known_old_total_lines: knownLinesValue,
1720
1652
  known_old_total_bytes: knownBytesValue,
1721
- content: contentValue,
1653
+ ...(contentValue !== undefined ? { content: contentValue } : {}),
1654
+ ...(padIdValue !== undefined ? { pad_id: padIdValue } : {}),
1655
+ ...(padRangeValue !== undefined ? { pad_range: padRangeValue } : {}),
1722
1656
  ...(contentFormat ? { content_format: contentFormat } : {}),
1723
1657
  });
1724
1658
  const result = toolCallOutputToString(output);
@@ -1738,23 +1672,26 @@ exports.teamMgmtOverwriteEntireFileTool = {
1738
1672
  }
1739
1673
  },
1740
1674
  };
1741
- exports.teamMgmtPrepareFileAppendTool = {
1675
+ exports.teamMgmtFileAppendTool = {
1742
1676
  type: 'func',
1743
- name: 'team_mgmt_prepare_file_append',
1744
- description: `Prepare an append-to-EOF modification under ${MINDS_DIR}/ (does not write yet).`,
1677
+ name: 'team_mgmt_file_append',
1678
+ description: `Append to a file under ${MINDS_DIR}/ directly.`,
1745
1679
  descriptionI18n: {
1746
- en: `Prepare an append-to-EOF modification under ${MINDS_DIR}/ (does not write yet).`,
1747
- zh: `规划 ${MINDS_DIR}/ 下“末尾追加”修改(不会立刻写入)。`,
1680
+ en: `Append to a file under ${MINDS_DIR}/ directly.`,
1681
+ zh: `直接向 ${MINDS_DIR}/ 下文件末尾追加内容。`,
1748
1682
  },
1749
1683
  parameters: {
1750
1684
  type: 'object',
1751
1685
  additionalProperties: false,
1752
- required: ['path', 'content'],
1686
+ required: ['path'],
1753
1687
  properties: {
1754
1688
  path: { type: 'string' },
1755
1689
  create: { type: 'boolean' },
1756
- existing_hunk_id: { type: 'string' },
1757
1690
  content: { type: 'string' },
1691
+ pad_id: { type: 'string' },
1692
+ pad_range: { type: 'string' },
1693
+ preview: { type: 'boolean' },
1694
+ show_diff: { type: 'boolean' },
1758
1695
  },
1759
1696
  },
1760
1697
  argsValidation: 'dominds',
@@ -1778,25 +1715,42 @@ exports.teamMgmtPrepareFileAppendTool = {
1778
1715
  if (createValue !== undefined && typeof createValue !== 'boolean') {
1779
1716
  throw new Error('Invalid create (expected boolean)');
1780
1717
  }
1781
- const existingHunkIdValue = args['existing_hunk_id'];
1782
- const existingHunkId = existingHunkIdValue === undefined
1783
- ? undefined
1784
- : typeof existingHunkIdValue === 'string'
1785
- ? existingHunkIdValue
1786
- : undefined;
1787
- if (existingHunkIdValue !== undefined && typeof existingHunkIdValue !== 'string') {
1788
- throw new Error('Invalid existing_hunk_id (expected string)');
1789
- }
1790
1718
  const contentValue = args['content'];
1791
- if (typeof contentValue !== 'string')
1719
+ if (contentValue !== undefined && typeof contentValue !== 'string') {
1792
1720
  throw new Error('Invalid content (expected string)');
1793
- const output = await txt_1.prepareFileAppendTool.call(dlg, proxyCaller, {
1721
+ }
1722
+ const padIdValue = args['pad_id'];
1723
+ if (padIdValue !== undefined && typeof padIdValue !== 'string') {
1724
+ throw new Error('Invalid pad_id (expected string)');
1725
+ }
1726
+ const padRangeValue = args['pad_range'];
1727
+ if (padRangeValue !== undefined && typeof padRangeValue !== 'string') {
1728
+ throw new Error('Invalid pad_range (expected string)');
1729
+ }
1730
+ const previewValue = args['preview'];
1731
+ if (previewValue !== undefined && typeof previewValue !== 'boolean') {
1732
+ throw new Error('Invalid preview (expected boolean)');
1733
+ }
1734
+ const showDiffValue = args['show_diff'];
1735
+ if (showDiffValue !== undefined && typeof showDiffValue !== 'boolean') {
1736
+ throw new Error('Invalid show_diff (expected boolean)');
1737
+ }
1738
+ const output = await txt_1.fileAppendTool.call(dlg, proxyCaller, {
1794
1739
  path: rel,
1795
1740
  ...(create !== undefined ? { create } : {}),
1796
- ...(existingHunkId ? { existing_hunk_id: existingHunkId } : {}),
1797
- content: contentValue,
1741
+ ...(contentValue !== undefined ? { content: contentValue } : {}),
1742
+ ...(padIdValue !== undefined ? { pad_id: padIdValue } : {}),
1743
+ ...(padRangeValue !== undefined ? { pad_range: padRangeValue } : {}),
1744
+ ...(previewValue !== undefined ? { preview: previewValue } : {}),
1745
+ ...(showDiffValue !== undefined ? { show_diff: showDiffValue } : {}),
1798
1746
  });
1799
1747
  const content = toolCallOutputToString(output);
1748
+ if (isSuccessfulYamlToolResult(content, 'file_append')) {
1749
+ await refreshDerivedStateAfterTeamMgmtWrite({
1750
+ relPaths: [rel],
1751
+ trigger: 'team_mgmt_file_append',
1752
+ });
1753
+ }
1800
1754
  return output;
1801
1755
  }
1802
1756
  catch (err) {
@@ -1807,25 +1761,28 @@ exports.teamMgmtPrepareFileAppendTool = {
1807
1761
  }
1808
1762
  },
1809
1763
  };
1810
- exports.teamMgmtPrepareInsertAfterTool = {
1764
+ exports.teamMgmtFileInsertAfterTool = {
1811
1765
  type: 'func',
1812
- name: 'team_mgmt_prepare_file_insert_after',
1813
- description: `Prepare an insertion after an anchor under ${MINDS_DIR}/ (does not write yet).`,
1766
+ name: 'team_mgmt_file_insert_after',
1767
+ description: `Insert after an anchor under ${MINDS_DIR}/ directly.`,
1814
1768
  descriptionI18n: {
1815
- en: `Prepare an insertion after an anchor under ${MINDS_DIR}/ (does not write yet).`,
1816
- zh: `按锚点规划 ${MINDS_DIR}/ 下“在其后插入”修改(不会立刻写入)。`,
1769
+ en: `Insert after an anchor under ${MINDS_DIR}/ directly.`,
1770
+ zh: `按锚点直接在 ${MINDS_DIR}/ 下文件中后插内容。`,
1817
1771
  },
1818
1772
  parameters: {
1819
1773
  type: 'object',
1820
1774
  additionalProperties: false,
1821
- required: ['path', 'anchor', 'content'],
1775
+ required: ['path', 'anchor'],
1822
1776
  properties: {
1823
1777
  path: { type: 'string' },
1824
1778
  anchor: { type: 'string' },
1825
1779
  occurrence: { type: ['integer', 'string'] },
1826
1780
  match: { type: 'string' },
1827
- existing_hunk_id: { type: 'string' },
1828
1781
  content: { type: 'string' },
1782
+ pad_id: { type: 'string' },
1783
+ pad_range: { type: 'string' },
1784
+ preview: { type: 'boolean' },
1785
+ show_diff: { type: 'boolean' },
1829
1786
  },
1830
1787
  },
1831
1788
  argsValidation: 'dominds',
@@ -1861,22 +1818,44 @@ exports.teamMgmtPrepareInsertAfterTool = {
1861
1818
  if (matchValue !== undefined && typeof matchValue !== 'string') {
1862
1819
  throw new Error("Invalid match (expected 'contains'|'equals')");
1863
1820
  }
1864
- const existingHunkIdValue = args['existing_hunk_id'];
1865
- if (existingHunkIdValue !== undefined && typeof existingHunkIdValue !== 'string') {
1866
- throw new Error('Invalid existing_hunk_id (expected string)');
1867
- }
1868
1821
  const contentValue = args['content'];
1869
- if (typeof contentValue !== 'string')
1822
+ if (contentValue !== undefined && typeof contentValue !== 'string') {
1870
1823
  throw new Error('Invalid content (expected string)');
1871
- const output = await txt_1.prepareFileInsertAfterTool.call(dlg, proxyCaller, {
1824
+ }
1825
+ const padIdValue = args['pad_id'];
1826
+ if (padIdValue !== undefined && typeof padIdValue !== 'string') {
1827
+ throw new Error('Invalid pad_id (expected string)');
1828
+ }
1829
+ const padRangeValue = args['pad_range'];
1830
+ if (padRangeValue !== undefined && typeof padRangeValue !== 'string') {
1831
+ throw new Error('Invalid pad_range (expected string)');
1832
+ }
1833
+ const previewValue = args['preview'];
1834
+ if (previewValue !== undefined && typeof previewValue !== 'boolean') {
1835
+ throw new Error('Invalid preview (expected boolean)');
1836
+ }
1837
+ const showDiffValue = args['show_diff'];
1838
+ if (showDiffValue !== undefined && typeof showDiffValue !== 'boolean') {
1839
+ throw new Error('Invalid show_diff (expected boolean)');
1840
+ }
1841
+ const output = await txt_1.fileInsertAfterTool.call(dlg, proxyCaller, {
1872
1842
  path: rel,
1873
1843
  anchor,
1874
1844
  ...(occurrenceValue !== undefined ? { occurrence: occurrenceValue } : {}),
1875
1845
  ...(matchValue !== undefined ? { match: matchValue } : {}),
1876
- ...(existingHunkIdValue ? { existing_hunk_id: existingHunkIdValue } : {}),
1877
- content: contentValue,
1846
+ ...(contentValue !== undefined ? { content: contentValue } : {}),
1847
+ ...(padIdValue !== undefined ? { pad_id: padIdValue } : {}),
1848
+ ...(padRangeValue !== undefined ? { pad_range: padRangeValue } : {}),
1849
+ ...(previewValue !== undefined ? { preview: previewValue } : {}),
1850
+ ...(showDiffValue !== undefined ? { show_diff: showDiffValue } : {}),
1878
1851
  });
1879
1852
  const content = toolCallOutputToString(output);
1853
+ if (isSuccessfulYamlToolResult(content, 'file_insert_after')) {
1854
+ await refreshDerivedStateAfterTeamMgmtWrite({
1855
+ relPaths: [rel],
1856
+ trigger: 'team_mgmt_file_insert_after',
1857
+ });
1858
+ }
1880
1859
  return output;
1881
1860
  }
1882
1861
  catch (err) {
@@ -1887,25 +1866,28 @@ exports.teamMgmtPrepareInsertAfterTool = {
1887
1866
  }
1888
1867
  },
1889
1868
  };
1890
- exports.teamMgmtPrepareInsertBeforeTool = {
1869
+ exports.teamMgmtFileInsertBeforeTool = {
1891
1870
  type: 'func',
1892
- name: 'team_mgmt_prepare_file_insert_before',
1893
- description: `Prepare an insertion before an anchor under ${MINDS_DIR}/ (does not write yet).`,
1871
+ name: 'team_mgmt_file_insert_before',
1872
+ description: `Insert before an anchor under ${MINDS_DIR}/ directly.`,
1894
1873
  descriptionI18n: {
1895
- en: `Prepare an insertion before an anchor under ${MINDS_DIR}/ (does not write yet).`,
1896
- zh: `按锚点规划 ${MINDS_DIR}/ 下“在其前插入”修改(不会立刻写入)。`,
1874
+ en: `Insert before an anchor under ${MINDS_DIR}/ directly.`,
1875
+ zh: `按锚点直接在 ${MINDS_DIR}/ 下文件中前插内容。`,
1897
1876
  },
1898
1877
  parameters: {
1899
1878
  type: 'object',
1900
1879
  additionalProperties: false,
1901
- required: ['path', 'anchor', 'content'],
1880
+ required: ['path', 'anchor'],
1902
1881
  properties: {
1903
1882
  path: { type: 'string' },
1904
1883
  anchor: { type: 'string' },
1905
1884
  occurrence: { type: ['integer', 'string'] },
1906
1885
  match: { type: 'string' },
1907
- existing_hunk_id: { type: 'string' },
1908
1886
  content: { type: 'string' },
1887
+ pad_id: { type: 'string' },
1888
+ pad_range: { type: 'string' },
1889
+ preview: { type: 'boolean' },
1890
+ show_diff: { type: 'boolean' },
1909
1891
  },
1910
1892
  },
1911
1893
  argsValidation: 'dominds',
@@ -1941,22 +1923,44 @@ exports.teamMgmtPrepareInsertBeforeTool = {
1941
1923
  if (matchValue !== undefined && typeof matchValue !== 'string') {
1942
1924
  throw new Error("Invalid match (expected 'contains'|'equals')");
1943
1925
  }
1944
- const existingHunkIdValue = args['existing_hunk_id'];
1945
- if (existingHunkIdValue !== undefined && typeof existingHunkIdValue !== 'string') {
1946
- throw new Error('Invalid existing_hunk_id (expected string)');
1947
- }
1948
1926
  const contentValue = args['content'];
1949
- if (typeof contentValue !== 'string')
1927
+ if (contentValue !== undefined && typeof contentValue !== 'string') {
1950
1928
  throw new Error('Invalid content (expected string)');
1951
- const output = await txt_1.prepareFileInsertBeforeTool.call(dlg, proxyCaller, {
1929
+ }
1930
+ const padIdValue = args['pad_id'];
1931
+ if (padIdValue !== undefined && typeof padIdValue !== 'string') {
1932
+ throw new Error('Invalid pad_id (expected string)');
1933
+ }
1934
+ const padRangeValue = args['pad_range'];
1935
+ if (padRangeValue !== undefined && typeof padRangeValue !== 'string') {
1936
+ throw new Error('Invalid pad_range (expected string)');
1937
+ }
1938
+ const previewValue = args['preview'];
1939
+ if (previewValue !== undefined && typeof previewValue !== 'boolean') {
1940
+ throw new Error('Invalid preview (expected boolean)');
1941
+ }
1942
+ const showDiffValue = args['show_diff'];
1943
+ if (showDiffValue !== undefined && typeof showDiffValue !== 'boolean') {
1944
+ throw new Error('Invalid show_diff (expected boolean)');
1945
+ }
1946
+ const output = await txt_1.fileInsertBeforeTool.call(dlg, proxyCaller, {
1952
1947
  path: rel,
1953
1948
  anchor,
1954
1949
  ...(occurrenceValue !== undefined ? { occurrence: occurrenceValue } : {}),
1955
1950
  ...(matchValue !== undefined ? { match: matchValue } : {}),
1956
- ...(existingHunkIdValue ? { existing_hunk_id: existingHunkIdValue } : {}),
1957
- content: contentValue,
1951
+ ...(contentValue !== undefined ? { content: contentValue } : {}),
1952
+ ...(padIdValue !== undefined ? { pad_id: padIdValue } : {}),
1953
+ ...(padRangeValue !== undefined ? { pad_range: padRangeValue } : {}),
1954
+ ...(previewValue !== undefined ? { preview: previewValue } : {}),
1955
+ ...(showDiffValue !== undefined ? { show_diff: showDiffValue } : {}),
1958
1956
  });
1959
1957
  const content = toolCallOutputToString(output);
1958
+ if (isSuccessfulYamlToolResult(content, 'file_insert_before')) {
1959
+ await refreshDerivedStateAfterTeamMgmtWrite({
1960
+ relPaths: [rel],
1961
+ trigger: 'team_mgmt_file_insert_before',
1962
+ });
1963
+ }
1960
1964
  return output;
1961
1965
  }
1962
1966
  catch (err) {
@@ -1967,18 +1971,18 @@ exports.teamMgmtPrepareInsertBeforeTool = {
1967
1971
  }
1968
1972
  },
1969
1973
  };
1970
- exports.teamMgmtPrepareBlockReplaceTool = {
1974
+ exports.teamMgmtFileBlockReplaceTool = {
1971
1975
  type: 'func',
1972
- name: 'team_mgmt_prepare_file_block_replace',
1973
- description: `Prepare a block replacement between anchors in a file under ${MINDS_DIR}/ (does not write yet).`,
1976
+ name: 'team_mgmt_file_block_replace',
1977
+ description: `Replace an anchor-delimited block under ${MINDS_DIR}/ directly.`,
1974
1978
  descriptionI18n: {
1975
- en: `Prepare a block replacement between anchors in a file under ${MINDS_DIR}/ (does not write yet).`,
1976
- zh: `按锚点规划 ${MINDS_DIR}/ 下文件的块替换(不会立刻写入)。`,
1979
+ en: `Replace an anchor-delimited block under ${MINDS_DIR}/ directly.`,
1980
+ zh: `按锚点直接替换 ${MINDS_DIR}/ 下文件中的块内容。`,
1977
1981
  },
1978
1982
  parameters: {
1979
1983
  type: 'object',
1980
1984
  additionalProperties: false,
1981
- required: ['path', 'start_anchor', 'end_anchor', 'content'],
1985
+ required: ['path', 'start_anchor', 'end_anchor'],
1982
1986
  properties: {
1983
1987
  path: { type: 'string' },
1984
1988
  start_anchor: { type: 'string' },
@@ -1988,8 +1992,11 @@ exports.teamMgmtPrepareBlockReplaceTool = {
1988
1992
  match: { type: 'string' },
1989
1993
  require_unique: { type: 'boolean' },
1990
1994
  strict: { type: 'boolean' },
1991
- existing_hunk_id: { type: 'string' },
1992
1995
  content: { type: 'string' },
1996
+ pad_id: { type: 'string' },
1997
+ pad_range: { type: 'string' },
1998
+ preview: { type: 'boolean' },
1999
+ show_diff: { type: 'boolean' },
1993
2000
  },
1994
2001
  },
1995
2002
  argsValidation: 'dominds',
@@ -2039,14 +2046,27 @@ exports.teamMgmtPrepareBlockReplaceTool = {
2039
2046
  if (strictValue !== undefined && typeof strictValue !== 'boolean') {
2040
2047
  throw new Error('Invalid strict (expected boolean)');
2041
2048
  }
2042
- const existingHunkIdValue = args['existing_hunk_id'];
2043
- if (existingHunkIdValue !== undefined && typeof existingHunkIdValue !== 'string') {
2044
- throw new Error('Invalid existing_hunk_id (expected string)');
2045
- }
2046
2049
  const contentValue = args['content'];
2047
- if (typeof contentValue !== 'string')
2050
+ if (contentValue !== undefined && typeof contentValue !== 'string') {
2048
2051
  throw new Error('Invalid content (expected string)');
2049
- const output = await txt_1.prepareFileBlockReplaceTool.call(dlg, proxyCaller, {
2052
+ }
2053
+ const padIdValue = args['pad_id'];
2054
+ if (padIdValue !== undefined && typeof padIdValue !== 'string') {
2055
+ throw new Error('Invalid pad_id (expected string)');
2056
+ }
2057
+ const padRangeValue = args['pad_range'];
2058
+ if (padRangeValue !== undefined && typeof padRangeValue !== 'string') {
2059
+ throw new Error('Invalid pad_range (expected string)');
2060
+ }
2061
+ const previewValue = args['preview'];
2062
+ if (previewValue !== undefined && typeof previewValue !== 'boolean') {
2063
+ throw new Error('Invalid preview (expected boolean)');
2064
+ }
2065
+ const showDiffValue = args['show_diff'];
2066
+ if (showDiffValue !== undefined && typeof showDiffValue !== 'boolean') {
2067
+ throw new Error('Invalid show_diff (expected boolean)');
2068
+ }
2069
+ const output = await txt_1.fileBlockReplaceTool.call(dlg, proxyCaller, {
2050
2070
  path: rel,
2051
2071
  start_anchor: startAnchor,
2052
2072
  end_anchor: endAnchor,
@@ -2055,10 +2075,19 @@ exports.teamMgmtPrepareBlockReplaceTool = {
2055
2075
  ...(matchValue !== undefined ? { match: matchValue } : {}),
2056
2076
  ...(requireUniqueValue !== undefined ? { require_unique: requireUniqueValue } : {}),
2057
2077
  ...(strictValue !== undefined ? { strict: strictValue } : {}),
2058
- ...(existingHunkIdValue ? { existing_hunk_id: existingHunkIdValue } : {}),
2059
- content: contentValue,
2078
+ ...(contentValue !== undefined ? { content: contentValue } : {}),
2079
+ ...(padIdValue !== undefined ? { pad_id: padIdValue } : {}),
2080
+ ...(padRangeValue !== undefined ? { pad_range: padRangeValue } : {}),
2081
+ ...(previewValue !== undefined ? { preview: previewValue } : {}),
2082
+ ...(showDiffValue !== undefined ? { show_diff: showDiffValue } : {}),
2060
2083
  });
2061
2084
  const content = toolCallOutputToString(output);
2085
+ if (isSuccessfulYamlToolResult(content, 'file_block_replace')) {
2086
+ await refreshDerivedStateAfterTeamMgmtWrite({
2087
+ relPaths: [rel],
2088
+ trigger: 'team_mgmt_file_block_replace',
2089
+ });
2090
+ }
2062
2091
  return output;
2063
2092
  }
2064
2093
  catch (err) {
@@ -2069,13 +2098,13 @@ exports.teamMgmtPrepareBlockReplaceTool = {
2069
2098
  }
2070
2099
  },
2071
2100
  };
2072
- exports.teamMgmtPrepareFileRangeEditTool = {
2101
+ exports.teamMgmtFileRangeEditTool = {
2073
2102
  type: 'func',
2074
- name: 'team_mgmt_prepare_file_range_edit',
2075
- description: `Prepare a single-file modification under ${MINDS_DIR}/ (does not write yet).`,
2103
+ name: 'team_mgmt_file_range_edit',
2104
+ description: `Directly write a precise line range under ${MINDS_DIR}/ from inline content or a ws_mod pad source.`,
2076
2105
  descriptionI18n: {
2077
- en: `Prepare a single-file modification under ${MINDS_DIR}/ (does not write yet).`,
2078
- zh: `按行号范围规划 ${MINDS_DIR}/ 下的单文件修改(不会立刻写入)。`,
2106
+ en: `Directly write a precise line range under ${MINDS_DIR}/ from inline content or a ws_mod pad source.`,
2107
+ zh: `用内联 content 或 ws_mod pad 来源按精确行号范围直接写入 ${MINDS_DIR}/ 下的文件。`,
2079
2108
  },
2080
2109
  parameters: {
2081
2110
  type: 'object',
@@ -2084,8 +2113,11 @@ exports.teamMgmtPrepareFileRangeEditTool = {
2084
2113
  properties: {
2085
2114
  path: { type: 'string' },
2086
2115
  range: { type: 'string' },
2087
- existing_hunk_id: { type: 'string' },
2088
2116
  content: { type: 'string' },
2117
+ pad_id: { type: 'string' },
2118
+ pad_range: { type: 'string' },
2119
+ preview: { type: 'boolean' },
2120
+ show_diff: { type: 'boolean' },
2089
2121
  },
2090
2122
  },
2091
2123
  argsValidation: 'dominds',
@@ -2105,24 +2137,45 @@ exports.teamMgmtPrepareFileRangeEditTool = {
2105
2137
  throw new Error('Path required');
2106
2138
  if (!rangeSpec)
2107
2139
  throw new Error('Range required (e.g. 10~20 or ~)');
2108
- const existingHunkIdValue = args['existing_hunk_id'];
2109
- if (existingHunkIdValue !== undefined && typeof existingHunkIdValue !== 'string') {
2110
- throw new Error('Invalid existing_hunk_id (expected string)');
2111
- }
2112
2140
  const contentValue = args['content'];
2113
2141
  if (contentValue !== undefined && typeof contentValue !== 'string') {
2114
2142
  throw new Error('Invalid content (expected string)');
2115
2143
  }
2144
+ const padIdValue = args['pad_id'];
2145
+ if (padIdValue !== undefined && typeof padIdValue !== 'string') {
2146
+ throw new Error('Invalid pad_id (expected string)');
2147
+ }
2148
+ const padRangeValue = args['pad_range'];
2149
+ if (padRangeValue !== undefined && typeof padRangeValue !== 'string') {
2150
+ throw new Error('Invalid pad_range (expected string)');
2151
+ }
2152
+ const previewValue = args['preview'];
2153
+ if (previewValue !== undefined && typeof previewValue !== 'boolean') {
2154
+ throw new Error('Invalid preview (expected boolean)');
2155
+ }
2156
+ const showDiffValue = args['show_diff'];
2157
+ if (showDiffValue !== undefined && typeof showDiffValue !== 'boolean') {
2158
+ throw new Error('Invalid show_diff (expected boolean)');
2159
+ }
2116
2160
  const rel = toMindsRelativePath(filePath);
2117
2161
  ensureMindsScopedPath(rel);
2118
2162
  const proxyCaller = makeMindsOnlyAccessMember(caller);
2119
- const output = await txt_1.prepareFileRangeEditTool.call(dlg, proxyCaller, {
2163
+ const output = await txt_1.fileRangeEditTool.call(dlg, proxyCaller, {
2120
2164
  path: rel,
2121
2165
  range: rangeSpec,
2122
- ...(existingHunkIdValue ? { existing_hunk_id: existingHunkIdValue } : {}),
2123
- ...(typeof contentValue === 'string' ? { content: contentValue } : {}),
2166
+ ...(contentValue !== undefined ? { content: contentValue } : {}),
2167
+ ...(padIdValue !== undefined ? { pad_id: padIdValue } : {}),
2168
+ ...(padRangeValue !== undefined ? { pad_range: padRangeValue } : {}),
2169
+ ...(previewValue !== undefined ? { preview: previewValue } : {}),
2170
+ ...(showDiffValue !== undefined ? { show_diff: showDiffValue } : {}),
2124
2171
  });
2125
2172
  const content = toolCallOutputToString(output);
2173
+ if (isSuccessfulYamlToolResult(content, 'file_range_edit')) {
2174
+ await refreshDerivedStateAfterTeamMgmtWrite({
2175
+ relPaths: [rel],
2176
+ trigger: 'team_mgmt_file_range_edit',
2177
+ });
2178
+ }
2126
2179
  return output;
2127
2180
  }
2128
2181
  catch (err) {
@@ -2133,19 +2186,27 @@ exports.teamMgmtPrepareFileRangeEditTool = {
2133
2186
  }
2134
2187
  },
2135
2188
  };
2136
- exports.teamMgmtApplyFileModificationTool = {
2189
+ exports.teamMgmtPrepareOccurrenceReplaceTool = {
2137
2190
  type: 'func',
2138
- name: 'team_mgmt_apply_file_modification',
2139
- description: `Apply a previously planned file modification under ${MINDS_DIR}/ by hunk id.`,
2191
+ name: 'team_mgmt_prepare_occurrence_replace',
2192
+ description: `Prepare a literal occurrence replacement plan under ${MINDS_DIR}/. Designed for batch same-literal replacement; direct file tools are usually clearer for single-block edits.`,
2140
2193
  descriptionI18n: {
2141
- en: `Apply a previously planned file modification under ${MINDS_DIR}/ by hunk id.`,
2142
- zh: `按 hunk id 应用之前规划的 ${MINDS_DIR}/ 下的单文件修改。`,
2194
+ en: `Prepare a literal occurrence replacement plan under ${MINDS_DIR}/. Designed for batch same-literal replacement; direct file tools are usually clearer for single-block edits.`,
2195
+ zh: `规划 ${MINDS_DIR}/ 下文件内的字面量 occurrence 替换。主要面向同一字面量的批量替换;单块编辑通常使用 direct file 工具更清晰。`,
2143
2196
  },
2144
2197
  parameters: {
2145
2198
  type: 'object',
2146
2199
  additionalProperties: false,
2147
- required: ['hunk_id'],
2148
- properties: { hunk_id: { type: 'string' } },
2200
+ required: ['path', 'find'],
2201
+ properties: {
2202
+ path: { type: 'string' },
2203
+ find: { type: 'string' },
2204
+ content: { type: 'string' },
2205
+ pad_id: { type: 'string' },
2206
+ pad_range: { type: 'string' },
2207
+ occurrence_indexes: { type: 'array', items: { type: 'integer' } },
2208
+ show_diff: { type: 'boolean' },
2209
+ },
2149
2210
  },
2150
2211
  argsValidation: 'dominds',
2151
2212
  async call(dlg, caller, args) {
@@ -2156,21 +2217,118 @@ exports.teamMgmtApplyFileModificationTool = {
2156
2217
  throw new Error(`${MINDS_DIR} exists but is not a directory: ${mindsState.abs}`);
2157
2218
  }
2158
2219
  await ensureMindsRootDirExists();
2159
- const hunkIdValue = args['hunk_id'];
2160
- const id = typeof hunkIdValue === 'string' ? hunkIdValue.trim() : '';
2161
- if (!id)
2162
- throw new Error('Hunk id required (e.g. a1b2c3d4)');
2220
+ const pathValue = args['path'];
2221
+ const findValue = args['find'];
2222
+ const rawPath = typeof pathValue === 'string' ? pathValue.trim() : '';
2223
+ const findText = typeof findValue === 'string' ? findValue : '';
2224
+ if (!rawPath)
2225
+ throw new Error('Path required');
2226
+ if (!findText)
2227
+ throw new Error('find is required');
2228
+ const contentValue = args['content'];
2229
+ if (contentValue !== undefined && typeof contentValue !== 'string') {
2230
+ throw new Error('Invalid content (expected string)');
2231
+ }
2232
+ const padIdValue = args['pad_id'];
2233
+ if (padIdValue !== undefined && typeof padIdValue !== 'string') {
2234
+ throw new Error('Invalid pad_id (expected string)');
2235
+ }
2236
+ const padRangeValue = args['pad_range'];
2237
+ if (padRangeValue !== undefined && typeof padRangeValue !== 'string') {
2238
+ throw new Error('Invalid pad_range (expected string)');
2239
+ }
2240
+ const occurrenceIndexesValue = args['occurrence_indexes'];
2241
+ if (occurrenceIndexesValue !== undefined) {
2242
+ if (!Array.isArray(occurrenceIndexesValue)) {
2243
+ throw new Error('Invalid occurrence_indexes (expected array of positive integers)');
2244
+ }
2245
+ const occurrenceIndexItems = occurrenceIndexesValue;
2246
+ for (const item of occurrenceIndexItems) {
2247
+ if (typeof item !== 'number' || !Number.isInteger(item) || item <= 0) {
2248
+ throw new Error('Invalid occurrence_indexes (expected array of positive integers)');
2249
+ }
2250
+ }
2251
+ }
2252
+ const showDiffValue = args['show_diff'];
2253
+ if (showDiffValue !== undefined && typeof showDiffValue !== 'boolean') {
2254
+ throw new Error('Invalid show_diff (expected boolean)');
2255
+ }
2256
+ const rel = toMindsRelativePath(rawPath);
2257
+ ensureMindsScopedPath(rel);
2258
+ const proxyCaller = makeMindsOnlyAccessMember(caller);
2259
+ return await txt_1.prepareOccurrenceReplaceTool.call(dlg, proxyCaller, {
2260
+ path: rel,
2261
+ find: findText,
2262
+ ...(contentValue !== undefined ? { content: contentValue } : {}),
2263
+ ...(padIdValue !== undefined ? { pad_id: padIdValue } : {}),
2264
+ ...(padRangeValue !== undefined ? { pad_range: padRangeValue } : {}),
2265
+ ...(occurrenceIndexesValue !== undefined
2266
+ ? { occurrence_indexes: occurrenceIndexesValue }
2267
+ : {}),
2268
+ ...(showDiffValue !== undefined ? { show_diff: showDiffValue } : {}),
2269
+ });
2270
+ }
2271
+ catch (err) {
2272
+ const msg = language === 'zh'
2273
+ ? `错误:${err instanceof Error ? err.message : String(err)}`
2274
+ : `Error: ${err instanceof Error ? err.message : String(err)}`;
2275
+ return fail(msg, [{ type: 'environment_msg', role: 'user', content: msg }]);
2276
+ }
2277
+ },
2278
+ };
2279
+ exports.teamMgmtApplyOccurrenceReplaceTool = {
2280
+ type: 'func',
2281
+ name: 'team_mgmt_apply_occurrence_replace',
2282
+ description: `Apply a prepared literal occurrence replacement plan under ${MINDS_DIR}/.`,
2283
+ descriptionI18n: {
2284
+ en: `Apply a prepared literal occurrence replacement plan under ${MINDS_DIR}/.`,
2285
+ zh: `应用 ${MINDS_DIR}/ 下文件的字面量 occurrence 替换 plan。`,
2286
+ },
2287
+ parameters: {
2288
+ type: 'object',
2289
+ additionalProperties: false,
2290
+ required: ['plan_id'],
2291
+ properties: {
2292
+ plan_id: { type: 'string' },
2293
+ show_diff: { type: 'boolean' },
2294
+ },
2295
+ },
2296
+ argsValidation: 'dominds',
2297
+ async call(dlg, caller, args) {
2298
+ const language = getUserLang(dlg);
2299
+ try {
2300
+ const mindsState = await getMindsDirState();
2301
+ if (mindsState.kind === 'not_directory') {
2302
+ throw new Error(`${MINDS_DIR} exists but is not a directory: ${mindsState.abs}`);
2303
+ }
2304
+ await ensureMindsRootDirExists();
2305
+ const planIdValue = args['plan_id'];
2306
+ const planId = typeof planIdValue === 'string' ? planIdValue.trim() : '';
2307
+ if (!planId)
2308
+ throw new Error('plan_id required');
2309
+ const showDiffValue = args['show_diff'];
2310
+ if (showDiffValue !== undefined && typeof showDiffValue !== 'boolean') {
2311
+ throw new Error('Invalid show_diff (expected boolean)');
2312
+ }
2163
2313
  const proxyCaller = makeMindsOnlyAccessMember(caller);
2164
- const output = await txt_1.applyFileModificationTool.call(dlg, proxyCaller, { hunk_id: id });
2314
+ const output = await txt_1.applyOccurrenceReplaceTool.call(dlg, proxyCaller, {
2315
+ plan_id: planId,
2316
+ ...(showDiffValue !== undefined ? { show_diff: showDiffValue } : {}),
2317
+ });
2165
2318
  const content = toolCallOutputToString(output);
2166
- if (isSuccessfulYamlToolResult(content, 'apply_file_modification')) {
2167
- const relPath = extractPathFromYamlToolOutput(content);
2168
- if (relPath) {
2169
- await refreshDerivedStateAfterTeamMgmtWrite({
2170
- relPaths: [relPath],
2171
- trigger: 'team_mgmt_apply_file_modification',
2319
+ if (isSuccessfulYamlToolResult(content, 'apply_occurrence_replace')) {
2320
+ const outputRel = extractPathFromYamlToolOutput(content);
2321
+ if (outputRel === null) {
2322
+ log.warn('Missing path in successful team_mgmt occurrence replace output', {
2323
+ trigger: 'team_mgmt_apply_occurrence_replace',
2324
+ planId,
2172
2325
  });
2173
2326
  }
2327
+ const rel = outputRel ?? MINDS_DIR;
2328
+ await refreshDerivedStateAfterTeamMgmtWrite({
2329
+ relPaths: [rel],
2330
+ trigger: 'team_mgmt_apply_occurrence_replace',
2331
+ });
2174
2332
  }
2175
2333
  return output;
2176
2334
  }
@@ -3479,7 +3637,7 @@ function renderTeamManual(language) {
3479
3637
  '想快速查看有哪些 provider / models / model_param_options:用 `team_mgmt_list_providers({})` 和 `team_mgmt_list_models({ provider_pattern: \"*\", model_pattern: \"*\" })`。',
3480
3638
  '不要把内置成员(例如 `fuxi` / `pangu`)的定义写入 `.minds/team.yaml`(这里只定义 rtws(运行时工作区)自己的成员):内置成员通常带有特殊权限/目录访问边界;重复定义可能引入冲突、权限误配或行为不一致。',
3481
3639
  '`hidden: true` 表示影子/隐藏成员:不会出现在系统提示的团队目录里,但仍然可以通过 tellask-special 函数诉请。',
3482
- '修改文件推荐流程:先 `team_mgmt_read_file({ path: \"team.yaml\", range: \"<start~end>\", max_lines: 0, show_linenos: true })` 定位行号;小改动用 `team_mgmt_prepare_file_range_edit({ path: \"team.yaml\", range: \"<line~range>\", existing_hunk_id: \"\", content: \"<new content>\" })` 生成 diff(工具会返回 hunk_id),再用 `team_mgmt_apply_file_modification({ hunk_id: \"<hunk_id>\" })` 显式确认写入。注意:prepare 只生成内存中的预览,apply 之前不会落盘;此时再次读取文件仍只能读到旧内容。若只是修订同一个尚未落盘的预览,可再次调用 `team_mgmt_prepare_file_range_edit({ path: \"team.yaml\", range: \"<line~range>\", existing_hunk_id: \"<hunk_id>\", content: \"<new content>\" })` 覆写;若想基于这次改动继续追加下一笔修改,必须先 apply 当前 hunk,再重新 read/prepare 新的改动。如确实需要整文件覆盖:先 `team_mgmt_read_file({ path: \"team.yaml\", range: \"\", max_lines: 0, show_linenos: true })` 从 YAML header 获取 total_lines/size_bytes,再用 `team_mgmt_overwrite_entire_file({ path: \"team.yaml\", known_old_total_lines: <n>, known_old_total_bytes: <n>, content_format: \"\", content: \"...\" })`。',
3640
+ '修改文件推荐流程:先 `team_mgmt_read_file({ path: \"team.yaml\", range: \"<start~end>\", max_lines: 0, show_linenos: true })` 定位行号;精确行号范围改动直接用 `team_mgmt_file_range_edit({ path: \"team.yaml\", range: \"<line~range>\", content: \"<new content>\" })` 写入。同一字面量多点批量替换推荐使用 `team_mgmt_prepare_occurrence_replace({ path: \"team.yaml\", find: \"<old>\", content: \"<new>\" })` 后接 `team_mgmt_apply_occurrence_replace({ plan_id: \"<plan_id>\" })`;若只选中单个 occurrence,工具会成功生成 plan 但返回 `notice: NOT_MULTI_OCCURRENCE`,此时通常改用 `team_mgmt_file_range_edit` `team_mgmt_file_block_replace` 更清晰。若需要先审阅差异,可加 `preview: true, show_diff: true` 做只读预览,确认后再去掉 `preview` 写入。如确实需要整文件覆盖:先 `team_mgmt_read_file({ path: \"team.yaml\", range: \"\", max_lines: 0, show_linenos: true })` 从 YAML header 获取 total_lines/size_bytes,再用 `team_mgmt_overwrite_entire_file({ path: \"team.yaml\", known_old_total_lines: <n>, known_old_total_bytes: <n>, content_format: \"\", content: \"...\" })`;大正文优先把内容准备到 pad,再传 `pad_id/pad_range`。',
3483
3641
  '部署/组织建议(可选):如果你不希望出现显在“团队管理者”,可由一个影子/隐藏成员持有 `team_mgmt` 负责维护 `.minds/**`(尤其 `team.yaml`),由人类在需要时触发其执行(例如初始化/调整权限/更新模型)。Dominds 不强制这种组织方式;你也可以让显在成员拥有 `team_mgmt` 或由人类直接维护文件。',
3484
3642
  ]) +
3485
3643
  fmtSubHeader('Schema Snapshot(自动生成,来自当前解析器白名单)') +
@@ -3543,7 +3701,7 @@ function renderTeamManual(language) {
3543
3701
  'Deployment/org suggestion (optional): if you do not want a visible team manager, keep `team_mgmt` only on a hidden/shadow member and have a human trigger it when needed; Dominds does not require this organizational setup.',
3544
3702
  'If a member is assigned team-management responsibility (especially by granting `team_mgmt`), that member’s `persona.*.md` must explicitly require reading the relevant `man({ "toolsetId": "team_mgmt" })` chapters before any team-management action, and maintaining `.minds/**` team mind assets by handbook-standard workflow rather than improvising ad hoc edits.',
3545
3703
  'Role ownership is not write permission: even if `.minds/team/<id>/*` belongs to a member role, editing it still depends on whether the current actor holds `team_mgmt` or equivalent team-asset maintenance authority. “This is your own persona/knowhow/pitfalls” does not mean “you may rewrite it yourself”.',
3546
- 'Recommended editing workflow: use `team_mgmt_read_file({ path: \"team.yaml\", range: \"<start~end>\", max_lines: 0, show_linenos: true })` to find line numbers; for small edits, run `team_mgmt_prepare_file_range_edit({ path: \"team.yaml\", range: \"<line~range>\", existing_hunk_id: \"\", content: \"<new content>\" })` to get a diff (the tool returns hunk_id), then confirm with `team_mgmt_apply_file_modification({ hunk_id: \"<hunk_id>\" })`. Important: prepare only creates an in-memory preview and does not persist anything before apply, so re-reading the file at this point still returns the old content. If you only want to revise the same not-yet-persisted preview, call `team_mgmt_prepare_file_range_edit({ path: \"team.yaml\", range: \"<line~range>\", existing_hunk_id: \"<hunk_id>\", content: \"<new content>\" })` again; if you want a further edit based on this change, you must apply the current hunk first, then read/prepare the next change. If you truly need a full overwrite: first `team_mgmt_read_file({ path: \"team.yaml\", range: \"\", max_lines: 0, show_linenos: true })` and read total_lines/size_bytes from the YAML header, then use `team_mgmt_overwrite_entire_file({ path: \"team.yaml\", known_old_total_lines: <n>, known_old_total_bytes: <n>, content_format: \"\", content: \"...\" })`.',
3704
+ 'Recommended editing workflow: use `team_mgmt_read_file({ path: \"team.yaml\", range: \"<start~end>\", max_lines: 0, show_linenos: true })` to find line numbers; for precise line-range edits, run `team_mgmt_file_range_edit({ path: \"team.yaml\", range: \"<line~range>\", content: \"<new content>\" })` directly. For multi-point replacement of the same literal, prefer `team_mgmt_prepare_occurrence_replace({ path: \"team.yaml\", find: \"<old>\", content: \"<new>\" })` followed by `team_mgmt_apply_occurrence_replace({ plan_id: \"<plan_id>\" })`; if only one occurrence is selected, the tool still creates a plan but returns `notice: NOT_MULTI_OCCURRENCE`, and `team_mgmt_file_range_edit` or `team_mgmt_file_block_replace` is usually clearer. If you need to review the diff first, pass `preview: true, show_diff: true`, then remove `preview` to write. If you truly need a full overwrite: first `team_mgmt_read_file({ path: \"team.yaml\", range: \"\", max_lines: 0, show_linenos: true })` and read total_lines/size_bytes from the YAML header, then use `team_mgmt_overwrite_entire_file({ path: \"team.yaml\", known_old_total_lines: <n>, known_old_total_bytes: <n>, content_format: \"\", content: \"...\" })`; for large bodies, prepare a pad first and pass `pad_id/pad_range`.',
3547
3705
  ])) +
3548
3706
  fmtSubHeader('Schema Snapshot (generated from parser allow-list)') +
3549
3707
  fmtList([
@@ -4985,12 +5143,13 @@ exports.teamMgmtTools = [
4985
5143
  exports.teamMgmtReadFileTool,
4986
5144
  exports.teamMgmtCreateNewFileTool,
4987
5145
  exports.teamMgmtOverwriteEntireFileTool,
4988
- exports.teamMgmtPrepareFileAppendTool,
4989
- exports.teamMgmtPrepareInsertAfterTool,
4990
- exports.teamMgmtPrepareInsertBeforeTool,
4991
- exports.teamMgmtPrepareBlockReplaceTool,
4992
- exports.teamMgmtPrepareFileRangeEditTool,
4993
- exports.teamMgmtApplyFileModificationTool,
5146
+ exports.teamMgmtFileAppendTool,
5147
+ exports.teamMgmtFileInsertAfterTool,
5148
+ exports.teamMgmtFileInsertBeforeTool,
5149
+ exports.teamMgmtFileBlockReplaceTool,
5150
+ exports.teamMgmtFileRangeEditTool,
5151
+ exports.teamMgmtPrepareOccurrenceReplaceTool,
5152
+ exports.teamMgmtApplyOccurrenceReplaceTool,
4994
5153
  exports.teamMgmtMkDirTool,
4995
5154
  exports.teamMgmtMoveFileTool,
4996
5155
  exports.teamMgmtMoveDirTool,