mocode-ai 1.1.6 → 1.1.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.
@@ -201,6 +201,34 @@ export function askHumanBudgetAnnotation(askHumanCountThisTurn, status) {
201
201
  // 第 3+ 次(超过预算):强硬提示,鼓励模型停下问自己是否还有意义。
202
202
  return `\n\n[ask budget EXCEEDED] This is ask_human call #${askHumanCountThisTurn} this turn (budget = ${ASK_HUMAN_PER_TURN_BUDGET}). Stop asking; choose a default, implement, and disclose the choice in your final reply. Continuing to ask is more harmful than a documented guess.`;
203
203
  }
204
+ /** NARR-01: 工具轮旁白(assistant 消息同时带 content + tool_calls)的软预算。
205
+ * 超出即追加回压提示;未超只 emit trace(可度量,不打扰)。
206
+ * 单位是 code point 而非字节——中文一字一 point,英文一字母一 point,
207
+ * 对两种语言都是"一句话大约多长"的直觉量级。 */
208
+ export const NARRATION_CHAR_BUDGET = 120;
209
+ /** 分类工具轮旁白。返回 null 表示这一轮没有旁白(纯工具调用,理想情况)。
210
+ * hint 仅在超预算时非 null:prompt 里的"工具轮保持静默"是软约束,
211
+ * 这里给出机制层面的回压,同时让 trace 能统计旁白率。 */
212
+ export function classifyNarration(content, toolCallCount) {
213
+ const text = content?.trim() ?? '';
214
+ if (!text)
215
+ return null;
216
+ const chars = [...text].length;
217
+ if (chars <= NARRATION_CHAR_BUDGET) {
218
+ return { chars, overBudget: false, hint: null };
219
+ }
220
+ const plural = toolCallCount === 1 ? '' : 's';
221
+ return {
222
+ chars,
223
+ overBudget: true,
224
+ hint: `\n\n[narration] Your previous message emitted ${chars} characters of prose alongside ` +
225
+ `${toolCallCount} tool call${plural} (soft budget = ${NARRATION_CHAR_BUDGET}). ` +
226
+ 'Interstitial commentary costs the user screen space and costs you context on every ' +
227
+ 'later step. For the rest of this turn, emit prose mid-turn ONLY to (a) flag a genuine ' +
228
+ 'decision fork needing user input, (b) disclose an error or risk, or (c) deliver the ' +
229
+ 'final answer once tools are done. Otherwise call the next tool with no preamble.',
230
+ };
231
+ }
204
232
  function pushToolResult(history, tc, output, pruner, lifecycle, _scheduler, runtimeContextState = contextState, succeededOverride) {
205
233
  const succeeded = succeededOverride ?? isToolResultSuccess(output);
206
234
  const ageAware = config.contextOptimize ? ageAwareStateFor(history) : null;
@@ -243,6 +271,15 @@ export async function runAgentCore(opts) {
243
271
  // 预算超过时,工具结果尾部追加 ask budget 提示,不直接拒绝调用(更轻量,
244
272
  // 也避免和现有 permission 系统的"拒绝"语义重叠)。
245
273
  let askHumanCountThisTurn = 0;
274
+ // NARR-01: 上一条 assistant 消息(带 tool_calls)超出旁白预算时,待注入的回压提示。
275
+ // 只挂在本批次的第一条工具结果上并立即清空——重复注入会变成新的噪声源。
276
+ let pendingNarrationHint = null;
277
+ /** 取出并清空待注入的旁白回压提示(每批工具只消费一次)。 */
278
+ const takeNarrationHint = () => {
279
+ const hint = pendingNarrationHint;
280
+ pendingNarrationHint = null;
281
+ return hint;
282
+ };
246
283
  // PROMPT-02: 解析 preCompletionChecklist 选项。undefined = 启用默认 middleware;
247
284
  // false = opt-out(checklist 自身调试用);function = 调用方自定义。
248
285
  const _checklistMiddleware = createPreCompletionChecklistMiddleware();
@@ -556,6 +593,22 @@ export async function runAgentCore(opts) {
556
593
  function: { name: tc.name, arguments: tc.arguments },
557
594
  })),
558
595
  });
596
+ // NARR-01: 这条 assistant 消息同时带正文 + tool_calls,即"工具轮旁白"。
597
+ // prompt 里的"工具轮保持静默"只是软约束,这里补上机制层:
598
+ // - 永远 emit trace(旁白率可度量,不再靠肉眼感觉);
599
+ // - 超预算时把回压提示挂到本批第一条工具结果尾部(复用 thrash/ask
600
+ // budget 已验证的注入接缝,不新增消息、不打断工具流)。
601
+ const narration = classifyNarration(result.content, result.toolCalls.length);
602
+ if (narration) {
603
+ emitTrace('narration', {
604
+ chars: narration.chars,
605
+ toolCalls: result.toolCalls.length,
606
+ budget: NARRATION_CHAR_BUDGET,
607
+ overBudget: narration.overBudget,
608
+ step,
609
+ });
610
+ pendingNarrationHint = narration.hint;
611
+ }
559
612
  // 工具分组执行(保 tool_calls 原顺序):safe parallel 工具照常并发;连续
560
613
  // resource-locked mutation 先按序完成权限预检,再按 canonical resource lock 启动。
561
614
  // registry 对所有真实资源访问统一持锁,所以不同 Agent 间的 read/write/process 也不会竞态。
@@ -685,7 +738,9 @@ export async function runAgentCore(opts) {
685
738
  }, tc.id ? { providerToolCallId: tc.id } : {});
686
739
  }
687
740
  const askBudget = askHumanBudgetAnnotation(askHumanCountThisTurn, outcome.status);
688
- const finalOutput = askBudget ? `${annotated}${askBudget}` : annotated;
741
+ // NARR-01: 旁白回压提示只挂本批第一条结果(takeNarrationHint 自清空)。
742
+ const narrationHint = takeNarrationHint();
743
+ const finalOutput = `${annotated}${askBudget ?? ''}${narrationHint ?? ''}`;
689
744
  pushToolResult(history, tc, finalOutput, relprune, lifecycle, scheduler, runtimeContextState, outcome.status === 'success');
690
745
  }
691
746
  hooks.onToolDone?.();
@@ -874,7 +929,9 @@ export async function runAgentCore(opts) {
874
929
  }, tc.id ? { providerToolCallId: tc.id } : {});
875
930
  }
876
931
  const askBudget = askHumanBudgetAnnotation(askHumanCountThisTurn, outcome.status);
877
- const finalOutput = askBudget ? `${annotated}${askBudget}` : annotated;
932
+ // NARR-01: 旁白回压提示只挂本批第一条结果(takeNarrationHint 自清空)。
933
+ const narrationHint = takeNarrationHint();
934
+ const finalOutput = `${annotated}${askBudget ?? ''}${narrationHint ?? ''}`;
878
935
  pushToolResult(history, tc, finalOutput, relprune, lifecycle, scheduler, runtimeContextState, outcome.status === 'success');
879
936
  const invalidatedFiles = [...new Set([
880
937
  ...(outcome.changedFiles ?? []),
@@ -33,30 +33,30 @@ export function inferModelFamily(model) {
33
33
  * 4 阶段核心纪律(英文)。4 个 model family 共用此文本,只在首句与标题
34
34
  * 标签上做轻量变体。保持短小,详细的完成检查由动态 checklist 按需注入。
35
35
  */
36
- const CORE_SECTION = `## Working discipline — coding tasks (Build-and-Self-Verify)
37
-
38
- Treat "verification" as a first-class part of the task, not an afterthought. Use the smallest evidence-driven loop below.
39
-
40
- ### Phase 1 — Plan & Discover
41
- - Open with a one-sentence restatement of your interpretation of the request; if a materially different reading exists, name it briefly before proceeding. This catches misunderstanding before any work is wasted.
42
- - State the goal and a concrete acceptance signal, then inspect the relevant code before changing it.
43
- - Ask only when an unresolved choice is high-impact or user-owned; otherwise follow repository evidence and proceed.
44
-
45
- ### Phase 2 — Build
46
- - Make the smallest coherent change; avoid unrelated refactors.
47
- - Add or update a focused test when behavior changes and the project has an applicable test suite.
48
- - Re-read only when a dependent edit needs fresh exact content or state may be stale.
49
-
50
- ### Phase 3 — Verify
51
- - Run the smallest executable check that proves the requested behavior, then read its complete result.
52
- - Compare evidence with the user's request, not merely with the diff.
53
-
54
- ### Phase 4 — Fix
55
- - Diagnose the root cause, make a focused correction, and rerun the relevant check.
56
- - After two identical failures, change the approach instead of repeating the same call.
57
-
58
- **Hard rule (non-negotiable):** "I read the code and it looks right" is not a completion signal. Report the verification performed, or state clearly why it could not be run.
59
-
36
+ const CORE_SECTION = `## Working discipline — coding tasks (Build-and-Self-Verify)
37
+
38
+ Treat "verification" as a first-class part of the task, not an afterthought. Use the smallest evidence-driven loop below.
39
+
40
+ ### Phase 1 — Plan & Discover
41
+ - Restate the request in one sentence ONLY when it admits two or more materially different readings; name the reading you picked and move on. An unambiguous request gets no restatement — start working. This is the single exception to staying silent during tool-calling turns.
42
+ - Settle on the goal and a concrete acceptance signal before inspecting the relevant code; keep them internal unless the user has to weigh in.
43
+ - Ask only when an unresolved choice is high-impact or user-owned; otherwise follow repository evidence and proceed.
44
+
45
+ ### Phase 2 — Build
46
+ - Make the smallest coherent change; avoid unrelated refactors.
47
+ - Add or update a focused test when behavior changes and the project has an applicable test suite.
48
+ - Re-read only when a dependent edit needs fresh exact content or state may be stale.
49
+
50
+ ### Phase 3 — Verify
51
+ - Run the smallest executable check that proves the requested behavior, then read its complete result.
52
+ - Compare evidence with the user's request, not merely with the diff.
53
+
54
+ ### Phase 4 — Fix
55
+ - Diagnose the root cause, make a focused correction, and rerun the relevant check.
56
+ - After two identical failures, change the approach instead of repeating the same call.
57
+
58
+ **Hard rule (non-negotiable):** "I read the code and it looks right" is not a completion signal. Report the verification performed, or state clearly why it could not be run.
59
+
60
60
  **Hard rule (non-negotiable):** Never invent file paths, APIs, config keys, flags, or behavior. Every claim about the codebase must trace to tool output in this conversation; explicitly label anything you have not verified as an assumption.`;
61
61
  /**
62
62
  * 把核心段适配到指定 model family:只替换首行(语序 / 强动词),段标题
@@ -68,16 +68,16 @@ function adapt(_model, opener) {
68
68
  return CORE_SECTION.replace('Treat "verification" as a first-class part of the task, not an afterthought.', opener);
69
69
  }
70
70
  /** ASK-01: only user-owned, high-impact choices should interrupt autonomous execution. */
71
- const ASK_WHITELIST_SECTION = `## When to ask instead of guess
72
-
73
- Call \`ask_human\` before coding only when repository evidence cannot resolve a user-owned, high-impact choice:
74
- 1. irreversible deletion, migration, security, permission, or external side effect;
75
- 2. public API compatibility (keep, deprecate, rename, or remove);
76
- 3. multiple reasonable options that materially change product behavior;
77
- 4. the request itself admits two or more materially different readings that lead to different deliverables (do not silently pick one and guess).
78
-
79
- For naming, implementation detail, and verification commands, follow repository precedent and choose the safest reversible default. Disclose any consequential assumption.
80
-
71
+ const ASK_WHITELIST_SECTION = `## When to ask instead of guess
72
+
73
+ Call \`ask_human\` before coding only when repository evidence cannot resolve a user-owned, high-impact choice:
74
+ 1. irreversible deletion, migration, security, permission, or external side effect;
75
+ 2. public API compatibility (keep, deprecate, rename, or remove);
76
+ 3. multiple reasonable options that materially change product behavior;
77
+ 4. the request itself admits two or more materially different readings that lead to different deliverables (do not silently pick one and guess).
78
+
79
+ For naming, implementation detail, and verification commands, follow repository precedent and choose the safest reversible default. Disclose any consequential assumption.
80
+
81
81
  Budget: at most 2 \`ask_human\` calls per turn. Beyond that, use the safest reversible default and disclose it in the final reply.`;
82
82
  /**
83
83
  * 拼出纪律段 + ASK-01 卡点白名单。返回完整段(两段用 \`\\n\\n\` 隔开);
@@ -200,6 +200,7 @@ ${buildWorkDisciplineSection(inferModelFamily(config.model))}
200
200
  ${buildCodegraphSection()}
201
201
 
202
202
  ## Tool use
203
+ - During tool-calling turns, stay silent unless something important enough must reach the user — otherwise just call the tool and let it run.
203
204
  - Go directly to a known path or symbol; use discovery tools only when the location is unknown.
204
205
  - Edit against a FRESH read: before any edit_file/write_file, call read_file on the exact path and copy both its latest hash and the exact target text. Never reconstruct old_string from a grep/summary/diff — those lose whitespace and indentation and cause edit failures.
205
206
  - A read_file hash from before a compaction, session resume, edit conflict, or external change is STALE and will be rejected — re-read rather than reuse an old hash.
package/dist/llm/index.js CHANGED
@@ -141,8 +141,11 @@ function logRetry(attempt, err, waitMs) {
141
141
  const e = err;
142
142
  const tag = e.status ? `HTTP ${e.status}` : e.name || 'Error';
143
143
  const msg = e.message ?? '未知错误';
144
- // stderr 而非 stdout —— 不污染流式正文;行首换行防止黏在上一行尾巴。
145
- process.stderr.write(`\n[llm] ${attempt}/${RETRY_MAX_ATTEMPTS} 次失败(${tag}: ${msg}),${(waitMs / 1000).toFixed(1)}s 后重试…\n`);
144
+ // console.error 而非 process.stderr.write:TUI active 时 layout.installConsoleGuard
145
+ // 已把 console.* 劫持到 contentWrite(内容区),错误会落在 agent 输出区,不污染输入框;
146
+ // 非 TTY(管道 / CI / 启动早期 TUI 未启)降级走原生 console.error → stderr,行为与改造前一致。
147
+ // 不写前导 \n —— contentWrite 由续写位管位置,前置换行会留空行;结尾 \n 由劫持逻辑补。
148
+ console.error(`[llm] 第 ${attempt}/${RETRY_MAX_ATTEMPTS} 次失败(${tag}: ${msg}),${(waitMs / 1000).toFixed(1)}s 后重试…`);
146
149
  }
147
150
  let createImplOverride = null;
148
151
  /** 仅供单测用:覆盖 chat() 内部实际调用的 create 桩。生产代码不要碰。 */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mocode-ai",
3
- "version": "1.1.6",
3
+ "version": "1.1.7",
4
4
  "description": "终端编码 agent:LLM + tool-call 循环 + 流式输出(含思考)+ 16 个工具,接任意 OpenAI 兼容后端。",
5
5
  "type": "module",
6
6
  "bin": {