mocode-ai 1.6.0 → 1.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -9
- package/README.zh-CN.md +355 -341
- package/dist/agent/model-turn.js +7 -8
- package/dist/agent/run-coordinator.js +4 -0
- package/dist/agent/stages/tool-dispatcher.js +1 -0
- package/dist/agent/tool-turn.js +1 -1
- package/dist/attachments/image.js +119 -2
- package/dist/config/index.js +84 -14
- package/dist/config/profiles.js +42 -10
- package/dist/context/encoders/search.js +6 -2
- package/dist/context/vision-window.js +2 -2
- package/dist/i18n/index.js +30 -14
- package/dist/repl/commands/image.js +7 -2
- package/dist/repl/commands/router.js +13 -1
- package/dist/repl/commands/system.js +29 -9
- package/dist/repl/commands/tool-group.js +1 -1
- package/dist/repl/commands.js +2 -0
- package/dist/runtime/dev-server-manager.js +4 -2
- package/dist/runtime/shell.js +153 -0
- package/dist/skills/builtin-skills.js +1 -1
- package/dist/tools/builtins/ask-human.js +2 -3
- package/dist/tools/builtins/dev-server.js +23 -6
- package/dist/tools/builtins/edit-file.js +5 -13
- package/dist/tools/builtins/glob.js +2 -2
- package/dist/tools/builtins/grep.js +93 -26
- package/dist/tools/builtins/index.js +6 -6
- package/dist/tools/builtins/note-append.js +4 -8
- package/dist/tools/builtins/plan-update.js +1 -6
- package/dist/tools/builtins/read-file.js +135 -18
- package/dist/tools/builtins/run-command.js +60 -11
- package/dist/tools/builtins/screenshot.js +17 -41
- package/dist/tools/builtins/use-skill.js +2 -2
- package/dist/tools/builtins/web-fetch.js +148 -35
- package/dist/tools/builtins/web-search.js +1 -2
- package/dist/tools/builtins/write-file.js +102 -7
- package/dist/tools/constants.js +7 -0
- package/dist/tools/policy.js +26 -7
- package/dist/tools/router.js +20 -4
- package/dist/tools/tool-runtime.js +63 -1
- package/dist/ui/render.js +20 -2
- package/package.json +1 -1
package/dist/i18n/index.js
CHANGED
|
@@ -27,7 +27,7 @@ const zhCN = {
|
|
|
27
27
|
'commands.subagentOff': '禁止 orchestration',
|
|
28
28
|
'commands.subagentStatus': '查看 orchestration gate',
|
|
29
29
|
'commands.fe': '浏览器调试/桌面观察自动路由 gate',
|
|
30
|
-
'commands.feOn': '允许按需路由 browser/
|
|
30
|
+
'commands.feOn': '允许按需路由 browser/screenshot',
|
|
31
31
|
'commands.feOff': '禁止 browser-debug/desktop-observe',
|
|
32
32
|
'commands.feStatus': '查看前端能力 gate',
|
|
33
33
|
'commands.cu': 'Computer Use 自动路由 gate(高危)',
|
|
@@ -46,8 +46,10 @@ const zhCN = {
|
|
|
46
46
|
'commands.modelShow': '显示当前模型配置',
|
|
47
47
|
'commands.modelUse': '按名称应用预设',
|
|
48
48
|
'commands.modelDelete': '删除指定预设',
|
|
49
|
-
'commands.router': '
|
|
50
|
-
'commands.routerStatus': '
|
|
49
|
+
'commands.router': '工具预路由开关与后端(LLM / Jev)',
|
|
50
|
+
'commands.routerStatus': '显示当前预路由开关、后端与配置',
|
|
51
|
+
'commands.routerOn': '开启工具预路由(默认)',
|
|
52
|
+
'commands.routerOff': '关闭工具预路由(仅保留默认工具簇)',
|
|
51
53
|
'commands.routerLlm': '用主 LLM 做预路由(默认)',
|
|
52
54
|
'commands.routerJev': '用 Jev systemone API 做预路由',
|
|
53
55
|
'commands.routerTest': '探测 Jev 端点连通性',
|
|
@@ -232,6 +234,7 @@ const zhCN = {
|
|
|
232
234
|
'toolSummary.emptyFile': '(空文件)',
|
|
233
235
|
'toolSummary.noMatches': '(无匹配)',
|
|
234
236
|
'toolSummary.noOutput': '(无输出)',
|
|
237
|
+
'toolSummary.imageAttached': '图片已作为视觉输入附加',
|
|
235
238
|
'toolError.unknown': '错误:未知工具 "{name}"',
|
|
236
239
|
'toolError.invalidJson': '错误:工具 {name} 的 arguments 不是合法 JSON: {arguments}',
|
|
237
240
|
'toolError.execution': '错误:工具 {name} 执行失败: {message}',
|
|
@@ -291,8 +294,8 @@ const zhCN = {
|
|
|
291
294
|
'fe.status': 'Browser/desktop 自动路由:{state}',
|
|
292
295
|
'fe.stateOn': '允许',
|
|
293
296
|
'fe.stateOff': '禁止',
|
|
294
|
-
'fe.changedOn': '已允许按需路由 browser-debug / desktop-observe
|
|
295
|
-
'fe.changedOff': '已禁止 browser-debug / desktop-observe;后续路由不会暴露 browser / dev_server
|
|
297
|
+
'fe.changedOn': '已允许按需路由 browser-debug / desktop-observe;图片读取(read_file)始终属于公共工具。',
|
|
298
|
+
'fe.changedOff': '已禁止 browser-debug / desktop-observe;后续路由不会暴露 browser / screenshot(dev_server 不受此开关影响,始终可路由)。',
|
|
296
299
|
'fe.usage': '用法:/fe on|off|status',
|
|
297
300
|
'cu.status': 'Computer-control 自动路由:{state}',
|
|
298
301
|
'cu.stateOn': '允许',
|
|
@@ -351,12 +354,17 @@ const zhCN = {
|
|
|
351
354
|
'upgrade.pkgName': '包名:mocode-ai',
|
|
352
355
|
'upgrade.failed': '[upgrade] 失败:{message}',
|
|
353
356
|
'upgrade.usage': '/upgrade 用法:\n /upgrade 升级到最新版本\n /upgrade now 同 /upgrade\n /upgrade check 检查当前版本与最新版本差异\n /upgrade status 显示当前本地版本',
|
|
354
|
-
'router.
|
|
357
|
+
'router.toggleStatus': '工具预路由:{state}',
|
|
358
|
+
'router.stateOn': '开启',
|
|
359
|
+
'router.stateOff': '关闭(仅保留默认工具簇)',
|
|
360
|
+
'router.status': '预路由后端:{mode}',
|
|
355
361
|
'router.modeLlm': 'LLM(与主 Agent 同一后端,默认)',
|
|
356
362
|
'router.modeJev': 'Jev(TypeSafe systemone API)',
|
|
357
363
|
'router.jevConfig': '端点 {baseUrl} · 模型 {model} · key {key} · 阈值 {min}/{minMcp}',
|
|
358
364
|
'router.source': '来源 {env}({path})',
|
|
359
|
-
'router.usage': '/router 用法:\n /router
|
|
365
|
+
'router.usage': '/router 用法:\n /router 显示开关、后端与配置\n /router on 开启预路由(默认)\n /router off 关闭预路由(仅保留默认工具簇)\n /router llm 用主 LLM 做预路由(默认)\n /router jev 用 Jev systemone API 做预路由\n /router base <url> 设置 Jev 端点(本地兼容层可指向 localhost)\n /router key <key> 设置 Jev API key\n /router model <name> 设置 Jev 模型名(默认 jev-latest)\n /router min <0-1> 设置通用出簇阈值(默认 0.65)\n /router min-mcp <0-1> 设置 mcp 专用阈值(默认 0.85)\n /router test 探测 Jev 端点连通性',
|
|
366
|
+
'router.enabledOn': '已开启工具预路由(下一轮生效)',
|
|
367
|
+
'router.enabledOff': '已关闭工具预路由:不再执行逐轮路由调用,仅保留默认工具簇(下一轮生效)',
|
|
360
368
|
'router.changed': '已切换预路由后端 → {mode}(下一轮生效)',
|
|
361
369
|
'router.set': '已设置 {field} = {value}(下一轮生效)',
|
|
362
370
|
'router.invalid': '参数无效:{detail}',
|
|
@@ -395,7 +403,7 @@ const en = {
|
|
|
395
403
|
'commands.subagentOff': 'Block orchestration',
|
|
396
404
|
'commands.subagentStatus': 'Show orchestration gate',
|
|
397
405
|
'commands.fe': 'Browser-debug and desktop-observe routing gate',
|
|
398
|
-
'commands.feOn': 'Allow browser/
|
|
406
|
+
'commands.feOn': 'Allow browser/screenshot routing',
|
|
399
407
|
'commands.feOff': 'Block browser-debug and desktop-observe',
|
|
400
408
|
'commands.feStatus': 'Show frontend capability gate',
|
|
401
409
|
'commands.cu': 'Computer Use automatic-routing gate (high-risk)',
|
|
@@ -414,8 +422,10 @@ const en = {
|
|
|
414
422
|
'commands.modelShow': 'Show current model configuration',
|
|
415
423
|
'commands.modelUse': 'Apply a preset by name',
|
|
416
424
|
'commands.modelDelete': 'Delete a preset',
|
|
417
|
-
'commands.router': 'Tool pre-routing backend (LLM / Jev)',
|
|
418
|
-
'commands.routerStatus': 'Show
|
|
425
|
+
'commands.router': 'Tool pre-routing switch and backend (LLM / Jev)',
|
|
426
|
+
'commands.routerStatus': 'Show pre-routing switch, backend, and config',
|
|
427
|
+
'commands.routerOn': 'Enable tool pre-routing (default)',
|
|
428
|
+
'commands.routerOff': 'Disable tool pre-routing (keep default groups only)',
|
|
419
429
|
'commands.routerLlm': 'Route with the main LLM (default)',
|
|
420
430
|
'commands.routerJev': 'Route with the Jev systemone API',
|
|
421
431
|
'commands.routerTest': 'Probe Jev endpoint connectivity',
|
|
@@ -598,6 +608,7 @@ const en = {
|
|
|
598
608
|
'toolSummary.emptyFile': '(empty file)',
|
|
599
609
|
'toolSummary.noMatches': '(no matches)',
|
|
600
610
|
'toolSummary.noOutput': '(no output)',
|
|
611
|
+
'toolSummary.imageAttached': 'Image attached as visual input',
|
|
601
612
|
'toolError.unknown': 'Error: unknown tool "{name}"',
|
|
602
613
|
'toolError.invalidJson': 'Error: arguments for tool {name} are not valid JSON: {arguments}',
|
|
603
614
|
'toolError.execution': 'Error: tool {name} failed: {message}',
|
|
@@ -657,8 +668,8 @@ const en = {
|
|
|
657
668
|
'fe.status': 'Browser/desktop automatic routing: {state}',
|
|
658
669
|
'fe.stateOn': 'allowed',
|
|
659
670
|
'fe.stateOff': 'blocked',
|
|
660
|
-
'fe.changedOn': 'Automatic routing may now select browser-debug / desktop-observe;
|
|
661
|
-
'fe.changedOff': 'Browser-debug / desktop-observe are blocked; browser /
|
|
671
|
+
'fe.changedOn': 'Automatic routing may now select browser-debug / desktop-observe; image reading (read_file) remains a common tool.',
|
|
672
|
+
'fe.changedOff': 'Browser-debug / desktop-observe are blocked; browser / screenshot will not be exposed (dev_server is unaffected and stays routable).',
|
|
662
673
|
'fe.usage': 'Usage: /fe on|off|status',
|
|
663
674
|
'cu.status': 'Computer-control automatic routing: {state}',
|
|
664
675
|
'cu.stateOn': 'allowed',
|
|
@@ -717,12 +728,17 @@ const en = {
|
|
|
717
728
|
'upgrade.pkgName': 'Package: mocode-ai',
|
|
718
729
|
'upgrade.failed': '[upgrade] failed: {message}',
|
|
719
730
|
'upgrade.usage': 'Usage: /upgrade\n /upgrade Upgrade to latest\n /upgrade now Same as /upgrade\n /upgrade check Check current vs latest\n /upgrade status Show current local version',
|
|
720
|
-
'router.
|
|
731
|
+
'router.toggleStatus': 'Tool pre-routing: {state}',
|
|
732
|
+
'router.stateOn': 'on',
|
|
733
|
+
'router.stateOff': 'off (default groups only)',
|
|
734
|
+
'router.status': 'Pre-routing backend: {mode}',
|
|
721
735
|
'router.modeLlm': 'LLM (same backend as the main agent; default)',
|
|
722
736
|
'router.modeJev': 'Jev (TypeSafe systemone API)',
|
|
723
737
|
'router.jevConfig': 'endpoint {baseUrl} · model {model} · key {key} · thresholds {min}/{minMcp}',
|
|
724
738
|
'router.source': 'Source {env} ({path})',
|
|
725
|
-
'router.usage': 'Usage: /router\n /router Show backend and config\n /router llm Route with the main LLM (default)\n /router jev Route with the Jev systemone API\n /router base <url> Set the Jev endpoint (point at localhost for a local compat server)\n /router key <key> Set the Jev API key\n /router model <name> Set the Jev model name (default jev-latest)\n /router min <0-1> Set the general activation threshold (default 0.65)\n /router min-mcp <0-1> Set the mcp-specific threshold (default 0.85)\n /router test Probe Jev endpoint connectivity',
|
|
739
|
+
'router.usage': 'Usage: /router\n /router Show switch, backend, and config\n /router on Enable pre-routing (default)\n /router off Disable pre-routing (keep default groups only)\n /router llm Route with the main LLM (default)\n /router jev Route with the Jev systemone API\n /router base <url> Set the Jev endpoint (point at localhost for a local compat server)\n /router key <key> Set the Jev API key\n /router model <name> Set the Jev model name (default jev-latest)\n /router min <0-1> Set the general activation threshold (default 0.65)\n /router min-mcp <0-1> Set the mcp-specific threshold (default 0.85)\n /router test Probe Jev endpoint connectivity',
|
|
740
|
+
'router.enabledOn': 'Tool pre-routing enabled (takes effect next turn)',
|
|
741
|
+
'router.enabledOff': 'Tool pre-routing disabled: no per-turn routing call, default tool groups only (takes effect next turn)',
|
|
726
742
|
'router.changed': 'Pre-routing backend switched → {mode} (takes effect next turn)',
|
|
727
743
|
'router.set': 'Set {field} = {value} (takes effect next turn)',
|
|
728
744
|
'router.invalid': 'Invalid argument: {detail}',
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import * as layout from '../../ui/layout.js';
|
|
11
11
|
import { ui } from '../../ui/theme.js';
|
|
12
12
|
import { config } from '../../config/index.js';
|
|
13
|
-
import {
|
|
13
|
+
import { loadImageAttachmentWithDownscale, renderChip, MAX_INLINE_BYTES_DEFAULT } from '../../attachments/image.js';
|
|
14
14
|
import { modelSupportsVision } from '../../llm/capabilities.js';
|
|
15
15
|
import { unhandled, next } from './types.js';
|
|
16
16
|
export const imageCommands = [
|
|
@@ -52,7 +52,7 @@ export const imageCommands = [
|
|
|
52
52
|
return next();
|
|
53
53
|
}
|
|
54
54
|
const maxBytes = config.maxImageBytes ?? MAX_INLINE_BYTES_DEFAULT;
|
|
55
|
-
const r = await
|
|
55
|
+
const r = await loadImageAttachmentWithDownscale(arg, { maxBytes });
|
|
56
56
|
if (!r.ok) {
|
|
57
57
|
layout.contentWrite(`${ui.red}[image] ${r.reason}${ui.reset}\n`);
|
|
58
58
|
return next();
|
|
@@ -60,6 +60,11 @@ export const imageCommands = [
|
|
|
60
60
|
if (!ctx.attachments.list().find((a) => a.id === r.att.id)) {
|
|
61
61
|
ctx.attachments.push(r.att);
|
|
62
62
|
}
|
|
63
|
+
// 用户粘/挂的高 DPI 截图超限时会被降采样:必须说出来,否则用户以为发的是原图,
|
|
64
|
+
// 之后模型「看不清小字」时无从归因。
|
|
65
|
+
if (r.downscaledFrom) {
|
|
66
|
+
layout.contentWrite(` ${ui.yellow}⚠ 原图 ${r.downscaledFrom.width}×${r.downscaledFrom.height} 超过内联上限,已降采样后附加(磁盘原图不变)。${ui.reset}\n`);
|
|
67
|
+
}
|
|
63
68
|
layout.contentWrite(` ${ui.dim}${renderChip(r.att)} — will attach to next message${ui.reset}\n`);
|
|
64
69
|
// 提前警告(不阻断附加):当前模型已知不支持视觉(如 MiniMax M2.x / gpt-3.5 等)时,
|
|
65
70
|
// 附加时就提示,而不是等发送后才在 catch 块里翻译 API 报错——减少一轮无意义请求。
|
|
@@ -15,12 +15,13 @@
|
|
|
15
15
|
import * as layout from '../../ui/layout.js';
|
|
16
16
|
import { ui } from '../../ui/theme.js';
|
|
17
17
|
import { t } from '../../i18n/index.js';
|
|
18
|
-
import { getRouterMode, updateRouterMode, getJevRouterConfig, updateJevRouterConfig, isJevRouterConfigured, } from '../../config/index.js';
|
|
18
|
+
import { getRouterMode, updateRouterMode, getJevRouterConfig, updateJevRouterConfig, isJevRouterConfigured, isToolRoutingEnabled, updateToolRoutingEnabled, } from '../../config/index.js';
|
|
19
19
|
import { writeConfigKeys, CONFIG_PATH } from '../../config/file.js';
|
|
20
20
|
import { probeJev } from '../../tools/jev-client.js';
|
|
21
21
|
import { maskKey } from '../commands.js';
|
|
22
22
|
import { unhandled, next } from './types.js';
|
|
23
23
|
const ROUTER_ENV_KEYS = {
|
|
24
|
+
enabled: 'MOCODE_ROUTER_ENABLED',
|
|
24
25
|
mode: 'MOCODE_ROUTER_MODE',
|
|
25
26
|
baseUrl: 'MOCODE_ROUTER_JEV_BASE_URL',
|
|
26
27
|
apiKey: 'MOCODE_ROUTER_JEV_API_KEY',
|
|
@@ -38,6 +39,9 @@ export const routerCommands = [
|
|
|
38
39
|
const sub = (spaceIndex === -1 ? rest : rest.slice(0, spaceIndex)).toLowerCase();
|
|
39
40
|
const arg = spaceIndex === -1 ? '' : rest.slice(spaceIndex + 1).trim();
|
|
40
41
|
const showStatus = () => {
|
|
42
|
+
const enabled = isToolRoutingEnabled();
|
|
43
|
+
const stateLabel = t(enabled ? 'router.stateOn' : 'router.stateOff');
|
|
44
|
+
layout.contentWrite(`${enabled ? ui.green : ui.yellow}${t('router.toggleStatus', { state: stateLabel })}${ui.reset}\n`);
|
|
41
45
|
const mode = getRouterMode();
|
|
42
46
|
const label = mode === 'jev' ? t('router.modeJev') : t('router.modeLlm');
|
|
43
47
|
layout.contentWrite(`${ui.accent}${t('router.status', { mode: label })}${ui.reset}\n`);
|
|
@@ -56,6 +60,14 @@ export const routerCommands = [
|
|
|
56
60
|
showStatus();
|
|
57
61
|
return next();
|
|
58
62
|
}
|
|
63
|
+
// /router on | /router off —— 预路由总开关。off = 跳过每轮路由调用,只保留默认簇。
|
|
64
|
+
if (sub === 'on' || sub === 'off') {
|
|
65
|
+
const enabled = sub === 'on';
|
|
66
|
+
updateToolRoutingEnabled(enabled);
|
|
67
|
+
writeConfigKeys({ [ROUTER_ENV_KEYS.enabled]: enabled ? 'true' : 'false' });
|
|
68
|
+
layout.contentWrite(`${ui.green}${t(enabled ? 'router.enabledOn' : 'router.enabledOff')}${ui.reset}\n`);
|
|
69
|
+
return next();
|
|
70
|
+
}
|
|
59
71
|
// /router llm | /router jev —— 切换后端
|
|
60
72
|
if (sub === 'llm' || sub === 'jev') {
|
|
61
73
|
const mode = sub === 'jev' ? 'jev' : 'llm';
|
|
@@ -17,40 +17,60 @@ import { buildSlashCommands, slashHelpLines, HELP_GROUPS } from '../commands.js'
|
|
|
17
17
|
import { startRunningListener, stopRunningListener } from '../running-input.js';
|
|
18
18
|
import { checkVersion, fetchLatestVersion, getCurrentVersion, runUpgradeForeground } from '../../commands/upgrade.js';
|
|
19
19
|
import { unhandled, next, exit, forward } from './types.js';
|
|
20
|
+
import fs from 'node:fs';
|
|
21
|
+
import path from 'node:path';
|
|
20
22
|
/**
|
|
21
|
-
* /init 指令:发给 agent 扫描项目并生成 AGENTS.md
|
|
23
|
+
* /init 指令:发给 agent 扫描项目并生成 AGENTS.md。整体重写(非增量追加),预算分配制:
|
|
24
|
+
* 总长 ≤4000 字且 ≤ 旧文件字数(代码侧动态注入具体数字),装不下的高价值事实进
|
|
25
|
+
* .mocode/agents-draft.md 草稿而非硬塞,消除「删事实 vs 守预算」两难。写完供 memory 子系统下轮加载。
|
|
22
26
|
*
|
|
23
|
-
* 函数化(非 const):.codegraph/
|
|
27
|
+
* 函数化(非 const):.codegraph/ 索引与旧文件字数的探测放在调用瞬间,没索引时不提 codegraph,
|
|
24
28
|
* 避免 LLM 调出失败。/init 是冷启动动作,IO 开销可忽略。
|
|
25
29
|
*/
|
|
26
30
|
function buildInitPrompt() {
|
|
27
31
|
const cg = hasCodegraphIndex()
|
|
28
32
|
? '- 若有 .codegraph/:用 use_skill 加载 codegraph skill 后用 run_command 调 codegraph explore "<架构或入口符号>" 一次拿相关源码+调用路径,别逐文件读!!!\n'
|
|
29
33
|
: '';
|
|
34
|
+
// 动态注入旧文件字数:具体数字的遵循率远高于抽象原则(「≤4000」);首次生成时无旧文件则只约束绝对上限。
|
|
35
|
+
let oldSizeNote = '';
|
|
36
|
+
try {
|
|
37
|
+
const old = fs.readFileSync(path.join(process.cwd(), 'AGENTS.md'), 'utf8');
|
|
38
|
+
if (old.trim())
|
|
39
|
+
oldSizeNote = `旧 AGENTS.md 为 ${old.length} 字(新文件净字数必须 ≤ ${Math.min(4000, old.length)}:既守 4000 上限也守零和,超出即继续删低价值条目直到达标)。`;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// 无旧文件:首次生成,只受 4000 字绝对上限约束。
|
|
43
|
+
}
|
|
30
44
|
return `分析当前项目(process.cwd()),生成 AGENTS.md 项目记忆文件,供 mocode 后续会话自动加载——目标是让后续会话无需重新摸索就能上手。
|
|
31
45
|
|
|
32
46
|
先探查(尽量少调用拿全貌):
|
|
33
47
|
${cg}- read_file package.json(或 Cargo.toml/pyproject.toml/go.mod 等):scripts、依赖、入口、模块类型。
|
|
34
48
|
- glob 顶层目录;read_file 入口文件 + 各子系统 index.ts/README。
|
|
35
|
-
- 若 AGENTS.md 已存在:read_file
|
|
49
|
+
- 若 AGENTS.md 已存在:read_file 读它。**整体重写,不是增量追加**——不要把旧文当底稿做增量,从下面的预算出发重新分配每一条;以当前代码为准,过时/弱事实直接删,准确且仍高价值的可沿用。
|
|
50
|
+
- 若 .mocode/agents-draft.md 存在:read_file 读它,把草稿里的稳定事实合入对应章节,完成后用 write_file 覆盖为空(或删除该文件)。
|
|
36
51
|
|
|
37
52
|
AGENTS.md 按以下结构写(每节简短,只写稳定、非显然的事实):
|
|
38
53
|
## 项目
|
|
39
54
|
一两句:是什么、技术栈、运行环境。
|
|
40
55
|
## 命令
|
|
41
|
-
install / dev / build / test / typecheck / lint 等——从 package.json scripts 提炼,写原样命令行(如 \`npm run
|
|
56
|
+
install / dev / build / test / typecheck / lint 等——从 package.json scripts 提炼,写原样命令行(如 \`npm run build\`);没有的注明"无测试"/"无 lint"。
|
|
42
57
|
## 目录结构
|
|
43
|
-
|
|
58
|
+
顶层各目录与子系统职责,一句话/个;不逐文件列。此节系统提示不会常驻注入(按需 read_file),够用即可。
|
|
44
59
|
## 约定
|
|
45
60
|
从代码与现有文档提炼的硬约定:模块系统(ESM?)、命名、错误处理、工具/函数契约、易踩坑点。只写非显然、会让人踩坑的;不写"保持简洁"这种正确废话。
|
|
46
61
|
## 扩展点
|
|
47
|
-
加工具/命令/provider/模块的接缝(改哪个文件、加在哪)
|
|
62
|
+
加工具/命令/provider/模块的接缝(改哪个文件、加在哪)。此节同样不常驻注入,保持简短。
|
|
63
|
+
|
|
64
|
+
预算(硬上限,按字分配,参考):
|
|
65
|
+
- 总长 ≤ 4000 字${oldSizeNote ? `,且 ${oldSizeNote}` : '(首次生成)'}。参考分配:项目 ≤200、命令 ≤650、目录结构 ≤400、约定 ≤2000、扩展点 ≤400。
|
|
66
|
+
- **预算装不下但确有高价值的事实不丢**:write_file(append=true) 追加到 \`.mocode/agents-draft.md\`(一行一条,注明所属章节),下次 /init 再评估合入。禁止为了塞进预算牺牲准确性,也禁止超预算硬塞。
|
|
48
67
|
|
|
49
68
|
硬要求:
|
|
50
69
|
- 从实际代码提炼,引用具体文件名/命令/符号;不编造、不泛泛。
|
|
51
|
-
-
|
|
52
|
-
-
|
|
53
|
-
-
|
|
70
|
+
- 只写后续会话有用的稳定事实,不写易变项(当前 bug、临时文件、未决 TODO)。
|
|
71
|
+
- 写入前 run_command \`wc -m AGENTS.md\` 不可用(文件还没写);改用你草稿的字数估算,并按 ${oldSizeNote ? '上述净删目标' : '4000 字上限'}自查,超了先删再写。
|
|
72
|
+
- 用 write_file 写入项目根 AGENTS.md;**写完立即 read_file AGENTS.md 复查实际字数**,超限则编辑收窄后重写,直到达标。
|
|
73
|
+
- 最终简述必须含对账三件套(供用户验收,缺一不可):①新旧字数对比(旧 X → 新 Y,无旧文件写"首建 Y");②删掉的条目清单(标题级);③新增的条目清单(标题级)+ 从代码里发现的 2-3 条非显然关键约定。`;
|
|
54
74
|
}
|
|
55
75
|
export const systemCommands = [
|
|
56
76
|
(ctx) => {
|
|
@@ -47,7 +47,7 @@ export const toolGroupCommands = [
|
|
|
47
47
|
return next();
|
|
48
48
|
},
|
|
49
49
|
// /fe|/frontend [status|on|off] — browser-debug + desktop-observe 的兼容 gate。
|
|
50
|
-
//
|
|
50
|
+
// 图片读取(read_file 魔数分流)是 common 工具,不受此开关影响。
|
|
51
51
|
(ctx) => {
|
|
52
52
|
const { line } = ctx;
|
|
53
53
|
const isFe = line === '/fe' || line.startsWith('/fe ') || line === '/frontend' || line.startsWith('/frontend ');
|
package/dist/repl/commands.js
CHANGED
|
@@ -94,6 +94,8 @@ export function buildSlashCommands() {
|
|
|
94
94
|
desc: d('commands.router'),
|
|
95
95
|
children: [
|
|
96
96
|
{ name: 'status', value: '/router', desc: d('commands.routerStatus') },
|
|
97
|
+
{ name: 'on', value: '/router on', desc: d('commands.routerOn') },
|
|
98
|
+
{ name: 'off', value: '/router off', desc: d('commands.routerOff') },
|
|
97
99
|
{ name: 'llm', value: '/router llm', desc: d('commands.routerLlm') },
|
|
98
100
|
{ name: 'jev', value: '/router jev', desc: d('commands.routerJev') },
|
|
99
101
|
{ name: 'test', value: '/router test', desc: d('commands.routerTest') },
|
|
@@ -13,6 +13,7 @@ import { mkdir, open, stat } from 'node:fs/promises';
|
|
|
13
13
|
import { dirname, join } from 'node:path';
|
|
14
14
|
import { randomBytes } from 'node:crypto';
|
|
15
15
|
import { filterEnv, getSandboxRoot, isCommandDenied, jailResolve } from '../sandbox/index.js';
|
|
16
|
+
import { defaultShellKind, shellSpawnSpec } from './shell.js';
|
|
16
17
|
export class DevServerManagerError extends Error {
|
|
17
18
|
code;
|
|
18
19
|
constructor(code, message) {
|
|
@@ -194,10 +195,11 @@ export async function startDevServer(opts) {
|
|
|
194
195
|
}
|
|
195
196
|
let child;
|
|
196
197
|
try {
|
|
197
|
-
|
|
198
|
+
const spec = shellSpawnSpec(opts.shell ?? defaultShellKind(), command);
|
|
199
|
+
child = spawn(spec.file, spec.args, {
|
|
198
200
|
cwd,
|
|
199
201
|
env: filterEnv(process.env),
|
|
200
|
-
windowsVerbatimArguments:
|
|
202
|
+
windowsVerbatimArguments: spec.windowsVerbatimArguments,
|
|
201
203
|
windowsHide: true,
|
|
202
204
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
203
205
|
});
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// shell 选择器:run_command / dev_server 共用的「命令 → spawn 规格」映射。
|
|
2
|
+
//
|
|
3
|
+
// 为什么存在:两处硬编码(win32 → cmd.exe,其余 → bash)让 Windows 上的模型只能用 cmd 语法,
|
|
4
|
+
// 而 LLM 的训练分布压倒性偏 POSIX/pwsh——`&&` 链、`$VAR`、单引号转义在 cmd 里语义不同甚至报错;
|
|
5
|
+
// 非交互 cmd 还有 `timeout /t` 直接失败这类暗坑。给模型一个显式 shell 参数,把选择权交出去。
|
|
6
|
+
//
|
|
7
|
+
// 默认 shell 的选择是**保守**的(刻意):
|
|
8
|
+
// - Windows 默认仍是 cmd.exe,与改动前逐字节一致 —— 不静默翻转所有 Windows 用户的默认解释器,
|
|
9
|
+
// 现有 prompt / skill / codegraph 的 .cmd 调用都假设 cmd,翻默认是产品级破坏性变更,须显式拍板。
|
|
10
|
+
// - 想用 bash/powershell:逐调用传 shell 参数,或设全局 MOCODE_SHELL=bash|powershell|cmd。
|
|
11
|
+
//
|
|
12
|
+
// Windows 的 Git Bash 探测(Git Bash 优先于 PATH 上的裸 bash,排除 WSL):
|
|
13
|
+
// - WSL 的 System32\bash.exe 也在 PATH 上,但它进的是 Linux 发行版:路径 /mnt/f/…、工具链、
|
|
14
|
+
// 甚至被安全策略拦截(wsl.exe 黑名单)都与 Windows 原生预期不符,绝不能当 bash 解析结果。
|
|
15
|
+
// - 只认 Git for Windows 风格的 bash.exe(常见安装路径 + PATH 上非 System32 的命中)。
|
|
16
|
+
// - 仅在**显式请求 shell=bash** 时才探测(懒求值 + 缓存):cmd 默认路径不付这个同步 IO 成本。
|
|
17
|
+
import { existsSync } from 'node:fs';
|
|
18
|
+
import { join, resolve } from 'node:path';
|
|
19
|
+
export const SHELL_KINDS = ['cmd', 'powershell', 'bash'];
|
|
20
|
+
const IS_WINDOWS = process.platform === 'win32';
|
|
21
|
+
/** Git for Windows 的常见固定安装位(不依赖 PATH 顺序)。 */
|
|
22
|
+
const GIT_BASH_CANDIDATES = [
|
|
23
|
+
'C:\\Program Files\\Git\\bin\\bash.exe',
|
|
24
|
+
'C:\\Program Files\\Git\\usr\\bin\\bash.exe',
|
|
25
|
+
'C:\\Program Files (x86)\\Git\\bin\\bash.exe',
|
|
26
|
+
`${process.env.LOCALAPPDATA ?? ''}\\Programs\\Git\\bin\\bash.exe`,
|
|
27
|
+
];
|
|
28
|
+
/** PATH 命中里要排除的目录片段:WSL bash 住在 System32,语义完全不同;
|
|
29
|
+
* WindowsApps 目录下是 wsl 存根的应用执行别名,同样不能当 bash 解析。 */
|
|
30
|
+
const WSL_BASH_HINTS = ['system32', 'sysnative', 'windowsapps'];
|
|
31
|
+
function findWindowsBash() {
|
|
32
|
+
for (const candidate of GIT_BASH_CANDIDATES) {
|
|
33
|
+
if (candidate && existsSync(candidate))
|
|
34
|
+
return candidate;
|
|
35
|
+
}
|
|
36
|
+
// PATH 探测:手工拆分而不是 where.exe —— 需要看到完整路径才能排除 System32(WSL)。
|
|
37
|
+
// 先看 PATH 上的 git.exe(Git for Windows 的 <root>\cmd\git.exe):由 git 根反推
|
|
38
|
+
// <root>\bin\bash.exe。自定义安装位(如 D:\Git)不在上面的候选表里,但 git 在 PATH 上
|
|
39
|
+
// 是极普遍形态 —— 从 git 推 bash 比枚举安装目录鲁棒得多。
|
|
40
|
+
const pathVar = process.env.PATH ?? process.env.Path ?? '';
|
|
41
|
+
for (const dir of pathVar.split(';')) {
|
|
42
|
+
if (!dir)
|
|
43
|
+
continue;
|
|
44
|
+
if (WSL_BASH_HINTS.some((hint) => dir.toLowerCase().includes(hint)))
|
|
45
|
+
continue;
|
|
46
|
+
if (!existsSync(join(dir, 'git.exe')))
|
|
47
|
+
continue;
|
|
48
|
+
const gitRoot = resolve(dir, '..');
|
|
49
|
+
for (const candidate of [join(gitRoot, 'bin', 'bash.exe'), join(gitRoot, 'usr', 'bin', 'bash.exe')]) {
|
|
50
|
+
if (existsSync(candidate))
|
|
51
|
+
return candidate;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// 兜底:PATH 上直接出现的 bash.exe(排除 System32/WindowsApps 后,多为与 git 同目录布局)。
|
|
55
|
+
for (const dir of pathVar.split(';')) {
|
|
56
|
+
if (!dir)
|
|
57
|
+
continue;
|
|
58
|
+
if (WSL_BASH_HINTS.some((hint) => dir.toLowerCase().includes(hint)))
|
|
59
|
+
continue;
|
|
60
|
+
const candidate = join(dir, 'bash.exe');
|
|
61
|
+
if (existsSync(candidate))
|
|
62
|
+
return candidate;
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
let cachedWindowsBash;
|
|
67
|
+
/** Windows 上的 Git Bash 可执行文件路径;探不到返回 null。结果缓存。 */
|
|
68
|
+
export function windowsGitBash() {
|
|
69
|
+
if (!IS_WINDOWS)
|
|
70
|
+
return null;
|
|
71
|
+
if (cachedWindowsBash === undefined)
|
|
72
|
+
cachedWindowsBash = findWindowsBash();
|
|
73
|
+
return cachedWindowsBash;
|
|
74
|
+
}
|
|
75
|
+
/** 测试/嵌入方显式重置探测缓存(例如单测里想模拟两种环境)。 */
|
|
76
|
+
export function resetShellDetectionCache() {
|
|
77
|
+
cachedWindowsBash = undefined;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* 平台默认 shell(**保守**,刻意不翻转既有行为):
|
|
81
|
+
* - Windows:cmd.exe(与改动前逐字节一致)。MOCODE_SHELL 显式指定可整体切换。
|
|
82
|
+
* —— 不自动探到 Git Bash 就当默认:那会给所有 Windows 用户做静默的破坏性解释器变更,
|
|
83
|
+
* 现有 prompt/skill/codegraph 的 .cmd 调用都假设 cmd,翻默认须产品级显式拍板。
|
|
84
|
+
* 想用 bash 的用户:逐调用传 shell=bash(此时才懒探测 Git Bash 路径),或设 MOCODE_SHELL=bash。
|
|
85
|
+
* - 其余平台:bash(与旧硬编码一致);MOCODE_SHELL 只在落到真实 shell(bash/powershell)时生效,
|
|
86
|
+
* cmd 在 Unix 无意义,忽略。
|
|
87
|
+
*/
|
|
88
|
+
export function defaultShellKind() {
|
|
89
|
+
const forced = parseShellKind(process.env.MOCODE_SHELL);
|
|
90
|
+
if (IS_WINDOWS)
|
|
91
|
+
return forced ?? 'cmd';
|
|
92
|
+
return forced === 'bash' || forced === 'powershell' ? forced : 'bash';
|
|
93
|
+
}
|
|
94
|
+
/** 解析模型/用户传入的 shell 值;大小写无关,别名 sh→bash、pwsh/powershell.exe→powershell。非法值返回 null。 */
|
|
95
|
+
export function parseShellKind(raw) {
|
|
96
|
+
if (typeof raw !== 'string')
|
|
97
|
+
return null;
|
|
98
|
+
const value = raw.trim().toLowerCase();
|
|
99
|
+
if (!value)
|
|
100
|
+
return null;
|
|
101
|
+
if (value === 'cmd' || value === 'cmd.exe')
|
|
102
|
+
return 'cmd';
|
|
103
|
+
if (value === 'bash' || value === 'sh')
|
|
104
|
+
return 'bash';
|
|
105
|
+
if (value === 'powershell' || value === 'pwsh' || value === 'powershell.exe' || value === 'pwsh.exe') {
|
|
106
|
+
return 'powershell';
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* 把 shell 种类 + 单行命令映射成 spawn 规格。
|
|
112
|
+
*
|
|
113
|
+
* bash on Windows:优先用探测到的 Git Bash 绝对路径;调用方显式 shell=bash 而探测失败时
|
|
114
|
+
* 仍按裸 'bash' spawn(交给 PATH 解析,包括 WSL——用户显式选择就尊重,失败会由
|
|
115
|
+
* spawn error 显式报出,不静默换 shell)。
|
|
116
|
+
*/
|
|
117
|
+
export function shellSpawnSpec(kind, command) {
|
|
118
|
+
switch (kind) {
|
|
119
|
+
case 'cmd':
|
|
120
|
+
return { file: 'cmd.exe', args: ['/d', '/s', '/c', command], windowsVerbatimArguments: true };
|
|
121
|
+
case 'powershell':
|
|
122
|
+
// -NoProfile:不执行用户 profile,启动快且行为可复现;-NonInteractive:禁交互提示;
|
|
123
|
+
// -Command:单行命令串(与 cmd /c 心智一致)。
|
|
124
|
+
return {
|
|
125
|
+
file: IS_WINDOWS ? 'powershell.exe' : 'pwsh',
|
|
126
|
+
args: ['-NoProfile', '-NonInteractive', '-Command', command],
|
|
127
|
+
windowsVerbatimArguments: false,
|
|
128
|
+
};
|
|
129
|
+
case 'bash': {
|
|
130
|
+
const bashPath = IS_WINDOWS ? (windowsGitBash() ?? 'bash') : 'bash';
|
|
131
|
+
return { file: bashPath, args: ['-c', command], windowsVerbatimArguments: false };
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/** 平台默认 shell 的可执行文件名(用于系统提示里的 PLATFORM_NOTE 措辞)。 */
|
|
136
|
+
export function defaultShellLabel() {
|
|
137
|
+
const kind = defaultShellKind();
|
|
138
|
+
if (kind === 'bash')
|
|
139
|
+
return IS_WINDOWS ? 'Git Bash (POSIX)' : 'bash';
|
|
140
|
+
if (kind === 'powershell')
|
|
141
|
+
return 'PowerShell';
|
|
142
|
+
return 'cmd.exe';
|
|
143
|
+
}
|
|
144
|
+
/** 供 run_command / dev_server 的 description 复用的 shell 参数说明(单一事实源,防漂移)。
|
|
145
|
+
* 默认值不在文案里烘焙静态答案:真实默认由 defaultShellKind()(运行时读 env)决定,
|
|
146
|
+
* shell.ts 模块求值时 config 链路未必已把 ~/.mocode/config 回填进 env,静态串会与
|
|
147
|
+
* 系统提示(MOCODE_SHELL 分支)自相矛盾。改为 getter,取用时才求值。
|
|
148
|
+
* 平台默认:Windows=cmd、其余=bash;MOCODE_SHELL 可翻转。 */
|
|
149
|
+
export const shellParamDescription = () => 'Shell to run the command in: cmd | powershell | bash (default: ' +
|
|
150
|
+
defaultShellLabel() +
|
|
151
|
+
'). Pick the shell whose syntax the command is written in; do not mix (e.g. no %VAR% in bash, no $VAR in cmd).';
|
|
152
|
+
/** 兼容旧引用:初始化即求值的快照(不追踪 MOCODE_SHELL 翻转;新代码用 shellParamDescription())。 */
|
|
153
|
+
export const SHELL_PARAM_DESCRIPTION = shellParamDescription();
|
|
@@ -20,7 +20,7 @@ run_command({ command: "codegraph node <symbol-or-path>" })
|
|
|
20
20
|
|
|
21
21
|
注意:
|
|
22
22
|
|
|
23
|
-
- Windows 上 \`codegraph\` 是 .cmd
|
|
23
|
+
- Windows 上 \`codegraph\` 是 .cmd:cmd 默认下直接传 \`codegraph ...\` 即可;若默认 shell 是 bash(MOCODE_SHELL=bash),Git Bash 同样能按裸名解析 .cmd(\`bash -c "codegraph --version"\` 实测可用),解析不到就显式 \`shell: "cmd"\`。
|
|
24
24
|
- 单次查询 ≤ 60s 超时,模型按需重试/换 query。
|
|
25
25
|
|
|
26
26
|
## 何时用
|
|
@@ -111,10 +111,9 @@ export const askHumanTool = {
|
|
|
111
111
|
name: 'ask_human',
|
|
112
112
|
description: [
|
|
113
113
|
'Ask the user a question and wait for their response.',
|
|
114
|
-
' CHOICES: pass 2-4 concrete options via `options`, each as { label, description? }
|
|
114
|
+
' CHOICES: pass 2-4 concrete options via `options`, each as { label, description? }; every non-empty option requires a non-empty `label`.',
|
|
115
115
|
' FREE-TEXT: pass `options: []` when the answer cannot be reduced to choices (e.g. "paste the error message").',
|
|
116
|
-
'
|
|
117
|
-
' DO NOT call when the task is clear and you can pick a sensible default.',
|
|
116
|
+
' DO NOT call when the task is clear and a sensible default exists.',
|
|
118
117
|
].join(' '),
|
|
119
118
|
parameters: {
|
|
120
119
|
type: 'object',
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DevServerManagerError, getDevServerStatus, readDevServerLogs, startDevServer, stopDevServer, } from '../../runtime/dev-server-manager.js';
|
|
2
|
+
import { shellParamDescription, parseShellKind } from '../../runtime/shell.js';
|
|
2
3
|
function result(status, code, payload) {
|
|
3
4
|
return { status, code, retryable: false, output: JSON.stringify(payload, null, 2) };
|
|
4
5
|
}
|
|
@@ -20,11 +21,14 @@ function failure(error) {
|
|
|
20
21
|
}
|
|
21
22
|
export const devServerTool = {
|
|
22
23
|
name: 'dev_server',
|
|
23
|
-
description: '
|
|
24
|
-
'
|
|
24
|
+
description: 'Run ANY long-running background process that must outlive this tool call: a dev server, an inference/model ' +
|
|
25
|
+
'service, a watcher, a log tail, a queue consumer. The process keeps running across tool calls, so you can ' +
|
|
26
|
+
'start it, poll it, inspect its output, and stop it later — foreground run_command blocks and is killed at ' +
|
|
27
|
+
'its timeout, so it cannot host such a process (do NOT work around that with `start /b`, `nohup`, `&` or ' +
|
|
28
|
+
'detached spawns: you lose the logs and the handle).\n' +
|
|
25
29
|
'Actions: start (needs command; optional readyUrl or readyPattern to wait until it actually serves), ' +
|
|
26
|
-
'status (all
|
|
27
|
-
'Always stop
|
|
30
|
+
'status (all processes, or one by id), logs (tail output; pass offset for incremental reads), stop (terminate the process tree).\n' +
|
|
31
|
+
'Always stop processes you started once you are done verifying.',
|
|
28
32
|
risk: 'dangerous',
|
|
29
33
|
parameters: {
|
|
30
34
|
type: 'object',
|
|
@@ -34,8 +38,11 @@ export const devServerTool = {
|
|
|
34
38
|
enum: ['start', 'status', 'logs', 'stop'],
|
|
35
39
|
description: 'Operation to perform',
|
|
36
40
|
},
|
|
37
|
-
id: { type: 'string', description: '
|
|
38
|
-
command: {
|
|
41
|
+
id: { type: 'string', description: 'Process id returned by start (required for logs/stop)' },
|
|
42
|
+
command: {
|
|
43
|
+
type: 'string',
|
|
44
|
+
description: 'Command to run for action=start, e.g. "npm run dev" or "python -m laya_play.serve --port 8765"',
|
|
45
|
+
},
|
|
39
46
|
cwd: { type: 'string', description: 'Working directory inside the workspace (default: workspace root)' },
|
|
40
47
|
readyUrl: {
|
|
41
48
|
type: 'string',
|
|
@@ -46,6 +53,7 @@ export const devServerTool = {
|
|
|
46
53
|
description: 'Wait until server output matches this case-insensitive regex, e.g. "ready in|listening on"',
|
|
47
54
|
},
|
|
48
55
|
timeoutMs: { type: 'integer', description: 'Readiness wait budget in ms (default 30000, max 180000)' },
|
|
56
|
+
shell: { type: 'string', enum: ['cmd', 'powershell', 'bash'], description: shellParamDescription() },
|
|
49
57
|
offset: { type: 'integer', description: 'Byte offset for action=logs (default: tail)' },
|
|
50
58
|
limit: { type: 'integer', description: 'Max bytes for action=logs (default 8000, max 64000)' },
|
|
51
59
|
},
|
|
@@ -59,12 +67,21 @@ export const devServerTool = {
|
|
|
59
67
|
const command = optionalString(args, 'command');
|
|
60
68
|
if (!command)
|
|
61
69
|
return invalid('action=start requires a non-empty command.');
|
|
70
|
+
// shell 非法值显式报错,不静默回落:模型以为在 powershell 里起服务却落到 cmd,
|
|
71
|
+
// 语法差异导致的启动失败极难归因。
|
|
72
|
+
let shell = null;
|
|
73
|
+
if (args.shell !== undefined) {
|
|
74
|
+
shell = parseShellKind(args.shell);
|
|
75
|
+
if (!shell)
|
|
76
|
+
return invalid(`Invalid shell "${String(args.shell)}". Use cmd, powershell, or bash.`);
|
|
77
|
+
}
|
|
62
78
|
const started = await startDevServer({
|
|
63
79
|
command,
|
|
64
80
|
cwd: optionalString(args, 'cwd'),
|
|
65
81
|
readyUrl: optionalString(args, 'readyUrl'),
|
|
66
82
|
readyPattern: optionalString(args, 'readyPattern'),
|
|
67
83
|
...(args.timeoutMs === undefined ? {} : { timeoutMs: Number(args.timeoutMs) }),
|
|
84
|
+
...(shell ? { shell } : {}),
|
|
68
85
|
...(ctx?.signal ? { signal: ctx.signal } : {}),
|
|
69
86
|
});
|
|
70
87
|
return result('success', 'OK', started);
|
|
@@ -13,23 +13,15 @@ function conflict(path, details) {
|
|
|
13
13
|
}
|
|
14
14
|
export const editFileTool = {
|
|
15
15
|
name: 'edit_file',
|
|
16
|
-
description: `Replace content in a file transactionally.
|
|
16
|
+
description: `Replace content in a file transactionally. Two modes:
|
|
17
17
|
|
|
18
|
-
**String replacement
|
|
18
|
+
**String replacement (default):** old_string must occur EXACTLY once, copied verbatim from a fresh read_file output — never reconstruct from memory, summaries, or grep output (whitespace/indentation/line-ending details are lost there).
|
|
19
19
|
|
|
20
|
-
**Line-range
|
|
20
|
+
**Line-range:** line_start/line_end (1-based, inclusive) instead of old_string — for large blocks, repeated patterns, or hard-to-reproduce text.
|
|
21
21
|
|
|
22
|
-
expected_hash
|
|
22
|
+
expected_hash (sha256 from read_file artifact header) is required and must match the current file; changed-since-read edits are rejected — re-read and retry with the new hash.
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
- String replacement: small, unique text fragments (function signatures, config keys, error messages)
|
|
26
|
-
- Line-range: large blocks, repeated patterns, or when whitespace precision is critical
|
|
27
|
-
|
|
28
|
-
**Anti-patterns (will fail):**
|
|
29
|
-
- old_string reconstructed from memory or a summary
|
|
30
|
-
- old_string copied from a previous tool call that may be stale
|
|
31
|
-
- old_string that appears multiple times (add more context to make it unique)
|
|
32
|
-
- expected_hash from a different file or an old read_file call`,
|
|
24
|
+
Anti-patterns (will fail): old_string from memory/summary/stale call; multiple occurrences (add context to disambiguate); hash from another file or old read.`,
|
|
33
25
|
risk: 'confirm',
|
|
34
26
|
parameters: {
|
|
35
27
|
type: 'object',
|
|
@@ -4,8 +4,8 @@ import { getSandboxRoot, isInsideRoot } from '../../sandbox/index.js';
|
|
|
4
4
|
// ---------- glob ----------
|
|
5
5
|
export const globTool = {
|
|
6
6
|
name: 'glob',
|
|
7
|
-
description: 'Find files matching a glob pattern (e.g. **/*.ts). Auto-excludes node_modules/.git.' +
|
|
8
|
-
'
|
|
7
|
+
description: 'Find files matching a glob pattern (e.g. **/*.ts). Auto-excludes node_modules/.git. ' +
|
|
8
|
+
'For architecture or call chains, prefer the codegraph skill.',
|
|
9
9
|
parameters: {
|
|
10
10
|
type: 'object',
|
|
11
11
|
properties: {
|