newmark-agent 0.3.12 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli-commands.d.ts +1 -0
- package/dist/cli-commands.js +11 -2
- package/dist/context/domain/types.d.ts +37 -0
- package/dist/context/services/context-orchestrator.js +2 -0
- package/dist/conversation-utility-host.bundle.cjs +1259 -132
- package/dist/conversation-utility-host.js +3 -0
- package/dist/core/agent.d.ts +139 -5
- package/dist/core/agent.js +964 -82
- package/dist/core/agentKernel/agent-loop.js +29 -3
- package/dist/core/agentKernel/types.d.ts +7 -0
- package/dist/core/agentKernelRunner.d.ts +2 -0
- package/dist/core/agentKernelRunner.js +121 -19
- package/dist/core/conversationKernel.d.ts +5 -0
- package/dist/core/conversationKernel.js +29 -0
- package/dist/core/dshCompatibility.d.ts +198 -0
- package/dist/core/dshCompatibility.js +600 -0
- package/dist/core/electronUtilityAgentClient.d.ts +4 -0
- package/dist/core/electronUtilityAgentClient.js +4 -0
- package/dist/core/electronUtilityRuntimePool.d.ts +8 -0
- package/dist/core/electronUtilityRuntimePool.js +14 -0
- package/dist/core/mcpManager.d.ts +1 -0
- package/dist/core/mcpManager.js +100 -10
- package/dist/core/subagent.d.ts +6 -0
- package/dist/core/subagent.js +22 -1
- package/dist/core/toolPolicy.d.ts +15 -0
- package/dist/core/toolPolicy.js +174 -1
- package/dist/core/types.d.ts +1 -1
- package/dist/core/utilityAgentProtocol.d.ts +8 -1
- package/dist/core/workspace.d.ts +9 -0
- package/dist/core/workspace.js +48 -1
- package/dist/core/wslAgentClient.d.ts +4 -0
- package/dist/core/wslAgentClient.js +4 -0
- package/dist/core/wslAgentProtocol.d.ts +8 -1
- package/dist/core/wslAgentRuntimePool.d.ts +8 -0
- package/dist/core/wslAgentRuntimePool.js +15 -0
- package/dist/launcher.js +8 -0
- package/dist/llm/provider.d.ts +1 -1
- package/dist/llm/provider.js +4 -3
- package/dist/main.js +163 -11
- package/dist/preload.js +11 -0
- package/dist/providers/chat-completions.adapter.js +41 -15
- package/dist/providers/provider-adapter.d.ts +3 -0
- package/dist/toolchain/registry/tool-registry.d.ts +13 -1
- package/dist/toolchain/registry/tool-registry.js +8 -0
- package/dist/toolchain/registry-seeder.js +52 -6
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.js +39 -2
- package/dist/tools/nativeTools.js +6 -1
- package/dist/tui/src/app.js +24 -0
- package/dist/tui/src/i18n.js +151 -0
- package/dist/tui/src/render.js +152 -61
- package/dist/tui/src/state.js +83 -0
- package/dist/ui/index.html +2669 -234
- package/dist/ui/lucide-sprite.svg +31 -0
- package/dist/wsl-agent-host.bundle.cjs +1259 -132
- package/dist/wsl-agent-host.js +3 -0
- package/package.json +6 -10
- package/Flow/Electron-Debug-Release.Flow.json +0 -43
- package/Flow/Flow.md +0 -9
- package/Flow/UI-Feature-Integration.Flow.json +0 -96
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ToolDescriptor, ToolIdempotency, RiskLevel } from '../../context/domain/types';
|
|
1
|
+
import { ToolDescriptor, ToolIdempotency, RiskLevel, ToolExecuteFn, ToolConcurrencySafeFn, ToolRenderFn, ToolPresentationMetaFn, ToolFinalizeContentFn, ToolPresentCallFn, ToolPresentResultFn } from '../../context/domain/types';
|
|
2
2
|
export interface ToolDescriptorInput {
|
|
3
3
|
toolId: string;
|
|
4
4
|
capabilityId: string;
|
|
@@ -15,6 +15,18 @@ export interface ToolDescriptorInput {
|
|
|
15
15
|
supportedScopes?: string[];
|
|
16
16
|
cacheGroup?: string;
|
|
17
17
|
implementationHash?: string;
|
|
18
|
+
/** DSH ToolDefinition.execute:命令式功能实现引用(cordis 核功能承载)。 */
|
|
19
|
+
execute?: ToolExecuteFn;
|
|
20
|
+
/** DSH ToolDefinition.isConcurrencySafe:运行时并发分类。 */
|
|
21
|
+
isConcurrencySafe?: ToolConcurrencySafeFn;
|
|
22
|
+
/** DSH ToolOutputDefinition.render / presentationMeta。 */
|
|
23
|
+
render?: ToolRenderFn;
|
|
24
|
+
presentationMeta?: ToolPresentationMetaFn;
|
|
25
|
+
/** DSH ToolDefinition.finalizeContent / timeoutMs / presentCall / presentResult。 */
|
|
26
|
+
finalizeContent?: ToolFinalizeContentFn;
|
|
27
|
+
timeoutMs?: number;
|
|
28
|
+
presentCall?: ToolPresentCallFn;
|
|
29
|
+
presentResult?: ToolPresentResultFn;
|
|
18
30
|
}
|
|
19
31
|
/**
|
|
20
32
|
* Tool Registry: the authoritative set of ToolDescriptors. Schemas are
|
|
@@ -28,6 +28,14 @@ class ToolRegistry {
|
|
|
28
28
|
implementationHash: input.implementationHash,
|
|
29
29
|
cacheGroup: input.cacheGroup || `${input.namespace}.${input.name}`,
|
|
30
30
|
enabled: true,
|
|
31
|
+
execute: input.execute,
|
|
32
|
+
isConcurrencySafe: input.isConcurrencySafe,
|
|
33
|
+
render: input.render,
|
|
34
|
+
presentationMeta: input.presentationMeta,
|
|
35
|
+
finalizeContent: input.finalizeContent,
|
|
36
|
+
timeoutMs: input.timeoutMs,
|
|
37
|
+
presentCall: input.presentCall,
|
|
38
|
+
presentResult: input.presentResult,
|
|
31
39
|
};
|
|
32
40
|
this.tools.set(input.toolId, descriptor);
|
|
33
41
|
return descriptor;
|
|
@@ -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
|
/**
|
|
@@ -60,15 +60,28 @@ function inferRiskLevel(name, description, annotations) {
|
|
|
60
60
|
return 'external';
|
|
61
61
|
if (READ_TOOL_PATTERN.test(name))
|
|
62
62
|
return 'read';
|
|
63
|
+
// DSH 读工具命名惯例(get_/list_/query_/inspect_/read_ 前缀):这些动词本质只读,
|
|
64
|
+
// 避免被启发式误判为 write(例如 DSH 的 get_goal / cordis_inspect_list)。
|
|
65
|
+
if (/^(get_|list_|query_|inspect_|read_)/.test(name) && !/_(create|update|set|write|delete|remove|run|execute|send|save|push|edit|toggle|define|stop|start)$/.test(name))
|
|
66
|
+
return 'read';
|
|
63
67
|
return 'write';
|
|
64
68
|
}
|
|
65
69
|
function inferIdempotency(name) {
|
|
66
|
-
|
|
70
|
+
// shell 命令工具每次执行都可能改变外部状态,本质非幂等(DSH pwsh/bash/terminal 等)。
|
|
71
|
+
if (/^(bash|pwsh|powershell|cmd|shell|terminal|computer_use|browser_use|run|exec|execute|task)$/.test(name))
|
|
67
72
|
return 'non_idempotent';
|
|
68
73
|
if (/^(write|edit|append|send|save|create|update|set|put|register|patch)/.test(name))
|
|
69
74
|
return 'conditionally_idempotent';
|
|
70
75
|
return undefined;
|
|
71
76
|
}
|
|
77
|
+
/** 从真实 tool description 提取简洁 shortDescription(首句或截断),保 cordis 核可读。 */
|
|
78
|
+
function compactDescription(description, fallback) {
|
|
79
|
+
const clean = String(description || '').replace(/\s+/g, ' ').trim();
|
|
80
|
+
if (!clean)
|
|
81
|
+
return fallback;
|
|
82
|
+
const firstSentence = clean.split(/(?<=[.!?])\s+/)[0] || clean;
|
|
83
|
+
return firstSentence.slice(0, 120);
|
|
84
|
+
}
|
|
72
85
|
function resolveDefinition(definition) {
|
|
73
86
|
if (!definition || typeof definition !== 'object')
|
|
74
87
|
return null;
|
|
@@ -82,11 +95,35 @@ function resolveDefinition(definition) {
|
|
|
82
95
|
};
|
|
83
96
|
}
|
|
84
97
|
if (typeof record.name === 'string') {
|
|
98
|
+
// 兼容两种字段名:Newmark 的 SeededToolDefinition 用 inputSchema,
|
|
99
|
+
// DSH 的 ToolSchema 用 parameters(dsh-llm ToolSchema:{name, description, parameters})。
|
|
100
|
+
const rawParameters = record.inputSchema ?? record.parameters;
|
|
101
|
+
const rawExecute = record.execute;
|
|
102
|
+
const rawConcurrencySafe = record.isConcurrencySafe;
|
|
103
|
+
// DSH ToolOutputDefinition 是嵌套对象 {schema, render, presentationMeta};
|
|
104
|
+
// outputSchema 从 output.schema 提取。
|
|
105
|
+
const rawOutput = record.output;
|
|
106
|
+
const outputSchema = record.outputSchema ?? rawOutput?.schema;
|
|
107
|
+
const render = rawOutput?.render;
|
|
108
|
+
const presentationMeta = rawOutput?.presentationMeta;
|
|
109
|
+
const finalizeContent = record.finalizeContent;
|
|
110
|
+
const timeoutMs = record.timeoutMs;
|
|
111
|
+
const presentCall = record.presentCall;
|
|
112
|
+
const presentResult = record.presentResult;
|
|
85
113
|
return {
|
|
86
114
|
name: record.name,
|
|
87
115
|
description: typeof record.description === 'string' ? record.description : '',
|
|
88
|
-
parameters:
|
|
116
|
+
parameters: rawParameters,
|
|
117
|
+
outputSchema,
|
|
89
118
|
annotations: record.annotations,
|
|
119
|
+
execute: typeof rawExecute === 'function' ? rawExecute : undefined,
|
|
120
|
+
isConcurrencySafe: typeof rawConcurrencySafe === 'function' ? rawConcurrencySafe : undefined,
|
|
121
|
+
render: typeof render === 'function' ? render : undefined,
|
|
122
|
+
presentationMeta: typeof presentationMeta === 'function' ? presentationMeta : undefined,
|
|
123
|
+
finalizeContent: typeof finalizeContent === 'function' ? finalizeContent : undefined,
|
|
124
|
+
timeoutMs: typeof timeoutMs === 'number' ? timeoutMs : undefined,
|
|
125
|
+
presentCall: typeof presentCall === 'function' ? presentCall : undefined,
|
|
126
|
+
presentResult: typeof presentResult === 'function' ? presentResult : undefined,
|
|
90
127
|
};
|
|
91
128
|
}
|
|
92
129
|
return null;
|
|
@@ -135,7 +172,7 @@ function seedToolchainFromDefinitions(definitions, options) {
|
|
|
135
172
|
if (riskLevel === 'destructive' || (riskLevel === 'external' && entry.input.riskLevel !== 'destructive')) {
|
|
136
173
|
entry.input.riskLevel = riskLevel;
|
|
137
174
|
}
|
|
138
|
-
entry.resolved.push({
|
|
175
|
+
entry.resolved.push({ ...definition, riskLevel, domain });
|
|
139
176
|
}
|
|
140
177
|
for (const [domain, entry] of byDomain) {
|
|
141
178
|
const requiredPermissions = entry.input.riskLevel === 'destructive'
|
|
@@ -166,13 +203,22 @@ function seedToolchainFromDefinitions(definitions, options) {
|
|
|
166
203
|
namespace,
|
|
167
204
|
name: tool.name,
|
|
168
205
|
version,
|
|
169
|
-
shortDescription: tool.name,
|
|
170
|
-
fullDescription: `${tool.name} (${domain})`,
|
|
206
|
+
shortDescription: compactDescription(tool.description, tool.name),
|
|
207
|
+
fullDescription: tool.description && tool.description.trim() ? tool.description : `${tool.name} (${domain})`,
|
|
171
208
|
inputSchema: tool.parameters ?? { type: 'object', properties: {}, required: [] },
|
|
209
|
+
outputSchema: tool.outputSchema,
|
|
172
210
|
riskLevel: tool.riskLevel,
|
|
173
211
|
idempotency,
|
|
174
212
|
requiredPermissions: required,
|
|
175
213
|
implementationHash: (0, deterministic_1.sha256)(tool.name),
|
|
214
|
+
execute: tool.execute,
|
|
215
|
+
isConcurrencySafe: tool.isConcurrencySafe,
|
|
216
|
+
render: tool.render,
|
|
217
|
+
presentationMeta: tool.presentationMeta,
|
|
218
|
+
finalizeContent: tool.finalizeContent,
|
|
219
|
+
timeoutMs: tool.timeoutMs,
|
|
220
|
+
presentCall: tool.presentCall,
|
|
221
|
+
presentResult: tool.presentResult,
|
|
176
222
|
};
|
|
177
223
|
core.registry.register(input);
|
|
178
224
|
toolIds.push(tool.name);
|
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']),
|
|
@@ -349,9 +350,9 @@ class ToolExecutor {
|
|
|
349
350
|
t('subagent_result', 'Return the persisted transcript, mailbox summary, status, and latest result for a peer agent. Target by exact id (preferred) or name.', { id: { type: 'string', description: 'Exact peer id from subagent_list.' }, name: { type: 'string', description: 'Convenience peer name.' } }, []),
|
|
350
351
|
t('subagent_close', 'Close a same-conversation peer. Root can close any peer; a peer can close only itself. Target by exact id (preferred) or name.', { id: { type: 'string', description: 'Exact peer id from subagent_list.' }, name: { type: 'string', description: 'Convenience peer name.' } }, []),
|
|
351
352
|
t('linked_plan', 'Read or update the current conversation linked Markdown plan. Update requires the current expected_revision.', { action: { type: 'string', enum: ['get', 'update'] }, markdown: { type: 'string' }, expected_revision: { type: 'number' } }, ['action']),
|
|
352
|
-
t('build_history_query', 'Read the concrete public work details of one historical Build Block. The prompt already exposes user input, final summary, and completion status; call this read-only tool only when the user asks what specifically happened. Select by newest-to-oldest history_index, or by run_id returned from an earlier query.', { history_index: { type: 'number', minimum: 1, description: '1-based historical Build Block index from the request ledger; 1 is the newest previous task.' }, run_id: { type: 'string', description: 'Exact run id returned by an earlier build_history_query result.' }, max_events: { type: 'number', minimum: 1, maximum: 200, description: 'Maximum trailing public work events; defaults to 80.' } }, []),
|
|
353
|
+
t('build_history_query', 'Read the concrete public work details of one historical Build Block. The prompt already exposes user input, final summary, and completion status; call this read-only tool only when the user asks what specifically happened. Select by newest-to-oldest history_index, or by run_id returned from an earlier query. Every activity/guide content is bounded to max_chars (default 2000) to keep the read lean and cache-friendly.', { history_index: { type: 'number', minimum: 1, description: '1-based historical Build Block index from the request ledger; 1 is the newest previous task.' }, run_id: { type: 'string', description: 'Exact run id returned by an earlier build_history_query result.' }, max_events: { type: 'number', minimum: 1, maximum: 200, description: 'Maximum trailing public work events; defaults to 80.' }, max_chars: { type: 'number', minimum: 100, maximum: 4000, description: 'Per-event/per-guide content character bound; defaults to 2000.' } }, []),
|
|
353
354
|
t('context_compress', 'Actively compress the LLM context history for this conversation. This collapses older history entries into a concise summary while preserving the recent tail, which reduces context tokens and cost. IMPORTANT: it affects only the LLM context (what the model sees); the displayed conversation history shown to the user is never altered. Call this when the conversation is long, token pressure is high, or you judge that older turns are no longer needed in full. Idempotent and safe: repeated calls produce incremental summaries.', { keep_recent: { type: 'number', minimum: 2, maximum: 60, description: 'Recent message count to keep uncompressed at the tail. Defaults to the configured keep_recent_messages.' }, force: { type: 'boolean', description: 'Compress even if the context is not yet over the automatic threshold. Defaults to false.' } }, []),
|
|
354
|
-
t('context_history_manage', 'Manage the LLM context history for this conversation without affecting the displayed conversation history. This is the active context-management surface. The hot cache stays bounded; evicted folded segments remain in a conversation-isolated append-only cold archive and are loaded only by explicit search/read/restore calls. Actions: list returns a bounded index of current context entries; remove
|
|
355
|
+
t('context_history_manage', 'Manage the LLM context history for this conversation without affecting the displayed conversation history. This is the active context-management surface. The hot cache stays bounded; evicted folded segments remain in a conversation-isolated append-only cold archive and are loaded only by explicit search/read/restore calls. Actions: list returns a bounded index of current context entries; remove declares one long-term entry for unload (see below); summarize folds a contiguous current range; restore reinserts a folded segment when its summary marker is still present; search finds matching hot or archived segments; read returns one bounded segment without injecting the whole archive; status reports budgets, hot cache, cold archive, the protected recent zone, and pending removals. The recent context tail and last user message are protected from remove/summarize unless dangerous is true. For cache-optimization, remove ONLY targets long-term history (never the protected recent tail or last user message) and does NOT unload immediately: the declared entry stays in context for the rest of the current Build Block so the provider prefix cache stays stable, then is physically removed when the Block ends — applying to subsequent Blocks only.', {
|
|
355
356
|
action: { type: 'string', enum: ['list', 'remove', 'summarize', 'restore', 'search', 'read', 'status'], description: 'list current entries; remove one; summarize a range; restore by restore_id; search hot/cold folded segments; read one bounded folded segment; status report context budgets and storage.' },
|
|
356
357
|
position: { type: 'number', minimum: 0, description: '0-based context entry index for remove, or the start of the range for summarize.' },
|
|
357
358
|
to: { type: 'number', minimum: 0, description: '0-based inclusive end of the range for summarize. Defaults to position.' },
|
|
@@ -363,6 +364,15 @@ class ToolExecutor {
|
|
|
363
364
|
max_chars: { type: 'number', minimum: 1000, maximum: 60000, description: 'Maximum message-content characters returned by read (default 12000).' },
|
|
364
365
|
dangerous: { type: 'boolean', description: 'Set true to override the protected recent-message zone and allow removing/summarizing entries that include the recent context tail or the last user message.' },
|
|
365
366
|
}, ['action']),
|
|
367
|
+
t('branch_list', 'List all branches in this conversation and their mailbox/activity status. Only available when the conversation was created with "允许分支交流" (allow branch communication) enabled. Returns each branch id, active flag, source message index, message/history/workRun counts, and unread mailbox counts so you can decide who to message or read next.', {}, []),
|
|
368
|
+
t('branch_send', 'Send a message to another branch in this conversation. Only available when branch communication is enabled. The message is persisted to the target branch mailbox and becomes visible to that branch on its next branch_read. Use this to coordinate work across concurrently running branches. A branch cannot message itself.', { to_branch: { type: 'string', description: 'Target branch id (from branch_list).' }, message: { type: 'string', description: 'Message body to deliver.' }, kind: { type: 'string', enum: ['message', 'directive', 'result'], description: 'Message kind; defaults to message.' }, correlation_id: { type: 'string', description: 'Optional correlation id for reply tracking.' }, reply_to: { type: 'string', description: 'Optional message id this message replies to.' } }, ['to_branch', 'message']),
|
|
369
|
+
t('branch_read', 'Read inbound messages and recent activity from another branch in this conversation. Only available when branch communication is enabled. Marks inbound messages read. Returns the branch metadata, inbound messages, and recent Build Block activity (final results and recent events).', { branch: { type: 'string', description: 'Branch id to read (from branch_list).' }, max_chars: { type: 'number', minimum: 100, maximum: 16000, description: 'Maximum characters for final-result text; defaults to 8000.' } }, ['branch']),
|
|
370
|
+
t('branch_create', 'Create a new conversation branch at a historical block position (a user message index) with a new initial instruction. Only available when branch communication is enabled. The new branch becomes an additional running branch. Use this to spin off alternative work from a past point without disturbing existing branches.', { message_index: { type: 'number', minimum: 0, description: '0-based index of a user message in the conversation history (the historical block position to branch from).' }, prompt: { type: 'string', description: 'The new branch initial instruction (becomes the branch user message).' }, message_id: { type: 'string', description: 'Optional exact message id to anchor the branch target.' }, guide_id: { type: 'string', description: 'Optional guide id to anchor the branch target.' } }, ['message_index', 'prompt']),
|
|
371
|
+
t('compress_tool_result', 'Compress ONE oversized tool result into a concise, format-preserving summary using the model, instead of hard truncation. Pass the artifact_id returned inline by an oversized tool result (the full raw content stays out of context, on disk). Use this when a read/grep/bash result reported an oversized_tool_result marker and you want to recover its structure (JSON arrays, table rows, code blocks, identifiers, error strings) as a compact summary. This runs an isolated compression call whose system prefix does not touch the conversation prompt cache, so it does not disturb cache hit rate.', { artifact_id: { type: 'string', description: 'The artifact_id from the oversized_tool_result marker returned inline by an oversized tool result.' }, content: { type: 'string', description: 'Optional fallback: a SHORT raw result text to compress directly when no artifact_id exists.' }, format_hint: { type: 'string', description: 'Optional description of the structure to preserve verbatim (e.g. "keep JSON arrays and file paths", "keep table columns and error strings").' } }, []),
|
|
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']),
|
|
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']),
|
|
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']),
|
|
375
|
+
t('conversation_rename', 'Rename the CURRENT conversation to a concise, descriptive title you choose. On the FIRST Build Block of a NEW conversation the runtime asks you to call this once so the conversation list shows a meaningful name instead of an auto-generated one. 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']),
|
|
366
376
|
t('question', 'Ask user a multiple-choice question', { questions: { type: 'array' } }, ['questions']),
|
|
367
377
|
t('skill_download', 'Download a skill', { name: { type: 'string' }, source: { type: 'string' } }, ['name', 'source']),
|
|
368
378
|
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 } }, []),
|
|
@@ -569,6 +579,7 @@ class ToolExecutor {
|
|
|
569
579
|
case 'read':
|
|
570
580
|
case 'write':
|
|
571
581
|
case 'edit':
|
|
582
|
+
case 'delete_file':
|
|
572
583
|
case 'grep':
|
|
573
584
|
case 'file_audit':
|
|
574
585
|
case 'pdf_read':
|
|
@@ -589,6 +600,15 @@ class ToolExecutor {
|
|
|
589
600
|
: null;
|
|
590
601
|
if (bashGuard)
|
|
591
602
|
return bashGuard;
|
|
603
|
+
// 硬性删除审查:允许单文件删除,拒绝脚本/命令批量删除。
|
|
604
|
+
const deletionGuardTarget = tool === 'bash' || (tool === 'terminal_takeover' && g('action') === 'write')
|
|
605
|
+
? g('command')
|
|
606
|
+
: null;
|
|
607
|
+
if (deletionGuardTarget !== null) {
|
|
608
|
+
const deletionGuard = (0, toolPolicy_1.evaluateDeletionGuard)(deletionGuardTarget);
|
|
609
|
+
if (deletionGuard.blocked)
|
|
610
|
+
return deletionGuard.reason || '[deletion guard] Batch deletion is not allowed.';
|
|
611
|
+
}
|
|
592
612
|
try {
|
|
593
613
|
switch (tool) {
|
|
594
614
|
case 'bash': return await this.bash(g('command'), wsPath, args.timeout_ms, context.signal);
|
|
@@ -596,6 +616,7 @@ class ToolExecutor {
|
|
|
596
616
|
case 'read': return this.fread(resolve(g('path')));
|
|
597
617
|
case 'write': return this.fwrite(resolve(g('path')), g('content'));
|
|
598
618
|
case 'edit': return this.fedit(resolve(g('path')), g('old_str'), g('new_str'));
|
|
619
|
+
case 'delete_file': return this.fdelete(resolve(g('path')));
|
|
599
620
|
case 'glob': return this.glob(g('pattern'), wsPath);
|
|
600
621
|
case 'grep': return this.grep(g('pattern'), resolve(g('path')));
|
|
601
622
|
case 'web_search': return await this.wsearch(g('query'), context.signal);
|
|
@@ -1127,6 +1148,22 @@ class ToolExecutor {
|
|
|
1127
1148
|
return `[edit] ${e}`;
|
|
1128
1149
|
}
|
|
1129
1150
|
}
|
|
1151
|
+
fdelete(p) {
|
|
1152
|
+
try {
|
|
1153
|
+
if (/[*?]/.test(p))
|
|
1154
|
+
return '[delete_file] Refused: wildcard paths are not allowed. Delete one file per call.';
|
|
1155
|
+
const resolved = path.resolve(p);
|
|
1156
|
+
const stat = fs.lstatSync(resolved);
|
|
1157
|
+
if (stat.isDirectory()) {
|
|
1158
|
+
return '[delete_file] Refused: deleting a directory is not allowed. Delete files one by one under Agent supervision.';
|
|
1159
|
+
}
|
|
1160
|
+
fs.unlinkSync(resolved);
|
|
1161
|
+
return `[delete_file] OK: ${resolved}`;
|
|
1162
|
+
}
|
|
1163
|
+
catch (e) {
|
|
1164
|
+
return `[delete_file] ${e instanceof Error ? e.message : String(e)}`;
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1130
1167
|
glob(pattern, ws) {
|
|
1131
1168
|
try {
|
|
1132
1169
|
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 },
|
|
@@ -38,10 +39,14 @@ exports.NATIVE_TOOL_CATALOG = [
|
|
|
38
39
|
{ name: 'subagent_send', label: 'Subagent send', description: 'Persist a message to a peer mailbox.', category: 'agent', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
|
|
39
40
|
{ name: 'subagent_result', label: 'Subagent result', description: 'Read peer transcript and result.', category: 'agent', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
|
|
40
41
|
{ name: 'subagent_close', label: 'Subagent close', description: 'Close a same-conversation peer agent.', category: 'agent', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
|
|
42
|
+
{ name: 'branch_list', label: 'Branch list', description: 'List all conversation branches and their mailbox/activity status. Only available when branch communication is enabled.', category: 'agent', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
|
|
43
|
+
{ name: 'branch_send', label: 'Branch send', description: 'Send a message to another conversation branch. Only available when branch communication is enabled.', category: 'agent', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
|
|
44
|
+
{ name: 'branch_read', label: 'Branch read', description: 'Read inbound messages and recent activity from another conversation branch. Only available when branch communication is enabled.', category: 'agent', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
|
|
45
|
+
{ name: 'branch_create', label: 'Branch create', description: 'Create a new conversation branch at a historical block position. Only available when branch communication is enabled.', category: 'agent', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
|
|
41
46
|
{ name: 'linked_plan', label: 'Linked plan', description: 'Read or conservatively update the conversation-linked Markdown plan.', category: 'agent', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
|
|
42
47
|
{ name: 'build_history_query', label: 'Build history query', description: 'Read concrete public work details for one historical Build Block.', category: 'agent', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
|
|
43
48
|
{ name: 'context_compress', label: 'Context compress', description: 'Actively compress the LLM context history, leaving the displayed conversation history unchanged.', category: 'agent', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
|
|
44
|
-
{ name: 'context_history_manage', label: 'Context history manage', description: 'Inspect, search, restore, or
|
|
49
|
+
{ name: 'context_history_manage', label: 'Context history manage', description: 'Inspect, search, restore, fold, or unload LLM context history without touching the displayed conversation history. Unload (remove) targets long-term entries only and takes effect after the current Build Block, for subsequent Blocks only.', category: 'agent', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
|
|
45
50
|
{ name: 'question', label: 'Ask question', description: 'Ask the user for structured option feedback.', category: 'agent', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
|
|
46
51
|
{ name: 'skill_download', label: 'Skill download', description: 'Download and install a skill.', category: 'agent', defaultEnabled: true },
|
|
47
52
|
{ name: 'skill', label: 'Skill', description: 'Search enabled skill metadata or load one skill body on demand.', category: 'agent', defaultEnabled: true, protected: true, availability: 'mode-scoped' },
|
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;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Lightweight terminal i18n for the Newmark TUI.
|
|
5
|
+
*
|
|
6
|
+
* The TUI chrome (navigation, section headers, view titles, and hints) is
|
|
7
|
+
* rendered in the language chosen under Settings → General → Language. Model,
|
|
8
|
+
* tool, provider, conversation, and workspace names are user data and stay
|
|
9
|
+
* verbatim. Keys are the English source strings, so an untranslated string
|
|
10
|
+
* silently falls back to English rather than dropping text.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const ZH = Object.freeze({
|
|
14
|
+
// Sidebar sections
|
|
15
|
+
"WORKSPACES": "工作区",
|
|
16
|
+
"OPERATIONS": "操作",
|
|
17
|
+
"ACTIVE TARGET": "当前目标",
|
|
18
|
+
"ACTIVE CONVERSATION": "当前对话",
|
|
19
|
+
"Agent TUI": "Agent 终端",
|
|
20
|
+
|
|
21
|
+
// Navigation labels (TUI/src/data.js)
|
|
22
|
+
"Conversations": "对话",
|
|
23
|
+
"Plan": "计划",
|
|
24
|
+
"Goal": "目标",
|
|
25
|
+
"Subagents": "子代理",
|
|
26
|
+
"Model": "模型",
|
|
27
|
+
"Flow Bar": "流程条",
|
|
28
|
+
"Flow List": "流程列表",
|
|
29
|
+
"Flow Task": "流程任务",
|
|
30
|
+
"Tools": "工具",
|
|
31
|
+
"Memory Lab": "记忆实验室",
|
|
32
|
+
"Automations": "自动化",
|
|
33
|
+
"WorkFlow": "工作流",
|
|
34
|
+
"Settings": "设置",
|
|
35
|
+
"Help": "帮助",
|
|
36
|
+
|
|
37
|
+
// Conversation context
|
|
38
|
+
"Conversation plan": "对话计划",
|
|
39
|
+
"Conversation goal": "对话目标",
|
|
40
|
+
"Conversation subagents": "对话子代理",
|
|
41
|
+
"Model and reasoning effort": "模型与推理档位",
|
|
42
|
+
"Flow bar": "流程条",
|
|
43
|
+
"Flow list": "流程列表",
|
|
44
|
+
"Flow task": "流程任务",
|
|
45
|
+
|
|
46
|
+
// Model view
|
|
47
|
+
"Reasoning effort": "推理档位",
|
|
48
|
+
"Deployment": "部署",
|
|
49
|
+
"Shared GUI/TUI request tier · ←/→ changes section": "GUI/TUI 共享请求档位 · ←/→ 切换区块",
|
|
50
|
+
"Used by this conversation, including its Plan and Subagents": "当前对话使用(含计划与子代理)",
|
|
51
|
+
"Enter applies the focused tier or deployment. Effort persists globally; deployments remain per conversation.":
|
|
52
|
+
"Enter 应用当前档位或部署。档位全局持久化,部署按对话保存。",
|
|
53
|
+
|
|
54
|
+
// Memory Lab
|
|
55
|
+
"Tags": "标签",
|
|
56
|
+
"Selected tag": "已选标签",
|
|
57
|
+
"Child tags": "子标签",
|
|
58
|
+
"Memory components": "记忆组件",
|
|
59
|
+
"Core memory": "核心记忆",
|
|
60
|
+
"No memory components": "无记忆组件",
|
|
61
|
+
"No component selected": "未选择组件",
|
|
62
|
+
"Overview": "总览",
|
|
63
|
+
"Memory overview": "记忆总览",
|
|
64
|
+
"Memory tag": "记忆标签",
|
|
65
|
+
|
|
66
|
+
// Chat view
|
|
67
|
+
"N new": "N 新建",
|
|
68
|
+
"Workspace": "工作区",
|
|
69
|
+
"Current": "当前",
|
|
70
|
+
"Preview": "预览",
|
|
71
|
+
"Current conversation": "当前对话",
|
|
72
|
+
"Enter to open this conversation": "Enter 打开此对话",
|
|
73
|
+
"Type a message · Shift+Enter newline · Enter send": "输入消息 · Shift+Enter 换行 · Enter 发送",
|
|
74
|
+
"Select a conversation · Enter to edit": "选择对话 · Enter 编辑",
|
|
75
|
+
|
|
76
|
+
// Settings categories
|
|
77
|
+
"General": "通用",
|
|
78
|
+
"Personalization": "个性化",
|
|
79
|
+
"Runtime": "运行时",
|
|
80
|
+
"Providers": "服务商",
|
|
81
|
+
"Models": "模型",
|
|
82
|
+
"Archive": "归档",
|
|
83
|
+
"Updates": "更新",
|
|
84
|
+
|
|
85
|
+
// Settings
|
|
86
|
+
"Categories": "分类",
|
|
87
|
+
"Language": "语言",
|
|
88
|
+
"Input mode": "输入模式",
|
|
89
|
+
"Conversation style": "对话风格",
|
|
90
|
+
"Option feedback": "选项反馈",
|
|
91
|
+
"Close behavior": "关闭行为",
|
|
92
|
+
"Expand tool usage": "展开工具使用",
|
|
93
|
+
"Theme": "主题",
|
|
94
|
+
"Application font": "应用字体",
|
|
95
|
+
"Font color": "字体颜色",
|
|
96
|
+
"Background color": "背景颜色",
|
|
97
|
+
"Glass intensity": "玻璃强度",
|
|
98
|
+
"Agent backend": "Agent 后端",
|
|
99
|
+
"WSL distribution": "WSL 发行版",
|
|
100
|
+
"Terminal timeout cap": "终端超时上限",
|
|
101
|
+
"Default shell": "默认 Shell",
|
|
102
|
+
"Automatic archive": "自动归档",
|
|
103
|
+
"Retention": "保留期",
|
|
104
|
+
"Include Memory Lab": "包含记忆实验室",
|
|
105
|
+
"Export format": "导出格式",
|
|
106
|
+
"Update channel": "更新通道",
|
|
107
|
+
"Automatic checks": "自动检查",
|
|
108
|
+
"Automatic download": "自动下载",
|
|
109
|
+
"Update source": "更新来源",
|
|
110
|
+
"Live color preview": "实时颜色预览",
|
|
111
|
+
|
|
112
|
+
// Plan / Goal / Agents
|
|
113
|
+
"Linked Plan": "关联计划",
|
|
114
|
+
"Next handoff": "下一交接",
|
|
115
|
+
"Linked goal": "关联目标",
|
|
116
|
+
"No active goal": "无活动目标",
|
|
117
|
+
"RESULT": "结果",
|
|
118
|
+
"No messages recorded yet.": "暂无消息记录。",
|
|
119
|
+
"Records come from the active Newmark conversation": "记录来自当前 Newmark 对话",
|
|
120
|
+
|
|
121
|
+
// Tools
|
|
122
|
+
"Tools & connectors": "工具与连接器",
|
|
123
|
+
"Live runtime": "实时运行时",
|
|
124
|
+
"Safety boundary": "安全边界",
|
|
125
|
+
|
|
126
|
+
// Automation / Workflow
|
|
127
|
+
"[+] New automation": "[+] 新建自动化",
|
|
128
|
+
"[+] New workflow": "[+] 新建工作流",
|
|
129
|
+
"No workflows configured.": "未配置工作流。",
|
|
130
|
+
|
|
131
|
+
// Help
|
|
132
|
+
"SHORTCUT GUIDE": "快捷键指南",
|
|
133
|
+
"Navigation": "导航",
|
|
134
|
+
"Conversation editing": "对话编辑",
|
|
135
|
+
"Running work": "运行中的工作",
|
|
136
|
+
"Operation content": "操作内容",
|
|
137
|
+
"Global": "全局"
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
function resolveLanguage(state) {
|
|
141
|
+
const value = String(state?.settings?.general?.language || "Auto");
|
|
142
|
+
return value === "中文" || value === "zh" ? "zh" : "en";
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function tr(state, text) {
|
|
146
|
+
const value = String(text ?? "");
|
|
147
|
+
if (resolveLanguage(state) === "zh") return ZH[value] || value;
|
|
148
|
+
return value;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
module.exports = { resolveLanguage, tr };
|