mocode-ai 1.3.9 → 1.4.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.
- package/README.md +78 -70
- package/README.zh-CN.md +81 -73
- package/bin/mocode-agent-host.js +1 -1
- package/dist/agent/core.js +226 -293
- package/dist/agent/index.js +14 -10
- package/dist/agent/mode.js +3 -3
- package/dist/agent/runtime-context.js +45 -0
- package/dist/agent/spawn.js +101 -86
- package/dist/agent/tool-helpers.js +134 -0
- package/dist/agent/trace-state.js +118 -0
- package/dist/agents/coordinator.js +6 -1
- package/dist/changeset/index.js +9 -8
- package/dist/config/index.js +109 -77
- package/dist/config/presets.js +3 -1
- package/dist/config/profiles.js +125 -0
- package/dist/context/age-aware.js +2 -4
- package/dist/context/artifacts.js +6 -13
- package/dist/context/budget.js +6 -8
- package/dist/context/classifier.js +1 -2
- package/dist/context/encoders/code.js +2 -6
- package/dist/context/encoders/command.js +1 -3
- package/dist/context/encoders/search.js +1 -3
- package/dist/context/index.js +1 -1
- package/dist/context/lifecycle.js +5 -3
- package/dist/context/pipeline.js +1 -1
- package/dist/context/relevance.js +2 -4
- package/dist/context/token-calibration.js +13 -18
- package/dist/host/stdio.js +73 -12
- package/dist/i18n/index.js +96 -44
- package/dist/index.js +1 -1
- package/dist/llm/index.js +39 -23
- package/dist/llm/provider.js +23 -0
- package/dist/llm/providers/anthropic.js +12 -18
- package/dist/llm/tool-schema.js +48 -0
- package/dist/mcp/client.js +70 -25
- package/dist/mcp/config.js +1 -3
- package/dist/mcp/index.js +3 -1
- package/dist/memory/reflect.js +3 -6
- package/dist/memory/store.js +10 -12
- package/dist/permissions/index.js +66 -22
- package/dist/pet/bridge.js +2 -2
- package/dist/repl/commands/appearance.js +72 -0
- package/dist/repl/commands/compact.js +114 -0
- package/dist/repl/commands/context.js +17 -0
- package/dist/repl/commands/image.js +71 -0
- package/dist/repl/commands/memory.js +51 -0
- package/dist/repl/commands/mode.js +47 -0
- package/dist/repl/commands/model.js +435 -0
- package/dist/repl/commands/pet.js +70 -0
- package/dist/repl/commands/registry.js +46 -0
- package/dist/repl/commands/session.js +102 -0
- package/dist/repl/commands/skill.js +95 -0
- package/dist/repl/commands/system.js +202 -0
- package/dist/repl/commands/tool-group.js +233 -0
- package/dist/repl/commands/types.js +4 -0
- package/dist/repl/commands.js +324 -0
- package/dist/repl/index.js +5 -2577
- package/dist/repl/message-format.js +151 -0
- package/dist/repl/running-input.js +147 -0
- package/dist/repl/runtime.js +816 -0
- package/dist/repl/status-bar.js +161 -0
- package/dist/rollback/index.js +44 -14
- package/dist/runtime/browser-manager.js +1 -1
- package/dist/runtime/dev-server-manager.js +1 -1
- package/dist/runtime/input-injector.js +313 -0
- package/dist/runtime/screen-capture.js +89 -0
- package/dist/runtime/screen-pipeline.js +223 -0
- package/dist/sandbox/index.js +1 -1
- package/dist/sandbox/policy.js +8 -2
- package/dist/session/compact.js +22 -34
- package/dist/session/index.js +2 -2
- package/dist/session/notes.js +39 -5
- package/dist/session/persist.js +14 -8
- package/dist/session/scheduler.js +3 -4
- package/dist/session/trace-metrics.js +6 -3
- package/dist/skills/activation.js +10 -5
- package/dist/skills/discover.js +5 -17
- package/dist/skills/index.js +1 -1
- package/dist/skills/runner.js +28 -44
- package/dist/skills/toolmap.js +5 -2
- package/dist/tools/builtins/browser.js +17 -3
- package/dist/tools/builtins/computer.js +322 -0
- package/dist/tools/builtins/dev-server.js +1 -3
- package/dist/tools/builtins/edit-file.js +21 -7
- package/dist/tools/builtins/grep.js +1 -1
- package/dist/tools/builtins/index.js +28 -25
- package/dist/tools/builtins/memory-forget.js +1 -1
- package/dist/tools/builtins/memory-graph.js +4 -7
- package/dist/tools/builtins/memory-list.js +2 -6
- package/dist/tools/builtins/memory-save.js +8 -2
- package/dist/tools/builtins/memory-search.js +4 -4
- package/dist/tools/builtins/note-append.js +14 -3
- package/dist/tools/builtins/plan-update.js +40 -9
- package/dist/tools/builtins/run-command.js +8 -8
- package/dist/tools/builtins/screenshot.js +11 -87
- package/dist/tools/builtins/task.js +29 -40
- package/dist/tools/builtins/use-skill.js +1 -1
- package/dist/tools/builtins/view-image.js +5 -3
- package/dist/tools/builtins/web-fetch.js +1 -3
- package/dist/tools/builtins/web-search.js +3 -7
- package/dist/tools/builtins/write-file.js +4 -2
- package/dist/tools/constants.js +50 -37
- package/dist/tools/policy.js +221 -0
- package/dist/tools/registry.js +40 -19
- package/dist/tools/resource-lock.js +4 -2
- package/dist/tools/router.js +143 -0
- package/dist/tools/validation.js +6 -5
- package/dist/ui/batch.js +63 -23
- package/dist/ui/clipboard.js +17 -5
- package/dist/ui/content.js +166 -13
- package/dist/ui/diff.js +61 -19
- package/dist/ui/fuzzy.js +35 -2
- package/dist/ui/intervention.js +2 -4
- package/dist/ui/layout-internal/content-write.js +587 -0
- package/dist/ui/layout-internal/core.js +590 -0
- package/dist/ui/layout-internal/input-paint.js +365 -0
- package/dist/ui/layout-internal/screen.js +128 -0
- package/dist/ui/layout-internal/scroll.js +152 -0
- package/dist/ui/layout-internal/selection.js +229 -0
- package/dist/ui/layout-internal/state.js +82 -0
- package/dist/ui/layout-internal/statusbar.js +351 -0
- package/dist/ui/layout-types.js +1 -0
- package/dist/ui/layout.js +3 -2288
- package/dist/ui/markdown.js +10 -2
- package/dist/ui/prompt-internal/editor.js +945 -0
- package/dist/ui/prompt-internal/paste.js +52 -0
- package/dist/ui/prompt-internal/pickers.js +519 -0
- package/dist/ui/prompt-internal/types.js +1 -0
- package/dist/ui/prompt.js +4 -1506
- package/dist/ui/render.js +87 -2
- package/dist/verification/affected.js +11 -5
- package/dist/verification/discovery.js +1 -3
- package/dist/verification/profile.js +11 -3
- package/package.json +11 -2
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 工具能力分组的叶子模块。生产主路径使用 COMMON_TOOL_NAMES + TOOL_ROUTE_GROUPS 进行
|
|
3
|
+
* 每 turn LLM 自动路由;文件尾的静态 profile 仅保留给未迁移嵌入调用,不再有用户命令。
|
|
4
|
+
* 本模块零业务依赖,供 config/llm/policy 安全共享。
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* 工具簇 → 工具名。新增工具时归到对应簇;一个工具只属一个簇。
|
|
8
|
+
* view_image 放 core-read,保证所有模式都能读取已有本地图片;工具产生的即时视觉结果通过
|
|
9
|
+
* modelAttachments 直接回灌,不依赖 view_image。screenshot 留 frontend(抓整个桌面,隐私敏感,
|
|
10
|
+
* 主要服务前端联调)。
|
|
11
|
+
*/
|
|
12
|
+
export const TOOL_GROUPS = {
|
|
13
|
+
'core-read': ['read_file', 'view_image', 'glob', 'grep'],
|
|
14
|
+
'core-write': ['write_file', 'edit_file', 'run_command'],
|
|
15
|
+
'agent-meta': ['plan_update', 'note_append', 'ask_human', 'use_skill', 'run_skill'],
|
|
16
|
+
web: ['web_search', 'web_fetch'],
|
|
17
|
+
frontend: ['browser', 'dev_server', 'screenshot'],
|
|
18
|
+
computer: ['computer'],
|
|
19
|
+
memory: ['memory_save', 'memory_search', 'memory_list', 'memory_update', 'memory_forget', 'memory_graph'],
|
|
20
|
+
subagent: ['sub-agent'],
|
|
21
|
+
};
|
|
22
|
+
// ── LLM 自动工具路由 ──────────────────────────────────────────────────────
|
|
23
|
+
/** 主 Agent 每一步都可见的低风险、高复用工具。 */
|
|
24
|
+
export const COMMON_TOOL_NAMES = [
|
|
25
|
+
'read_file',
|
|
26
|
+
'view_image',
|
|
27
|
+
'glob',
|
|
28
|
+
'grep',
|
|
29
|
+
'web_search',
|
|
30
|
+
'web_fetch',
|
|
31
|
+
'plan_update',
|
|
32
|
+
'note_append',
|
|
33
|
+
'ask_human',
|
|
34
|
+
'use_skill',
|
|
35
|
+
];
|
|
36
|
+
/** 主模型用于在执行中单向扩容工具面的虚拟控制工具;不进入 registry。 */
|
|
37
|
+
export const ADD_TOOL_GROUPS_TOOL_NAME = 'add_tool_groups';
|
|
38
|
+
/**
|
|
39
|
+
* 可组合工具簇目录。路由器选择的是簇而不是单个工具;一个 turn 内只允许追加、不允许缩减。
|
|
40
|
+
* doing/debug/computer 场景刻意拆开文件写入、命令执行、浏览器调试和桌面控制,避免为完成
|
|
41
|
+
* 一个窄任务直接暴露 full 工具集。
|
|
42
|
+
*/
|
|
43
|
+
export const TOOL_ROUTE_GROUPS = {
|
|
44
|
+
'workspace-write': {
|
|
45
|
+
tools: ['write_file', 'edit_file'],
|
|
46
|
+
description: 'Create or edit workspace files for implementation, fixes, refactors, or generated code.',
|
|
47
|
+
},
|
|
48
|
+
'shell-debug': {
|
|
49
|
+
tools: ['run_command'],
|
|
50
|
+
description: 'Run tests, builds, linters, Git, package managers, logs, diagnostics, and foreground commands.',
|
|
51
|
+
},
|
|
52
|
+
'browser-debug': {
|
|
53
|
+
tools: ['browser', 'dev_server'],
|
|
54
|
+
description: 'Start local development servers and debug web UIs through DOM, console, network, and page sessions.',
|
|
55
|
+
gateEnv: 'MOCODE_FRONTEND_TOOLS_ENABLED',
|
|
56
|
+
},
|
|
57
|
+
'desktop-observe': {
|
|
58
|
+
tools: ['screenshot'],
|
|
59
|
+
description: 'Capture the whole desktop or system dialogs without controlling mouse or keyboard.',
|
|
60
|
+
gateEnv: 'MOCODE_FRONTEND_TOOLS_ENABLED',
|
|
61
|
+
},
|
|
62
|
+
'computer-control': {
|
|
63
|
+
tools: ['computer'],
|
|
64
|
+
description: 'Control real desktop applications with mouse, keyboard, scrolling, and visual feedback.',
|
|
65
|
+
gateEnv: 'MOCODE_COMPUTER_USE_ENABLED',
|
|
66
|
+
},
|
|
67
|
+
'memory-read': {
|
|
68
|
+
tools: ['memory_search', 'memory_list'],
|
|
69
|
+
description: 'Recall cross-session project facts, decisions, conventions, and prior context.',
|
|
70
|
+
gateEnv: 'MEMORY_ENABLED',
|
|
71
|
+
},
|
|
72
|
+
'memory-write': {
|
|
73
|
+
tools: ['memory_save', 'memory_update', 'memory_forget', 'memory_graph'],
|
|
74
|
+
description: 'Persist, revise, forget, or link cross-session knowledge when the user explicitly requests it.',
|
|
75
|
+
gateEnv: 'MEMORY_ENABLED',
|
|
76
|
+
},
|
|
77
|
+
orchestration: {
|
|
78
|
+
tools: ['sub-agent', 'run_skill'],
|
|
79
|
+
description: 'Delegate genuinely independent work or execute a packaged fork skill in an isolated worker.',
|
|
80
|
+
gateEnv: 'MOCODE_SUBAGENT_ENABLED',
|
|
81
|
+
},
|
|
82
|
+
mcp: {
|
|
83
|
+
tools: [],
|
|
84
|
+
description: 'Use connected MCP extension tools when their server capabilities directly match the task.',
|
|
85
|
+
gateEnv: 'MOCODE_MCP_ENABLED',
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
export const TOOL_ROUTE_GROUP_NAMES = Object.keys(TOOL_ROUTE_GROUPS);
|
|
89
|
+
export function isToolRouteGroupName(value) {
|
|
90
|
+
return typeof value === 'string' && TOOL_ROUTE_GROUP_NAMES.includes(value);
|
|
91
|
+
}
|
|
92
|
+
/** 返回簇内实际工具名;MCP 工具由运行时注册表动态发现。 */
|
|
93
|
+
export function getToolRouteGroupNames(group, registeredNames = []) {
|
|
94
|
+
return group === 'mcp' ? registeredNames.filter((name) => name.startsWith('mcp__')) : TOOL_ROUTE_GROUPS[group].tools;
|
|
95
|
+
}
|
|
96
|
+
/** 模式 → 包含的工具簇。保留旧 profile 数据仅供迁移期兼容;主 Agent 已改用 ToolPolicy。 */
|
|
97
|
+
export const PROFILE_GROUPS = {
|
|
98
|
+
// 默认:写码 + 联网检索,无浏览器自动化/桌面/记忆,兼顾日常效率与 token 成本。
|
|
99
|
+
coding: ['core-read', 'core-write', 'agent-meta', 'web'],
|
|
100
|
+
// 前端联调:coding 能力 + 结构化 browser/dev_server/独立 screenshot。
|
|
101
|
+
frontend: ['core-read', 'core-write', 'agent-meta', 'web', 'frontend'],
|
|
102
|
+
// 通用桌面 GUI 操控:coding 能力 + 自带视觉闭环的 computer;不等价包含 frontend。
|
|
103
|
+
'computer-use': ['core-read', 'core-write', 'agent-meta', 'web', 'computer'],
|
|
104
|
+
// 项目源码只读调研:不直接暴露 core-write;仍可写 session note/memory,skill 有独立执行语义。
|
|
105
|
+
research: ['core-read', 'agent-meta', 'web', 'memory'],
|
|
106
|
+
// 全量:所有簇。
|
|
107
|
+
full: ['core-read', 'core-write', 'agent-meta', 'web', 'frontend', 'computer', 'memory', 'subagent'],
|
|
108
|
+
};
|
|
109
|
+
export const PROFILE_NAMES = Object.keys(PROFILE_GROUPS);
|
|
110
|
+
export function isProfileName(v) {
|
|
111
|
+
return typeof v === 'string' && PROFILE_NAMES.includes(v);
|
|
112
|
+
}
|
|
113
|
+
/** 模式包含的工具名集合。 */
|
|
114
|
+
export function getProfileToolNames(profile) {
|
|
115
|
+
const out = new Set();
|
|
116
|
+
for (const g of PROFILE_GROUPS[profile]) {
|
|
117
|
+
for (const name of TOOL_GROUPS[g])
|
|
118
|
+
out.add(name);
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
/** 某模式是否含某簇(派生查询的单一事实源)。 */
|
|
123
|
+
export function profileHasGroup(profile, group) {
|
|
124
|
+
return PROFILE_GROUPS[profile].includes(group);
|
|
125
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Pressure-only tool-result encoding coordinator.
|
|
2
2
|
// Normal pushes are raw apart from the per-result hard safety cap.
|
|
3
3
|
import { optimizeToolResult } from './pipeline.js';
|
|
4
|
-
import { canonicalizePath, extractPath, isToolResultSuccess, toText
|
|
4
|
+
import { canonicalizePath, extractPath, isToolResultSuccess, toText } from './utils.js';
|
|
5
5
|
/** Rebuilds records from current history for each pressure pass. */
|
|
6
6
|
export class AgeAwareEncodingState {
|
|
7
7
|
pushOrdinal = 0;
|
|
@@ -79,9 +79,7 @@ export class AgeAwareEncodingState {
|
|
|
79
79
|
continue;
|
|
80
80
|
const content = toText(toolMessage.content);
|
|
81
81
|
const succeeded = isToolResultSuccess(content);
|
|
82
|
-
const path = call.name === 'read_file'
|
|
83
|
-
? canonicalizePath(extractPath(call.argsRaw))
|
|
84
|
-
: null;
|
|
82
|
+
const path = call.name === 'read_file' ? canonicalizePath(extractPath(call.argsRaw)) : null;
|
|
85
83
|
const isFirstRead = path ? !this.seenReadPaths.has(path) : undefined;
|
|
86
84
|
this.records.set(id, {
|
|
87
85
|
toolCallId: id,
|
|
@@ -13,15 +13,6 @@ function stateFor(state) {
|
|
|
13
13
|
}
|
|
14
14
|
return current;
|
|
15
15
|
}
|
|
16
|
-
function parseArgs(raw) {
|
|
17
|
-
try {
|
|
18
|
-
const value = JSON.parse(raw || '{}');
|
|
19
|
-
return value && typeof value === 'object' ? value : null;
|
|
20
|
-
}
|
|
21
|
-
catch {
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
16
|
function callArgs(history, idx) {
|
|
26
17
|
const id = history[idx].tool_call_id;
|
|
27
18
|
if (!id)
|
|
@@ -30,7 +21,8 @@ function callArgs(history, idx) {
|
|
|
30
21
|
const message = history[cursor];
|
|
31
22
|
if (message.role !== 'assistant')
|
|
32
23
|
continue;
|
|
33
|
-
const calls = message
|
|
24
|
+
const calls = message
|
|
25
|
+
.tool_calls;
|
|
34
26
|
const hit = calls?.find((call) => call.id === id);
|
|
35
27
|
if (hit?.function?.name)
|
|
36
28
|
return { tool: hit.function.name, argsRaw: hit.function.arguments ?? '{}' };
|
|
@@ -54,7 +46,7 @@ function pathsFromOutput(tool, output) {
|
|
|
54
46
|
paths.add(normalized);
|
|
55
47
|
};
|
|
56
48
|
if (tool === 'grep' || tool === 'run_command') {
|
|
57
|
-
const expression = /^(.+?\.[A-Za-z0-9]+):(?:\d+|\s*\d+\s*(?:处匹配|matches?))/
|
|
49
|
+
const expression = /^(.+?\.[A-Za-z0-9]+):(?:\d+|\s*\d+\s*(?:处匹配|matches?))/gim;
|
|
58
50
|
let match;
|
|
59
51
|
while ((match = expression.exec(output)))
|
|
60
52
|
add(match[1]);
|
|
@@ -99,7 +91,6 @@ export function recordArtifact(state, history, idx, output, succeeded) {
|
|
|
99
91
|
const type = sourceType(call.tool);
|
|
100
92
|
if (!type)
|
|
101
93
|
return;
|
|
102
|
-
const args = parseArgs(call.argsRaw);
|
|
103
94
|
const id = history[idx].tool_call_id ?? `${idx}`;
|
|
104
95
|
const directPath = canonicalizePath(extractPath(call.argsRaw));
|
|
105
96
|
let dependencies = directPath
|
|
@@ -197,7 +188,9 @@ export function rehydrateArtifacts(state, history) {
|
|
|
197
188
|
retained.tokenCount = estimateTokens(content);
|
|
198
189
|
retained.freshness = content.startsWith(STALE_PREFIX)
|
|
199
190
|
? 'stale'
|
|
200
|
-
: content.startsWith('⌦[')
|
|
191
|
+
: content.startsWith('⌦[')
|
|
192
|
+
? 'stubbed'
|
|
193
|
+
: retained.freshness;
|
|
201
194
|
artifactState.artifacts.set(id, retained);
|
|
202
195
|
}
|
|
203
196
|
else {
|
package/dist/context/budget.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// This module only estimates and reports. session/scheduler.ts owns the sole
|
|
4
4
|
// automatic rewrite sequence and starts all pressure cleanup plus compact_history
|
|
5
5
|
// when corrected or raw request occupancy reaches 80%.
|
|
6
|
-
import { chatTools, estimateMessagesTokens, estimateToolSchemaTokens, messageTokens
|
|
6
|
+
import { chatTools, estimateMessagesTokens, estimateToolSchemaTokens, messageTokens } from '../llm/index.js';
|
|
7
7
|
import { toText } from './utils.js';
|
|
8
8
|
/** 五区分账(占比对齐 CONTEXT_WINDOW)。顺序固定,便于遍历。 */
|
|
9
9
|
export const BUDGET_LAYERS = [
|
|
@@ -17,10 +17,10 @@ export const BUDGET_LAYERS = [
|
|
|
17
17
|
export const DEFAULT_BUDGET_POLICY = {
|
|
18
18
|
ratios: {
|
|
19
19
|
system: 0.15,
|
|
20
|
-
history: 0.
|
|
20
|
+
history: 0.2,
|
|
21
21
|
toolRecent: 0.25,
|
|
22
22
|
toolOld: 0.25,
|
|
23
|
-
summary: 0.
|
|
23
|
+
summary: 0.1,
|
|
24
24
|
reserve: 0.05,
|
|
25
25
|
},
|
|
26
26
|
hotTurnWindow: 4,
|
|
@@ -29,8 +29,8 @@ export const DEFAULT_BUDGET_POLICY = {
|
|
|
29
29
|
compactKeepRatio: 0.15,
|
|
30
30
|
compactForceKeepRatio: 0.05,
|
|
31
31
|
compactKeepMaxTokens: 48000,
|
|
32
|
-
pressureTriggerRatio: 0.
|
|
33
|
-
schedulerTargetRatio: 0.
|
|
32
|
+
pressureTriggerRatio: 0.8,
|
|
33
|
+
schedulerTargetRatio: 0.8,
|
|
34
34
|
estimateSafetyFactor: 1.05,
|
|
35
35
|
compactHeadroomTokens: 1500,
|
|
36
36
|
};
|
|
@@ -72,9 +72,7 @@ export function evaluateBudget(history, window, step = 0, correction = 1, active
|
|
|
72
72
|
// 裸总量(不乘 correction):硬闸用它判断,防止 correction 折扣否决真实溢出。
|
|
73
73
|
let rawTotal = 0;
|
|
74
74
|
const sysMsg = history[0];
|
|
75
|
-
const safeEphemeral = Number.isFinite(ephemeralTokens)
|
|
76
|
-
? Math.max(0, Math.round(ephemeralTokens))
|
|
77
|
-
: 0;
|
|
75
|
+
const safeEphemeral = Number.isFinite(ephemeralTokens) ? Math.max(0, Math.round(ephemeralTokens)) : 0;
|
|
78
76
|
const systemCosts = {
|
|
79
77
|
prompt: sysMsg ? msgTokens(sysMsg) : 0,
|
|
80
78
|
toolSchemas: estimateToolSchemaTokens(activeTools),
|
|
@@ -49,8 +49,7 @@ function classifyByShape(output) {
|
|
|
49
49
|
return 'log';
|
|
50
50
|
// 路径列表:多行都是含分隔符的相对路径(glob 风格)
|
|
51
51
|
const lines = output.split('\n').filter((l) => l.trim().length > 0);
|
|
52
|
-
if (lines.length >= 3 &&
|
|
53
|
-
lines.every((l) => /^[\w.\-\\/ ]+$/.test(l.trim()) && /[\\/]/.test(l))) {
|
|
52
|
+
if (lines.length >= 3 && lines.every((l) => /^[\w.\-\\/ ]+$/.test(l.trim()) && /[\\/]/.test(l))) {
|
|
54
53
|
return 'tree';
|
|
55
54
|
}
|
|
56
55
|
// JSON 结构化(web_fetch 的 JSON 响应等)→ doc 渲染
|
|
@@ -72,9 +72,7 @@ function collapseImportBlocks(text) {
|
|
|
72
72
|
i = j;
|
|
73
73
|
}
|
|
74
74
|
const result = out.join('\n');
|
|
75
|
-
return result.length < text.length
|
|
76
|
-
? { text: result, collapsed }
|
|
77
|
-
: { text, collapsed: 0 };
|
|
75
|
+
return result.length < text.length ? { text: result, collapsed } : { text, collapsed: 0 };
|
|
78
76
|
}
|
|
79
77
|
function braceDelta(line, state) {
|
|
80
78
|
let delta = 0;
|
|
@@ -164,9 +162,7 @@ function collapseFunctionBodies(text) {
|
|
|
164
162
|
}
|
|
165
163
|
}
|
|
166
164
|
const result = out.join('\n');
|
|
167
|
-
return result.length < text.length
|
|
168
|
-
? { text: result, collapsed }
|
|
169
|
-
: { text, collapsed: 0 };
|
|
165
|
+
return result.length < text.length ? { text: result, collapsed } : { text, collapsed: 0 };
|
|
170
166
|
}
|
|
171
167
|
export const codeEncoder = {
|
|
172
168
|
kind: 'code',
|
|
@@ -25,9 +25,7 @@ function parseDiagnostic(line) {
|
|
|
25
25
|
}
|
|
26
26
|
function splitStatus(text) {
|
|
27
27
|
const lines = text.split('\n');
|
|
28
|
-
const status = /^\[(?:退出码 [^\]]+|已中断|超时,已终止)\]$/.test(lines[0] ?? '')
|
|
29
|
-
? lines.shift()
|
|
30
|
-
: null;
|
|
28
|
+
const status = /^\[(?:退出码 [^\]]+|已中断|超时,已终止)\]$/.test(lines[0] ?? '') ? lines.shift() : null;
|
|
31
29
|
return { status, lines };
|
|
32
30
|
}
|
|
33
31
|
function formatDiagnostics(lines) {
|
|
@@ -119,9 +119,7 @@ export const searchEncoder = {
|
|
|
119
119
|
},
|
|
120
120
|
};
|
|
121
121
|
}
|
|
122
|
-
const text = isAgedCold(input)
|
|
123
|
-
? collapseLegacyBodies(legacy.text)
|
|
124
|
-
: legacy.text;
|
|
122
|
+
const text = isAgedCold(input) ? collapseLegacyBodies(legacy.text) : legacy.text;
|
|
125
123
|
return {
|
|
126
124
|
text,
|
|
127
125
|
meta: {
|
package/dist/context/index.js
CHANGED
|
@@ -4,6 +4,6 @@
|
|
|
4
4
|
export { optimizeToolResult } from './pipeline.js';
|
|
5
5
|
export { classify, knownToolKinds } from './classifier.js';
|
|
6
6
|
export { recordArtifact, invalidateArtifacts, rehydrateArtifacts, refreshArtifactFreshness, pruneStaleArtifacts, collectArtifactRefs, formatArtifactTokenSources, knownEditTargets, } from './artifacts.js';
|
|
7
|
-
export { registerEncoder, registerAll, getEncoder, registeredKinds
|
|
7
|
+
export { registerEncoder, registerAll, getEncoder, registeredKinds } from './registry.js';
|
|
8
8
|
// ── Context Budget Scheduler ───────────────────────────────────────────────
|
|
9
9
|
export { evaluateBudget, scheduleActions, formatReport, quickEstimate, userTurnBoundary, BUDGET_LAYERS, DEFAULT_BUDGET_POLICY, BUDGET_RATIO, HOT_TURN_WINDOW, } from './budget.js';
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Tool-call count is not context pressure, so this module never ages, digests,
|
|
4
4
|
// stubs, or otherwise rewrites history. It only records producer/consumer
|
|
5
5
|
// relationships for diagnostics and future pressure-aware decisions.
|
|
6
|
-
import { canonicalizePath, extractPath, isToolResultSuccess, toText, toolNameOf
|
|
6
|
+
import { canonicalizePath, extractPath, isToolResultSuccess, toText, toolNameOf } from './utils.js';
|
|
7
7
|
const OBSERVER_TOOLS = new Set(['grep', 'glob', 'web_search', 'web_fetch']);
|
|
8
8
|
const CONSUMER_TOOLS = new Set(['read_file', 'edit_file', 'write_file']);
|
|
9
9
|
const DIGEST_PREFIX = '⌦[摘要:';
|
|
@@ -17,7 +17,7 @@ function extractProducerPaths(toolName, content) {
|
|
|
17
17
|
try {
|
|
18
18
|
if (toolName === 'grep') {
|
|
19
19
|
const rawLine = /^(.+?\.[A-Za-z0-9]+):\d+:/gm;
|
|
20
|
-
const summary = /^(.+?\.[A-Za-z0-9]+):\s*\d+\s*(?:处匹配|matches?)[,,]/
|
|
20
|
+
const summary = /^(.+?\.[A-Za-z0-9]+):\s*\d+\s*(?:处匹配|matches?)[,,]/gim;
|
|
21
21
|
let match;
|
|
22
22
|
while ((match = rawLine.exec(content)))
|
|
23
23
|
add(match[1]);
|
|
@@ -27,7 +27,9 @@ function extractProducerPaths(toolName, content) {
|
|
|
27
27
|
else if (toolName === 'glob') {
|
|
28
28
|
for (const line of content.split(/\r?\n/)) {
|
|
29
29
|
const value = line.trim();
|
|
30
|
-
if (value &&
|
|
30
|
+
if (value &&
|
|
31
|
+
!value.includes(' ') &&
|
|
32
|
+
(/\.[A-Za-z0-9]+$/.test(value) || value.includes('/') || value.includes('\\')))
|
|
31
33
|
add(value);
|
|
32
34
|
}
|
|
33
35
|
}
|
package/dist/context/pipeline.js
CHANGED
|
@@ -13,7 +13,7 @@ import { builtinEncoders } from './encoders/index.js';
|
|
|
13
13
|
import { passthroughEncoder } from './encoders/passthrough.js';
|
|
14
14
|
import { capToolResultForHistory } from '../session/compact.js';
|
|
15
15
|
import { config } from '../config/index.js';
|
|
16
|
-
import { MAX_HISTORY_RESULT, MAX_SKILL_RESULT, MAX_MEMORY_RESULT
|
|
16
|
+
import { MAX_HISTORY_RESULT, MAX_SKILL_RESULT, MAX_MEMORY_RESULT } from '../tools/constants.js';
|
|
17
17
|
let booted = false;
|
|
18
18
|
/** 懒注册内置 encoder(首次调用 optimizeToolResult 时触发,避免模块加载期循环 import)。 */
|
|
19
19
|
function boot() {
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
// Relevance Pruner: statically removes tool results that a newer observation supersedes.
|
|
2
2
|
// It never deletes messages or changes tool_call_id pairing; only tool content is stubbed.
|
|
3
|
-
import { canonicalizePath, extractPath, isToolResultSuccess, toText
|
|
3
|
+
import { canonicalizePath, extractPath, isToolResultSuccess, toText } from './utils.js';
|
|
4
4
|
/** Shared prefix lets /context count read and observation supersession together. */
|
|
5
5
|
const STUB_PREFIX = '⌦[已过时:';
|
|
6
6
|
const READ_STUB_REASON = '同 path 已有新 read / 已被 mutation 覆写';
|
|
7
7
|
function parseArgs(raw) {
|
|
8
8
|
try {
|
|
9
9
|
const parsed = raw.trim() ? JSON.parse(raw) : {};
|
|
10
|
-
return parsed && typeof parsed === 'object'
|
|
11
|
-
? parsed
|
|
12
|
-
: null;
|
|
10
|
+
return parsed && typeof parsed === 'object' ? parsed : null;
|
|
13
11
|
}
|
|
14
12
|
catch {
|
|
15
13
|
return null;
|
|
@@ -10,8 +10,7 @@ const MAX_ENTRIES = 64;
|
|
|
10
10
|
let cache;
|
|
11
11
|
const toolFingerprints = new WeakMap();
|
|
12
12
|
function cachePath() {
|
|
13
|
-
return process.env.MOCODE_TOKEN_CALIBRATION_CACHE
|
|
14
|
-
|| path.join(os.homedir(), '.mocode', 'token-calibration.json');
|
|
13
|
+
return process.env.MOCODE_TOKEN_CALIBRATION_CACHE || path.join(os.homedir(), '.mocode', 'token-calibration.json');
|
|
15
14
|
}
|
|
16
15
|
function hash(value) {
|
|
17
16
|
return createHash('sha256').update(value).digest('hex');
|
|
@@ -64,35 +63,31 @@ function writeCache() {
|
|
|
64
63
|
}
|
|
65
64
|
}
|
|
66
65
|
function validEntry(entry) {
|
|
67
|
-
return !!entry
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
66
|
+
return (!!entry &&
|
|
67
|
+
Number.isFinite(entry.correction) &&
|
|
68
|
+
entry.correction >= MIN_CORRECTION &&
|
|
69
|
+
entry.correction <= MAX_CORRECTION &&
|
|
70
|
+
Number.isInteger(entry.samples) &&
|
|
71
|
+
entry.samples > 0);
|
|
73
72
|
}
|
|
74
73
|
/** 读取指定 provider/model/工具集合的历史校准;未命中时退回 1。 */
|
|
75
74
|
export function getTokenCalibration(baseURL, model, tools) {
|
|
76
75
|
const entry = readCache().entries[calibrationKey(baseURL, model, tools)];
|
|
77
|
-
return validEntry(entry)
|
|
78
|
-
? { correction: entry.correction, samples: entry.samples }
|
|
79
|
-
: { correction: 1, samples: 0 };
|
|
76
|
+
return validEntry(entry) ? { correction: entry.correction, samples: entry.samples } : { correction: 1, samples: 0 };
|
|
80
77
|
}
|
|
81
78
|
/** 用一次真实 prompt usage 更新 EWMA;只落比例和样本数,不保存任何消息内容。 */
|
|
82
79
|
export function updateTokenCalibration(baseURL, model, tools, estimatedTokens, actualTokens) {
|
|
83
|
-
if (estimatedTokens <= 100
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
80
|
+
if (estimatedTokens <= 100 ||
|
|
81
|
+
actualTokens <= 100 ||
|
|
82
|
+
!Number.isFinite(estimatedTokens) ||
|
|
83
|
+
!Number.isFinite(actualTokens)) {
|
|
87
84
|
return getTokenCalibration(baseURL, model, tools);
|
|
88
85
|
}
|
|
89
86
|
const key = calibrationKey(baseURL, model, tools);
|
|
90
87
|
const store = readCache();
|
|
91
88
|
const previous = store.entries[key];
|
|
92
89
|
const raw = Math.max(MIN_CORRECTION, Math.min(MAX_CORRECTION, actualTokens / estimatedTokens));
|
|
93
|
-
const correction = validEntry(previous)
|
|
94
|
-
? previous.correction * (1 - EWMA_ALPHA) + raw * EWMA_ALPHA
|
|
95
|
-
: raw;
|
|
90
|
+
const correction = validEntry(previous) ? previous.correction * (1 - EWMA_ALPHA) + raw * EWMA_ALPHA : raw;
|
|
96
91
|
const next = {
|
|
97
92
|
correction,
|
|
98
93
|
samples: validEntry(previous) ? previous.samples + 1 : 1,
|
package/dist/host/stdio.js
CHANGED
|
@@ -6,24 +6,34 @@ import { buildBasePrompt, config } from '../config/index.js';
|
|
|
6
6
|
import { refreshChatTools, estimateMessagesTokens } from '../llm/index.js';
|
|
7
7
|
import { initializeAllMcp, getMcpTools, getMcpWarnings, closeAllMcp } from '../mcp/index.js';
|
|
8
8
|
import { setSandboxRoot } from '../sandbox/index.js';
|
|
9
|
-
import { createContextState, loadSession, newSessionId, saveSession } from '../session/index.js';
|
|
9
|
+
import { appendCurrentSessionTraceEvent, createContextState, loadSession, newSessionId, saveSession, } from '../session/index.js';
|
|
10
10
|
import { setCurrentSessionId } from '../session/state.js';
|
|
11
11
|
import { manualCompact } from '../session/scheduler.js';
|
|
12
12
|
import { effectiveSystemPrompt } from '../skills/index.js';
|
|
13
|
+
import { clearSkillActivation } from '../skills/activation.js';
|
|
13
14
|
import { registerToolsExtension } from '../tools/registry.js';
|
|
15
|
+
import { ToolPolicyController } from '../tools/policy.js';
|
|
16
|
+
import { routeToolGroups } from '../tools/router.js';
|
|
17
|
+
// 装配官方默认工具包:registry 不再顶层 import builtins(破模块循环),host 入口须显式装配。
|
|
18
|
+
import '../tools/builtins/index.js';
|
|
14
19
|
import { parseCommand } from './protocol.js';
|
|
15
20
|
let initialized = null;
|
|
16
21
|
let activeRun = null;
|
|
17
22
|
let sessionId = '';
|
|
18
23
|
let history = [];
|
|
19
24
|
let queryHistory = [];
|
|
25
|
+
let lastToolGroups = [];
|
|
20
26
|
let contextState = createContextState();
|
|
21
27
|
const approvals = new Map();
|
|
22
|
-
function write(envelope) {
|
|
28
|
+
function write(envelope) {
|
|
29
|
+
process.stdout.write(`${JSON.stringify(envelope)}\n`);
|
|
30
|
+
}
|
|
23
31
|
function emit(event, payload = {}, requestId) {
|
|
24
32
|
write({ type: 'event', event, payload, requestId });
|
|
25
33
|
}
|
|
26
|
-
function error(message, requestId) {
|
|
34
|
+
function error(message, requestId) {
|
|
35
|
+
write({ type: 'error', error: message, requestId });
|
|
36
|
+
}
|
|
27
37
|
async function initializeRuntime() {
|
|
28
38
|
if (initialized)
|
|
29
39
|
return initialized;
|
|
@@ -42,13 +52,16 @@ async function initializeRuntime() {
|
|
|
42
52
|
})();
|
|
43
53
|
return initialized;
|
|
44
54
|
}
|
|
45
|
-
function systemMessage() {
|
|
55
|
+
function systemMessage() {
|
|
56
|
+
return effectiveSystemPrompt(buildBasePrompt(sessionId));
|
|
57
|
+
}
|
|
46
58
|
function createSession() {
|
|
47
59
|
sessionId = newSessionId();
|
|
48
60
|
setCurrentSessionId(sessionId, process.cwd());
|
|
49
61
|
setAgentMode('auto');
|
|
50
62
|
history = [{ role: 'system', content: systemMessage() }];
|
|
51
63
|
queryHistory = [];
|
|
64
|
+
lastToolGroups = [];
|
|
52
65
|
contextState = createContextState();
|
|
53
66
|
}
|
|
54
67
|
function restoreSession(id) {
|
|
@@ -64,6 +77,7 @@ function restoreSession(id) {
|
|
|
64
77
|
else
|
|
65
78
|
history.unshift({ role: 'system', content: systemMessage() });
|
|
66
79
|
queryHistory = [...(loaded.queryHistory ?? [])];
|
|
80
|
+
lastToolGroups = [...(loaded.lastToolGroups ?? [])];
|
|
67
81
|
contextState = createContextState();
|
|
68
82
|
return true;
|
|
69
83
|
}
|
|
@@ -83,7 +97,7 @@ function waitForApproval(runId, request) {
|
|
|
83
97
|
approvalId,
|
|
84
98
|
title: request.title,
|
|
85
99
|
detail: request.detail ?? '',
|
|
86
|
-
options: (request.options ?? []).map((option) => typeof option === 'string' ? option : option.label),
|
|
100
|
+
options: (request.options ?? []).map((option) => (typeof option === 'string' ? option : option.label)),
|
|
87
101
|
}, runId);
|
|
88
102
|
return new Promise((resolve) => approvals.set(approvalId, { resolve, runId }));
|
|
89
103
|
}
|
|
@@ -104,19 +118,25 @@ async function run(command) {
|
|
|
104
118
|
return error('An agent run is already active for this project.', command.id);
|
|
105
119
|
if (!command.prompt.trim())
|
|
106
120
|
return;
|
|
121
|
+
let toolPolicy;
|
|
107
122
|
try {
|
|
108
123
|
await initializeRuntime();
|
|
109
124
|
const resumed = prepareSession(command.sessionId);
|
|
110
125
|
if (resumed === null)
|
|
111
126
|
return error(`Session ${command.sessionId} could not be restored.`, command.id);
|
|
127
|
+
// 每个被接受的 stdio run 都是真实用户 turn;在路由与 Agent 执行前清除上一轮 skill deny。
|
|
128
|
+
clearSkillActivation();
|
|
112
129
|
const controller = new AbortController();
|
|
113
130
|
activeRun = { id: command.id, controller };
|
|
114
131
|
queryHistory.push(command.prompt);
|
|
115
132
|
const userInput = command.attachments?.length
|
|
116
|
-
? [
|
|
133
|
+
? [
|
|
134
|
+
{ type: 'text', text: command.prompt },
|
|
135
|
+
...command.attachments.map((attachment) => ({
|
|
117
136
|
type: 'image_url',
|
|
118
137
|
image_url: { url: attachment.dataUrl },
|
|
119
|
-
}))
|
|
138
|
+
})),
|
|
139
|
+
]
|
|
120
140
|
: command.prompt;
|
|
121
141
|
emit('run_started', {
|
|
122
142
|
sessionId,
|
|
@@ -126,15 +146,47 @@ async function run(command) {
|
|
|
126
146
|
promptCache: config.provider === 'anthropic' && config.anthropicPromptCache,
|
|
127
147
|
attachments: command.attachments?.map((attachment) => attachment.name) ?? [],
|
|
128
148
|
}, command.id);
|
|
149
|
+
const previousGroups = [...lastToolGroups];
|
|
150
|
+
const decision = await routeToolGroups({
|
|
151
|
+
input: command.prompt,
|
|
152
|
+
previousGroups,
|
|
153
|
+
planMode: false,
|
|
154
|
+
attachmentNames: command.attachments?.map((attachment) => attachment.name),
|
|
155
|
+
signal: controller.signal,
|
|
156
|
+
});
|
|
157
|
+
toolPolicy = new ToolPolicyController({
|
|
158
|
+
groups: decision.groups,
|
|
159
|
+
reason: decision.reason,
|
|
160
|
+
confidence: decision.confidence,
|
|
161
|
+
});
|
|
162
|
+
lastToolGroups = toolPolicy.groupNames;
|
|
163
|
+
const initialToolRoute = {
|
|
164
|
+
policyId: toolPolicy.id,
|
|
165
|
+
groups: toolPolicy.groupNames,
|
|
166
|
+
previousGroups,
|
|
167
|
+
inheritPrevious: decision.inheritPrevious,
|
|
168
|
+
confidence: decision.confidence,
|
|
169
|
+
reason: decision.reason,
|
|
170
|
+
latencyMs: decision.latencyMs,
|
|
171
|
+
fallback: decision.fallback,
|
|
172
|
+
planMode: false,
|
|
173
|
+
};
|
|
174
|
+
emit('tool_route', initialToolRoute, command.id);
|
|
175
|
+
const turnId = queryHistory.length;
|
|
129
176
|
const result = await runAgentCore({
|
|
130
177
|
history,
|
|
131
178
|
userInput,
|
|
132
179
|
signal: controller.signal,
|
|
133
180
|
hooks: hooksFor(command.id),
|
|
134
181
|
contextState,
|
|
182
|
+
toolPolicy,
|
|
183
|
+
initialToolRoute,
|
|
184
|
+
traceContext: { sessionId, turnId },
|
|
185
|
+
onTraceEvent: appendCurrentSessionTraceEvent,
|
|
135
186
|
permissionPrompt: (request) => waitForApproval(command.id, request),
|
|
136
187
|
});
|
|
137
|
-
|
|
188
|
+
lastToolGroups = toolPolicy.groupNames;
|
|
189
|
+
saveSession(history, sessionId, queryHistory, lastToolGroups);
|
|
138
190
|
emit('run_completed', {
|
|
139
191
|
sessionId,
|
|
140
192
|
completed: result.completed,
|
|
@@ -149,12 +201,21 @@ async function run(command) {
|
|
|
149
201
|
}
|
|
150
202
|
catch (cause) {
|
|
151
203
|
const message = cause instanceof Error ? cause.message : String(cause);
|
|
204
|
+
if (toolPolicy)
|
|
205
|
+
lastToolGroups = toolPolicy.groupNames;
|
|
152
206
|
try {
|
|
153
207
|
if (sessionId)
|
|
154
|
-
saveSession(history, sessionId, queryHistory);
|
|
208
|
+
saveSession(history, sessionId, queryHistory, lastToolGroups);
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
/* Preserve the runtime error. */
|
|
212
|
+
}
|
|
213
|
+
if (activeRun?.id === command.id && activeRun.controller.signal.aborted) {
|
|
214
|
+
emit('run_aborted', {}, command.id);
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
emit('run_failed', { message }, command.id);
|
|
155
218
|
}
|
|
156
|
-
catch { /* Preserve the runtime error. */ }
|
|
157
|
-
emit('run_failed', { message }, command.id);
|
|
158
219
|
}
|
|
159
220
|
finally {
|
|
160
221
|
for (const [approvalId, waiter] of approvals) {
|
|
@@ -193,7 +254,7 @@ async function compact(command) {
|
|
|
193
254
|
createSession();
|
|
194
255
|
emit('status', { value: 'compacting' }, command.id);
|
|
195
256
|
const log = await manualCompact(history, command.focus, { force: true });
|
|
196
|
-
saveSession(history, sessionId, queryHistory);
|
|
257
|
+
saveSession(history, sessionId, queryHistory, lastToolGroups);
|
|
197
258
|
const pct = contextUsagePercent();
|
|
198
259
|
emit('compact_done', {
|
|
199
260
|
compacted: log.compactHistoryCalled,
|