newmark-agent 0.4.2 → 0.4.4
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 +352 -43
- package/dist/conversation-utility-host.js +3 -0
- package/dist/core/agent.d.ts +52 -4
- package/dist/core/agent.js +274 -27
- package/dist/core/agentKernelRunner.js +34 -17
- package/dist/core/config.d.ts +8 -0
- package/dist/core/conversationKernel.d.ts +24 -0
- package/dist/core/conversationKernel.js +78 -6
- package/dist/core/electronUtilityAgentClient.d.ts +1 -0
- package/dist/core/electronUtilityAgentClient.js +4 -0
- package/dist/core/electronUtilityRuntimePool.d.ts +2 -0
- package/dist/core/electronUtilityRuntimePool.js +11 -0
- package/dist/core/toolPolicy.js +3 -0
- package/dist/core/utilityAgentProtocol.d.ts +7 -0
- package/dist/core/wslAgentClient.d.ts +1 -0
- package/dist/core/wslAgentClient.js +4 -0
- package/dist/core/wslAgentProtocol.d.ts +7 -0
- package/dist/core/wslAgentRuntimePool.d.ts +2 -0
- package/dist/core/wslAgentRuntimePool.js +12 -0
- package/dist/llm/provider.d.ts +13 -1
- package/dist/llm/provider.js +42 -1
- package/dist/main.js +36 -7
- package/dist/providers/provider-adapter.d.ts +3 -1
- package/dist/tools/index.js +4 -2
- package/dist/tui/src/app.js +51 -6
- package/dist/tui/src/data.js +28 -0
- package/dist/tui/src/render.js +121 -60
- package/dist/tui/src/state.js +3 -0
- package/dist/ui/index.html +78 -9
- package/dist/wsl-agent-host.bundle.cjs +352 -43
- package/dist/wsl-agent-host.js +3 -0
- package/package.json +5 -3
|
@@ -93,6 +93,9 @@ async function handle(request) {
|
|
|
93
93
|
if (request.method === 'set_mode') {
|
|
94
94
|
return kernel.setMode(checkedTarget(request.params.target), request.params.mode);
|
|
95
95
|
}
|
|
96
|
+
if (request.method === 'set_model') {
|
|
97
|
+
return kernel.setModel(checkedTarget(request.params.target), request.params.model);
|
|
98
|
+
}
|
|
96
99
|
if (request.method === 'set_input_mode') {
|
|
97
100
|
return kernel.setInputMode(checkedTarget(request.params.target), request.params.mode);
|
|
98
101
|
}
|
package/dist/core/agent.d.ts
CHANGED
|
@@ -314,6 +314,18 @@ export declare class Agent {
|
|
|
314
314
|
model: string;
|
|
315
315
|
fallback: boolean;
|
|
316
316
|
} | null;
|
|
317
|
+
providerUsageTotals: {
|
|
318
|
+
input: number;
|
|
319
|
+
output: number;
|
|
320
|
+
cacheRead: number;
|
|
321
|
+
cacheWrite: number;
|
|
322
|
+
};
|
|
323
|
+
lastProviderUsage: {
|
|
324
|
+
input: number;
|
|
325
|
+
output: number;
|
|
326
|
+
cacheRead: number;
|
|
327
|
+
cacheWrite: number;
|
|
328
|
+
};
|
|
317
329
|
private compressionCache;
|
|
318
330
|
private pendingHistoryRemovals;
|
|
319
331
|
private branchMailbox;
|
|
@@ -631,12 +643,18 @@ export declare class Agent {
|
|
|
631
643
|
setConversationPinned(id: string, pinned: boolean): boolean;
|
|
632
644
|
renameConversation(id: string, title: string): boolean;
|
|
633
645
|
/**
|
|
634
|
-
* 首 Build
|
|
635
|
-
* (2) 其持久化 title 仍是自动生成(含为空)时返回 true
|
|
636
|
-
*
|
|
637
|
-
*
|
|
646
|
+
* 首 Build 命名判定:仅当 (1) 当前对话尚无历史 Build(排除当前 run)且
|
|
647
|
+
* (2) 其持久化 title 仍是自动生成(含为空)时返回 true。dev-0.4.3 起不再
|
|
648
|
+
* 用该判定注入首轮 tool-call 指令,而是在首个完成 Build 的最终响应处自动
|
|
649
|
+
* 命名(见 maybeAutoRenameConversationFromRun)。判定本身只读存储、无副作用。
|
|
638
650
|
*/
|
|
639
651
|
shouldPromptConversationRename(): boolean;
|
|
652
|
+
/**
|
|
653
|
+
* 从首个 Build 的最终响应中提取一个简短对话标题。跳过 Markdown 标题/列表
|
|
654
|
+
* 符号与固定 section 标题,取第一条有意义的摘要句并做保守清洗。
|
|
655
|
+
*/
|
|
656
|
+
private deriveConversationTitleFromSummary;
|
|
657
|
+
private maybeAutoRenameConversationFromRun;
|
|
640
658
|
reorderConversations(ids: string[]): boolean;
|
|
641
659
|
flushConversationState(): void;
|
|
642
660
|
private titleFromMessages;
|
|
@@ -730,6 +748,19 @@ export declare class Agent {
|
|
|
730
748
|
* 已有的 renameConversation 持久化路径。返回简短结果以保缓存友好。
|
|
731
749
|
*/
|
|
732
750
|
handleConversationRename(args: string): NewmarkToolResult;
|
|
751
|
+
/**
|
|
752
|
+
* task_read:读取当前对话的持久化内联任务清单(conversationPlan)。
|
|
753
|
+
* 只读、无副作用,输出有界(每项 text 截断到 240 字符),保缓存友好。
|
|
754
|
+
* Agent 在需要具体任务状态时调用,替代把动态清单注入每个 provider request。
|
|
755
|
+
*/
|
|
756
|
+
handleTaskRead(): NewmarkToolResult;
|
|
757
|
+
/**
|
|
758
|
+
* task_create:把任务项写入当前对话的持久化内联任务清单(conversationPlan)。
|
|
759
|
+
* action=create 追加单项;action=update 按 id/index 改状态或文本;action=clear
|
|
760
|
+
* 移除已完成项。写入走 updateConversationPlan 持久化路径,GUI Task 面板与
|
|
761
|
+
* TUI plan 视图立即反映。返回紧凑确认,避免回显全量清单。
|
|
762
|
+
*/
|
|
763
|
+
handleTaskCreate(args: string): NewmarkToolResult;
|
|
733
764
|
private conversationTree;
|
|
734
765
|
private currentRuntimeBranchId;
|
|
735
766
|
handleBranchList(args: string): NewmarkToolResult;
|
|
@@ -773,6 +804,12 @@ export declare class Agent {
|
|
|
773
804
|
modelLabel(): string;
|
|
774
805
|
estimateContextTokens(messages?: Array<Record<string, unknown>>): number;
|
|
775
806
|
private estimateContextTokenComponents;
|
|
807
|
+
recordProviderUsage(input: {
|
|
808
|
+
input: number;
|
|
809
|
+
output: number;
|
|
810
|
+
cacheRead: number;
|
|
811
|
+
cacheWrite: number;
|
|
812
|
+
}): void;
|
|
776
813
|
contextWindow(modelName?: string): {
|
|
777
814
|
estimatedTokens: number;
|
|
778
815
|
maxTokens: number;
|
|
@@ -789,6 +826,12 @@ export declare class Agent {
|
|
|
789
826
|
compressionEnabled?: boolean;
|
|
790
827
|
cacheEntries?: number;
|
|
791
828
|
archiveEntries?: number;
|
|
829
|
+
providerTotalTokens?: number;
|
|
830
|
+
providerInputTokens?: number;
|
|
831
|
+
providerOutputTokens?: number;
|
|
832
|
+
providerCacheReadTokens?: number;
|
|
833
|
+
providerCacheWriteTokens?: number;
|
|
834
|
+
providerCacheReadRatio?: number;
|
|
792
835
|
};
|
|
793
836
|
private resolveWindowModel;
|
|
794
837
|
private contextMaxTokens;
|
|
@@ -892,6 +935,11 @@ export declare class Agent {
|
|
|
892
935
|
modelValidationStatus(): ModelValidationProgress;
|
|
893
936
|
private runModelValidation;
|
|
894
937
|
engineModel(): LLMProvider | null;
|
|
938
|
+
/**
|
|
939
|
+
* dev-0.4.3 模型原生思考强度档位映射表(模型名 → thinking_tier_map)。
|
|
940
|
+
* 未配置映射的模型返回 undefined,provider 侧维持默认透传(不变动映射)。
|
|
941
|
+
*/
|
|
942
|
+
private modelThinkingTierMaps;
|
|
895
943
|
editorModelRequest(input: {
|
|
896
944
|
path?: string;
|
|
897
945
|
content?: string;
|
package/dist/core/agent.js
CHANGED
|
@@ -96,6 +96,11 @@ function throwIfAgentAborted(signal) {
|
|
|
96
96
|
error.name = 'AbortError';
|
|
97
97
|
throw error;
|
|
98
98
|
}
|
|
99
|
+
/** task_read 输出的单项文本上限:清单项只作状态索引,不承载长描述。 */
|
|
100
|
+
function compactPlanItemText(value) {
|
|
101
|
+
const clean = String(value || '').replace(/\s+/g, ' ').trim();
|
|
102
|
+
return clean.length <= 240 ? clean : `${clean.slice(0, 237)}...`;
|
|
103
|
+
}
|
|
99
104
|
let CORE_SYSTEM_PROMPT = `You are Newmark Agent, a powerful AI coding assistant built into a native desktop application.
|
|
100
105
|
|
|
101
106
|
## Available Tools
|
|
@@ -149,8 +154,8 @@ let CORE_SYSTEM_PROMPT = `You are Newmark Agent, a powerful AI coding assistant
|
|
|
149
154
|
- When the current instruction is a new task, complete that task without silently appending unrelated historical work.
|
|
150
155
|
|
|
151
156
|
## Inline Task Management (Mandatory)
|
|
152
|
-
- For every multi-step conversation task,
|
|
153
|
-
-
|
|
157
|
+
- For every multi-step conversation task, persist a compact task checklist through the task_create tool: create actionable items when the work starts, update each item's status (pending|in_progress|done) as work progresses, and mark items done only after verification. These items are the same list the GUI Task panel and TUI plan view render, and they persist across Build Blocks.
|
|
158
|
+
- Call task_read to reload the concrete checklist state whenever you need it; the system prompt intentionally does not inject the live list so the provider prefix cache stays stable. Keep items bounded to actionable task labels; never expose hidden reasoning.
|
|
154
159
|
- The inline checklist is the per-turn task manager. The durable linked-plan document exists and is available through the linked_plan tool when explicitly needed, but its full contents are not injected into every request.
|
|
155
160
|
|
|
156
161
|
## Guidelines
|
|
@@ -214,6 +219,8 @@ class Agent {
|
|
|
214
219
|
continuations = [];
|
|
215
220
|
activeConversationId = 'default';
|
|
216
221
|
lastCompression = null;
|
|
222
|
+
providerUsageTotals = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
223
|
+
lastProviderUsage = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
217
224
|
compressionCache = [];
|
|
218
225
|
pendingHistoryRemovals = [];
|
|
219
226
|
branchMailbox = [];
|
|
@@ -2153,8 +2160,10 @@ class Agent {
|
|
|
2153
2160
|
if (run.status !== status)
|
|
2154
2161
|
return false;
|
|
2155
2162
|
const terminalAt = run.endedAt || endedAt;
|
|
2156
|
-
if (status === 'completed')
|
|
2163
|
+
if (status === 'completed') {
|
|
2157
2164
|
this.ensureCompletedWorkRunFinalResult(run);
|
|
2165
|
+
this.maybeAutoRenameConversationFromRun(run);
|
|
2166
|
+
}
|
|
2158
2167
|
const goalAudit = this.auditGoalAtWorkRunEnd(run, status, terminalAt);
|
|
2159
2168
|
this.enforceGoalTerminalInvariant(status, goalAudit);
|
|
2160
2169
|
this.persistBuildBlockWorkOverview(run, status, terminalAt, goalAudit);
|
|
@@ -2202,6 +2211,8 @@ class Agent {
|
|
|
2202
2211
|
run.status = status;
|
|
2203
2212
|
run.endedAt = endedAt;
|
|
2204
2213
|
run.expanded = true;
|
|
2214
|
+
if (status === 'completed')
|
|
2215
|
+
this.maybeAutoRenameConversationFromRun(run);
|
|
2205
2216
|
this.activeWorkRunId = '';
|
|
2206
2217
|
this.finalizingWorkRunId = '';
|
|
2207
2218
|
this.managedWorkRunIds.delete(run.runId);
|
|
@@ -2320,6 +2331,8 @@ class Agent {
|
|
|
2320
2331
|
const goalAudit = this.auditGoalAtWorkRunEnd(activeRun, terminalStatus, terminalAt);
|
|
2321
2332
|
this.enforceGoalTerminalInvariant(terminalStatus, goalAudit);
|
|
2322
2333
|
this.persistBuildBlockWorkOverview(activeRun, terminalStatus, terminalAt, goalAudit);
|
|
2334
|
+
if (terminalStatus === 'completed')
|
|
2335
|
+
this.maybeAutoRenameConversationFromRun(activeRun);
|
|
2323
2336
|
}
|
|
2324
2337
|
return event;
|
|
2325
2338
|
}
|
|
@@ -3414,10 +3427,10 @@ class Agent {
|
|
|
3414
3427
|
return true;
|
|
3415
3428
|
}
|
|
3416
3429
|
/**
|
|
3417
|
-
* 首 Build
|
|
3418
|
-
* (2) 其持久化 title 仍是自动生成(含为空)时返回 true
|
|
3419
|
-
*
|
|
3420
|
-
*
|
|
3430
|
+
* 首 Build 命名判定:仅当 (1) 当前对话尚无历史 Build(排除当前 run)且
|
|
3431
|
+
* (2) 其持久化 title 仍是自动生成(含为空)时返回 true。dev-0.4.3 起不再
|
|
3432
|
+
* 用该判定注入首轮 tool-call 指令,而是在首个完成 Build 的最终响应处自动
|
|
3433
|
+
* 命名(见 maybeAutoRenameConversationFromRun)。判定本身只读存储、无副作用。
|
|
3421
3434
|
*/
|
|
3422
3435
|
shouldPromptConversationRename() {
|
|
3423
3436
|
if (this.conversationBuildHistory(1).length > 0)
|
|
@@ -3431,6 +3444,46 @@ class Agent {
|
|
|
3431
3444
|
const messages = entry?.chatMessages || this.chatMessages;
|
|
3432
3445
|
return this.isGeneratedConversationTitle(priorTitle, conversationId, messages);
|
|
3433
3446
|
}
|
|
3447
|
+
/**
|
|
3448
|
+
* 从首个 Build 的最终响应中提取一个简短对话标题。跳过 Markdown 标题/列表
|
|
3449
|
+
* 符号与固定 section 标题,取第一条有意义的摘要句并做保守清洗。
|
|
3450
|
+
*/
|
|
3451
|
+
deriveConversationTitleFromSummary(summary) {
|
|
3452
|
+
const clean = this.sanitizeAssistantOutput(summary || '').replace(/\r/g, '');
|
|
3453
|
+
const lines = clean.split('\n').map(line => line.trim()).filter(Boolean);
|
|
3454
|
+
for (const line of lines) {
|
|
3455
|
+
const withoutHeading = line
|
|
3456
|
+
.replace(/^#{1,6}\s*/, '')
|
|
3457
|
+
.replace(/^[-*+>]\s*/, '')
|
|
3458
|
+
.trim();
|
|
3459
|
+
if (!withoutHeading)
|
|
3460
|
+
continue;
|
|
3461
|
+
if (/^(做了什么|验证|文件|问题|下一步|What changed|Verification|Files|Issues|Next)[::]?$/i.test(withoutHeading))
|
|
3462
|
+
continue;
|
|
3463
|
+
const firstSentence = withoutHeading.split(/[。!?!?.;;]/)[0].trim() || withoutHeading;
|
|
3464
|
+
const title = firstSentence
|
|
3465
|
+
.replace(/[{}[\]()<>"'`]/g, '')
|
|
3466
|
+
.replace(/\s+/g, ' ')
|
|
3467
|
+
.trim()
|
|
3468
|
+
.slice(0, 48);
|
|
3469
|
+
if (title.length >= 2)
|
|
3470
|
+
return title;
|
|
3471
|
+
}
|
|
3472
|
+
return '';
|
|
3473
|
+
}
|
|
3474
|
+
maybeAutoRenameConversationFromRun(run) {
|
|
3475
|
+
if (run.status !== 'completed')
|
|
3476
|
+
return;
|
|
3477
|
+
if (!this.shouldPromptConversationRename())
|
|
3478
|
+
return;
|
|
3479
|
+
const finalEvent = [...run.events].reverse().find(event => event.type === 'final_response');
|
|
3480
|
+
const finalMessage = [...this.chatMessages].reverse().find(message => message.role === 'assistant' && message.runId === run.runId);
|
|
3481
|
+
const raw = finalEvent?.content || finalMessage?.content || '';
|
|
3482
|
+
const summary = this.sanitizePublicWorkContent(raw).slice(0, 2000);
|
|
3483
|
+
const title = this.deriveConversationTitleFromSummary(summary);
|
|
3484
|
+
if (title)
|
|
3485
|
+
this.renameConversation(this.activeConversationId || 'default', title);
|
|
3486
|
+
}
|
|
3434
3487
|
reorderConversations(ids) {
|
|
3435
3488
|
const prefix = this.workspaceConversationPrefix() || '';
|
|
3436
3489
|
const normalized = Array.from(new Set((Array.isArray(ids) ? ids : []).map(id => this.safeConversationId(id)).filter(Boolean)));
|
|
@@ -4124,8 +4177,8 @@ class Agent {
|
|
|
4124
4177
|
const tool = String(input.tool || '').trim();
|
|
4125
4178
|
if (!tool)
|
|
4126
4179
|
return { ok: false, output: '[background_tool] tool is required.', error: 'tool is required.' };
|
|
4127
|
-
if (tool === 'background_tool' || tool === 'read_tool_result' || tool === 'compress_tool_result' || tool === 'goal_manage' || tool === 'conversation_rename') {
|
|
4128
|
-
return { ok: false, output: '[background_tool] cannot background the control tools (background_tool/read_tool_result/compress_tool_result/goal_manage/conversation_rename).', error: 'control-tool-unsupported.' };
|
|
4180
|
+
if (tool === 'background_tool' || tool === 'read_tool_result' || tool === 'compress_tool_result' || tool === 'goal_manage' || tool === 'conversation_rename' || tool === 'task_read' || tool === 'task_create') {
|
|
4181
|
+
return { ok: false, output: '[background_tool] cannot background the control tools (background_tool/read_tool_result/compress_tool_result/goal_manage/conversation_rename/task_read/task_create).', error: 'control-tool-unsupported.' };
|
|
4129
4182
|
}
|
|
4130
4183
|
// 禁止后台化子代理/编排类工具——它们有独立的生命周期管理,后台化会破坏其
|
|
4131
4184
|
// mailbox/abort/persistence 语义。
|
|
@@ -4280,6 +4333,106 @@ class Agent {
|
|
|
4280
4333
|
metadata: { kind: 'conversation-rename' },
|
|
4281
4334
|
};
|
|
4282
4335
|
}
|
|
4336
|
+
/**
|
|
4337
|
+
* task_read:读取当前对话的持久化内联任务清单(conversationPlan)。
|
|
4338
|
+
* 只读、无副作用,输出有界(每项 text 截断到 240 字符),保缓存友好。
|
|
4339
|
+
* Agent 在需要具体任务状态时调用,替代把动态清单注入每个 provider request。
|
|
4340
|
+
*/
|
|
4341
|
+
handleTaskRead() {
|
|
4342
|
+
const plan = this.normalizeConversationPlan(this.conversationPlan);
|
|
4343
|
+
const items = plan.items.map((item, index) => ({
|
|
4344
|
+
index,
|
|
4345
|
+
id: item.id,
|
|
4346
|
+
status: item.status,
|
|
4347
|
+
task: compactPlanItemText(item.text),
|
|
4348
|
+
updatedAt: item.updatedAt || '',
|
|
4349
|
+
}));
|
|
4350
|
+
return {
|
|
4351
|
+
ok: true,
|
|
4352
|
+
output: JSON.stringify({
|
|
4353
|
+
ok: true,
|
|
4354
|
+
conversationId: this.activeConversationId || 'default',
|
|
4355
|
+
total: items.length,
|
|
4356
|
+
unfinished: items.filter(item => item.status !== 'done').length,
|
|
4357
|
+
items,
|
|
4358
|
+
}, null, 2),
|
|
4359
|
+
metadata: { kind: 'task-read' },
|
|
4360
|
+
};
|
|
4361
|
+
}
|
|
4362
|
+
/**
|
|
4363
|
+
* task_create:把任务项写入当前对话的持久化内联任务清单(conversationPlan)。
|
|
4364
|
+
* action=create 追加单项;action=update 按 id/index 改状态或文本;action=clear
|
|
4365
|
+
* 移除已完成项。写入走 updateConversationPlan 持久化路径,GUI Task 面板与
|
|
4366
|
+
* TUI plan 视图立即反映。返回紧凑确认,避免回显全量清单。
|
|
4367
|
+
*/
|
|
4368
|
+
handleTaskCreate(args) {
|
|
4369
|
+
let input = {};
|
|
4370
|
+
try {
|
|
4371
|
+
input = JSON.parse(args || '{}');
|
|
4372
|
+
}
|
|
4373
|
+
catch { }
|
|
4374
|
+
const action = String(input.action || 'create').trim();
|
|
4375
|
+
const plan = this.normalizeConversationPlan(this.conversationPlan);
|
|
4376
|
+
const now = new Date().toISOString();
|
|
4377
|
+
if (action === 'create') {
|
|
4378
|
+
const text = String(input.task || input.text || '').replace(/\s+/g, ' ').trim();
|
|
4379
|
+
if (!text)
|
|
4380
|
+
return { ok: false, output: '[task_create] task text is required.', error: 'task text is required.' };
|
|
4381
|
+
const item = {
|
|
4382
|
+
id: `plan-${Date.now()}-${Math.random().toString(16).slice(2, 6)}`,
|
|
4383
|
+
text: text.slice(0, 400),
|
|
4384
|
+
status: 'pending',
|
|
4385
|
+
createdAt: now,
|
|
4386
|
+
updatedAt: now,
|
|
4387
|
+
};
|
|
4388
|
+
plan.items.push(item);
|
|
4389
|
+
this.updateConversationPlan(plan);
|
|
4390
|
+
return {
|
|
4391
|
+
ok: true,
|
|
4392
|
+
output: JSON.stringify({ ok: true, action, id: item.id, status: item.status, total: plan.items.length }, null, 2),
|
|
4393
|
+
metadata: { kind: 'task-create' },
|
|
4394
|
+
};
|
|
4395
|
+
}
|
|
4396
|
+
if (action === 'update') {
|
|
4397
|
+
const id = String(input.id || '').trim();
|
|
4398
|
+
const index = Number(input.index);
|
|
4399
|
+
const status = String(input.status || '').trim();
|
|
4400
|
+
const text = String(input.task || input.text || '').replace(/\s+/g, ' ').trim();
|
|
4401
|
+
const target = id
|
|
4402
|
+
? plan.items.find(item => item.id === id)
|
|
4403
|
+
: Number.isInteger(index) && index >= 0 && index < plan.items.length
|
|
4404
|
+
? plan.items[index]
|
|
4405
|
+
: undefined;
|
|
4406
|
+
if (!target)
|
|
4407
|
+
return { ok: false, output: '[task_create] no matching task item for update (pass id or valid index).', error: 'task item not found.' };
|
|
4408
|
+
if (status) {
|
|
4409
|
+
if (!['pending', 'in_progress', 'done', 'blocked'].includes(status)) {
|
|
4410
|
+
return { ok: false, output: '[task_create] status must be pending|in_progress|done|blocked.', error: 'invalid status.' };
|
|
4411
|
+
}
|
|
4412
|
+
target.status = status === 'blocked' ? 'pending' : status;
|
|
4413
|
+
}
|
|
4414
|
+
if (text)
|
|
4415
|
+
target.text = text.slice(0, 400);
|
|
4416
|
+
target.updatedAt = now;
|
|
4417
|
+
this.updateConversationPlan(plan);
|
|
4418
|
+
return {
|
|
4419
|
+
ok: true,
|
|
4420
|
+
output: JSON.stringify({ ok: true, action, id: target.id, status: target.status, total: plan.items.length }, null, 2),
|
|
4421
|
+
metadata: { kind: 'task-create' },
|
|
4422
|
+
};
|
|
4423
|
+
}
|
|
4424
|
+
if (action === 'clear') {
|
|
4425
|
+
const remaining = plan.items.filter(item => item.status !== 'done');
|
|
4426
|
+
const removed = plan.items.length - remaining.length;
|
|
4427
|
+
this.updateConversationPlan({ items: remaining });
|
|
4428
|
+
return {
|
|
4429
|
+
ok: true,
|
|
4430
|
+
output: JSON.stringify({ ok: true, action, removed, total: remaining.length }, null, 2),
|
|
4431
|
+
metadata: { kind: 'task-create' },
|
|
4432
|
+
};
|
|
4433
|
+
}
|
|
4434
|
+
return { ok: false, output: '[task_create] action must be create|update|clear.', error: 'invalid action.' };
|
|
4435
|
+
}
|
|
4283
4436
|
conversationTree() {
|
|
4284
4437
|
const stateKey = this.workspaceConversationStateKey();
|
|
4285
4438
|
const stored = this.readStoredConversationState();
|
|
@@ -5099,6 +5252,20 @@ class Agent {
|
|
|
5099
5252
|
buildBlockTokens: estimate(buildBlockAsciiChars, buildBlockNonAsciiChars, buildBlockStructuralChars, true),
|
|
5100
5253
|
};
|
|
5101
5254
|
}
|
|
5255
|
+
recordProviderUsage(input) {
|
|
5256
|
+
const bounded = (value) => Math.max(0, Math.floor(Number(value) || 0));
|
|
5257
|
+
const usage = {
|
|
5258
|
+
input: bounded(input.input),
|
|
5259
|
+
output: bounded(input.output),
|
|
5260
|
+
cacheRead: bounded(input.cacheRead),
|
|
5261
|
+
cacheWrite: bounded(input.cacheWrite),
|
|
5262
|
+
};
|
|
5263
|
+
this.lastProviderUsage = usage;
|
|
5264
|
+
this.providerUsageTotals.input += usage.input;
|
|
5265
|
+
this.providerUsageTotals.output += usage.output;
|
|
5266
|
+
this.providerUsageTotals.cacheRead += usage.cacheRead;
|
|
5267
|
+
this.providerUsageTotals.cacheWrite += usage.cacheWrite;
|
|
5268
|
+
}
|
|
5102
5269
|
contextWindow(modelName = this.model) {
|
|
5103
5270
|
const estimatedTokens = this.estimateContextTokens();
|
|
5104
5271
|
// Display and compression must share one window resolution. Both resolve
|
|
@@ -5127,12 +5294,32 @@ class Agent {
|
|
|
5127
5294
|
compressionEnabled: this.config.getBool('context', 'auto_compress'),
|
|
5128
5295
|
cacheEntries: this.compressionCache.length,
|
|
5129
5296
|
archiveEntries: this.compressionArchiveEntryCount(),
|
|
5297
|
+
providerTotalTokens: this.providerUsageTotals.input + this.providerUsageTotals.output,
|
|
5298
|
+
providerInputTokens: this.providerUsageTotals.input,
|
|
5299
|
+
providerOutputTokens: this.providerUsageTotals.output,
|
|
5300
|
+
providerCacheReadTokens: this.providerUsageTotals.cacheRead,
|
|
5301
|
+
providerCacheWriteTokens: this.providerUsageTotals.cacheWrite,
|
|
5302
|
+
providerCacheReadRatio: this.providerUsageTotals.input > 0
|
|
5303
|
+
? Math.min(1, this.providerUsageTotals.cacheRead / this.providerUsageTotals.input)
|
|
5304
|
+
: 0,
|
|
5130
5305
|
};
|
|
5131
5306
|
}
|
|
5132
5307
|
resolveWindowModel(modelName) {
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5308
|
+
// The context window (display ring, inspector, and compaction trigger) must
|
|
5309
|
+
// resolve the deployment that is actually running. For the active selection
|
|
5310
|
+
// — auto or a fixed model — resolve through the active deployment so a
|
|
5311
|
+
// qualified selection or two same-named models across providers never fall
|
|
5312
|
+
// through to the 128000 default. Only a caller-supplied foreign name (for
|
|
5313
|
+
// example a validation probe against another model) resolves by bare name.
|
|
5314
|
+
if (modelName === 'auto' || modelName === this.model || modelName === this.activeModelName()) {
|
|
5315
|
+
const active = this.activeModelConfig();
|
|
5316
|
+
if (active)
|
|
5317
|
+
return active;
|
|
5318
|
+
}
|
|
5319
|
+
const byName = this.config.findModel(modelName);
|
|
5320
|
+
if (byName)
|
|
5321
|
+
return byName;
|
|
5322
|
+
return this.config.findModel(this.config.getStr('models', 'default_model'));
|
|
5136
5323
|
}
|
|
5137
5324
|
contextMaxTokens(modelName = this.model) {
|
|
5138
5325
|
const model = this.resolveWindowModel(modelName);
|
|
@@ -6368,7 +6555,7 @@ class Agent {
|
|
|
6368
6555
|
this.modelValidationProgress = { ...this.modelValidationProgress, currentModel, currentCheck: 'catalog' };
|
|
6369
6556
|
const inferredVision = !!m.vision || (0, config_1.inferModelVisionCapability)(m.name, m.display, m.description, m.provider, m.provider_protocol);
|
|
6370
6557
|
const inferredImageOutput = !!m.image_output || /(?:^|[-_.])(gpt-image|dall-e|imagen|imagegen|image-generation)(?:$|[-_.])/i.test(m.name);
|
|
6371
|
-
const p = new provider_1.LLMProvider(m.provider, m.provider_url, m.api_key, m.provider_protocol, this.config.openAIApiMode(), this.config.contextFlag('provider_adapters_v2'));
|
|
6558
|
+
const p = new provider_1.LLMProvider(m.provider, m.provider_url, m.api_key, m.provider_protocol, this.config.openAIApiMode(), this.config.contextFlag('provider_adapters_v2'), undefined, this.modelThinkingTierMaps(m));
|
|
6372
6559
|
let catalog = catalogByProvider.get(m.provider_id);
|
|
6373
6560
|
if (!catalog && m.provider_url && m.api_key) {
|
|
6374
6561
|
try {
|
|
@@ -6484,7 +6671,17 @@ class Agent {
|
|
|
6484
6671
|
const m = this.activeModelConfig();
|
|
6485
6672
|
if (!m)
|
|
6486
6673
|
return null;
|
|
6487
|
-
return new provider_1.LLMProvider(m.provider, m.provider_url, m.api_key, m.provider_protocol, this.config.openAIApiMode(), this.config.contextFlag('provider_adapters_v2'));
|
|
6674
|
+
return new provider_1.LLMProvider(m.provider, m.provider_url, m.api_key, m.provider_protocol, this.config.openAIApiMode(), this.config.contextFlag('provider_adapters_v2'), undefined, this.modelThinkingTierMaps(m));
|
|
6675
|
+
}
|
|
6676
|
+
/**
|
|
6677
|
+
* dev-0.4.3 模型原生思考强度档位映射表(模型名 → thinking_tier_map)。
|
|
6678
|
+
* 未配置映射的模型返回 undefined,provider 侧维持默认透传(不变动映射)。
|
|
6679
|
+
*/
|
|
6680
|
+
modelThinkingTierMaps(model) {
|
|
6681
|
+
const map = model?.thinking_tier_map;
|
|
6682
|
+
if (!model?.name || !map || typeof map !== 'object' || !Object.keys(map).length)
|
|
6683
|
+
return undefined;
|
|
6684
|
+
return { [model.name]: map };
|
|
6488
6685
|
}
|
|
6489
6686
|
async editorModelRequest(input, signal) {
|
|
6490
6687
|
const models = this.config.allModels().filter(model => {
|
|
@@ -6510,8 +6707,8 @@ class Agent {
|
|
|
6510
6707
|
if (!selected?.api_key || !selected.provider_url)
|
|
6511
6708
|
return { ok: false, text: '', error: 'No available editor prediction model.' };
|
|
6512
6709
|
const provider = input.completion
|
|
6513
|
-
? new provider_1.LLMProvider(selected.provider, selected.provider_url, selected.api_key, selected.provider_protocol, 'chat_stream', this.config.contextFlag('provider_adapters_v2'), EDITOR_COMPLETION_TIMEOUT_MS)
|
|
6514
|
-
: new provider_1.LLMProvider(selected.provider, selected.provider_url, selected.api_key, selected.provider_protocol, this.config.openAIApiMode(), this.config.contextFlag('provider_adapters_v2'));
|
|
6710
|
+
? new provider_1.LLMProvider(selected.provider, selected.provider_url, selected.api_key, selected.provider_protocol, 'chat_stream', this.config.contextFlag('provider_adapters_v2'), EDITOR_COMPLETION_TIMEOUT_MS, this.modelThinkingTierMaps(selected))
|
|
6711
|
+
: new provider_1.LLMProvider(selected.provider, selected.provider_url, selected.api_key, selected.provider_protocol, this.config.openAIApiMode(), this.config.contextFlag('provider_adapters_v2'), undefined, this.modelThinkingTierMaps(selected));
|
|
6515
6712
|
const language = path.extname(String(input.path || '')).replace(/^\./, '') || 'text';
|
|
6516
6713
|
const system = input.completion
|
|
6517
6714
|
? 'You are an inline code completion engine. Return only the exact text to insert at the cursor. Do not use Markdown fences or explanations.'
|
|
@@ -6807,20 +7004,70 @@ class Agent {
|
|
|
6807
7004
|
throw new Error(message);
|
|
6808
7005
|
}
|
|
6809
7006
|
}
|
|
6810
|
-
|
|
7007
|
+
let text = typeof input === 'string' ? input : String(input.text || '');
|
|
6811
7008
|
const inputEnvelope = typeof input === 'string' ? null : input;
|
|
6812
|
-
|
|
6813
|
-
// An empty selection may be repaired to the configured default, but an
|
|
6814
|
-
// explicitly requested fixed model must remain visible to the
|
|
6815
|
-
// fail-closed availability check below. Otherwise a typo such as
|
|
6816
|
-
// provider-that-does-not-exist/missing-model silently becomes the first
|
|
6817
|
-
// configured fixture/default deployment.
|
|
7009
|
+
let hiddenUserInput = inputEnvelope?.hiddenUserInput === true;
|
|
6818
7010
|
const explicitFixedModel = this.model !== '' && this.model !== 'auto';
|
|
6819
7011
|
if (!explicitFixedModel)
|
|
6820
7012
|
this.ensureUsableModelSelection();
|
|
6821
|
-
|
|
7013
|
+
let clientMessageId = String(inputEnvelope?.clientMessageId || '').trim();
|
|
6822
7014
|
const inputRunId = String(inputEnvelope?.runId || this.activeWorkRunId || '').trim();
|
|
6823
|
-
|
|
7015
|
+
let rawImages = typeof input === 'string' ? [] : (Array.isArray(input.images) ? input.images : []);
|
|
7016
|
+
// dev-0.4.3 Guide 批量续接:同一 Build block 内连续到达的多个 Guide
|
|
7017
|
+
// 由 conversation kernel 合并到这里一次注入,不再每来一个 Guide 响应一次。
|
|
7018
|
+
const batchGuides = Array.isArray(inputEnvelope?.batchGuides) ? inputEnvelope.batchGuides : [];
|
|
7019
|
+
if (batchGuides.length) {
|
|
7020
|
+
const batchRunId = inputRunId || this.activeWorkRunId || '';
|
|
7021
|
+
const batchTarget = this.currentConversationTarget();
|
|
7022
|
+
const appliedAt = this.nowIso();
|
|
7023
|
+
const persisted = [];
|
|
7024
|
+
const batchImages = [];
|
|
7025
|
+
for (const guide of batchGuides) {
|
|
7026
|
+
const guideClientMessageId = String(guide.clientMessageId || '').trim();
|
|
7027
|
+
if (!guideClientMessageId)
|
|
7028
|
+
continue;
|
|
7029
|
+
let guideImages = [];
|
|
7030
|
+
let guideAttachments = [];
|
|
7031
|
+
try {
|
|
7032
|
+
const prepared = this.prepareSubmittedConversationImages(guide.images);
|
|
7033
|
+
guideImages = prepared.images;
|
|
7034
|
+
guideAttachments = prepared.attachments;
|
|
7035
|
+
}
|
|
7036
|
+
catch (error) {
|
|
7037
|
+
this.status = 'idle';
|
|
7038
|
+
return [{ type: 'text', text: `[Attachment rejected] ${error instanceof Error ? error.message : String(error)}` }];
|
|
7039
|
+
}
|
|
7040
|
+
const guideDisplay = guideImages.length
|
|
7041
|
+
? `${guide.text}${guide.text ? '\n\n' : ''}[${guideImages.length} image attachment${guideImages.length === 1 ? '' : 's'}]`
|
|
7042
|
+
: guide.text;
|
|
7043
|
+
batchImages.push(...guideImages);
|
|
7044
|
+
// 图片统一由主 hidden user 消息携带,Guide 自身 history 只保留文本,
|
|
7045
|
+
// 避免同一批图片在上下文中重复计费。
|
|
7046
|
+
this.persistGuideMessage(guideClientMessageId, guideDisplay, batchRunId, guide.text, guideAttachments, String(guide.guideId || ''));
|
|
7047
|
+
this.recordGuideReceipt({
|
|
7048
|
+
clientMessageId: guideClientMessageId,
|
|
7049
|
+
guideId: String(guide.guideId || '') || undefined,
|
|
7050
|
+
target: batchTarget,
|
|
7051
|
+
runId: batchRunId,
|
|
7052
|
+
status: 'applied',
|
|
7053
|
+
content: guideDisplay,
|
|
7054
|
+
createdAt: appliedAt,
|
|
7055
|
+
updatedAt: appliedAt,
|
|
7056
|
+
appliedAt,
|
|
7057
|
+
});
|
|
7058
|
+
this.consumeConversationContinuation({ content: guide.text, queueMode: 'steer', clientMessageId: guideClientMessageId });
|
|
7059
|
+
persisted.push({ text: guide.text, clientMessageId: guideClientMessageId });
|
|
7060
|
+
}
|
|
7061
|
+
if (persisted.length === 1) {
|
|
7062
|
+
text = persisted[0].text;
|
|
7063
|
+
}
|
|
7064
|
+
else {
|
|
7065
|
+
text = `Apply the following intervening Guides in submission order within the current Build Block and continue automatically:\n${persisted.map((guide, index) => `Guide ${index + 1}: ${guide.text}`).join('\n')}`;
|
|
7066
|
+
}
|
|
7067
|
+
hiddenUserInput = true;
|
|
7068
|
+
clientMessageId = '';
|
|
7069
|
+
rawImages = batchImages;
|
|
7070
|
+
}
|
|
6824
7071
|
let autoRouteEvaluated = false;
|
|
6825
7072
|
let attachments = [];
|
|
6826
7073
|
let images = [];
|
|
@@ -7639,7 +7886,7 @@ class Agent {
|
|
|
7639
7886
|
const activeModel = this.activeModelConfig();
|
|
7640
7887
|
const activeProvider = this.engineModel();
|
|
7641
7888
|
const assignedProvider = assignedModel && assignedModel.provider_id !== activeModel?.provider_id
|
|
7642
|
-
? new provider_1.LLMProvider(assignedModel.provider, assignedModel.provider_url, assignedModel.api_key, assignedModel.provider_protocol, this.config.openAIApiMode(), this.config.contextFlag('provider_adapters_v2'))
|
|
7889
|
+
? new provider_1.LLMProvider(assignedModel.provider, assignedModel.provider_url, assignedModel.api_key, assignedModel.provider_protocol, this.config.openAIApiMode(), this.config.contextFlag('provider_adapters_v2'), undefined, this.modelThinkingTierMaps(assignedModel))
|
|
7643
7890
|
: activeProvider;
|
|
7644
7891
|
if (!assignedProvider || !model) {
|
|
7645
7892
|
throw new Error('No LLM configured. Add provider in Settings > Models.');
|
|
@@ -8623,7 +8870,7 @@ class Agent {
|
|
|
8623
8870
|
'- Memory Lab is governed by an explicit Policy chain: pre-think whether memory is needed; prefer bounded memory_lab_query retrieval; then choose ADD/UPDATE/DELETE only when the user authorizes durable memory mutation.',
|
|
8624
8871
|
'- Before memory_lab_update, inspect the target with memory_lab_query or memory_lab_read. For an existing component pass expectedUpdatedAt so concurrent/stale writes fail closed; preserve established tag parent paths.',
|
|
8625
8872
|
'- Use memory_lab_delete only for an explicit user request to forget/remove memory. Prior revisions are retained under Memory Lab/archive and mutation decisions are appended to policy.jsonl for replay.',
|
|
8626
|
-
'- Build history disclosure is two-layered. The request prompt contains only each historical Build Block user input, final summary, and completion status.
|
|
8873
|
+
'- Build history disclosure is two-layered. The request prompt contains only each historical Build Block user input, final summary, and completion status. When the current task continues, fixes, verifies, or depends on earlier Build Blocks, proactively call build_history_query to read the concrete tool activity and results of the relevant block, and reuse that information instead of re-investigating (re-running commands or re-reading files) from scratch. Querying history is read-only and never authorizes resuming that work; do not query merely to answer completion status already shown in the prompt.',
|
|
8627
8874
|
'- Linked plan disclosure: a durable conversation-linked Markdown plan exists and can be inspected or updated with linked_plan when explicitly needed or required by Plan mode. Its full Markdown and revision are not injected into every model request.',
|
|
8628
8875
|
'- A memory_lab_update, memory_lab_delete, or memory_lab_reindex call is unfinished until its awaited tool result contains rebuildReceipt.completed=true. The completion receipt is represented by the tool activity inside the current Build block and should not be repeated as a separate completion message.',
|
|
8629
8876
|
`- Skills and subagents: skill searches enabled metadata and loads one SKILL.md body on demand; skill_download installs offline skill folders; task creates constrained subagents tracked in agent state.`,
|