newmark-agent 0.4.0 → 0.4.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.
- package/config.example.json +5 -0
- package/dist/conversation-utility-host.bundle.cjs +419 -34
- package/dist/core/agent.d.ts +52 -4
- package/dist/core/agent.js +260 -23
- package/dist/core/agentKernelRunner.js +33 -16
- package/dist/core/config.d.ts +8 -0
- package/dist/core/conversationKernel.d.ts +16 -0
- package/dist/core/conversationKernel.js +34 -1
- package/dist/core/toolPolicy.d.ts +9 -0
- package/dist/core/toolPolicy.js +128 -0
- package/dist/llm/provider.d.ts +13 -1
- package/dist/llm/provider.js +42 -1
- package/dist/providers/provider-adapter.d.ts +3 -1
- package/dist/toolchain/registry-seeder.js +1 -1
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.js +31 -1
- package/dist/tools/nativeTools.js +1 -0
- package/dist/tui/src/app.js +24 -0
- package/dist/tui/src/i18n.js +151 -0
- package/dist/tui/src/render.js +205 -90
- package/dist/tui/src/state.js +86 -0
- package/dist/ui/index.html +239 -71
- package/dist/ui/lucide-sprite.svg +5 -0
- package/dist/wsl-agent-host.bundle.cjs +419 -34
- package/package.json +7 -11
- package/Flow/Electron-Debug-Release.Flow.json +0 -43
- package/Flow/Flow.md +0 -9
- package/Flow/UI-Feature-Integration.Flow.json +0 -96
|
@@ -21,6 +21,22 @@ export interface AgentPromptMessage {
|
|
|
21
21
|
clientMessageId?: string;
|
|
22
22
|
guideId?: string;
|
|
23
23
|
runId?: string;
|
|
24
|
+
/**
|
|
25
|
+
* dev-0.4.3: 同一 Build block 内连续到达的多个 Guide 会被 conversation
|
|
26
|
+
* kernel 合并为一次 provider 续接,而不是每来一个 Guide 就响应一次。
|
|
27
|
+
* 数组顺序即用户提交顺序(顺序执行且自动接续)。
|
|
28
|
+
*/
|
|
29
|
+
batchGuides?: Array<{
|
|
30
|
+
clientMessageId: string;
|
|
31
|
+
guideId?: string;
|
|
32
|
+
text: string;
|
|
33
|
+
images?: Array<{
|
|
34
|
+
dataUrl: string;
|
|
35
|
+
name?: string;
|
|
36
|
+
type?: string;
|
|
37
|
+
}>;
|
|
38
|
+
attachments?: ConversationImageAttachment[];
|
|
39
|
+
}>;
|
|
24
40
|
routePolicy?: {
|
|
25
41
|
mode?: 'quality' | 'balanced' | 'cost' | 'speed';
|
|
26
42
|
maxQualityLoss?: number;
|
|
@@ -586,7 +586,40 @@ class ConversationKernel {
|
|
|
586
586
|
if (runtime.stopRequestedRunId === runtime.runId)
|
|
587
587
|
return this.result(runtime, lastTokens);
|
|
588
588
|
const next = runtime.pendingNextTurn.shift();
|
|
589
|
-
|
|
589
|
+
if (next.queueMode === 'steer' && typeof next.message !== 'string' && !!next.message.clientMessageId) {
|
|
590
|
+
const batchGuides = [];
|
|
591
|
+
const pushGuide = (message) => {
|
|
592
|
+
batchGuides.push({
|
|
593
|
+
clientMessageId: String(message.clientMessageId || ''),
|
|
594
|
+
guideId: message.guideId,
|
|
595
|
+
text: message.text,
|
|
596
|
+
images: message.images?.map(image => ({ ...image })),
|
|
597
|
+
attachments: message.attachments?.map(attachment => ({ ...attachment })),
|
|
598
|
+
});
|
|
599
|
+
};
|
|
600
|
+
pushGuide(next.message);
|
|
601
|
+
while (runtime.pendingNextTurn.length > 0
|
|
602
|
+
&& runtime.pendingNextTurn[0].queueMode === 'steer'
|
|
603
|
+
&& typeof runtime.pendingNextTurn[0].message !== 'string'
|
|
604
|
+
&& !!runtime.pendingNextTurn[0].message.clientMessageId) {
|
|
605
|
+
const guide = runtime.pendingNextTurn.shift();
|
|
606
|
+
pushGuide(guide.message);
|
|
607
|
+
}
|
|
608
|
+
if (batchGuides.length === 1) {
|
|
609
|
+
lastTokens = await this.runSingle(runtime, next.message, next.queueMode);
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
const batchText = batchGuides.map((guide, index) => `Guide ${index + 1}: ${guide.text}`).join('\n');
|
|
613
|
+
const batchMessage = {
|
|
614
|
+
text: `Apply the following intervening Guides in submission order within the current Build Block and continue automatically:\n${batchText}`,
|
|
615
|
+
hiddenUserInput: true,
|
|
616
|
+
batchGuides,
|
|
617
|
+
};
|
|
618
|
+
lastTokens = await this.runSingle(runtime, batchMessage, 'steer');
|
|
619
|
+
}
|
|
620
|
+
else {
|
|
621
|
+
lastTokens = await this.runSingle(runtime, next.message, next.queueMode);
|
|
622
|
+
}
|
|
590
623
|
}
|
|
591
624
|
const rootMessage = runtime.runner.subagents.readRootInbox()[0];
|
|
592
625
|
if (!rootMessage) {
|
|
@@ -25,4 +25,13 @@ export declare function toolAvailability(name: string): ToolAvailability;
|
|
|
25
25
|
export declare function evaluateToolPolicy(request: ToolPolicyRequest): ToolPolicyDecision;
|
|
26
26
|
export declare function filterToolDefinitions<T>(definitions: T[], request: Omit<ToolPolicyRequest, 'name' | 'args'>): T[];
|
|
27
27
|
export declare function planModePolicyPrompt(): string;
|
|
28
|
+
export interface DeletionGuardDecision {
|
|
29
|
+
blocked: boolean;
|
|
30
|
+
reason?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* 硬性删除命令审查。blocked=true 表示该命令构成脚本/命令批量删除,必须拒绝并
|
|
34
|
+
* 引导 Agent 使用受监管的 delete_file 工具逐个删除。
|
|
35
|
+
*/
|
|
36
|
+
export declare function evaluateDeletionGuard(command: string): DeletionGuardDecision;
|
|
28
37
|
//# sourceMappingURL=toolPolicy.d.ts.map
|
package/dist/core/toolPolicy.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.toolAvailability = toolAvailability;
|
|
|
7
7
|
exports.evaluateToolPolicy = evaluateToolPolicy;
|
|
8
8
|
exports.filterToolDefinitions = filterToolDefinitions;
|
|
9
9
|
exports.planModePolicyPrompt = planModePolicyPrompt;
|
|
10
|
+
exports.evaluateDeletionGuard = evaluateDeletionGuard;
|
|
10
11
|
const REQUIRED_TOOLS = new Set(['pwd', 'read', 'glob', 'grep']);
|
|
11
12
|
const MODE_SCOPED_TOOLS = new Set([
|
|
12
13
|
'image_inspect',
|
|
@@ -22,6 +23,8 @@ const MODE_SCOPED_TOOLS = new Set([
|
|
|
22
23
|
'read_tool_result',
|
|
23
24
|
'goal_manage',
|
|
24
25
|
'conversation_rename',
|
|
26
|
+
'task_read',
|
|
27
|
+
'task_create',
|
|
25
28
|
'question',
|
|
26
29
|
'task',
|
|
27
30
|
'subagent_list',
|
|
@@ -35,6 +38,7 @@ const MODE_SCOPED_TOOLS = new Set([
|
|
|
35
38
|
'branch_create',
|
|
36
39
|
]);
|
|
37
40
|
const PLAN_READ_ONLY_TOOLS = new Set([
|
|
41
|
+
'task_read',
|
|
38
42
|
'pwd',
|
|
39
43
|
'read',
|
|
40
44
|
'glob',
|
|
@@ -167,4 +171,128 @@ function planModePolicyPrompt() {
|
|
|
167
171
|
'Runtime policy rejects stale or hidden mutating tool calls even if a prompt asks for them.',
|
|
168
172
|
].join(' ');
|
|
169
173
|
}
|
|
174
|
+
/** 删除命令动词(跨 POSIX / PowerShell / cmd)。注意:不含单独 "remove"(避免匹配普通英文)。 */
|
|
175
|
+
const DELETE_VERB_SOURCE = '(?:remove-item|rmdir|unlink|erase|del|rm|rd|ri)';
|
|
176
|
+
const DELETE_VERB_BOUNDARY = new RegExp(`(?:^|[\\s;&|()\\n])${DELETE_VERB_SOURCE}(?:\\s|$)`, 'i');
|
|
177
|
+
function hasDeletionVerb(text) {
|
|
178
|
+
return DELETE_VERB_BOUNDARY.test(text);
|
|
179
|
+
}
|
|
180
|
+
function deletionVerbCount(text) {
|
|
181
|
+
const matches = text.match(new RegExp(DELETE_VERB_BOUNDARY.source, 'gi'));
|
|
182
|
+
return matches ? matches.length : 0;
|
|
183
|
+
}
|
|
184
|
+
/** 循环结构批量删除:foreach / for…in / for( / while( / bash do…done。 */
|
|
185
|
+
function hasLoopDeletion(text) {
|
|
186
|
+
const lower = text.toLowerCase();
|
|
187
|
+
if (/\bforeach\b/.test(lower))
|
|
188
|
+
return true; // PowerShell foreach
|
|
189
|
+
if (/\bfor\b\s*[$({]/.test(lower))
|
|
190
|
+
return true; // PowerShell/C for(...)
|
|
191
|
+
if (/\bfor\b\s+\S+\s+in\b/.test(lower))
|
|
192
|
+
return true; // bash for f in ...
|
|
193
|
+
if (/\bwhile\b\s*[({]/.test(lower))
|
|
194
|
+
return true; // while(...)
|
|
195
|
+
if (/\bwhile\b\s+\S/.test(lower) && /\bdo\b/.test(lower))
|
|
196
|
+
return true; // bash while ... do
|
|
197
|
+
if (/\bdone\b/.test(lower))
|
|
198
|
+
return true; // bash 循环结束标记
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
/** find -delete / find -exec rm / xargs rm 批量删除。 */
|
|
202
|
+
function hasFindXargsDeletion(text) {
|
|
203
|
+
if (/\bfind\b[^\n;&|]*-(?:delete\b|exec(?:dir)?\s+(?:rm|del|erase)\b)/i.test(text))
|
|
204
|
+
return true;
|
|
205
|
+
if (/\bxargs\b[^\n;&|]*\b(?:rm|del|erase|remove-item)\b/i.test(text))
|
|
206
|
+
return true;
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
/** 按 shell 语义切分参数:引号内的空格不拆分,返回去引号后的 token。 */
|
|
210
|
+
function splitCommandArgs(args) {
|
|
211
|
+
const tokens = [];
|
|
212
|
+
const re = /"([^"]*)"|'([^']*)'|(\S+)/g;
|
|
213
|
+
let m;
|
|
214
|
+
while ((m = re.exec(args)) !== null) {
|
|
215
|
+
const token = m[1] ?? m[2] ?? m[3] ?? '';
|
|
216
|
+
if (token)
|
|
217
|
+
tokens.push(token);
|
|
218
|
+
}
|
|
219
|
+
return tokens;
|
|
220
|
+
}
|
|
221
|
+
/** 管道接收端删除:上游产出多项,删除动词作为接收端即批量删除。 */
|
|
222
|
+
function hasPipeDeletion(text) {
|
|
223
|
+
return new RegExp(`\\|\\s*${DELETE_VERB_SOURCE}\\b`, 'i').test(text);
|
|
224
|
+
}
|
|
225
|
+
/** 递归删除标志:rm -r/-R/--recursive、Remove-Item -Recurse、rmdir/rd /s、del /s。 */
|
|
226
|
+
function hasRecursiveDeletionFlag(text) {
|
|
227
|
+
const lower = text.toLowerCase();
|
|
228
|
+
if (/\brm\b\s+(-[a-z]*r[a-z]*|--recursive)\b/.test(lower))
|
|
229
|
+
return true;
|
|
230
|
+
if (/\bremove-item\b[^\n;&|]*\s+-(?:recurse|r)\b/.test(lower))
|
|
231
|
+
return true;
|
|
232
|
+
if (/\b(?:rmdir|rd)\b\s+(-r\b|\/[s]\b)/.test(lower))
|
|
233
|
+
return true;
|
|
234
|
+
if (/\bdel\b\s+\/[s]\b/.test(lower))
|
|
235
|
+
return true;
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
/** 删除命令后跟含通配符的目标 token。 */
|
|
239
|
+
function hasWildcardDeletionTarget(text) {
|
|
240
|
+
const segmentRe = new RegExp(`(?:^|[\\s;&|()\\n])${DELETE_VERB_SOURCE}([\\s][^;&|\\n]*)?`, 'gi');
|
|
241
|
+
let m;
|
|
242
|
+
while ((m = segmentRe.exec(text)) !== null) {
|
|
243
|
+
const args = m[1] || '';
|
|
244
|
+
for (const token of splitCommandArgs(args)) {
|
|
245
|
+
if (!token || token.startsWith('-') || /^\/[A-Za-z]/.test(token))
|
|
246
|
+
continue;
|
|
247
|
+
if (/[*?]/.test(token))
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
/** 单条删除命令后跟 >= 2 个明确目标(非 flag、非 shell 开关)。 */
|
|
254
|
+
function hasMultipleDeleteTargets(text) {
|
|
255
|
+
const segmentRe = new RegExp(`(?:^|[\\s;&|()\\n])${DELETE_VERB_SOURCE}([\\s][^;&|\\n]*)?`, 'gi');
|
|
256
|
+
let m;
|
|
257
|
+
while ((m = segmentRe.exec(text)) !== null) {
|
|
258
|
+
const args = m[1] || '';
|
|
259
|
+
const targets = splitCommandArgs(args)
|
|
260
|
+
.filter(t => t && !t.startsWith('-') && !/^\/[A-Za-z]/.test(t) && !/^(&&|\|\||;|\||&|>|>>|<|2>&1)$/.test(t));
|
|
261
|
+
if (targets.length >= 2)
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* 硬性删除命令审查。blocked=true 表示该命令构成脚本/命令批量删除,必须拒绝并
|
|
268
|
+
* 引导 Agent 使用受监管的 delete_file 工具逐个删除。
|
|
269
|
+
*/
|
|
270
|
+
function evaluateDeletionGuard(command) {
|
|
271
|
+
const text = String(command || '');
|
|
272
|
+
if (!text.trim())
|
|
273
|
+
return { blocked: false };
|
|
274
|
+
// find -delete / find -exec rm 中,-delete 不含标准删除动词,需在入口单独识别为批量删除意图。
|
|
275
|
+
const findXargs = hasFindXargsDeletion(text);
|
|
276
|
+
if (!hasDeletionVerb(text) && !findXargs)
|
|
277
|
+
return { blocked: false };
|
|
278
|
+
const refuse = (kind) => ({
|
|
279
|
+
blocked: true,
|
|
280
|
+
reason: `[deletion guard] ${kind} batch deletion is not allowed. Delete files one by one with the delete_file tool under Agent supervision.`,
|
|
281
|
+
});
|
|
282
|
+
if (hasLoopDeletion(text))
|
|
283
|
+
return refuse('Loop-based');
|
|
284
|
+
if (findXargs)
|
|
285
|
+
return refuse('find/xargs');
|
|
286
|
+
if (hasPipeDeletion(text))
|
|
287
|
+
return refuse('Pipe-fed');
|
|
288
|
+
if (hasRecursiveDeletionFlag(text))
|
|
289
|
+
return refuse('Recursive');
|
|
290
|
+
if (hasWildcardDeletionTarget(text))
|
|
291
|
+
return refuse('Wildcard');
|
|
292
|
+
if (hasMultipleDeleteTargets(text))
|
|
293
|
+
return refuse('Multiple-target');
|
|
294
|
+
if (deletionVerbCount(text) >= 2)
|
|
295
|
+
return refuse('Multiple-statement');
|
|
296
|
+
return { blocked: false };
|
|
297
|
+
}
|
|
170
298
|
//# sourceMappingURL=toolPolicy.js.map
|
package/dist/llm/provider.d.ts
CHANGED
|
@@ -29,13 +29,25 @@ export declare class LLMProvider {
|
|
|
29
29
|
openAIMode: OpenAITransportMode | boolean;
|
|
30
30
|
useProviderAdaptersV2: boolean;
|
|
31
31
|
requestTimeoutMs: number;
|
|
32
|
+
/** dev-0.4.3 模型原生思考强度档位映射(模型名 → { 模型原生档位: Newmark 档位 })。 */
|
|
33
|
+
thinkingTierMaps?: Record<string, Record<string, string>> | undefined;
|
|
32
34
|
static nodeHttpTransport: ((method: 'GET' | 'POST', url: string, headers: Record<string, string>, body?: string) => Promise<NodeHttpResult>) | null;
|
|
33
35
|
static powershellTransport: ((method: 'GET' | 'POST', url: string, headers: Record<string, string>, body?: string) => Promise<NodeHttpResult>) | null;
|
|
34
|
-
constructor(name: string, baseUrl: string, apiKey: string, explicitProtocol?: ProviderProtocol | undefined, openAIMode?: OpenAITransportMode | boolean, useProviderAdaptersV2?: boolean, requestTimeoutMs?: number
|
|
36
|
+
constructor(name: string, baseUrl: string, apiKey: string, explicitProtocol?: ProviderProtocol | undefined, openAIMode?: OpenAITransportMode | boolean, useProviderAdaptersV2?: boolean, requestTimeoutMs?: number,
|
|
37
|
+
/** dev-0.4.3 模型原生思考强度档位映射(模型名 → { 模型原生档位: Newmark 档位 })。 */
|
|
38
|
+
thinkingTierMaps?: Record<string, Record<string, string>> | undefined);
|
|
35
39
|
private effectiveRequestTimeout;
|
|
36
40
|
private withRequestTimeout;
|
|
37
41
|
intelligenceConfig(tier: string): IntelligenceConfig;
|
|
38
42
|
private reasoningEffort;
|
|
43
|
+
/**
|
|
44
|
+
* dev-0.4.3 模型原生思考强度档位映射。不同模型的原生 reasoning_effort
|
|
45
|
+
* 档位配置可能不同(档位数量或档位命名不同)。模型配置 `thinking_tier_map`
|
|
46
|
+
* 以「模型原生档位名 → Newmark 档位」声明映射,这里把 Newmark 档位反查为
|
|
47
|
+
* 模型原生档位名;未配置映射(或映射为空)时返回 undefined,由调用方
|
|
48
|
+
* 维持默认透传行为(默认不变动映射)。
|
|
49
|
+
*/
|
|
50
|
+
private mappedNativeEffort;
|
|
39
51
|
private applyChatReasoningEffort;
|
|
40
52
|
private protocol;
|
|
41
53
|
private openAITransportMode;
|
package/dist/llm/provider.js
CHANGED
|
@@ -98,9 +98,12 @@ class LLMProvider {
|
|
|
98
98
|
openAIMode;
|
|
99
99
|
useProviderAdaptersV2;
|
|
100
100
|
requestTimeoutMs;
|
|
101
|
+
thinkingTierMaps;
|
|
101
102
|
static nodeHttpTransport = null;
|
|
102
103
|
static powershellTransport = null;
|
|
103
|
-
constructor(name, baseUrl, apiKey, explicitProtocol, openAIMode = 'chat_stream', useProviderAdaptersV2 = false, requestTimeoutMs = DEFAULT_PROVIDER_REQUEST_TIMEOUT_MS
|
|
104
|
+
constructor(name, baseUrl, apiKey, explicitProtocol, openAIMode = 'chat_stream', useProviderAdaptersV2 = false, requestTimeoutMs = DEFAULT_PROVIDER_REQUEST_TIMEOUT_MS,
|
|
105
|
+
/** dev-0.4.3 模型原生思考强度档位映射(模型名 → { 模型原生档位: Newmark 档位 })。 */
|
|
106
|
+
thinkingTierMaps) {
|
|
104
107
|
this.name = name;
|
|
105
108
|
this.baseUrl = baseUrl;
|
|
106
109
|
this.apiKey = apiKey;
|
|
@@ -108,6 +111,7 @@ class LLMProvider {
|
|
|
108
111
|
this.openAIMode = openAIMode;
|
|
109
112
|
this.useProviderAdaptersV2 = useProviderAdaptersV2;
|
|
110
113
|
this.requestTimeoutMs = requestTimeoutMs;
|
|
114
|
+
this.thinkingTierMaps = thinkingTierMaps;
|
|
111
115
|
}
|
|
112
116
|
effectiveRequestTimeout(timeoutMs) {
|
|
113
117
|
const requested = Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : DEFAULT_PROVIDER_REQUEST_TIMEOUT_MS;
|
|
@@ -140,6 +144,9 @@ class LLMProvider {
|
|
|
140
144
|
}
|
|
141
145
|
}
|
|
142
146
|
reasoningEffort(model, tier) {
|
|
147
|
+
const mapped = this.mappedNativeEffort(model, tier);
|
|
148
|
+
if (mapped !== undefined)
|
|
149
|
+
return mapped;
|
|
143
150
|
if (!/^(?:gpt-5|o[134](?:-|$)|codex)|(?:reasoner|reasoning|deepseek-r1|deepseek-reasoner|\br1\b)/i.test(model))
|
|
144
151
|
return undefined;
|
|
145
152
|
const effort = tier === 'low' || tier === 'high' || tier === 'xhigh' || tier === 'max'
|
|
@@ -150,6 +157,39 @@ class LLMProvider {
|
|
|
150
157
|
// OpenAI-compatible/Codex gateways may expose the user-facing max tier.
|
|
151
158
|
return effort === 'max' && /^https:\/\/(?:api\.)?openai\.com(?:\/|$)/i.test(this.cleanBaseUrl()) ? 'xhigh' : effort;
|
|
152
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* dev-0.4.3 模型原生思考强度档位映射。不同模型的原生 reasoning_effort
|
|
162
|
+
* 档位配置可能不同(档位数量或档位命名不同)。模型配置 `thinking_tier_map`
|
|
163
|
+
* 以「模型原生档位名 → Newmark 档位」声明映射,这里把 Newmark 档位反查为
|
|
164
|
+
* 模型原生档位名;未配置映射(或映射为空)时返回 undefined,由调用方
|
|
165
|
+
* 维持默认透传行为(默认不变动映射)。
|
|
166
|
+
*/
|
|
167
|
+
mappedNativeEffort(model, tier) {
|
|
168
|
+
const map = this.thinkingTierMaps?.[model];
|
|
169
|
+
if (!map || typeof map !== 'object')
|
|
170
|
+
return undefined;
|
|
171
|
+
const order = ['low', 'medium', 'high', 'xhigh', 'max'];
|
|
172
|
+
const entries = Object.entries(map)
|
|
173
|
+
.filter((entry) => order.includes(entry[1]))
|
|
174
|
+
.sort((a, b) => order.indexOf(a[1]) - order.indexOf(b[1]));
|
|
175
|
+
if (!entries.length)
|
|
176
|
+
return undefined;
|
|
177
|
+
const normalized = tier === 'ultra'
|
|
178
|
+
? 'max'
|
|
179
|
+
: (order.includes(tier) ? tier : 'medium');
|
|
180
|
+
const exact = entries.find(([, newmark]) => newmark === normalized);
|
|
181
|
+
if (exact)
|
|
182
|
+
return exact[0];
|
|
183
|
+
// 就近降级:取强度不超过目标档位的最高已映射档位
|
|
184
|
+
const targetIndex = order.indexOf(normalized);
|
|
185
|
+
for (let i = targetIndex; i >= 0; i--) {
|
|
186
|
+
const candidate = entries.find(([, newmark]) => newmark === order[i]);
|
|
187
|
+
if (candidate)
|
|
188
|
+
return candidate[0];
|
|
189
|
+
}
|
|
190
|
+
// 全部高于目标档位:取最低档位
|
|
191
|
+
return entries[0]?.[0];
|
|
192
|
+
}
|
|
153
193
|
applyChatReasoningEffort(body, model, tier) {
|
|
154
194
|
const effort = this.reasoningEffort(model, tier);
|
|
155
195
|
if (effort)
|
|
@@ -859,6 +899,7 @@ class LLMProvider {
|
|
|
859
899
|
tools: this.toNormalizedTools(tools),
|
|
860
900
|
temperature,
|
|
861
901
|
maxOutputTokens: maxTokens,
|
|
902
|
+
reasoningEffort: this.reasoningEffort(model, reasoningTier),
|
|
862
903
|
apiKey: this.apiKey,
|
|
863
904
|
baseUrl: this.cleanBaseUrl(),
|
|
864
905
|
...(sessionId ? { sessionId } : {}),
|
|
@@ -52,7 +52,9 @@ export interface NormalizedAgentRequest {
|
|
|
52
52
|
tools: NormalizedTool[];
|
|
53
53
|
temperature: number;
|
|
54
54
|
maxOutputTokens: number;
|
|
55
|
-
|
|
55
|
+
/** dev-0.4.3:模型原生思考强度档位名。Newmark 档位经模型配置
|
|
56
|
+
* `thinking_tier_map` 映射后可能不再是 Newmark 档位名,故为自由字符串。 */
|
|
57
|
+
reasoningEffort?: string;
|
|
56
58
|
apiKey: string;
|
|
57
59
|
baseUrl: string;
|
|
58
60
|
/** 可选的会话标识。仅当目标 provider 显式支持 session_id 语义的上下文缓存
|
|
@@ -15,7 +15,7 @@ const DOMAIN_PREFIXES = [
|
|
|
15
15
|
[/^web_/, 'web'],
|
|
16
16
|
[/^computer_use$/, 'computer'],
|
|
17
17
|
[/^(image_|ocr_|pdf_)/, 'media'],
|
|
18
|
-
[/^(bash|pwd|read|write|edit|glob|grep)$/, 'core'],
|
|
18
|
+
[/^(bash|pwd|read|write|edit|delete_file|glob|grep)$/, 'core'],
|
|
19
19
|
];
|
|
20
20
|
const READ_TOOL_PATTERN = /^(pwd|read|glob|grep|git_status|git_log|git_diff|git_branch|git_show|memory_lab_read|memory_lab_query|skill|linked_plan|build_history_query|subagent_read|subagent_result|subagent_list|subagent_progress|question|image_inspect|image_display|ocr_read|pdf_read|automation_list|automation_status)$/;
|
|
21
21
|
/**
|
package/dist/tools/index.d.ts
CHANGED
package/dist/tools/index.js
CHANGED
|
@@ -227,6 +227,7 @@ class ToolExecutor {
|
|
|
227
227
|
t('read', 'Read file contents. Use ABSOLUTE paths. The working directory is given in system prompt.', { path: { type: 'string' } }, ['path']),
|
|
228
228
|
t('write', 'Write/create a file. Use ABSOLUTE paths.', { path: { type: 'string' }, content: { type: 'string' } }, ['path', 'content']),
|
|
229
229
|
t('edit', 'Edit file with find-and-replace. Use ABSOLUTE paths.', { path: { type: 'string' }, old_str: { type: 'string' }, new_str: { type: 'string' } }, ['path', 'old_str', 'new_str']),
|
|
230
|
+
t('delete_file', 'Delete ONE file under Agent supervision. Use ABSOLUTE paths. This tool refuses directory deletion and wildcard paths; delete files one by one. Never use bash rm/del/Remove-Item for batch (recursive/wildcard/loop/pipe/multi-target) deletion — the runtime hard-blocks such commands.', { path: { type: 'string' } }, ['path']),
|
|
230
231
|
t('glob', 'Find files by glob pattern (e.g. **/*.ts, src/**/*.html)', { pattern: { type: 'string' } }, ['pattern']),
|
|
231
232
|
t('grep', 'Search file content with regex', { pattern: { type: 'string' }, path: { type: 'string' } }, ['pattern', 'path']),
|
|
232
233
|
t('web_search', 'Search the web', { query: { type: 'string' } }, ['query']),
|
|
@@ -371,7 +372,9 @@ class ToolExecutor {
|
|
|
371
372
|
t('background_tool', 'Run a tool call in the background WITHOUT blocking the conversation turn. Pass the target tool name and its arguments; this tool returns a background_id IMMEDIATELY, and the real tool keeps running in the background. The result is persisted and can be retrieved later with read_tool_result. Use this for long-running or non-critical tools (bash, web_fetch, long read/grep) so the conversation continues without waiting. The background result stays OUT of context until you explicitly read it, preserving prompt-cache hit rate. Orchestration/flow/subagent/question tools cannot be backgrounded.', { tool: { type: 'string', description: 'The tool name to run in the background (e.g. bash, web_fetch, read, grep).' }, args: { type: 'object', description: 'The arguments object for the target tool, matching its normal schema.' } }, ['tool']),
|
|
372
373
|
t('read_tool_result', 'Read the result of a background tool. Pass the background_id returned by background_tool. When status is running, returns a running marker; when done, returns the persisted result (optionally release it from storage after reading); when error, returns the failure. Background results are released from storage only when you set release=true.', { background_id: { type: 'string', description: 'The background_id returned by background_tool.' }, release: { type: 'boolean', description: 'Set true to release the persisted result from storage after reading it.' } }, ['background_id']),
|
|
373
374
|
t('goal_manage', 'Actively manage the persistent Goal state for this conversation. You may enter Goal mode, update (edit) its objective, mark it complete, or exit Goal mode yourself. Call this when the user asks you to pursue a persistent objective, when the objective changes, when you have verified the objective is genuinely achieved, or when you judge the Goal is no longer needed and should be cleared. This is the agent-side state control that mirrors the GUI goal panel controls. enter/update require objective; complete marks the objective verified and exits Goal mode; exit clears the Goal (and returns to Build mode) without claiming completion.', { action: { type: 'string', enum: ['enter', 'update', 'complete', 'exit'], description: 'enter=enter Goal mode and set the objective; update=edit the objective (records a change); complete=mark the objective verified-achieved and exit Goal mode; exit=clear the Goal and return to Build mode without claiming completion.' }, objective: { type: 'string', description: 'The Goal objective text. Required for enter and update.' }, reason: { type: 'string', description: 'Optional one-line reason for the state change, recorded for audit.' } }, ['action']),
|
|
374
|
-
t('
|
|
375
|
+
t('task_read', 'Read the persistent inline task checklist of the CURRENT conversation (the same list the GUI Task panel and TUI plan view render). Returns bounded items (id, status, task text <=240 chars) plus unfinished count. Read-only and side-effect free; call this whenever you need concrete task-list state instead of assuming it. Kept out of the system prompt so provider prefix caching stays stable.', {}, []),
|
|
376
|
+
t('task_create', 'Maintain the persistent inline task checklist of the CURRENT conversation. This is the durable replacement for ephemeral in-reply checklists: items you create here appear in the GUI Task panel and TUI plan view immediately and persist across Build Blocks. Actions: create appends one task item; update changes status (pending|in_progress|done) or text of one item by id or index; clear removes completed items. Use create when starting a multi-step task, update as each item progresses, and update status=done when verified. Returns a compact confirmation, never the full list.', { action: { type: 'string', enum: ['create', 'update', 'clear'], description: 'create=append a task item; update=change one item status/text; clear=remove completed items.' }, task: { type: 'string', description: 'Task text for create, or new text for update. Short actionable label (<=400 chars).' }, text: { type: 'string', description: 'Alias of task.' }, id: { type: 'string', description: 'Item id from task_read for update.' }, index: { type: 'number', description: '0-based item index for update (alternative to id).' }, status: { type: 'string', enum: ['pending', 'in_progress', 'done', 'blocked'], description: 'New status for update; blocked is stored as pending.' } }, ['action']),
|
|
377
|
+
t('conversation_rename', 'Rename the CURRENT conversation to a concise, descriptive title you choose. Use this when the user asks to rename the conversation or when the current auto-generated title no longer describes the work. Keep the title short (a few words) and cache-friendly: a concrete noun phrase describing the task, never a sentence or quoted prompt.', { title: { type: 'string', description: 'The new conversation title (a short noun phrase, e.g. "Fix TUI color leak", "Add goal_manage tool").' } }, ['title']),
|
|
375
378
|
t('question', 'Ask user a multiple-choice question', { questions: { type: 'array' } }, ['questions']),
|
|
376
379
|
t('skill_download', 'Download a skill', { name: { type: 'string' }, source: { type: 'string' } }, ['name', 'source']),
|
|
377
380
|
t('skill', 'Search enabled skill metadata or load one exact skill body on demand. Use query when unsure, then name to load the selected skill.', { query: { type: 'string', maxLength: 200 }, name: { type: 'string', maxLength: 200 } }, []),
|
|
@@ -578,6 +581,7 @@ class ToolExecutor {
|
|
|
578
581
|
case 'read':
|
|
579
582
|
case 'write':
|
|
580
583
|
case 'edit':
|
|
584
|
+
case 'delete_file':
|
|
581
585
|
case 'grep':
|
|
582
586
|
case 'file_audit':
|
|
583
587
|
case 'pdf_read':
|
|
@@ -598,6 +602,15 @@ class ToolExecutor {
|
|
|
598
602
|
: null;
|
|
599
603
|
if (bashGuard)
|
|
600
604
|
return bashGuard;
|
|
605
|
+
// 硬性删除审查:允许单文件删除,拒绝脚本/命令批量删除。
|
|
606
|
+
const deletionGuardTarget = tool === 'bash' || (tool === 'terminal_takeover' && g('action') === 'write')
|
|
607
|
+
? g('command')
|
|
608
|
+
: null;
|
|
609
|
+
if (deletionGuardTarget !== null) {
|
|
610
|
+
const deletionGuard = (0, toolPolicy_1.evaluateDeletionGuard)(deletionGuardTarget);
|
|
611
|
+
if (deletionGuard.blocked)
|
|
612
|
+
return deletionGuard.reason || '[deletion guard] Batch deletion is not allowed.';
|
|
613
|
+
}
|
|
601
614
|
try {
|
|
602
615
|
switch (tool) {
|
|
603
616
|
case 'bash': return await this.bash(g('command'), wsPath, args.timeout_ms, context.signal);
|
|
@@ -605,6 +618,7 @@ class ToolExecutor {
|
|
|
605
618
|
case 'read': return this.fread(resolve(g('path')));
|
|
606
619
|
case 'write': return this.fwrite(resolve(g('path')), g('content'));
|
|
607
620
|
case 'edit': return this.fedit(resolve(g('path')), g('old_str'), g('new_str'));
|
|
621
|
+
case 'delete_file': return this.fdelete(resolve(g('path')));
|
|
608
622
|
case 'glob': return this.glob(g('pattern'), wsPath);
|
|
609
623
|
case 'grep': return this.grep(g('pattern'), resolve(g('path')));
|
|
610
624
|
case 'web_search': return await this.wsearch(g('query'), context.signal);
|
|
@@ -1136,6 +1150,22 @@ class ToolExecutor {
|
|
|
1136
1150
|
return `[edit] ${e}`;
|
|
1137
1151
|
}
|
|
1138
1152
|
}
|
|
1153
|
+
fdelete(p) {
|
|
1154
|
+
try {
|
|
1155
|
+
if (/[*?]/.test(p))
|
|
1156
|
+
return '[delete_file] Refused: wildcard paths are not allowed. Delete one file per call.';
|
|
1157
|
+
const resolved = path.resolve(p);
|
|
1158
|
+
const stat = fs.lstatSync(resolved);
|
|
1159
|
+
if (stat.isDirectory()) {
|
|
1160
|
+
return '[delete_file] Refused: deleting a directory is not allowed. Delete files one by one under Agent supervision.';
|
|
1161
|
+
}
|
|
1162
|
+
fs.unlinkSync(resolved);
|
|
1163
|
+
return `[delete_file] OK: ${resolved}`;
|
|
1164
|
+
}
|
|
1165
|
+
catch (e) {
|
|
1166
|
+
return `[delete_file] ${e instanceof Error ? e.message : String(e)}`;
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1139
1169
|
glob(pattern, ws) {
|
|
1140
1170
|
try {
|
|
1141
1171
|
const results = globSync(pattern, {
|
|
@@ -11,6 +11,7 @@ exports.NATIVE_TOOL_CATALOG = [
|
|
|
11
11
|
{ name: 'read', label: 'Read file', description: 'Read workspace file contents.', category: 'core', defaultEnabled: true, protected: true, availability: 'required' },
|
|
12
12
|
{ name: 'write', label: 'Write file', description: 'Create or overwrite workspace files.', category: 'core', defaultEnabled: true },
|
|
13
13
|
{ name: 'edit', label: 'Edit file', description: 'Patch workspace files through exact find and replace.', category: 'core', defaultEnabled: true },
|
|
14
|
+
{ name: 'delete_file', label: 'Delete file', description: 'Delete one file at a time under Agent supervision; refuses directory and wildcard deletion.', category: 'core', defaultEnabled: true },
|
|
14
15
|
{ name: 'glob', label: 'Glob files', description: 'Find files by glob pattern.', category: 'core', defaultEnabled: true, protected: true, availability: 'required' },
|
|
15
16
|
{ name: 'grep', label: 'Search files', description: 'Search workspace text by regex.', category: 'core', defaultEnabled: true, protected: true, availability: 'required' },
|
|
16
17
|
{ name: 'web_search', label: 'Web search', description: 'Search the web from the Agent.', category: 'web', defaultEnabled: true },
|
package/dist/tui/src/app.js
CHANGED
|
@@ -17,9 +17,12 @@ const {
|
|
|
17
17
|
createState,
|
|
18
18
|
cycleConversationMode,
|
|
19
19
|
enterConversation,
|
|
20
|
+
enterHistoryEventFocus,
|
|
21
|
+
exitHistoryEventFocus,
|
|
20
22
|
filteredCommands,
|
|
21
23
|
moveFocusHorizontal,
|
|
22
24
|
moveConversationHistoryCursor,
|
|
25
|
+
moveHistoryEventCursor,
|
|
23
26
|
moveInputCursorVertical,
|
|
24
27
|
moveMenuSelection,
|
|
25
28
|
moveSettingChoiceSelection,
|
|
@@ -37,6 +40,7 @@ const {
|
|
|
37
40
|
toggleAgentHistory,
|
|
38
41
|
toggleConversationPinned,
|
|
39
42
|
toggleSelectedBuildBlock,
|
|
43
|
+
toggleSelectedBuildEvent,
|
|
40
44
|
toggleSelected,
|
|
41
45
|
toggleWorkflowDetails,
|
|
42
46
|
validateSelectedModel
|
|
@@ -330,7 +334,21 @@ function start(options = {}) {
|
|
|
330
334
|
Promise.resolve(requestConversationStop(state)).finally(paint);
|
|
331
335
|
} else if (key.name === "tab") {
|
|
332
336
|
state.conversationHistoryFocus = false;
|
|
337
|
+
state.historyEventFocus = false;
|
|
338
|
+
state.historyEventIndex = -1;
|
|
333
339
|
returnToConversationSelection(state);
|
|
340
|
+
} else if (state.historyEventFocus) {
|
|
341
|
+
if (key.name === "left") {
|
|
342
|
+
exitHistoryEventFocus(state);
|
|
343
|
+
} else if (key.name === "up") {
|
|
344
|
+
moveHistoryEventCursor(state, -1);
|
|
345
|
+
} else if (key.name === "down") {
|
|
346
|
+
moveHistoryEventCursor(state, 1);
|
|
347
|
+
} else if (key.name === "return" || key.name === "space") {
|
|
348
|
+
toggleSelectedBuildEvent(state);
|
|
349
|
+
}
|
|
350
|
+
} else if (key.name === "right") {
|
|
351
|
+
enterHistoryEventFocus(state);
|
|
334
352
|
} else if (key.name === "up") {
|
|
335
353
|
moveConversationHistoryCursor(state, -1);
|
|
336
354
|
} else if (key.name === "down") {
|
|
@@ -490,6 +508,12 @@ function start(options = {}) {
|
|
|
490
508
|
state.focusRegion = "content";
|
|
491
509
|
state.contentColumn = 0;
|
|
492
510
|
state.notice = "Search Memory Lab tags";
|
|
511
|
+
} else if (state.view === "memory" && !state.inputMode && (str === "o" || str === "O")) {
|
|
512
|
+
state.notice = "Opening Memory Lab Overview";
|
|
513
|
+
var overviewResult = state.adapter.openMemoryOverview();
|
|
514
|
+
if (overviewResult && typeof overviewResult.then === "function") {
|
|
515
|
+
overviewResult.catch(function(error) { state.notice = "Overview failed: " + error.message; }).finally(paint);
|
|
516
|
+
}
|
|
493
517
|
} else if (key.name === "tab") {
|
|
494
518
|
state.focusRegion = "menu";
|
|
495
519
|
state.contentColumn = 0;
|