mocode-ai 1.2.2 → 1.2.3

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.
@@ -337,9 +337,20 @@ export async function runAgentCore(opts) {
337
337
  const modelStartedAt = Date.now();
338
338
  const provider = safeProviderId(requestBaseURL);
339
339
  emitTrace('model_start', { model: requestModel, provider });
340
- const dynamicSystemSuffix = historyRebuilt
341
- ? '## Post-compaction recovery\nContext was compacted before this request. Re-establish the current objective and unresolved work from retained evidence or the session note, avoid repeating completed investigation, and re-read exact file context before any dependent edit.'
342
- : '';
340
+ // 动态注入(仅追加到 system 末尾,不触碰 staticBody 前缀 → 不破坏 prompt 缓存):
341
+ // - 开场分析:仅主线(step===0 !suppressOpeningAnalysis)注入——即"用户发一个任务后,
342
+ // agent 第一次模型调用"。子代理经 spawn.ts 传 suppressOpeningAnalysis:true 排除。
343
+ // - historyRebuilt:compact 恢复步;要求重新锚定目标。两者可叠加但互不串扰。
344
+ // .filter(Boolean) 保证空段不产生多余空行;后续 step 均为空串 → system 前缀稳定命中缓存。
345
+ // 安全保证:此段只拼进 requestHistory(新建对象),绝不回写 history[0],故不会跨 step/跨 turn 残留。
346
+ const dynamicSystemSuffix = [
347
+ (!opts.suppressOpeningAnalysis && step === 0)
348
+ ? '## Opening analysis\nBegin your FIRST response of this turn with a brief analysis of the request and your planned approach (1-3 sentences, no filler), THEN start tool calls. This opening is the only place where pre-tool prose is expected; after it, work quietly with no narration between tool calls.'
349
+ : '',
350
+ historyRebuilt
351
+ ? '## Post-compaction recovery\nContext was compacted before this request. Re-establish the current objective and unresolved work from retained evidence or the session note, avoid repeating completed investigation, and re-read exact file context before any dependent edit.'
352
+ : '',
353
+ ].filter(Boolean).join('\n\n');
343
354
  const systemMessage = history[0];
344
355
  const requestHistory = dynamicSystemSuffix
345
356
  && systemMessage?.role === 'system'
@@ -225,6 +225,7 @@ export async function spawnAgent(opts) {
225
225
  maxSteps,
226
226
  toolsOverride,
227
227
  contextState: localContextState,
228
+ suppressOpeningAnalysis: true, // 子代理不注入「开场分析」:仅主线面对用户的首次响应用
228
229
  onToolOutcome: (tool, args) => {
229
230
  if (tool === 'read_file' && typeof args.path === 'string')
230
231
  readSet.add(args.path);
@@ -210,7 +210,7 @@ export async function promptIntervention(req) {
210
210
  layout.resetScroll();
211
211
  else
212
212
  layout.repaintViewport();
213
- // 恢复走时计时器(RUNNING 态):面板期间 stopTurnTimer 停了心跳,退出后恢复状态行 200ms 刷新。
213
+ // 恢复走时计时器(RUNNING 态):面板期间 stopTurnTimer 停了心跳,退出后恢复状态行 80ms 刷新。
214
214
  layout.startTurnTimerIfRunning();
215
215
  }
216
216
  function onKey(_str, key) {
@@ -339,7 +339,7 @@ export async function promptIntervention(req) {
339
339
  return new Promise((res, rej) => {
340
340
  resolve = res;
341
341
  try {
342
- // 进入面板:停 spinner(避免 onFrame 覆盖)+ 停走时计时器(避免 drawStatusBar 200ms 心跳
342
+ // 进入面板:停 spinner(避免 onFrame 覆盖)+ 停走时计时器(避免 drawStatusBar 80ms 心跳
343
343
  // 把真光标拉到 runningCaretPos 覆盖 paintInput 的正确光标位)+ 禁鼠标框选(防拖拽 viewport 重画覆盖菜单)+ 回尾(若用户正滚动回看)
344
344
  Spinner.pauseCurrent();
345
345
  layout.stopTurnTimer();
package/dist/ui/layout.js CHANGED
@@ -82,7 +82,7 @@ const SCROLL_LOCK_MS = 400; // 锁时长:覆盖 OS 缓冲残留 + 常规滚轮
82
82
  let base = null;
83
83
  // 运行态实时 token 用量(agent core 流式推送,轮末 repl 清 undefined)。
84
84
  // composeModelLine 在 RUNNING 态把它画成 chip 放 context 进度条左侧;
85
- // 不主动触发重画——RUNNING 态 turnTimer 200ms 心跳重画自然取最新值。
85
+ // 不主动触发重画——RUNNING 态 turnTimer 80ms 心跳重画自然取最新值。
86
86
  let liveUsage;
87
87
  let statusText = '';
88
88
  let spinnerFrame;
@@ -92,8 +92,8 @@ let turnTimer = null; // 走时刷新计时器(独立于 spinner):流式期间 s
92
92
  * 驱动 paintInput setRegion(fh) 动态撑高脚栏;drawStatusBar 据此画 1/2 行 plan,
93
93
  * 与 spinner/上线/输入/下线/model 行的 +1/+2/+3/+4/+5 偏移天然一致(contentBottom 自动重算)。 */
94
94
  let planRows = 1;
95
- let runningFrame = -1; // 运行态状态行 chip 心跳帧(♥/♡ 明灭);INPUT 态 -1 退回静态 ●
96
- const RUNNING_FRAMES = ['', ''];
95
+ let runningFrame = -1; // 运行态状态行 chip 心跳帧(转圈帧 明灭);INPUT 态 -1 退回静态 ●
96
+ const RUNNING_FRAMES = ['', '', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
97
97
  // 运行态用户打字时暂停流式物理写:流式每个 token 要 cup 到 contentRow 写入,IME 候选窗逐光标移动跟踪会跟过去;
98
98
  // 用户打字期间只喂缓冲、不物理写,光标留输入框;停手 USER_ACTIVE_PAUSE_MS 后 flush 重画缓冲内容。
99
99
  let userActiveUntil = 0; // 打字活跃截止时刻(Date.now()+PAUSE);0=未活跃
@@ -1332,7 +1332,7 @@ function twoColumn(leftStr, leftW, rightStr, rightW, cols) {
1332
1332
  * 示例:
1333
1333
  * INPUT: ● 空闲
1334
1334
  * 思考中: ⠹ 思考中… 0.5s
1335
- * 运行心跳: 0.5s
1335
+ * 运行心跳: 生成中 0.5s
1336
1336
  * 滚动回看: ● 空闲 历史 ↑3 (PgDn 回底) */
1337
1337
  function composeSpinnerLine(status, cols) {
1338
1338
  const spinning = mode === 'running' && runningFrame >= 0;
@@ -1361,10 +1361,11 @@ function composeSpinnerLine(status, cols) {
1361
1361
  leadW = 1 + 1 + displayWidth(status.status) + (elapsed ? 1 + displayWidth(elapsed) : 0);
1362
1362
  }
1363
1363
  else if (spinning) {
1364
- // 运行态心跳帧(流式输出中):帧 + 走时
1364
+ // 运行态心跳帧(流式输出中):帧 + 生成中 + 走时
1365
+ const label = '生成中';
1365
1366
  const ePart = elapsed ? ` ${ui.dim}${elapsed}${ui.reset}` : '';
1366
- lead = `${ui.bold}${ui.accent}${RUNNING_FRAMES[runningFrame]}${ui.reset}${ePart}`;
1367
- leadW = 1 + (elapsed ? 1 + displayWidth(elapsed) : 0);
1367
+ lead = `${ui.bold}${ui.accent}${RUNNING_FRAMES[runningFrame]}${ui.reset} ${ui.dim}${label}${ui.reset}${ePart}`;
1368
+ leadW = 1 + 1 + displayWidth(label) + (elapsed ? 1 + displayWidth(elapsed) : 0);
1368
1369
  }
1369
1370
  else {
1370
1371
  // INPUT 态:● + 状态文字(无走时)
@@ -1539,9 +1540,9 @@ export function setStatus(status, frame) {
1539
1540
  drawStatusBar();
1540
1541
  }
1541
1542
  /**
1542
- * 启走时刷新计时器:RUNNING 态每 200ms 重画状态行,使 composeStatus 重算 elapsed。
1543
+ * 启走时刷新计时器:RUNNING 态每 80ms 重画状态行,使 composeStatus 重算 elapsed。
1543
1544
  * 必要性:spinner 在首 token 到达即 stop,思考/正文流式期间状态行不再经 spinner 刷新;
1544
- * 若走时只挂 spinner onFrame,流式那几十秒会冻住。此计时器独立续刷,与 spinner 80ms 重叠幂等无妨。
1545
+ * 若走时只挂 spinner onFrame,流式那几十秒会冻住。此计时器独立续刷,与 spinner 80ms 同速,重叠幂等无妨。
1545
1546
  * 非 TTY 不启(active=false 时 drawStatusBar 为 no-op)。
1546
1547
  */
1547
1548
  function startTurnTimer() {
@@ -1552,10 +1553,10 @@ function startTurnTimer() {
1552
1553
  turnTimer = setInterval(() => {
1553
1554
  runningFrame = (runningFrame + 1) % RUNNING_FRAMES.length; // 推进心跳帧,让前导符跳动
1554
1555
  drawStatusBar();
1555
- }, 200);
1556
+ }, 80);
1556
1557
  turnTimer.unref();
1557
1558
  }
1558
- /** 停走时计时器。enterInputMode / exitAltScreen 调;intervention 面板进入时也调(防 drawStatusBar 200ms 心跳把光标拉到 runningCaretPos,覆盖 paintInput 的正确光标位)。 */
1559
+ /** 停走时计时器。enterInputMode / exitAltScreen 调;intervention 面板进入时也调(防 drawStatusBar 80ms 心跳把光标拉到 runningCaretPos,覆盖 paintInput 的正确光标位)。 */
1559
1560
  export function stopTurnTimer() {
1560
1561
  if (turnTimer) {
1561
1562
  clearInterval(turnTimer);
@@ -1646,7 +1647,7 @@ export function clearLiveAtCursor() {
1646
1647
  frameCol = 0;
1647
1648
  }
1648
1649
  /** 推送 / 清空运行态实时 token 用量(agent core 流式推送;repl 轮末清 undefined)。
1649
- * 不触发重画:RUNNING 态 turnTimer 200ms 心跳重画状态行,自然取到最新值。 */
1650
+ * 不触发重画:RUNNING 态 turnTimer 80ms 心跳重画状态行,自然取到最新值。 */
1650
1651
  export function setLiveUsage(u) {
1651
1652
  liveUsage = u;
1652
1653
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mocode-ai",
3
- "version": "1.2.2",
3
+ "version": "1.2.3",
4
4
  "description": "终端编码 agent:LLM + tool-call 循环 + 流式输出(含思考)+ 16 个工具,接任意 OpenAI 兼容后端。",
5
5
  "type": "module",
6
6
  "bin": {