mocode-ai 1.5.5 → 1.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/agent/model-turn.js
CHANGED
|
@@ -25,6 +25,9 @@ export async function runModelTurn(input) {
|
|
|
25
25
|
const storedCalibration = ctx.getTokenCalibration(requestBaseURL, requestModel, activeTools);
|
|
26
26
|
runtimeContextState.correction = storedCalibration.correction;
|
|
27
27
|
runtimeContextState.calibrationSamples = storedCalibration.samples;
|
|
28
|
+
// 记录本步实际发送的工具集(policy 收窄后),底栏用量条按它估算 schema,
|
|
29
|
+
// 而不是全量 chatTools(含未路由的 MCP/写工具),避免凭空高估。
|
|
30
|
+
runtimeContextState.activeTools = activeTools;
|
|
28
31
|
let sessionStateText = opts.suppressSessionState ? '' : ctx.buildSessionStateReminder();
|
|
29
32
|
runtimeContextState.ephemeralText = sessionStateText || undefined;
|
|
30
33
|
onContextUpdate?.();
|
|
@@ -243,6 +246,16 @@ export async function runModelTurn(input) {
|
|
|
243
246
|
const updated = ctx.updateTokenCalibration(requestBaseURL, requestModel, activeTools, estimated, result.usage.promptTokens);
|
|
244
247
|
runtimeContextState.correction = updated.correction;
|
|
245
248
|
runtimeContextState.calibrationSamples = updated.samples;
|
|
249
|
+
// 钉住底栏用量条:此刻 requestHistory == 真实发出的请求,实测 promptTokens 是它的真实大小。
|
|
250
|
+
// 记录基线裸估算/history 长度/工具集/ephemeral,供底栏对随后增长的消息做精确外推。
|
|
251
|
+
const requestHistoryLen = requestHistory.length;
|
|
252
|
+
runtimeContextState.promptAnchor = {
|
|
253
|
+
measuredPromptTokens: result.usage.promptTokens,
|
|
254
|
+
baseRaw: estimated,
|
|
255
|
+
historyLen: sessionStateText ? requestHistoryLen - 1 : requestHistoryLen,
|
|
256
|
+
tools: activeTools,
|
|
257
|
+
ephemeralText: sessionStateText,
|
|
258
|
+
};
|
|
246
259
|
}
|
|
247
260
|
hooks.onChatDone?.();
|
|
248
261
|
onContextUpdate?.();
|
package/dist/config/index.js
CHANGED
|
@@ -373,9 +373,10 @@ export function buildBasePrompt(sessionId = getCurrentSessionId()) {
|
|
|
373
373
|
const notepadSection = buildNotepadSection(sessionId);
|
|
374
374
|
// 静态主体:稳定段落集中在前,让支持 prompt caching 的后端能命中前缀缓存(#12)。
|
|
375
375
|
// 约束:staticBody 的前缀段(尤其 ## Identity 第一行)必须是纯静态文本,
|
|
376
|
-
// 不得嵌入会话级可变函数调用(如
|
|
377
|
-
//
|
|
378
|
-
//
|
|
376
|
+
// 不得嵌入会话级可变函数调用(如 config.model)。否则 /model 切换会让最敏感的
|
|
377
|
+
// 前缀变化,破坏自动前缀缓存命中。
|
|
378
|
+
// 回复语言不写入提示词:模型按用户当轮提问语言自动识别(Voice 段的
|
|
379
|
+
// "Match the user's style and language" 已覆盖),/language 只切换终端 UI 文案。
|
|
379
380
|
const staticBody = `## Identity
|
|
380
381
|
You are mocode, a terminal coding agent.
|
|
381
382
|
|
|
@@ -422,8 +423,7 @@ ${buildVoiceSection()}
|
|
|
422
423
|
- Stop immediately when no more tools are needed; give conclusions directly.
|
|
423
424
|
- **Do not stop prematurely during exploration**: if you started investigating but haven't gathered enough information to answer the user's question, keep calling tools. Only stop when you have sufficient evidence or hit a dead end.
|
|
424
425
|
- **No flattery / no preamble in conclusions**: skip "Sure", "好的", "我已经完成了" and similar no-information prefixes — jump straight to substance.
|
|
425
|
-
- Report honestly: say success when successful, say where you're stuck when failing, and mention anything skipped. Reference code in "path:line" format (e.g., src/index.ts:42). Keep it concise
|
|
426
|
-
${t('assistant.languageInstruction')}`;
|
|
426
|
+
- Report honestly: say success when successful, say where you're stuck when failing, and mention anything skipped. Reference code in "path:line" format (e.g., src/index.ts:42). Keep it concise.`;
|
|
427
427
|
// 动态段(置于末尾):AGENTS.md 项目记忆 + notepad 索引/说明。工具簇特定指导由
|
|
428
428
|
// ToolPolicyController.reminder() 按当前 turn 的 route 注入,避免旧全局 profile 与真实 schema 分裂。
|
|
429
429
|
// 按需注入(#13):有内容的索引才拼对应标题,避免空标题噪声。
|
|
@@ -735,7 +735,7 @@ export function updateMemoryConfig(enabled) {
|
|
|
735
735
|
config.memoryEnabled = enabled;
|
|
736
736
|
process.env.MEMORY_ENABLED = enabled ? 'true' : 'false';
|
|
737
737
|
}
|
|
738
|
-
/**
|
|
738
|
+
/** 切换终端界面语言(回复语言不写入提示词,由模型按用户提问自动识别);持久化由 REPL 调用 config/file.ts 完成。 */
|
|
739
739
|
export function updateLanguageConfig(language) {
|
|
740
740
|
setLanguage(language);
|
|
741
741
|
process.env.MOCODE_LANGUAGE = language;
|
package/dist/i18n/index.js
CHANGED
|
@@ -54,7 +54,7 @@ const zhCN = {
|
|
|
54
54
|
'commands.imageAttach': '附加本地图片',
|
|
55
55
|
'commands.imageList': '列出待发送图片',
|
|
56
56
|
'commands.imageClear': '清空待发送图片',
|
|
57
|
-
'commands.language': '
|
|
57
|
+
'commands.language': '切换界面语言',
|
|
58
58
|
'commands.languageZh': '切换到中文',
|
|
59
59
|
'commands.languageEn': 'Switch to English',
|
|
60
60
|
'commands.upgrade': '升级 mocode 到最新版本',
|
|
@@ -327,7 +327,6 @@ const zhCN = {
|
|
|
327
327
|
'upgrade.pkgName': '包名:mocode-ai',
|
|
328
328
|
'upgrade.failed': '[upgrade] 失败:{message}',
|
|
329
329
|
'upgrade.usage': '/upgrade 用法:\n /upgrade 升级到最新版本\n /upgrade now 同 /upgrade\n /upgrade check 检查当前版本与最新版本差异\n /upgrade status 显示当前本地版本',
|
|
330
|
-
'assistant.languageInstruction': 'Reply to the user in Chinese.',
|
|
331
330
|
};
|
|
332
331
|
const en = {
|
|
333
332
|
'common.none': '(none)',
|
|
@@ -385,7 +384,7 @@ const en = {
|
|
|
385
384
|
'commands.imageAttach': 'Attach a local image',
|
|
386
385
|
'commands.imageList': 'List pending images',
|
|
387
386
|
'commands.imageClear': 'Clear pending images',
|
|
388
|
-
'commands.language': 'Switch interface
|
|
387
|
+
'commands.language': 'Switch interface language',
|
|
389
388
|
'commands.languageZh': '切换到中文',
|
|
390
389
|
'commands.languageEn': 'Switch to English',
|
|
391
390
|
'commands.upgrade': 'Upgrade mocode to the latest version',
|
|
@@ -658,7 +657,6 @@ const en = {
|
|
|
658
657
|
'upgrade.pkgName': 'Package: mocode-ai',
|
|
659
658
|
'upgrade.failed': '[upgrade] failed: {message}',
|
|
660
659
|
'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',
|
|
661
|
-
'assistant.languageInstruction': 'Reply to the user in English.',
|
|
662
660
|
};
|
|
663
661
|
const resources = {
|
|
664
662
|
'zh-CN': zhCN,
|
|
@@ -20,6 +20,8 @@ export const sessionCommands = [
|
|
|
20
20
|
ctx.contextState.lastUsage = undefined;
|
|
21
21
|
ctx.contextState.lifecycleStats = undefined;
|
|
22
22
|
ctx.contextState.ephemeralText = undefined;
|
|
23
|
+
ctx.contextState.activeTools = undefined;
|
|
24
|
+
ctx.contextState.promptAnchor = undefined; // 新会话不得用旧会话的实测锚点外推
|
|
23
25
|
ctx.state.lastTurnUsage = undefined; // 清空旧轮的 token 累计
|
|
24
26
|
ctx.state.lastToolGroups = []; // 新会话不得继承旧会话的路由回退
|
|
25
27
|
ctx.attachments.clear(); // 一并清空待发图片
|
package/dist/repl/runtime.js
CHANGED
|
@@ -639,6 +639,8 @@ export async function startRepl(initialHistory, sessionId, sandboxRootOverride,
|
|
|
639
639
|
contextState.lastUsage = undefined;
|
|
640
640
|
contextState.lifecycleStats = undefined;
|
|
641
641
|
contextState.ephemeralText = undefined;
|
|
642
|
+
contextState.activeTools = undefined;
|
|
643
|
+
contextState.promptAnchor = undefined; // 续接:旧会话的实测锚点不得外推到新 history
|
|
642
644
|
lastTurnUsage = undefined; // 续接:旧会话的 token 累计已无意义,清空等下轮覆写
|
|
643
645
|
layout.clearContent();
|
|
644
646
|
// 锚点行:会话 id / 消息数 / 当前模型,给续接的会话一个定位起点(与 --resume 启动路径同文案)。
|
package/dist/repl/status-bar.js
CHANGED
|
@@ -6,7 +6,7 @@ import { t } from '../i18n/index.js';
|
|
|
6
6
|
import { config } from '../config/index.js';
|
|
7
7
|
import { contextState } from '../session/index.js';
|
|
8
8
|
import { DEFAULT_BUDGET_POLICY } from '../context/budget.js';
|
|
9
|
-
import { estimateMessagesTokens,
|
|
9
|
+
import { chatTools, correctTokenEstimate, estimateMessagesTokens, estimateToolSchemaTokens, estimateTokens, } from '../llm/index.js';
|
|
10
10
|
import { computePruneStats } from '../context/relevance.js';
|
|
11
11
|
import { formatArtifactTokenSources } from '../context/artifacts.js';
|
|
12
12
|
import { getAgentMode } from '../agent/mode.js';
|
|
@@ -51,7 +51,9 @@ export function renderContextBar(history) {
|
|
|
51
51
|
return `${ui.gray}[${pctCol}${bar}${ui.reset}] ${Math.round(pct * 100)}% ${k(est)}/${k(win)} tokens · ${t('status.messages', { count: history.length })} (${src})${measuredNote}${ui.reset}${artifactLine}${lifecycleLine}${archiveLine}`;
|
|
52
52
|
}
|
|
53
53
|
/** 状态行用量条(精简版,进底栏):[bar] pct% k/k。
|
|
54
|
-
*
|
|
54
|
+
* 空对话(只有 system 提示、还没发任何消息)恒显 0% 0/window:固定 system prompt + 工具
|
|
55
|
+
* schema 是每次请求的基础设施开销,不算用户占用的上下文,避免刚进 REPL 就显示十几 k。
|
|
56
|
+
* 有对话后必须**用全 prompt 估算**(消息 + 工具 schema + 尾部 ephemeral 注入),与压缩触发器
|
|
55
57
|
* evaluateBudget 的 system+history+toolOld+toolRecent 总账对齐——任何一段漏算都会让
|
|
56
58
|
* bar 与触发器口径不一致、看着没到 80% 实际已经在压。
|
|
57
59
|
* 触发器用 `Math.max(rawTotal, total) >= 0.8 * window`,bar 也照搬:校正后和校正前
|
|
@@ -63,17 +65,50 @@ export function renderContextBar(history) {
|
|
|
63
65
|
* /context 命令仍是 dialog-only(见 renderContextBar):它的设计意图是"我说了多少"而非
|
|
64
66
|
* "还剩多少空间",两条职责分开。 */
|
|
65
67
|
export function renderContextBarInline(history) {
|
|
66
|
-
const baseRaw = estimatePromptTokens(history, chatTools, 1);
|
|
67
|
-
const baseAdj = estimatePromptTokens(history, chatTools, contextState.correction);
|
|
68
|
-
const ephemeral = contextState.ephemeralText ? estimateTokens(contextState.ephemeralText) : 0;
|
|
69
|
-
// 与触发器同样的「取大」语义:correction<1 时 raw 更大,bar 不会假装很安全。
|
|
70
|
-
const est = Math.max(baseRaw, baseAdj) + ephemeral;
|
|
71
68
|
const win = config.contextWindowTokens;
|
|
69
|
+
let est;
|
|
70
|
+
// 还没有任何真实对话(只有 system 提示)时显示 0:system prompt 与工具 schema 是每次请求
|
|
71
|
+
// 都要发的固定"基础设施开销",不属于用户对话占用的上下文——刚进 REPL 一句话没问就显示
|
|
72
|
+
// 十几 k 会让人误以为已经用掉了上下文。发送第一条消息后才计入。与 /context 的 dialog-only
|
|
73
|
+
// 口径一致;压缩触发器(evaluateBudget)不受影响,它只在真正发请求时按全量评估。
|
|
74
|
+
if (!history.some((m) => m.role !== 'system')) {
|
|
75
|
+
est = 0;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
// schema 必须按「最近一步实际发送的工具集」估算(ToolPolicy 收窄后);缺省(尚未跑过任何
|
|
79
|
+
// 模型步)才回退到全量 chatTools。旧实现恒用全量,会把未路由的 MCP/写工具 schema 凭空计入,
|
|
80
|
+
// 导致底栏 ~16k 而本轮 API 实测只有 ~5k。
|
|
81
|
+
const activeTools = contextState.activeTools ?? chatTools;
|
|
82
|
+
const ephemeralRaw = contextState.ephemeralText ? estimateTokens(contextState.ephemeralText) : 0;
|
|
83
|
+
const fullRaw = estimateMessagesTokens(history) + estimateToolSchemaTokens(activeTools) + ephemeralRaw;
|
|
84
|
+
const anchor = contextState.promptAnchor;
|
|
85
|
+
// 锚点有效:锚定时 history 是当前 history 的严格前缀(同一长度或更长),工具集/ephemeral
|
|
86
|
+
// 未变,且此后未发生压缩(裸估算不小于锚点基线)。用「实测值 + 新增消息的裸估算」外推,
|
|
87
|
+
// 单步轮 history 未变时结果 == API 实测 promptTokens,与轮末行的 ↑ 数字精确一致。
|
|
88
|
+
if (anchor &&
|
|
89
|
+
history.length >= anchor.historyLen &&
|
|
90
|
+
anchor.tools === activeTools &&
|
|
91
|
+
anchor.ephemeralText === (contextState.ephemeralText ?? '') &&
|
|
92
|
+
// 容差 4:锚点把 ephemeral 当独立 system 消息(3 priming + 结构开销),bar 把它单独相加,
|
|
93
|
+
// 同一快照下 fullRaw 可比 baseRaw 小 3;压缩后 fullRaw 会骤降数千 token,不会被该容差掩盖。
|
|
94
|
+
fullRaw >= anchor.baseRaw - 4) {
|
|
95
|
+
const anchorHistory = history.slice(0, anchor.historyLen);
|
|
96
|
+
const grownRaw = estimateMessagesTokens(history) - estimateMessagesTokens(anchorHistory);
|
|
97
|
+
est = anchor.measuredPromptTokens + Math.max(0, grownRaw);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
// 无有效锚点(/clear 后、压缩后、工具集或 ephemeral 刚变化):走保守估算,
|
|
101
|
+
// 与触发器同样的「取大」语义——correction<1 时 raw 更大,bar 不会假装很安全。
|
|
102
|
+
const baseAdj = correctTokenEstimate(fullRaw, contextState.correction);
|
|
103
|
+
est = Math.max(fullRaw, baseAdj);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
72
106
|
const pct = Math.min(1, est / win);
|
|
73
107
|
const W = 10;
|
|
74
108
|
const filled = Math.round(pct * W);
|
|
75
109
|
const bar = '█'.repeat(filled) + '░'.repeat(W - filled);
|
|
76
|
-
|
|
110
|
+
// 与轮末 token 行 formatTurnTokens 同一格式:<10k 保留一位小数,锚点生效时两处逐字一致(5.2k)。
|
|
111
|
+
const k = (n) => (n < 1000 ? `${Math.round(n)}` : `${(n / 1000).toFixed(n >= 10000 ? 0 : 1)}k`);
|
|
77
112
|
const pctCol = pct >= DEFAULT_BUDGET_POLICY.pressureTriggerRatio ? ui.yellow : ui.accent;
|
|
78
113
|
return `${ui.gray}[${pctCol}${bar}${ui.reset}] ${pctCol}${Math.round(pct * 100)}%${ui.reset} ${ui.dim}${k(est)}/${k(win)}${ui.reset}`;
|
|
79
114
|
}
|