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,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Computer Use 截图管线:捕获 → 缩放/裁剪(纯 JS PNG) → norm1000 坐标换算。
|
|
3
|
+
*
|
|
4
|
+
* 职责边界:
|
|
5
|
+
* - 抓屏本身在 screen-capture.ts(平台相关),本文件只管图像后处理与坐标数学。
|
|
6
|
+
* - 坐标对外只暴露归一化 0-1000 整数网格(x 右 / y 下),模型从不感知物理分辨率;
|
|
7
|
+
* 执行层按最近一次截图的 scale 因子换算回物理像素。
|
|
8
|
+
* - 零原生图像依赖:PNG 解码/编码用 Node 内置 zlib 手写,最近邻重采样。
|
|
9
|
+
* 只支持截图工具产出的 8-bit RGB/RGBA PNG(screencapture/PowerShell Bitmap 均为该族)。
|
|
10
|
+
*/
|
|
11
|
+
import { inflateSync, deflateSync } from 'node:zlib';
|
|
12
|
+
const PNG_SIG = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
|
|
13
|
+
function paeth(a, b, c) {
|
|
14
|
+
const p = a + b - c;
|
|
15
|
+
const pa = Math.abs(p - a);
|
|
16
|
+
const pb = Math.abs(p - b);
|
|
17
|
+
const pc = Math.abs(p - c);
|
|
18
|
+
if (pa <= pb && pa <= pc)
|
|
19
|
+
return a;
|
|
20
|
+
if (pb <= pc)
|
|
21
|
+
return b;
|
|
22
|
+
return c;
|
|
23
|
+
}
|
|
24
|
+
/** 解码 PNG → RGBA 像素。非 8-bit RGB/RGBA 直接抛错(截图工具不会产出其它形态)。 */
|
|
25
|
+
export function decodePng(buf) {
|
|
26
|
+
if (buf.length < 8 || !buf.subarray(0, 8).equals(PNG_SIG))
|
|
27
|
+
throw new Error('not a PNG');
|
|
28
|
+
let pos = 8;
|
|
29
|
+
let width = 0;
|
|
30
|
+
let height = 0;
|
|
31
|
+
let bitDepth = 0;
|
|
32
|
+
let colorType = 0;
|
|
33
|
+
const idat = [];
|
|
34
|
+
while (pos + 8 <= buf.length) {
|
|
35
|
+
const len = buf.readUInt32BE(pos);
|
|
36
|
+
const type = buf.toString('ascii', pos + 4, pos + 8);
|
|
37
|
+
const data = buf.subarray(pos + 8, pos + 8 + len);
|
|
38
|
+
if (type === 'IHDR') {
|
|
39
|
+
width = data.readUInt32BE(0);
|
|
40
|
+
height = data.readUInt32BE(4);
|
|
41
|
+
bitDepth = data[8];
|
|
42
|
+
colorType = data[9];
|
|
43
|
+
}
|
|
44
|
+
else if (type === 'IDAT') {
|
|
45
|
+
idat.push(Buffer.from(data));
|
|
46
|
+
}
|
|
47
|
+
else if (type === 'IEND') {
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
pos += 8 + len + 4; // +4 CRC
|
|
51
|
+
}
|
|
52
|
+
if (!width || !height || bitDepth !== 8 || (colorType !== 2 && colorType !== 6)) {
|
|
53
|
+
throw new Error(`unsupported PNG (bitDepth=${bitDepth} colorType=${colorType})`);
|
|
54
|
+
}
|
|
55
|
+
const bpp = colorType === 6 ? 4 : 3;
|
|
56
|
+
const stride = width * bpp;
|
|
57
|
+
const raw = inflateSync(Buffer.concat(idat));
|
|
58
|
+
const out = Buffer.alloc(stride * height);
|
|
59
|
+
// 反滤镜
|
|
60
|
+
for (let y = 0; y < height; y++) {
|
|
61
|
+
const f = raw[y * (stride + 1)];
|
|
62
|
+
const rowIn = y * (stride + 1) + 1;
|
|
63
|
+
const rowOut = y * stride;
|
|
64
|
+
for (let x = 0; x < stride; x++) {
|
|
65
|
+
const cur = raw[rowIn + x];
|
|
66
|
+
const a = x >= bpp ? out[rowOut + x - bpp] : 0;
|
|
67
|
+
const b = y > 0 ? out[rowOut + x - stride] : 0;
|
|
68
|
+
const c = x >= bpp && y > 0 ? out[rowOut + x - stride - bpp] : 0;
|
|
69
|
+
let v;
|
|
70
|
+
switch (f) {
|
|
71
|
+
case 0:
|
|
72
|
+
v = cur;
|
|
73
|
+
break;
|
|
74
|
+
case 1:
|
|
75
|
+
v = cur + a;
|
|
76
|
+
break;
|
|
77
|
+
case 2:
|
|
78
|
+
v = cur + b;
|
|
79
|
+
break;
|
|
80
|
+
case 3:
|
|
81
|
+
v = cur + ((a + b) >> 1);
|
|
82
|
+
break;
|
|
83
|
+
case 4:
|
|
84
|
+
v = cur + paeth(a, b, c);
|
|
85
|
+
break;
|
|
86
|
+
default: throw new Error(`bad PNG filter ${f}`);
|
|
87
|
+
}
|
|
88
|
+
out[rowOut + x] = v & 0xff;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// RGB → RGBA
|
|
92
|
+
if (bpp === 4)
|
|
93
|
+
return { width, height, data: out };
|
|
94
|
+
const rgba = Buffer.alloc(width * height * 4);
|
|
95
|
+
for (let i = 0, j = 0; i < out.length; i += 3, j += 4) {
|
|
96
|
+
rgba[j] = out[i];
|
|
97
|
+
rgba[j + 1] = out[i + 1];
|
|
98
|
+
rgba[j + 2] = out[i + 2];
|
|
99
|
+
rgba[j + 3] = 255;
|
|
100
|
+
}
|
|
101
|
+
return { width, height, data: rgba };
|
|
102
|
+
}
|
|
103
|
+
function crc32(buf) {
|
|
104
|
+
let c;
|
|
105
|
+
let crc = 0xffffffff;
|
|
106
|
+
for (let i = 0; i < buf.length; i++) {
|
|
107
|
+
c = (crc ^ buf[i]) & 0xff;
|
|
108
|
+
for (let k = 0; k < 8; k++)
|
|
109
|
+
c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1;
|
|
110
|
+
crc = (crc >>> 8) ^ c;
|
|
111
|
+
}
|
|
112
|
+
return (crc ^ 0xffffffff) >>> 0;
|
|
113
|
+
}
|
|
114
|
+
function chunk(type, data) {
|
|
115
|
+
const len = Buffer.alloc(4);
|
|
116
|
+
len.writeUInt32BE(data.length, 0);
|
|
117
|
+
const body = Buffer.concat([Buffer.from(type, 'ascii'), data]);
|
|
118
|
+
const crc = Buffer.alloc(4);
|
|
119
|
+
crc.writeUInt32BE(crc32(body), 0);
|
|
120
|
+
return Buffer.concat([len, body, crc]);
|
|
121
|
+
}
|
|
122
|
+
/** 编码 RGBA → PNG(colorType 6,filter 0)。 */
|
|
123
|
+
export function encodePng(img) {
|
|
124
|
+
const { width, height, data } = img;
|
|
125
|
+
const stride = width * 4;
|
|
126
|
+
const raw = Buffer.alloc((stride + 1) * height);
|
|
127
|
+
for (let y = 0; y < height; y++) {
|
|
128
|
+
raw[y * (stride + 1)] = 0; // filter none
|
|
129
|
+
data.copy(raw, y * (stride + 1) + 1, y * stride, y * stride + stride);
|
|
130
|
+
}
|
|
131
|
+
const ihdr = Buffer.alloc(13);
|
|
132
|
+
ihdr.writeUInt32BE(width, 0);
|
|
133
|
+
ihdr.writeUInt32BE(height, 4);
|
|
134
|
+
ihdr[8] = 8; // bitDepth
|
|
135
|
+
ihdr[9] = 6; // RGBA
|
|
136
|
+
ihdr[10] = 0; // compression
|
|
137
|
+
ihdr[11] = 0; // filter
|
|
138
|
+
ihdr[12] = 0; // interlace
|
|
139
|
+
return Buffer.concat([
|
|
140
|
+
PNG_SIG,
|
|
141
|
+
chunk('IHDR', ihdr),
|
|
142
|
+
chunk('IDAT', deflateSync(raw)),
|
|
143
|
+
chunk('IEND', Buffer.alloc(0)),
|
|
144
|
+
]);
|
|
145
|
+
}
|
|
146
|
+
// ── 缩放 / 裁剪(最近邻)─────────────────────────────────────────────────
|
|
147
|
+
function resample(img, newW, newH) {
|
|
148
|
+
const { width, height, data } = img;
|
|
149
|
+
const out = Buffer.alloc(newW * newH * 4);
|
|
150
|
+
for (let y = 0; y < newH; y++) {
|
|
151
|
+
const srcY = Math.min(height - 1, Math.floor((y * height) / newH));
|
|
152
|
+
for (let x = 0; x < newW; x++) {
|
|
153
|
+
const srcX = Math.min(width - 1, Math.floor((x * width) / newW));
|
|
154
|
+
const s = (srcY * width + srcX) * 4;
|
|
155
|
+
const d = (y * newW + x) * 4;
|
|
156
|
+
out[d] = data[s];
|
|
157
|
+
out[d + 1] = data[s + 1];
|
|
158
|
+
out[d + 2] = data[s + 2];
|
|
159
|
+
out[d + 3] = data[s + 3];
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return { width: newW, height: newH, data: out };
|
|
163
|
+
}
|
|
164
|
+
/** 长边缩到 ≤ maxEdge,保持纵横比。已 ≤ maxEdge 时原样返回(零成本直通)。 */
|
|
165
|
+
export function downscale(img, maxEdge) {
|
|
166
|
+
const long = Math.max(img.width, img.height);
|
|
167
|
+
if (long <= maxEdge)
|
|
168
|
+
return { img, scale: 1 };
|
|
169
|
+
const scale = maxEdge / long;
|
|
170
|
+
const newW = Math.max(1, Math.round(img.width * scale));
|
|
171
|
+
const newH = Math.max(1, Math.round(img.height * scale));
|
|
172
|
+
return { img: resample(img, newW, newH), scale };
|
|
173
|
+
}
|
|
174
|
+
/** 长边精确缩放到 edge(放大或缩小,zoom 动作用),保持纵横比。 */
|
|
175
|
+
export function fitToLongEdge(img, edge) {
|
|
176
|
+
const long = Math.max(img.width, img.height);
|
|
177
|
+
if (long === edge)
|
|
178
|
+
return img;
|
|
179
|
+
const scale = edge / long;
|
|
180
|
+
const newW = Math.max(1, Math.round(img.width * scale));
|
|
181
|
+
const newH = Math.max(1, Math.round(img.height * scale));
|
|
182
|
+
return resample(img, newW, newH);
|
|
183
|
+
}
|
|
184
|
+
/** 裁出 (x,y,w,h) 矩形,越界自动收敛到图像内。 */
|
|
185
|
+
export function crop(img, x, y, w, h) {
|
|
186
|
+
const cx = Math.max(0, Math.min(img.width - 1, Math.round(x)));
|
|
187
|
+
const cy = Math.max(0, Math.min(img.height - 1, Math.round(y)));
|
|
188
|
+
const cw = Math.max(1, Math.min(img.width - cx, Math.round(w)));
|
|
189
|
+
const ch = Math.max(1, Math.min(img.height - cy, Math.round(h)));
|
|
190
|
+
const out = Buffer.alloc(cw * ch * 4);
|
|
191
|
+
for (let row = 0; row < ch; row++) {
|
|
192
|
+
const s = ((cy + row) * img.width + cx) * 4;
|
|
193
|
+
img.data.copy(out, row * cw * 4, s, s + cw * 4);
|
|
194
|
+
}
|
|
195
|
+
return { width: cw, height: ch, data: out };
|
|
196
|
+
}
|
|
197
|
+
// ── norm1000 坐标换算 ──────────────────────────────────────────────────
|
|
198
|
+
/** 归一化坐标(0-1000)→ 物理像素。scale = 喂模型的图相对物理的缩放比。 */
|
|
199
|
+
export function normToPhysical(nx, ny, physW, physH) {
|
|
200
|
+
const x = Math.round((clamp1000(nx) / 1000) * physW);
|
|
201
|
+
const y = Math.round((clamp1000(ny) / 1000) * physH);
|
|
202
|
+
return [Math.min(physW - 1, x), Math.min(physH - 1, y)];
|
|
203
|
+
}
|
|
204
|
+
/** 物理像素 → 归一化坐标(0-1000)。 */
|
|
205
|
+
export function physicalToNorm(px, py, physW, physH) {
|
|
206
|
+
return [clamp1000(Math.round((px / physW) * 1000)), clamp1000(Math.round((py / physH) * 1000))];
|
|
207
|
+
}
|
|
208
|
+
/** 归一化矩形 [x,y,w,h](0-1000)→ 物理矩形像素。 */
|
|
209
|
+
export function normRectToPhysical(rect, physW, physH) {
|
|
210
|
+
const [nx, ny, nw, nh] = rect;
|
|
211
|
+
const [x0, y0] = normToPhysical(nx, ny, physW, physH);
|
|
212
|
+
const [x1, y1] = normToPhysical(nx + nw, ny + nh, physW, physH);
|
|
213
|
+
return { x: x0, y: y0, w: Math.max(1, x1 - x0), h: Math.max(1, y1 - y0) };
|
|
214
|
+
}
|
|
215
|
+
function clamp1000(v) {
|
|
216
|
+
if (Number.isNaN(v))
|
|
217
|
+
return 0;
|
|
218
|
+
return Math.max(0, Math.min(1000, Math.round(v)));
|
|
219
|
+
}
|
|
220
|
+
/** 校验一个值是否为有限数且落在 norm1000 区间。 */
|
|
221
|
+
export function isNormCoord(v) {
|
|
222
|
+
return typeof v === 'number' && Number.isFinite(v) && v >= 0 && v <= 1000;
|
|
223
|
+
}
|
package/dist/sandbox/index.js
CHANGED
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
export { getSandboxRoot, setSandboxRoot, withSandboxRoot } from './root.js';
|
|
3
3
|
export { jailResolve, jailGlobPattern, isInsideRoot } from './jail.js';
|
|
4
4
|
export { filterEnv, isCommandDenied } from './command.js';
|
|
5
|
-
export { SANDBOX_EXEMPT_TOOLS, SANDBOX_PATH_TOOLS, enforceSandbox
|
|
5
|
+
export { SANDBOX_EXEMPT_TOOLS, SANDBOX_PATH_TOOLS, enforceSandbox } from './policy.js';
|
package/dist/sandbox/policy.js
CHANGED
|
@@ -14,9 +14,15 @@ import { jailResolve, jailGlobPattern } from './jail.js';
|
|
|
14
14
|
* Shift+Tab 等用户面触发,不经工具路径。
|
|
15
15
|
*/
|
|
16
16
|
export const SANDBOX_EXEMPT_TOOLS = new Set([
|
|
17
|
-
'memory_save',
|
|
17
|
+
'memory_save',
|
|
18
|
+
'memory_update',
|
|
19
|
+
'memory_forget',
|
|
20
|
+
'memory_search',
|
|
21
|
+
'memory_list',
|
|
22
|
+
'memory_graph',
|
|
18
23
|
'use_skill',
|
|
19
|
-
'web_search',
|
|
24
|
+
'web_search',
|
|
25
|
+
'web_fetch',
|
|
20
26
|
'ask_human',
|
|
21
27
|
'sub-agent',
|
|
22
28
|
]);
|
package/dist/session/compact.js
CHANGED
|
@@ -19,7 +19,7 @@ export function truncateMid(text, max) {
|
|
|
19
19
|
return text;
|
|
20
20
|
const removed = text.length - max;
|
|
21
21
|
const marker = `…[已截断 ${removed} 字符]…`;
|
|
22
|
-
|
|
22
|
+
const remain = max - marker.length;
|
|
23
23
|
if (remain <= 0)
|
|
24
24
|
return marker.slice(0, Math.max(0, max));
|
|
25
25
|
const head = Math.ceil(remain * 0.6);
|
|
@@ -179,7 +179,10 @@ export function splitSummaryText(raw) {
|
|
|
179
179
|
break;
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
|
-
const keyFacts = lines
|
|
182
|
+
const keyFacts = lines
|
|
183
|
+
.slice(headIdx + 1, endIdx)
|
|
184
|
+
.join('\n')
|
|
185
|
+
.trim();
|
|
183
186
|
const rest = [...lines.slice(0, headIdx), ...lines.slice(endIdx)].join('\n').trim();
|
|
184
187
|
return { keyFacts, rest };
|
|
185
188
|
}
|
|
@@ -219,7 +222,10 @@ export function extractProgressSnapshot(summaryRest) {
|
|
|
219
222
|
for (const line of lines) {
|
|
220
223
|
const m = line.match(/^ {0,3}##\s+(.+?)\s*$/);
|
|
221
224
|
if (m) {
|
|
222
|
-
const name = m[1]
|
|
225
|
+
const name = m[1]
|
|
226
|
+
.replace(/\s*[—–-].*$/, '')
|
|
227
|
+
.trim()
|
|
228
|
+
.toLowerCase();
|
|
223
229
|
taking = WANT.has(name);
|
|
224
230
|
}
|
|
225
231
|
if (taking)
|
|
@@ -275,10 +281,7 @@ function isUserGroup(g) {
|
|
|
275
281
|
/** 摘要 group 判定:非 history[0] 的 system 消息,以 `# 会话摘要` 开头,且不带 tool_calls。 */
|
|
276
282
|
function isSummaryGroup(g) {
|
|
277
283
|
const a = g.assistant;
|
|
278
|
-
return
|
|
279
|
-
a.role === 'system' &&
|
|
280
|
-
g.tools.length === 0 &&
|
|
281
|
-
toText(a.content).startsWith(SUMMARY_MARKER));
|
|
284
|
+
return !!a && a.role === 'system' && g.tools.length === 0 && toText(a.content).startsWith(SUMMARY_MARKER);
|
|
282
285
|
}
|
|
283
286
|
/** 从尾向头划分 group;history[0](system)排除。连续 tool 归到前导 assistant。 */
|
|
284
287
|
function groupFromEnd(history) {
|
|
@@ -292,9 +295,7 @@ function groupFromEnd(history) {
|
|
|
292
295
|
tools.unshift(history[i]);
|
|
293
296
|
i--;
|
|
294
297
|
}
|
|
295
|
-
if (i >= 1 &&
|
|
296
|
-
history[i].role === 'assistant' &&
|
|
297
|
-
history[i].tool_calls) {
|
|
298
|
+
if (i >= 1 && history[i].role === 'assistant' && history[i].tool_calls) {
|
|
298
299
|
groups.unshift({ assistant: history[i], tools });
|
|
299
300
|
i--;
|
|
300
301
|
}
|
|
@@ -377,7 +378,7 @@ function capMessageText(text, max) {
|
|
|
377
378
|
return text;
|
|
378
379
|
const removed = text.length - max;
|
|
379
380
|
const marker = `\n…[中间 ${removed} 字符已省略]…\n`;
|
|
380
|
-
|
|
381
|
+
const remain = max - marker.length;
|
|
381
382
|
if (remain <= 0)
|
|
382
383
|
return text.slice(0, Math.max(0, max)) + marker;
|
|
383
384
|
const head = Math.ceil(remain * 0.6);
|
|
@@ -448,7 +449,7 @@ async function defaultSummarize(older, focus, signal) {
|
|
|
448
449
|
content: 'You are an aggressive session compressor writing a handoff note for an agent that lost its context. ' +
|
|
449
450
|
'The agent will continue the task with ONLY your summary plus a few most-recent messages, so your summary is the sole memory of everything older.\n' +
|
|
450
451
|
'Output ONLY the summary body in this exact structure (omit empty sections):\n' +
|
|
451
|
-
|
|
452
|
+
"## Objective — the user's core request(s); cover EVERY distinct user request in the transcript, in order, noting which are completed vs pending.\n" +
|
|
452
453
|
'## Completed — what is already done: files created/modified (exact paths), key change per file, commands run and their outcomes (pass/fail, key numbers), decisions made and why.\n' +
|
|
453
454
|
'## In Progress — what is being worked on right now and exactly where it stopped (e.g. "edit applied to foo.ts, test not yet run").\n' +
|
|
454
455
|
'## Next Steps — the concrete next actions in order.\n' +
|
|
@@ -503,24 +504,19 @@ export async function compactHistory(history, opts) {
|
|
|
503
504
|
const pinnedGroups = allGroups.filter(isSummaryGroup);
|
|
504
505
|
const groups = pinnedGroups.length > 0 ? allGroups.filter((g) => !isSummaryGroup(g)) : allGroups;
|
|
505
506
|
const pinned = pinnedGroups.length > 0
|
|
506
|
-
? splitSummaryText(pinnedGroups
|
|
507
|
-
.map((g) => toText(g.assistant?.content))
|
|
508
|
-
.join('\n\n'))
|
|
507
|
+
? splitSummaryText(pinnedGroups.map((g) => toText(g.assistant?.content)).join('\n\n'))
|
|
509
508
|
: null;
|
|
510
509
|
// 保近期:按策略中的 token 比例累积(至少保 1 组),永不劈开 group。
|
|
511
510
|
// force(手动 /compact 默认、硬闸触发)用更激进的保留比例;再加绝对上限,
|
|
512
511
|
// 防大窗口(如 256k)下保留区按比例仍过大——压缩目标 = 摘要 + 最小续工上下文。
|
|
513
|
-
const keepRatio = opts.force
|
|
514
|
-
? DEFAULT_BUDGET_POLICY.compactForceKeepRatio
|
|
515
|
-
: DEFAULT_BUDGET_POLICY.compactKeepRatio;
|
|
512
|
+
const keepRatio = opts.force ? DEFAULT_BUDGET_POLICY.compactForceKeepRatio : DEFAULT_BUDGET_POLICY.compactKeepRatio;
|
|
516
513
|
const keepBudget = Math.min(Math.floor(opts.window * keepRatio), DEFAULT_BUDGET_POLICY.compactKeepMaxTokens);
|
|
517
514
|
const kept = [];
|
|
518
515
|
let keptTokens = 0;
|
|
519
516
|
for (let k = groups.length - 1; k >= 0; k--) {
|
|
520
517
|
const g = groups[k];
|
|
521
518
|
const nextTokens = keptTokens + groupTokens(g);
|
|
522
|
-
if (kept.length >= 1
|
|
523
|
-
&& correctTokenEstimate(nextTokens, state.correction) > keepBudget)
|
|
519
|
+
if (kept.length >= 1 && correctTokenEstimate(nextTokens, state.correction) > keepBudget)
|
|
524
520
|
break;
|
|
525
521
|
kept.unshift(g);
|
|
526
522
|
keptTokens = nextTokens;
|
|
@@ -576,8 +572,7 @@ export async function compactHistory(history, opts) {
|
|
|
576
572
|
// force 但只剩单组(如首轮的单个 assistant+tools 组):原地微压缩、不动结构。
|
|
577
573
|
// 纯 user 原话组无可压项(不能毁掉用户请求),落到下方 noop 提示。
|
|
578
574
|
const single = groups[0];
|
|
579
|
-
const userOnly = single.tools.length === 0
|
|
580
|
-
&& single.assistant?.role === 'user';
|
|
575
|
+
const userOnly = single.tools.length === 0 && single.assistant?.role === 'user';
|
|
581
576
|
if (!userOnly && microcompactGroup(single)) {
|
|
582
577
|
const estimateAfter = estimatePromptTokens(history, activeTools, state.correction);
|
|
583
578
|
state.lastEstimate = estimateAfter;
|
|
@@ -642,9 +637,7 @@ export async function compactHistory(history, opts) {
|
|
|
642
637
|
}
|
|
643
638
|
if (summary) {
|
|
644
639
|
const artifactRefs = collectArtifactRefs(older);
|
|
645
|
-
const provenance = artifactRefs.length > 0
|
|
646
|
-
? `\n\n[artifact refs: ${artifactRefs.join(', ')}]`
|
|
647
|
-
: '';
|
|
640
|
+
const provenance = artifactRefs.length > 0 ? `\n\n[artifact refs: ${artifactRefs.join(', ')}]` : '';
|
|
648
641
|
// 钉住的 Key Facts 拼在段尾:单条 system 消息里尾部注意力最强,老事实不会被"读漏"。
|
|
649
642
|
// 恒为单条——并排多条 system 摘要会触发近因效应,早期摘要形同虚设。
|
|
650
643
|
const parsed = splitSummaryText(summary);
|
|
@@ -658,12 +651,7 @@ export async function compactHistory(history, opts) {
|
|
|
658
651
|
if (snapshot)
|
|
659
652
|
writeCompactionSnapshot(snapshot);
|
|
660
653
|
}
|
|
661
|
-
const body = [
|
|
662
|
-
parsed.rest,
|
|
663
|
-
pinnedFacts ? `## Key Facts\n${pinnedFacts}` : '',
|
|
664
|
-
]
|
|
665
|
-
.filter(Boolean)
|
|
666
|
-
.join('\n\n');
|
|
654
|
+
const body = [parsed.rest, pinnedFacts ? `## Key Facts\n${pinnedFacts}` : ''].filter(Boolean).join('\n\n');
|
|
667
655
|
const summaryMsg = {
|
|
668
656
|
role: 'system',
|
|
669
657
|
content: `${SUMMARY_MARKER}\n${body}${provenance}`,
|
|
@@ -754,9 +742,9 @@ signal) {
|
|
|
754
742
|
const isManual = manualOpts?.manual === true;
|
|
755
743
|
// Hard guard: the scheduler has already attempted pressure cleanup; if raw
|
|
756
744
|
// content remains near the provider limit, compact even when correction < 1.
|
|
757
|
-
let hardCap = typeof report?.rawTotal === 'number'
|
|
758
|
-
|
|
759
|
-
|
|
745
|
+
let hardCap = typeof report?.rawTotal === 'number' &&
|
|
746
|
+
typeof report?.window === 'number' &&
|
|
747
|
+
report.rawTotal >= DEFAULT_BUDGET_POLICY.pressureTriggerRatio * report.window;
|
|
760
748
|
// 手动路径:旁路 autoCompact / report / 总阈三重门
|
|
761
749
|
if (!isManual) {
|
|
762
750
|
if (!hardCap && !config.autoCompact)
|
package/dist/session/index.js
CHANGED
|
@@ -9,8 +9,8 @@ export { compactHistory, maybeCompact, capToolResultForHistory, truncateMid, con
|
|
|
9
9
|
// agent/core.ts 在 age-aware sweep 后调用 runScheduler(history, step):评估五区预算,
|
|
10
10
|
// 只执行可落地的 warn / compact_history;开关关闭时退化为 maybeCompact。
|
|
11
11
|
// repl /compact 命令调 manualCompact(history, focus?):与自动路径共享决策,focus 透传摘要 prompt。
|
|
12
|
-
export { runScheduler, manualCompact, createBudgetScheduler
|
|
13
|
-
export { newSessionId, saveSession, loadSession, listSessions, sessionDir
|
|
12
|
+
export { runScheduler, manualCompact, createBudgetScheduler } from './scheduler.js';
|
|
13
|
+
export { newSessionId, saveSession, loadSession, listSessions, sessionDir } from './persist.js';
|
|
14
14
|
export { appendCurrentSessionTrace, appendCurrentSessionTraceEvent, appendCurrentSessionRuntimeEvent, createTraceEvent, } from './trace.js';
|
|
15
15
|
export { reduceTraceMetrics, readTraceEvents, readTraceMetrics } from './trace-metrics.js';
|
|
16
16
|
export { summarizeToolArguments, hashTraceValue, safeProviderId } from './trace-sanitize.js';
|
package/dist/session/notes.js
CHANGED
|
@@ -27,9 +27,21 @@ export function getNotesMtime(sessionId = getCurrentSessionId()) {
|
|
|
27
27
|
return null;
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
|
+
/** 单个步骤短标签的字符上限(软截断,防模型把 content 整段塞进 title)。 */
|
|
31
|
+
export const PLAN_STEP_TITLE_MAX = 60;
|
|
32
|
+
/** 清洗步骤短标签:压平换行、去掉星号(避免破坏 `**title**` 的行首解析)。 */
|
|
33
|
+
function sanitizeStepTitle(raw) {
|
|
34
|
+
return raw
|
|
35
|
+
.replace(/[\r\n]+/g, ' ')
|
|
36
|
+
.replace(/\*/g, '')
|
|
37
|
+
.replace(/\s+/g, ' ')
|
|
38
|
+
.trim()
|
|
39
|
+
.slice(0, PLAN_STEP_TITLE_MAX);
|
|
40
|
+
}
|
|
30
41
|
/**
|
|
31
42
|
* 把 plan 渲染为 canonical markdown 段(不含首尾多余空行)。
|
|
32
43
|
* 复选框格式 `- [ ] N.` / `- [x] N.` 与 repl 状态栏 chip 的计数正则严格对齐;
|
|
44
|
+
* 步骤正文为 `**短标签** — 详细描述`(有 title 时)或裸 content(老 plan 兼容),
|
|
33
45
|
* in_progress 步骤追加 ` ◀ 当前` 后缀(仍计为未完成),让 compact 重注入后模型能直接续上。
|
|
34
46
|
* 全部完成时标题写为 `## Done:`(自动结算),extractActivePlanSection 随之返回 null。
|
|
35
47
|
*/
|
|
@@ -42,15 +54,34 @@ export function renderPlanSection(plan) {
|
|
|
42
54
|
lines.push('', '### Steps');
|
|
43
55
|
plan.steps.forEach((s, i) => {
|
|
44
56
|
const box = s.status === 'completed' ? '[x]' : '[ ]';
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
lines.push(`- ${box} ${i + 1}. ${
|
|
57
|
+
const title = s.title ? sanitizeStepTitle(s.title) : '';
|
|
58
|
+
const body = title ? `**${title}** — ${s.content}` : s.content;
|
|
59
|
+
const suffix = s.status === 'in_progress' ? ` ◀ ${(s.activeForm ?? '').trim() || 'current'}` : '';
|
|
60
|
+
lines.push(`- ${box} ${i + 1}. ${body}${suffix}`);
|
|
49
61
|
});
|
|
50
62
|
const done = plan.steps.filter((s) => s.status === 'completed').length;
|
|
51
63
|
lines.push('', '### Progress', `- ${done}/${plan.steps.length} steps complete`);
|
|
52
64
|
return lines.join('\n');
|
|
53
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* 从步骤行正文(去掉 `- [ ] N. ` 前缀后)提取 UI 用的短标签。兼容三种写法:
|
|
68
|
+
* - `**短标签** — 详细描述 ◀ 进行中` (plan_update 写了 title 的新格式)
|
|
69
|
+
* - `**短标签** — 详细描述`
|
|
70
|
+
* - `详细描述 ◀ 进行中` (老 plan,无 title)
|
|
71
|
+
* 优先级 title > activeForm > content:状态栏 chip 只有一行,短的先上,细节交给
|
|
72
|
+
* composePlanLines 截断保单行(脚栏不撑高)。老 plan 无 title 时行为与加 title 前一致。
|
|
73
|
+
* 与 renderPlanSection 配对(一个写一个读),格式定义与解析同处,避免散落两处走样。
|
|
74
|
+
*/
|
|
75
|
+
export function planStepLabel(raw) {
|
|
76
|
+
const body = raw.trim();
|
|
77
|
+
const afIdx = body.lastIndexOf('◀');
|
|
78
|
+
const activeForm = afIdx >= 0 ? body.slice(afIdx + 1).trim() : '';
|
|
79
|
+
const main = (afIdx >= 0 ? body.slice(0, afIdx) : body).trim();
|
|
80
|
+
const m = main.match(/^\*\*(.+?)\*\*(?:\s*—\s*)?([\s\S]*)$/);
|
|
81
|
+
const title = m?.[1]?.trim() ?? '';
|
|
82
|
+
const content = (m ? (m[2] ?? '') : main).trim();
|
|
83
|
+
return title || activeForm || content || main;
|
|
84
|
+
}
|
|
54
85
|
/** 读取当前活跃 `## Plan:` 段的标题;无活跃 plan 返 null。 */
|
|
55
86
|
export function readActivePlanTitle(sessionId = getCurrentSessionId()) {
|
|
56
87
|
const p = getNotesFilePath(sessionId);
|
|
@@ -127,7 +158,10 @@ const SECTION_PRIORITY = {
|
|
|
127
158
|
// compaction_snapshot 提到最高:它是压缩当次的权威进度快照,比手填 findings 更能
|
|
128
159
|
// 直接告诉模型「做到哪了」,压缩后第一步最该看到的就是它。
|
|
129
160
|
compaction_snapshot: 5,
|
|
130
|
-
risks: 4,
|
|
161
|
+
risks: 4,
|
|
162
|
+
findings: 3,
|
|
163
|
+
decisions: 2,
|
|
164
|
+
open_questions: 1,
|
|
131
165
|
};
|
|
132
166
|
/** 常驻笔记正文总预算(token)。5k:占百万级 context 的 0.5%,可常驻相当量笔记。 */
|
|
133
167
|
const NOTES_INJECT_BUDGET_TOKENS = 5000;
|
package/dist/session/persist.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { config, getActiveModel } from '../config/index.js';
|
|
4
|
+
import { isToolRouteGroupName } from '../config/profiles.js';
|
|
4
5
|
import { truncateDisplay } from '../ui/render.js';
|
|
5
6
|
/** 会话目录(确保存在)。 */
|
|
6
7
|
export function sessionDir() {
|
|
@@ -35,7 +36,9 @@ function toText(content) {
|
|
|
35
36
|
function firstUserOf(history) {
|
|
36
37
|
for (const m of history) {
|
|
37
38
|
if (m.role === 'user') {
|
|
38
|
-
const text = toText(m.content)
|
|
39
|
+
const text = toText(m.content)
|
|
40
|
+
.replace(/\n/g, ' ')
|
|
41
|
+
.trim();
|
|
39
42
|
return truncateDisplay(text, 40);
|
|
40
43
|
}
|
|
41
44
|
}
|
|
@@ -45,7 +48,7 @@ function sessionPath(id) {
|
|
|
45
48
|
return path.join(config.sessionDir, id, 'session.json');
|
|
46
49
|
}
|
|
47
50
|
/** 保存会话到磁盘。全新且没有 query 的会话不创建文件;已有会话即使回滚为空也必须覆盖旧记录。 */
|
|
48
|
-
export function saveSession(history, id, queryHistory = []) {
|
|
51
|
+
export function saveSession(history, id, queryHistory = [], lastToolGroups = []) {
|
|
49
52
|
const meta = {
|
|
50
53
|
id,
|
|
51
54
|
createdAt: idToIso(id),
|
|
@@ -56,15 +59,17 @@ export function saveSession(history, id, queryHistory = []) {
|
|
|
56
59
|
};
|
|
57
60
|
const currentPath = sessionPath(id);
|
|
58
61
|
const legacyPath = path.join(config.sessionDir, `${id}.json`);
|
|
59
|
-
if (history.length <= 1 &&
|
|
60
|
-
queryHistory.length === 0 &&
|
|
61
|
-
!existsSync(currentPath) &&
|
|
62
|
-
!existsSync(legacyPath)) {
|
|
62
|
+
if (history.length <= 1 && queryHistory.length === 0 && !existsSync(currentPath) && !existsSync(legacyPath)) {
|
|
63
63
|
return meta;
|
|
64
64
|
}
|
|
65
65
|
const dir = path.join(config.sessionDir, id);
|
|
66
66
|
mkdirSync(dir, { recursive: true });
|
|
67
|
-
const record = {
|
|
67
|
+
const record = {
|
|
68
|
+
...meta,
|
|
69
|
+
history,
|
|
70
|
+
queryHistory: [...queryHistory],
|
|
71
|
+
lastToolGroups: [...lastToolGroups],
|
|
72
|
+
};
|
|
68
73
|
writeFileSync(currentPath, JSON.stringify(record), 'utf8');
|
|
69
74
|
// 一旦写入新式目录,删除旧式扁平副本,避免已回滚消息仍残留在磁盘。
|
|
70
75
|
if (existsSync(legacyPath))
|
|
@@ -94,6 +99,7 @@ export function loadSession(id) {
|
|
|
94
99
|
queryHistory: Array.isArray(rec.queryHistory)
|
|
95
100
|
? rec.queryHistory.filter((query) => typeof query === 'string')
|
|
96
101
|
: undefined,
|
|
102
|
+
lastToolGroups: Array.isArray(rec.lastToolGroups) ? rec.lastToolGroups.filter(isToolRouteGroupName) : undefined,
|
|
97
103
|
};
|
|
98
104
|
}
|
|
99
105
|
catch {
|
|
@@ -8,8 +8,7 @@ import { pruneStaleArtifacts, refreshArtifactFreshness } from '../context/artifa
|
|
|
8
8
|
import { pruneSuperseded } from '../context/relevance.js';
|
|
9
9
|
import { createAgeAwareEncodingState } from '../context/age-aware.js';
|
|
10
10
|
function atPressure(report) {
|
|
11
|
-
return Math.max(report.rawTotal, report.total)
|
|
12
|
-
>= report.window * DEFAULT_BUDGET_POLICY.pressureTriggerRatio;
|
|
11
|
+
return Math.max(report.rawTotal, report.total) >= report.window * DEFAULT_BUDGET_POLICY.pressureTriggerRatio;
|
|
13
12
|
}
|
|
14
13
|
function emptyPressure(report) {
|
|
15
14
|
return {
|
|
@@ -51,7 +50,7 @@ export function createBudgetScheduler(state = contextState) {
|
|
|
51
50
|
const actions = scheduleActions(report);
|
|
52
51
|
let compactHistoryCalled = false;
|
|
53
52
|
let historyRebuilt = false;
|
|
54
|
-
for (const
|
|
53
|
+
for (const _action of actions) {
|
|
55
54
|
const result = await maybeCompact(history, report, undefined, state, activeTools, signal);
|
|
56
55
|
compactHistoryCalled = true;
|
|
57
56
|
historyRebuilt ||= result?.historyRebuilt === true;
|
|
@@ -112,7 +111,7 @@ export async function manualCompact(history, focus, opts) {
|
|
|
112
111
|
actions = [...actions, { kind: 'compact_history', focus }];
|
|
113
112
|
}
|
|
114
113
|
else if (focus) {
|
|
115
|
-
actions = actions.map((action) => action.kind === 'compact_history' ? { ...action, focus } : action);
|
|
114
|
+
actions = actions.map((action) => (action.kind === 'compact_history' ? { ...action, focus } : action));
|
|
116
115
|
}
|
|
117
116
|
let compactHistoryCalled = false;
|
|
118
117
|
let compactDetail;
|
|
@@ -51,9 +51,12 @@ export function readTraceEvents(file) {
|
|
|
51
51
|
continue;
|
|
52
52
|
try {
|
|
53
53
|
const value = JSON.parse(line);
|
|
54
|
-
if (value.schemaVersion === 1 &&
|
|
55
|
-
typeof value.
|
|
56
|
-
|
|
54
|
+
if (value.schemaVersion === 1 &&
|
|
55
|
+
typeof value.type === 'string' &&
|
|
56
|
+
typeof value.sessionId === 'string' &&
|
|
57
|
+
typeof value.turnId === 'number' &&
|
|
58
|
+
value.data &&
|
|
59
|
+
typeof value.data === 'object') {
|
|
57
60
|
events.push(value);
|
|
58
61
|
}
|
|
59
62
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
// inline skill 激活态(设计 §3.6):use_skill 成功加载 inline skill
|
|
1
|
+
// inline skill 激活态(设计 §3.6):use_skill 成功加载 inline skill 时置位,
|
|
2
2
|
// 该 turn 内模型的 disallowed-tools 约束生效(constants.getRuntimeDisabledTools 消费)。
|
|
3
|
-
//
|
|
3
|
+
// 同一 turn 连续加载多个 inline skill 时 deny 取并集,只能继续收窄;下一次真实用户轮清除。
|
|
4
4
|
//
|
|
5
5
|
// 刻意不设 allowed 授权:inline 加载不过信任门禁,自动放行 confirm 级工具会让
|
|
6
6
|
// 未信任的项目 skill 绕过权限确认。allowed-tools 只在 fork 模式下经 spawnAgent
|
|
@@ -9,12 +9,17 @@
|
|
|
9
9
|
// 叶子模块:仅依赖 toolmap(亦叶子),不引 tools/permissions/agent,避免环。
|
|
10
10
|
import { mapSkillToolName } from './toolmap.js';
|
|
11
11
|
let active = null;
|
|
12
|
-
/** use_skill 成功加载 inline skill
|
|
12
|
+
/** use_skill 成功加载 inline skill 时调用;同一 turn 的 deny 只累加、不撤销。 */
|
|
13
13
|
export function activateSkill(skill) {
|
|
14
|
-
const disallowed =
|
|
14
|
+
const disallowed = new Set(active?.disallowed ?? []);
|
|
15
|
+
for (const token of skill.disallowedTools ?? []) {
|
|
16
|
+
const mapped = mapSkillToolName(token);
|
|
17
|
+
if (mapped)
|
|
18
|
+
disallowed.add(mapped);
|
|
19
|
+
}
|
|
15
20
|
active = {
|
|
16
21
|
name: skill.name,
|
|
17
|
-
disallowed: disallowed
|
|
22
|
+
disallowed: disallowed.size > 0 ? disallowed : null,
|
|
18
23
|
};
|
|
19
24
|
}
|
|
20
25
|
/** 用户新轮开始时清激活态。 */
|
package/dist/skills/discover.js
CHANGED
|
@@ -89,23 +89,12 @@ function resolveContext(meta) {
|
|
|
89
89
|
return 'fork'; // 旧内部别名
|
|
90
90
|
return 'inline';
|
|
91
91
|
}
|
|
92
|
-
/**
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (!a)
|
|
96
|
-
return undefined;
|
|
97
|
-
const low = a.toLowerCase();
|
|
98
|
-
if (['explore', 'plan', 'research', 'read'].includes(low))
|
|
99
|
-
return 'read';
|
|
100
|
-
if (['general-purpose', 'write'].includes(low))
|
|
101
|
-
return 'write';
|
|
102
|
-
warnings.push(`未知 agent 值 "${a}",按 read(只读,保守)处理`);
|
|
103
|
-
return 'read';
|
|
104
|
-
}
|
|
105
|
-
/** 收集本轮不支持、已忽略的字段,用于 /skills 提示(不阻断加载)。 */
|
|
92
|
+
/** 收集本轮不支持、已忽略的字段,用于 /skills 提示(不阻断加载)。
|
|
93
|
+
* `agent:` 曾在旧 read/write 双模式下决定子 agent 工具面;子 agent 现与主 agent 同权,
|
|
94
|
+
* 该字段不再产生任何效果,按"已忽略"提示作者,避免其误以为仍有限权语义。 */
|
|
106
95
|
function collectWarnings(meta) {
|
|
107
96
|
const warnings = [];
|
|
108
|
-
for (const k of ['hooks', 'model', 'effort', 'paths']) {
|
|
97
|
+
for (const k of ['hooks', 'model', 'effort', 'paths', 'agent']) {
|
|
109
98
|
if (k in meta)
|
|
110
99
|
warnings.push(`字段 "${k}" 当前不支持,已忽略`);
|
|
111
100
|
}
|
|
@@ -230,7 +219,7 @@ export function discoverSkills() {
|
|
|
230
219
|
continue;
|
|
231
220
|
seenMdPaths.push(skillMdPath);
|
|
232
221
|
const content = readFileSync(skillMdPath, 'utf8');
|
|
233
|
-
const { meta
|
|
222
|
+
const { meta } = parseFrontmatter(content);
|
|
234
223
|
const skillName = (scalar(meta, 'name') || name).trim();
|
|
235
224
|
const description = (scalar(meta, 'description') || '').trim();
|
|
236
225
|
if (!description)
|
|
@@ -245,7 +234,6 @@ export function discoverSkills() {
|
|
|
245
234
|
skillMdPath,
|
|
246
235
|
// body 留空:用户/项目 skill 由 getSkillBody 走文件读取(保留热重载语义)。
|
|
247
236
|
context: resolveContext(meta),
|
|
248
|
-
agentMode: resolveAgentMode(meta, warnings),
|
|
249
237
|
allowedTools: asArray(meta, 'allowed-tools'),
|
|
250
238
|
disallowedTools: asArray(meta, 'disallowed-tools'),
|
|
251
239
|
modelInvocable: !truthy(meta, 'disable-model-invocation'),
|