mocode-ai 0.5.4 → 0.5.6
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/core.js +34 -49
- package/dist/agent/index.js +96 -52
- package/dist/agent/spawn.js +7 -2
- package/dist/config/index.js +0 -1
- package/dist/context/budget.js +22 -33
- package/dist/context/index.js +1 -1
- package/dist/context/lifecycle.js +1 -55
- package/dist/context/relevance.js +1 -62
- package/dist/context/utils.js +68 -0
- package/dist/project-skill/initializer.js +83 -83
- package/dist/project-snapshot/llm-snapshot.js +69 -69
- package/dist/repl/index.js +28 -22
- package/dist/rollback/index.js +1 -13
- package/dist/session/compact.js +20 -24
- package/dist/session/drop.js +1 -43
- package/dist/session/index.js +1 -1
- package/dist/session/scheduler.js +8 -8
- package/dist/tools/builtins/todolist.js +2 -4
- package/dist/ui/batch.js +142 -207
- package/dist/ui/content.js +22 -0
- package/dist/ui/layout.js +55 -37
- package/dist/ui/markdown.js +8 -2
- package/package.json +1 -1
package/dist/session/compact.js
CHANGED
|
@@ -5,10 +5,13 @@ import { ui } from '../ui/theme.js';
|
|
|
5
5
|
import { Spinner } from '../ui/spinner.js';
|
|
6
6
|
import * as layout from '../ui/layout.js';
|
|
7
7
|
import { pruneAfterCompaction } from '../rollback/index.js';
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
import { toText } from '../context/utils.js';
|
|
9
|
+
export function createContextState() {
|
|
10
|
+
return { lastEstimate: 0, correction: 1 };
|
|
11
|
+
}
|
|
10
12
|
export const contextState = {
|
|
11
13
|
lastEstimate: 0,
|
|
14
|
+
correction: 1,
|
|
12
15
|
};
|
|
13
16
|
/** 中截:text 太长时保 head + 标记 + tail,总长 ≤ max。 */
|
|
14
17
|
export function truncateMid(text, max) {
|
|
@@ -97,19 +100,7 @@ export function capToolResultForHistory(name, output) {
|
|
|
97
100
|
return output;
|
|
98
101
|
return truncateMid(output, MAX_HISTORY_RESULT);
|
|
99
102
|
}
|
|
100
|
-
// ──
|
|
101
|
-
function toText(content) {
|
|
102
|
-
if (content == null)
|
|
103
|
-
return '';
|
|
104
|
-
if (typeof content === 'string')
|
|
105
|
-
return content;
|
|
106
|
-
try {
|
|
107
|
-
return JSON.stringify(content);
|
|
108
|
-
}
|
|
109
|
-
catch {
|
|
110
|
-
return String(content);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
103
|
+
// ── 内部:group 划分(toText 已移至 context/utils.ts 统一维护) ────────────
|
|
113
104
|
/**
|
|
114
105
|
* 把多模态 content 拍平成纯文本(供摘要 transcript 用):text parts 拼接;image_url parts
|
|
115
106
|
* 替换为 `[图片已剥离: <mime>]` stub,避免 base64 进摘要 prompt(LLM 看到也无意义,反而撑爆 token)。
|
|
@@ -248,9 +239,10 @@ async function defaultSummarize(older, focus) {
|
|
|
248
239
|
* 不检查阈值——调用方(maybeCompact)决定是否调;/compact 直接调以强制压缩。
|
|
249
240
|
*/
|
|
250
241
|
export async function compactHistory(history, opts) {
|
|
242
|
+
const state = opts.contextState ?? contextState;
|
|
251
243
|
const schemaTokens = estimateToolSchemaTokens();
|
|
252
244
|
const estimateBefore = estimateMessagesTokens(history) + schemaTokens;
|
|
253
|
-
|
|
245
|
+
state.lastEstimate = estimateBefore;
|
|
254
246
|
const groups = groupFromEnd(history);
|
|
255
247
|
// 保近期:从尾向前累积直到预算花完(至少保 1 组),永不劈开 group。
|
|
256
248
|
const keepBudget = Math.floor(opts.window * 0.4);
|
|
@@ -321,8 +313,9 @@ export async function compactHistory(history, opts) {
|
|
|
321
313
|
history.push(...rebuilt);
|
|
322
314
|
pruneAfterCompaction(history);
|
|
323
315
|
const estimateAfter = estimateMessagesTokens(history) + schemaTokens;
|
|
324
|
-
|
|
325
|
-
|
|
316
|
+
state.lastEstimate = estimateAfter;
|
|
317
|
+
state.lastUsage = undefined;
|
|
318
|
+
state.correction = 1;
|
|
326
319
|
layout.contentWrite(` ${ui.bold}${ui.accent}●${ui.reset} ${ui.accent}强制压缩(focus on early history)${ui.reset} ${ui.dim}${estimateBefore} → ${estimateAfter} tokens${ui.reset}\n`);
|
|
327
320
|
return {
|
|
328
321
|
compacted: true,
|
|
@@ -407,8 +400,9 @@ export async function compactHistory(history, opts) {
|
|
|
407
400
|
history.push(...rebuilt);
|
|
408
401
|
pruneAfterCompaction(history); // 摘要删了旧轮次 → 按存活轮次裁剪回滚日志
|
|
409
402
|
const estimateAfter = estimateMessagesTokens(history) + schemaTokens;
|
|
410
|
-
|
|
411
|
-
|
|
403
|
+
state.lastEstimate = estimateAfter;
|
|
404
|
+
state.lastUsage = undefined; // 压缩后旧 usage 失效,/context 改用估算
|
|
405
|
+
state.correction = 1;
|
|
412
406
|
layout.contentWrite(` ${ui.bold}${ui.accent}●${ui.reset} ${ui.accent}压缩上下文${ui.reset} ${ui.dim}${estimateBefore} → ${estimateAfter} tokens${ui.reset}\n`);
|
|
413
407
|
// 抖动保护:压缩后仍超阈 → 提示 /clear,不死循环
|
|
414
408
|
if (estimateAfter >= opts.threshold * opts.window) {
|
|
@@ -424,8 +418,9 @@ export async function compactHistory(history, opts) {
|
|
|
424
418
|
}
|
|
425
419
|
// 摘要失败:回退仅微压缩(tool content 已原地改),结构不动
|
|
426
420
|
const estimateAfter = estimateMessagesTokens(history) + schemaTokens;
|
|
427
|
-
|
|
428
|
-
|
|
421
|
+
state.lastEstimate = estimateAfter;
|
|
422
|
+
state.lastUsage = undefined; // 结构虽未变,但 token 数已变,旧 usage 失效
|
|
423
|
+
state.correction = 1;
|
|
429
424
|
if (microcompactDone) {
|
|
430
425
|
layout.contentWrite(` ${ui.bold}${ui.accent}●${ui.reset} ${ui.accent}微压缩旧工具结果${ui.reset} ${ui.dim}${estimateBefore} → ${estimateAfter} tokens${ui.reset}\n`);
|
|
431
426
|
return {
|
|
@@ -463,10 +458,10 @@ export async function compactHistory(history, opts) {
|
|
|
463
458
|
* 强制走 compactHistory(manual/force 参数透传)。返 CompactResult 给 caller 文案展示。
|
|
464
459
|
* 默认 manual=false 自动路径完全不变。
|
|
465
460
|
*/
|
|
466
|
-
export async function maybeCompact(history, report, manualOpts) {
|
|
461
|
+
export async function maybeCompact(history, report, manualOpts, state = contextState) {
|
|
467
462
|
const schemaTokens = estimateToolSchemaTokens();
|
|
468
463
|
const est = estimateMessagesTokens(history) + schemaTokens;
|
|
469
|
-
|
|
464
|
+
state.lastEstimate = est;
|
|
470
465
|
const isManual = manualOpts?.manual === true;
|
|
471
466
|
// 手动路径:旁路 autoCompact / report / 总阈三重门
|
|
472
467
|
if (!isManual) {
|
|
@@ -490,6 +485,7 @@ export async function maybeCompact(history, report, manualOpts) {
|
|
|
490
485
|
focus: manualOpts?.focus,
|
|
491
486
|
manual: isManual,
|
|
492
487
|
force: manualOpts?.force,
|
|
488
|
+
contextState: state,
|
|
493
489
|
});
|
|
494
490
|
if (isManual)
|
|
495
491
|
return r;
|
package/dist/session/drop.js
CHANGED
|
@@ -11,49 +11,7 @@
|
|
|
11
11
|
// - 原地修改 history(同 compact:length=0;push 重建,repl 持有同一引用)。
|
|
12
12
|
// - 永不抛错(对齐「调度器永不抛错」契约);无匹配 / 无可剔除 → 返 dropped=0。
|
|
13
13
|
import { messageTokens, estimateTokens, } from '../llm/index.js';
|
|
14
|
-
|
|
15
|
-
function toText(content) {
|
|
16
|
-
if (content == null)
|
|
17
|
-
return '';
|
|
18
|
-
if (typeof content === 'string')
|
|
19
|
-
return content;
|
|
20
|
-
try {
|
|
21
|
-
return JSON.stringify(content);
|
|
22
|
-
}
|
|
23
|
-
catch {
|
|
24
|
-
return String(content);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* 从 history 末尾向前找最后一个 user 消息的索引;无 user 返 -1。
|
|
29
|
-
* 用于划定"当前轮保护区"——该 user 及其之后的消息一律不剔除。
|
|
30
|
-
*/
|
|
31
|
-
function lastUserIndex(history) {
|
|
32
|
-
for (let i = history.length - 1; i >= 1; i--) {
|
|
33
|
-
if (history[i].role === 'user')
|
|
34
|
-
return i;
|
|
35
|
-
}
|
|
36
|
-
return -1;
|
|
37
|
-
}
|
|
38
|
-
/** 取 tool 消息对应的工具名(从紧邻的前导 assistant.tool_calls 按 tool_call_id 配对找)。 */
|
|
39
|
-
function toolNameOf(history, idx) {
|
|
40
|
-
const tcId = history[idx].tool_call_id;
|
|
41
|
-
if (!tcId)
|
|
42
|
-
return null;
|
|
43
|
-
for (let j = idx - 1; j >= 1; j--) {
|
|
44
|
-
const m = history[j];
|
|
45
|
-
if (m.role !== 'assistant')
|
|
46
|
-
continue;
|
|
47
|
-
const tcs = m
|
|
48
|
-
.tool_calls;
|
|
49
|
-
if (!tcs)
|
|
50
|
-
continue;
|
|
51
|
-
const hit = tcs.find((tc) => tc?.id === tcId);
|
|
52
|
-
if (hit)
|
|
53
|
-
return hit.function?.name ?? null;
|
|
54
|
-
}
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
14
|
+
import { lastUserIndex, toText, toolNameOf } from '../context/utils.js';
|
|
57
15
|
/**
|
|
58
16
|
* 剔除历史里命中的旧 tool 结果(原地修改 history)。
|
|
59
17
|
*
|
package/dist/session/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* - persist.ts:history 序列化到磁盘 + --resume / /resume
|
|
5
5
|
* 依赖方向:session → {llm(摘要复用 chat), config, ui};llm 不反向依赖 session。
|
|
6
6
|
*/
|
|
7
|
-
export { compactHistory, maybeCompact, capToolResultForHistory, truncateMid, contextState, } from './compact.js';
|
|
7
|
+
export { compactHistory, maybeCompact, capToolResultForHistory, truncateMid, contextState, createContextState, } from './compact.js';
|
|
8
8
|
// ── Context Budget Scheduler 接缝 ────────────────────────────────────────
|
|
9
9
|
// agent/core.ts 步前调 runScheduler(history, step):评估五区预算 → 按 ROI 调度
|
|
10
10
|
// shrink_cold_tools / cap_hot_tools / compact_history。开关关闭时退化为 maybeCompact。
|
|
@@ -38,14 +38,14 @@ import { ui } from '../ui/theme.js';
|
|
|
38
38
|
/** 创建 runAgentCore 闭包持有的 scheduler(每次 agent 启动一个新实例)。
|
|
39
39
|
* observePush 当前只是占位:真正 L1/L2/L3 已由 cap / pruner / lifecycle 在 push 时跑;
|
|
40
40
|
* 保留接口为后续「调度器注入 hotBoundary 给 lifecycle」演进留接缝。 */
|
|
41
|
-
export function createBudgetScheduler() {
|
|
41
|
+
export function createBudgetScheduler(state = contextState) {
|
|
42
42
|
const obs = {
|
|
43
43
|
lastRunLog: null,
|
|
44
44
|
observePush(_history, _idx) {
|
|
45
45
|
// 占位:push-time 三闸(cap / pruner / lifecycle)已自动跑;此接缝供将来演进。
|
|
46
46
|
},
|
|
47
47
|
async runStep(history, step) {
|
|
48
|
-
const report = evaluateBudget(history, config.contextWindowTokens, step);
|
|
48
|
+
const report = evaluateBudget(history, config.contextWindowTokens, step, state.correction);
|
|
49
49
|
const actions = scheduleActions(report);
|
|
50
50
|
let compactHistoryCalled = false;
|
|
51
51
|
for (const a of actions) {
|
|
@@ -55,7 +55,7 @@ export function createBudgetScheduler() {
|
|
|
55
55
|
}
|
|
56
56
|
else if (a.kind === 'compact_history') {
|
|
57
57
|
// 路由到 maybeCompact(history, report)——按 ROI 调度(只有 history 超 / totalOver 才真压)
|
|
58
|
-
await maybeCompact(history, report);
|
|
58
|
+
await maybeCompact(history, report, undefined, state);
|
|
59
59
|
compactHistoryCalled = true;
|
|
60
60
|
}
|
|
61
61
|
// shrink_cold_tools L1/L2/L3 与 cap_hot_tools:已由 push-time 闸在每次 push 自动跑
|
|
@@ -71,14 +71,14 @@ export function createBudgetScheduler() {
|
|
|
71
71
|
};
|
|
72
72
|
obs.lastRunLog = log;
|
|
73
73
|
// 暴露给 /context 共享读(repl / context 命令)
|
|
74
|
-
|
|
74
|
+
state.schedulerLog = log;
|
|
75
75
|
},
|
|
76
76
|
};
|
|
77
77
|
return obs;
|
|
78
78
|
}
|
|
79
79
|
/** 便捷:agent/core.ts 不需要每次 createBudgetScheduler,直接 runScheduler(history, step)。 */
|
|
80
|
-
export async function runScheduler(history, step) {
|
|
81
|
-
const s = createBudgetScheduler();
|
|
80
|
+
export async function runScheduler(history, step, state = contextState) {
|
|
81
|
+
const s = createBudgetScheduler(state);
|
|
82
82
|
await s.runStep(history, step);
|
|
83
83
|
}
|
|
84
84
|
/** 手动 /compact 入口(repl):与自动路径完全一致——五区 ROI 调度,但 history 摘要强制执行。
|
|
@@ -104,7 +104,7 @@ export async function manualCompact(history, focus, opts) {
|
|
|
104
104
|
}));
|
|
105
105
|
return {
|
|
106
106
|
step: -1,
|
|
107
|
-
report: evaluateBudget(history, config.contextWindowTokens, -1),
|
|
107
|
+
report: evaluateBudget(history, config.contextWindowTokens, -1, contextState.correction),
|
|
108
108
|
actions: [{ kind: 'compact_history', focus }],
|
|
109
109
|
compactHistoryCalled: true,
|
|
110
110
|
ts: Date.now(),
|
|
@@ -118,7 +118,7 @@ export async function manualCompact(history, focus, opts) {
|
|
|
118
118
|
},
|
|
119
119
|
};
|
|
120
120
|
}
|
|
121
|
-
const report = evaluateBudget(history, config.contextWindowTokens, -1);
|
|
121
|
+
const report = evaluateBudget(history, config.contextWindowTokens, -1, contextState.correction);
|
|
122
122
|
let actions = scheduleActions(report);
|
|
123
123
|
// 用户显式说「要压」:即使 report 不含 history 触发,仍追加 compact_history
|
|
124
124
|
const hasCompact = actions.some(a => a.kind === 'compact_history');
|
|
@@ -36,10 +36,8 @@ export const todolistTool = {
|
|
|
36
36
|
'BAD: "打开文件A → 修改函数X → 保存文件A → 运行测试" (too fine-grained, just do it)',
|
|
37
37
|
'',
|
|
38
38
|
'## UPDATE WORKFLOW',
|
|
39
|
-
'Update
|
|
40
|
-
'
|
|
41
|
-
'- Use `update` for single steps; `batch_update` ONLY when 2-3 steps finish simultaneously.',
|
|
42
|
-
'- ⛔ NEVER defer all updates to the end — this defeats the purpose of the progress chip.',
|
|
39
|
+
'Update in real-time after completing each step (one step = one update, or batch_update for 2-3 at once).',
|
|
40
|
+
'Do NOT batch all updates at the end — update as you go so the chip reflects real progress.',
|
|
43
41
|
'All steps done/skipped → plan auto-finishes, archives, and chip disappears.',
|
|
44
42
|
'',
|
|
45
43
|
'## LIFECYCLE',
|