minimal-agent 0.18.7 → 0.18.9
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/package.json +1 -1
- package/src/context/persistContext.js +5 -1
- package/src/context/persistContext.ts +5 -2
- package/src/loop.js +75 -25
- package/src/loop.ts +72 -23
- package/src/memory/factLearning.js +3 -3
- package/src/memory/factLearning.ts +3 -7
- package/src/memory/fileStore.js +2 -34
- package/src/memory/fileStore.ts +2 -26
- package/src/memory/jobPolicy.js +21 -13
- package/src/memory/jobPolicy.ts +22 -11
- package/src/memory/learning.js +3 -3
- package/src/memory/learning.ts +3 -3
- package/src/memory/learningModel.js +4 -2
- package/src/memory/learningModel.ts +4 -2
- package/src/prompts/delegation.js +15 -0
- package/src/prompts/delegation.ts +20 -0
- package/src/prompts/policyRegistry.js +7 -10
- package/src/prompts/policyRegistry.ts +9 -10
- package/src/prompts/promptAssembly.js +16 -23
- package/src/prompts/promptAssembly.ts +22 -27
- package/src/prompts/visualPresentation.js +28 -0
- package/src/prompts/visualPresentation.ts +26 -0
- package/src/runtime/agentRuntimeContext.js +3 -0
- package/src/runtime/agentRuntimeContext.ts +4 -0
- package/src/runtime/fileLock.js +75 -0
- package/src/runtime/fileLock.ts +73 -0
- package/src/runtime/finalAnswer.js +4 -2
- package/src/runtime/finalAnswer.ts +6 -2
- package/src/runtime/outputContract.js +17 -0
- package/src/runtime/outputContract.ts +34 -0
- package/src/session/runAgentTurn.js +7 -2
- package/src/session/runAgentTurn.ts +7 -2
- package/src/session/runtimeStore.js +11 -28
- package/src/session/runtimeStore.ts +9 -23
- package/src/tools/agent/agent.js +9 -6
- package/src/tools/agent/agent.ts +9 -5
- package/src/tools/memory/maintenance.js +2 -0
- package/src/tools/memory/maintenance.ts +2 -0
- package/src/tools/memory/memory.js +58 -14
- package/src/tools/memory/memory.ts +57 -13
- package/src/tools/memory/policy.js +16 -1
- package/src/tools/memory/policy.ts +16 -1
- package/src/tools/skill/searchSkills.js +2 -2
- package/src/tools/skill/searchSkills.ts +2 -2
- package/src/tools/skill/skill.js +4 -0
- package/src/tools/skill/skill.ts +3 -0
- package/src/tools/toolPromptGuides.js +4 -3
- package/src/tools/toolPromptGuides.ts +4 -3
- package/src/types.ts +4 -0
- package/src/web/protocol.ts +3 -1
|
@@ -6,7 +6,7 @@ import { memoryRecordPath } from '../../memory/fileStore.js';
|
|
|
6
6
|
import { memoryService, } from '../../memory/memoryService.js';
|
|
7
7
|
import { toToolParameters } from '../../utils/zodToJson.js';
|
|
8
8
|
import { DEFAULT_MAX_RESULT_SIZE_CHARS } from '../types.js';
|
|
9
|
-
import { EXPLICIT_CROSS_WORKSPACE_INTENT, EXPLICIT_FORGET_INTENT, EXPLICIT_MEMORY_INTENT, EXPLICIT_PERSONA_INTENT, confirmSensitiveMemory, denyUnlessLocalOwner, requireCurrentEvidence, } from './policy.js';
|
|
9
|
+
import { EXPLICIT_CROSS_WORKSPACE_INTENT, EXPLICIT_FORGET_INTENT, EXPLICIT_MEMORY_INTENT, EXPLICIT_PERSONA_INTENT, confirmSensitiveMemory, confirmMemoryCorrection, denyUnlessLocalOwner, requireCurrentEvidence, } from './policy.js';
|
|
10
10
|
const scopeSchema = z.enum(['personal', 'workspace']);
|
|
11
11
|
const categorySchema = z.enum([
|
|
12
12
|
'profile',
|
|
@@ -21,7 +21,7 @@ function formatRecord(record, version) {
|
|
|
21
21
|
? ` workspace=${record.workspaceId}`
|
|
22
22
|
: '';
|
|
23
23
|
const lifecycle = ` status=${record.status ?? 'active'} revision=${record.revision ?? 1}`;
|
|
24
|
-
return `- [${record.id}] scope=${record.scope} ${record.category}/${record.subject}${workspace}${lifecycle}${version ? ` version=${version}` : ''}: ${record.statement}(${record.evidence},${new Date(record.updatedAt).toISOString()}
|
|
24
|
+
return `- [${record.id}] scope=${record.scope} ${record.category}/${record.subject}${workspace}${lifecycle}${version ? ` version=${version}` : ''}: ${record.statement}(${record.evidence},${new Date(record.updatedAt).toISOString()})${record.status === 'needs_review' && record.confirmationContext ? `\n复核线索(非事实):${record.confirmationContext}` : ''}`;
|
|
25
25
|
}
|
|
26
26
|
function diagnosticSuffix(diagnostics) {
|
|
27
27
|
if (!diagnostics.length)
|
|
@@ -159,15 +159,18 @@ export const rememberMemoryTool = {
|
|
|
159
159
|
},
|
|
160
160
|
};
|
|
161
161
|
const updateSchema = z.object({
|
|
162
|
+
action: z.enum(['update', 'review', 'confirm']).optional().describe('默认 update:当前用户原话纠正;review:仅标记待复核,不改事实;confirm:通过宿主向用户确认拟更新内容后保存'),
|
|
163
|
+
reason: z.string().min(1).max(1000).optional().describe('review/confirm 必填:冲突或纠正依据,注明来源;不得伪造用户陈述'),
|
|
162
164
|
id: z.string().uuid(),
|
|
163
165
|
scope: scopeSchema,
|
|
164
166
|
category: categorySchema.optional(),
|
|
165
167
|
subject: z.string().min(1).max(120).optional(),
|
|
166
|
-
statement: z.string().min(1).max(1000),
|
|
168
|
+
statement: z.string().min(1).max(1000).optional(),
|
|
167
169
|
evidenceQuote: z
|
|
168
170
|
.string()
|
|
169
171
|
.min(1)
|
|
170
172
|
.max(1000)
|
|
173
|
+
.optional()
|
|
171
174
|
.describe('当前用户消息中的逐字证据片段'),
|
|
172
175
|
sensitivity: z.enum(['normal', 'sensitive']).optional(),
|
|
173
176
|
aliases: z.array(z.string().min(1).max(120)).max(20).optional(),
|
|
@@ -184,7 +187,7 @@ const updateSchema = z.object({
|
|
|
184
187
|
});
|
|
185
188
|
export const updateMemoryTool = {
|
|
186
189
|
name: 'UpdateMemory',
|
|
187
|
-
description: '
|
|
190
|
+
description: '纠正用户事实记忆:先 RecallMemory 读取 ID/version。默认 update 需要 statement 与当前用户原话 evidenceQuote;review 需要 reason,仅标记待复核、保留原事实与证据;confirm 需要 statement/reason,通过宿主展示新旧内容并取得用户确认再更新。拒绝或无法交互不写入。项目知识和经验走 MemoryMaintenance,不伪造用户证据。',
|
|
188
191
|
inputSchema: updateSchema,
|
|
189
192
|
parameters: toToolParameters(updateSchema),
|
|
190
193
|
isReadOnly: false,
|
|
@@ -196,9 +199,23 @@ export const updateMemoryTool = {
|
|
|
196
199
|
if (denied)
|
|
197
200
|
return denied;
|
|
198
201
|
const current = ctx.currentUserInput ?? '';
|
|
199
|
-
const
|
|
200
|
-
if (
|
|
201
|
-
return
|
|
202
|
+
const action = input.action ?? 'update';
|
|
203
|
+
if (action === 'review' && (input.statement !== undefined || input.evidenceQuote !== undefined)) {
|
|
204
|
+
return { ok: false, error: 'review 仅提交 reason,不提交 statement/evidenceQuote;标记疑点不代表确认新事实' };
|
|
205
|
+
}
|
|
206
|
+
if (action !== 'update' && (input.category !== undefined || input.subject !== undefined || input.aliases !== undefined || input.supersedesIds !== undefined || input.sensitivity !== undefined)) {
|
|
207
|
+
return { ok: false, error: 'review/confirm 只处理当前记录的复核状态或正文,不同时修改分类、敏感级别、别名或替代其他记录' };
|
|
208
|
+
}
|
|
209
|
+
if (action === 'update') {
|
|
210
|
+
if (!input.statement || !input.evidenceQuote)
|
|
211
|
+
return { ok: false, error: 'update 需要 statement 和当前用户原话 evidenceQuote' };
|
|
212
|
+
const invalidEvidence = requireCurrentEvidence(ctx, input.evidenceQuote);
|
|
213
|
+
if (invalidEvidence)
|
|
214
|
+
return invalidEvidence;
|
|
215
|
+
}
|
|
216
|
+
else if (!input.reason || (action === 'confirm' && !input.statement)) {
|
|
217
|
+
return { ok: false, error: 'review 需要 reason;confirm 需要 reason 和 statement' };
|
|
218
|
+
}
|
|
202
219
|
if (input.scope === 'workspace' && !ctx.workspaceId) {
|
|
203
220
|
return {
|
|
204
221
|
ok: false,
|
|
@@ -206,7 +223,7 @@ export const updateMemoryTool = {
|
|
|
206
223
|
};
|
|
207
224
|
}
|
|
208
225
|
if (!(await memoryService.settings()).autoRemember &&
|
|
209
|
-
!EXPLICIT_MEMORY_INTENT.test(current)) {
|
|
226
|
+
!EXPLICIT_MEMORY_INTENT.test(current) && action !== 'confirm') {
|
|
210
227
|
return {
|
|
211
228
|
ok: false,
|
|
212
229
|
error: '用户已关闭自动记忆;当前消息没有明确要求更新记忆',
|
|
@@ -215,18 +232,43 @@ export const updateMemoryTool = {
|
|
|
215
232
|
const currentRecord = await memoryService.readWithVersion(input.id, input.scope, input.scope === 'workspace' ? ctx.workspaceId : undefined);
|
|
216
233
|
if (!currentRecord)
|
|
217
234
|
return { ok: false, error: '未找到该记忆,未做修改' };
|
|
218
|
-
const
|
|
235
|
+
const expectedVersion = input.expectedVersion ?? currentRecord.version;
|
|
236
|
+
if (expectedVersion !== currentRecord.version) {
|
|
237
|
+
return {
|
|
238
|
+
ok: false,
|
|
239
|
+
error: '记忆已被手工或并发修改。未应用本次更新;保留以下当前记录的并发修改后可直接重试,不存在时重新定位。\n'
|
|
240
|
+
+ JSON.stringify({ current: currentRecord }),
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
if (action === 'review') {
|
|
244
|
+
signal?.throwIfAborted();
|
|
245
|
+
try {
|
|
246
|
+
const reviewed = await memoryService.updateWithVersion(input.id, input.scope, expectedVersion, { status: 'needs_review', confirmationContext: `待复核原因(非确认事实):${input.reason}` }, input.scope === 'workspace' ? ctx.workspaceId : undefined);
|
|
247
|
+
return reviewed ? { ok: true, content: `已标记待复核,原事实和证据未改写。id=${input.id}\nversion=${reviewed.version}` }
|
|
248
|
+
: { ok: false, error: '记录已不存在,未做修改' };
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
if (error.message === 'MEMORY_VERSION_CONFLICT')
|
|
252
|
+
return { ok: false, error: '记忆并发变更,请重新读取;未标记待复核' };
|
|
253
|
+
throw error;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
const statement = input.statement;
|
|
257
|
+
const confirmed = action === 'confirm' && await confirmMemoryCorrection(currentRecord.record.subject, currentRecord.record.statement, statement, input.reason, signal);
|
|
258
|
+
if (action === 'confirm' && !confirmed)
|
|
259
|
+
return { ok: false, error: '未获得用户确认(拒绝或宿主无法交互),原记忆未改写;可标记待复核,不要重复请求确认' };
|
|
260
|
+
const evidenceQuote = confirmed ? `用户通过纠正记忆确认:${statement}` : input.evidenceQuote;
|
|
261
|
+
const sensitivity = currentRecord.record.sensitivity === 'sensitive' ? 'sensitive' : input.sensitivity ?? 'normal';
|
|
219
262
|
if (sensitivity === 'sensitive' &&
|
|
220
|
-
|
|
263
|
+
!confirmed && statement.trim() === evidenceQuote.trim()) {
|
|
221
264
|
return {
|
|
222
265
|
ok: false,
|
|
223
266
|
error: '敏感内容不得逐字更新;请先概括、去除不必要细节后再请求确认',
|
|
224
267
|
};
|
|
225
268
|
}
|
|
226
|
-
const evidence = await confirmSensitiveMemory(
|
|
269
|
+
const evidence = confirmed ? 'user_confirmed' : await confirmSensitiveMemory(statement, sensitivity, signal);
|
|
227
270
|
if (!evidence)
|
|
228
271
|
return { ok: false, error: '敏感记忆更新未获确认' };
|
|
229
|
-
const expectedVersion = input.expectedVersion ?? currentRecord.version;
|
|
230
272
|
let updated;
|
|
231
273
|
try {
|
|
232
274
|
updated = await memoryService.updateWithVersion(input.id, input.scope, expectedVersion, {
|
|
@@ -234,9 +276,11 @@ export const updateMemoryTool = {
|
|
|
234
276
|
? { category: input.category }
|
|
235
277
|
: {}),
|
|
236
278
|
...(input.subject ? { subject: input.subject } : {}),
|
|
237
|
-
statement
|
|
279
|
+
statement,
|
|
280
|
+
status: 'active',
|
|
238
281
|
evidence,
|
|
239
|
-
evidenceQuote
|
|
282
|
+
evidenceQuote,
|
|
283
|
+
confirmationContext: confirmed ? `宿主确认纠正:${input.reason}` : '',
|
|
240
284
|
sourceSessionId: ctx.sessionId ?? 'unknown',
|
|
241
285
|
sourceMessageId: ctx.userMessageId ?? 'unknown',
|
|
242
286
|
sensitivity,
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
EXPLICIT_MEMORY_INTENT,
|
|
19
19
|
EXPLICIT_PERSONA_INTENT,
|
|
20
20
|
confirmSensitiveMemory,
|
|
21
|
+
confirmMemoryCorrection,
|
|
21
22
|
denyUnlessLocalOwner,
|
|
22
23
|
requireCurrentEvidence,
|
|
23
24
|
} from './policy.js';
|
|
@@ -39,7 +40,7 @@ function formatRecord(
|
|
|
39
40
|
? ` workspace=${record.workspaceId}`
|
|
40
41
|
: '';
|
|
41
42
|
const lifecycle = ` status=${record.status ?? 'active'} revision=${record.revision ?? 1}`;
|
|
42
|
-
return `- [${record.id}] scope=${record.scope} ${record.category}/${record.subject}${workspace}${lifecycle}${version ? ` version=${version}` : ''}: ${record.statement}(${record.evidence},${new Date(record.updatedAt).toISOString()}
|
|
43
|
+
return `- [${record.id}] scope=${record.scope} ${record.category}/${record.subject}${workspace}${lifecycle}${version ? ` version=${version}` : ''}: ${record.statement}(${record.evidence},${new Date(record.updatedAt).toISOString()})${record.status === 'needs_review' && record.confirmationContext ? `\n复核线索(非事实):${record.confirmationContext}` : ''}`;
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
function diagnosticSuffix(
|
|
@@ -188,15 +189,18 @@ export const rememberMemoryTool: Tool<z.infer<typeof rememberSchema>> = {
|
|
|
188
189
|
};
|
|
189
190
|
|
|
190
191
|
const updateSchema = z.object({
|
|
192
|
+
action: z.enum(['update', 'review', 'confirm']).optional().describe('默认 update:当前用户原话纠正;review:仅标记待复核,不改事实;confirm:通过宿主向用户确认拟更新内容后保存'),
|
|
193
|
+
reason: z.string().min(1).max(1000).optional().describe('review/confirm 必填:冲突或纠正依据,注明来源;不得伪造用户陈述'),
|
|
191
194
|
id: z.string().uuid(),
|
|
192
195
|
scope: scopeSchema,
|
|
193
196
|
category: categorySchema.optional(),
|
|
194
197
|
subject: z.string().min(1).max(120).optional(),
|
|
195
|
-
statement: z.string().min(1).max(1000),
|
|
198
|
+
statement: z.string().min(1).max(1000).optional(),
|
|
196
199
|
evidenceQuote: z
|
|
197
200
|
.string()
|
|
198
201
|
.min(1)
|
|
199
202
|
.max(1000)
|
|
203
|
+
.optional()
|
|
200
204
|
.describe('当前用户消息中的逐字证据片段'),
|
|
201
205
|
sensitivity: z.enum(['normal', 'sensitive']).optional(),
|
|
202
206
|
aliases: z.array(z.string().min(1).max(120)).max(20).optional(),
|
|
@@ -217,7 +221,7 @@ const updateSchema = z.object({
|
|
|
217
221
|
export const updateMemoryTool: Tool<z.infer<typeof updateSchema>> = {
|
|
218
222
|
name: 'UpdateMemory',
|
|
219
223
|
description:
|
|
220
|
-
'
|
|
224
|
+
'纠正用户事实记忆:先 RecallMemory 读取 ID/version。默认 update 需要 statement 与当前用户原话 evidenceQuote;review 需要 reason,仅标记待复核、保留原事实与证据;confirm 需要 statement/reason,通过宿主展示新旧内容并取得用户确认再更新。拒绝或无法交互不写入。项目知识和经验走 MemoryMaintenance,不伪造用户证据。',
|
|
221
225
|
inputSchema: updateSchema,
|
|
222
226
|
parameters: toToolParameters(updateSchema),
|
|
223
227
|
isReadOnly: false,
|
|
@@ -228,8 +232,20 @@ export const updateMemoryTool: Tool<z.infer<typeof updateSchema>> = {
|
|
|
228
232
|
const denied = denyUnlessLocalOwner(ctx);
|
|
229
233
|
if (denied) return denied;
|
|
230
234
|
const current = ctx.currentUserInput ?? '';
|
|
231
|
-
const
|
|
232
|
-
if (
|
|
235
|
+
const action = input.action ?? 'update';
|
|
236
|
+
if (action === 'review' && (input.statement !== undefined || input.evidenceQuote !== undefined)) {
|
|
237
|
+
return { ok: false, error: 'review 仅提交 reason,不提交 statement/evidenceQuote;标记疑点不代表确认新事实' };
|
|
238
|
+
}
|
|
239
|
+
if (action !== 'update' && (input.category !== undefined || input.subject !== undefined || input.aliases !== undefined || input.supersedesIds !== undefined || input.sensitivity !== undefined)) {
|
|
240
|
+
return { ok: false, error: 'review/confirm 只处理当前记录的复核状态或正文,不同时修改分类、敏感级别、别名或替代其他记录' };
|
|
241
|
+
}
|
|
242
|
+
if (action === 'update') {
|
|
243
|
+
if (!input.statement || !input.evidenceQuote) return { ok: false, error: 'update 需要 statement 和当前用户原话 evidenceQuote' };
|
|
244
|
+
const invalidEvidence = requireCurrentEvidence(ctx, input.evidenceQuote);
|
|
245
|
+
if (invalidEvidence) return invalidEvidence;
|
|
246
|
+
} else if (!input.reason || (action === 'confirm' && !input.statement)) {
|
|
247
|
+
return { ok: false, error: 'review 需要 reason;confirm 需要 reason 和 statement' };
|
|
248
|
+
}
|
|
233
249
|
if (input.scope === 'workspace' && !ctx.workspaceId) {
|
|
234
250
|
return {
|
|
235
251
|
ok: false,
|
|
@@ -238,7 +254,7 @@ export const updateMemoryTool: Tool<z.infer<typeof updateSchema>> = {
|
|
|
238
254
|
}
|
|
239
255
|
if (
|
|
240
256
|
!(await memoryService.settings()).autoRemember &&
|
|
241
|
-
!EXPLICIT_MEMORY_INTENT.test(current)
|
|
257
|
+
!EXPLICIT_MEMORY_INTENT.test(current) && action !== 'confirm'
|
|
242
258
|
) {
|
|
243
259
|
return {
|
|
244
260
|
ok: false,
|
|
@@ -251,23 +267,49 @@ export const updateMemoryTool: Tool<z.infer<typeof updateSchema>> = {
|
|
|
251
267
|
input.scope === 'workspace' ? ctx.workspaceId : undefined,
|
|
252
268
|
);
|
|
253
269
|
if (!currentRecord) return { ok: false, error: '未找到该记忆,未做修改' };
|
|
254
|
-
const
|
|
270
|
+
const expectedVersion = input.expectedVersion ?? currentRecord.version;
|
|
271
|
+
if (expectedVersion !== currentRecord.version) {
|
|
272
|
+
return {
|
|
273
|
+
ok: false,
|
|
274
|
+
error:
|
|
275
|
+
'记忆已被手工或并发修改。未应用本次更新;保留以下当前记录的并发修改后可直接重试,不存在时重新定位。\n'
|
|
276
|
+
+ JSON.stringify({ current: currentRecord }),
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
if (action === 'review') {
|
|
280
|
+
signal?.throwIfAborted();
|
|
281
|
+
try {
|
|
282
|
+
const reviewed = await memoryService.updateWithVersion(input.id, input.scope, expectedVersion,
|
|
283
|
+
{ status: 'needs_review', confirmationContext: `待复核原因(非确认事实):${input.reason}` },
|
|
284
|
+
input.scope === 'workspace' ? ctx.workspaceId : undefined);
|
|
285
|
+
return reviewed ? { ok: true, content: `已标记待复核,原事实和证据未改写。id=${input.id}\nversion=${reviewed.version}` }
|
|
286
|
+
: { ok: false, error: '记录已不存在,未做修改' };
|
|
287
|
+
} catch (error) {
|
|
288
|
+
if ((error as Error).message === 'MEMORY_VERSION_CONFLICT') return { ok: false, error: '记忆并发变更,请重新读取;未标记待复核' };
|
|
289
|
+
throw error;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
const statement = input.statement!;
|
|
293
|
+
const confirmed = action === 'confirm' && await confirmMemoryCorrection(currentRecord.record.subject,
|
|
294
|
+
currentRecord.record.statement, statement, input.reason!, signal);
|
|
295
|
+
if (action === 'confirm' && !confirmed) return { ok: false, error: '未获得用户确认(拒绝或宿主无法交互),原记忆未改写;可标记待复核,不要重复请求确认' };
|
|
296
|
+
const evidenceQuote = confirmed ? `用户通过纠正记忆确认:${statement}` : input.evidenceQuote!;
|
|
297
|
+
const sensitivity = currentRecord.record.sensitivity === 'sensitive' ? 'sensitive' : input.sensitivity ?? 'normal';
|
|
255
298
|
if (
|
|
256
299
|
sensitivity === 'sensitive' &&
|
|
257
|
-
|
|
300
|
+
!confirmed && statement.trim() === evidenceQuote.trim()
|
|
258
301
|
) {
|
|
259
302
|
return {
|
|
260
303
|
ok: false,
|
|
261
304
|
error: '敏感内容不得逐字更新;请先概括、去除不必要细节后再请求确认',
|
|
262
305
|
};
|
|
263
306
|
}
|
|
264
|
-
const evidence = await confirmSensitiveMemory(
|
|
265
|
-
|
|
307
|
+
const evidence = confirmed ? 'user_confirmed' : await confirmSensitiveMemory(
|
|
308
|
+
statement,
|
|
266
309
|
sensitivity,
|
|
267
310
|
signal,
|
|
268
311
|
);
|
|
269
312
|
if (!evidence) return { ok: false, error: '敏感记忆更新未获确认' };
|
|
270
|
-
const expectedVersion = input.expectedVersion ?? currentRecord.version;
|
|
271
313
|
let updated: Awaited<ReturnType<typeof memoryService.updateWithVersion>>;
|
|
272
314
|
try {
|
|
273
315
|
updated = await memoryService.updateWithVersion(
|
|
@@ -279,9 +321,11 @@ export const updateMemoryTool: Tool<z.infer<typeof updateSchema>> = {
|
|
|
279
321
|
? { category: input.category as MemoryCategory }
|
|
280
322
|
: {}),
|
|
281
323
|
...(input.subject ? { subject: input.subject } : {}),
|
|
282
|
-
statement
|
|
324
|
+
statement,
|
|
325
|
+
status: 'active',
|
|
283
326
|
evidence,
|
|
284
|
-
evidenceQuote
|
|
327
|
+
evidenceQuote,
|
|
328
|
+
confirmationContext: confirmed ? `宿主确认纠正:${input.reason}` : '',
|
|
285
329
|
sourceSessionId: ctx.sessionId ?? 'unknown',
|
|
286
330
|
sourceMessageId: ctx.userMessageId ?? 'unknown',
|
|
287
331
|
sensitivity,
|
|
@@ -25,9 +25,24 @@ export function requireCurrentEvidence(ctx, evidenceQuote) {
|
|
|
25
25
|
? null
|
|
26
26
|
: {
|
|
27
27
|
ok: false,
|
|
28
|
-
error: 'evidenceQuote
|
|
28
|
+
error: 'evidenceQuote 不是当前用户消息的逐字片段,未写入。不要改写或伪造引用重试:用户事实可用 UpdateMemory(action=confirm) 请求确认,或 action=review 标记待复核;项目知识/经验使用 MemoryMaintenance,不把工具证据当用户原话。',
|
|
29
29
|
};
|
|
30
30
|
}
|
|
31
|
+
export async function confirmMemoryCorrection(subject, previous, proposed, reason, signal) {
|
|
32
|
+
if (!hasAskHandler())
|
|
33
|
+
return false;
|
|
34
|
+
signal?.throwIfAborted();
|
|
35
|
+
const [answer] = await requestUserChoice([{
|
|
36
|
+
header: '纠正记忆',
|
|
37
|
+
question: `是否更新“${subject}”?\n原记录:${previous}\n拟更新为:${proposed}\n纠正依据:${reason}`,
|
|
38
|
+
options: [
|
|
39
|
+
{ label: '暂不更新', description: '保留原记录,不把建议视为事实。' },
|
|
40
|
+
{ label: '确认更新', description: '确认拟更新内容准确,并保存为本机长期记忆。' },
|
|
41
|
+
],
|
|
42
|
+
}], signal);
|
|
43
|
+
signal?.throwIfAborted();
|
|
44
|
+
return answer?.selected.length === 1 && answer.selected[0] === '确认更新';
|
|
45
|
+
}
|
|
31
46
|
export async function confirmSensitiveMemory(statement, sensitivity, signal) {
|
|
32
47
|
if (sensitivity === 'normal')
|
|
33
48
|
return 'user_stated';
|
|
@@ -38,10 +38,25 @@ export function requireCurrentEvidence(
|
|
|
38
38
|
? null
|
|
39
39
|
: {
|
|
40
40
|
ok: false,
|
|
41
|
-
error: 'evidenceQuote
|
|
41
|
+
error: 'evidenceQuote 不是当前用户消息的逐字片段,未写入。不要改写或伪造引用重试:用户事实可用 UpdateMemory(action=confirm) 请求确认,或 action=review 标记待复核;项目知识/经验使用 MemoryMaintenance,不把工具证据当用户原话。',
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
export async function confirmMemoryCorrection(subject: string, previous: string, proposed: string, reason: string, signal?: AbortSignal): Promise<boolean> {
|
|
46
|
+
if (!hasAskHandler()) return false;
|
|
47
|
+
signal?.throwIfAborted();
|
|
48
|
+
const [answer] = await requestUserChoice([{
|
|
49
|
+
header: '纠正记忆',
|
|
50
|
+
question: `是否更新“${subject}”?\n原记录:${previous}\n拟更新为:${proposed}\n纠正依据:${reason}`,
|
|
51
|
+
options: [
|
|
52
|
+
{ label: '暂不更新', description: '保留原记录,不把建议视为事实。' },
|
|
53
|
+
{ label: '确认更新', description: '确认拟更新内容准确,并保存为本机长期记忆。' },
|
|
54
|
+
],
|
|
55
|
+
}], signal);
|
|
56
|
+
signal?.throwIfAborted();
|
|
57
|
+
return answer?.selected.length === 1 && answer.selected[0] === '确认更新';
|
|
58
|
+
}
|
|
59
|
+
|
|
45
60
|
export async function confirmSensitiveMemory(
|
|
46
61
|
statement: string,
|
|
47
62
|
sensitivity: 'normal' | 'sensitive',
|
|
@@ -24,7 +24,7 @@ export const searchSkillsTool = {
|
|
|
24
24
|
}),
|
|
25
25
|
callWithContext: async (input, signal, ctx) => {
|
|
26
26
|
signal?.throwIfAborted();
|
|
27
|
-
const skills = visibleSkills(await discoverSkills(ctx.cwd, ctx));
|
|
27
|
+
const skills = visibleSkills(await discoverSkills(ctx.cwd, ctx), ctx.availableToolNames?.());
|
|
28
28
|
signal?.throwIfAborted();
|
|
29
29
|
const terms = (input.query ?? '')
|
|
30
30
|
.toLocaleLowerCase()
|
|
@@ -48,7 +48,7 @@ export const searchSkillsTool = {
|
|
|
48
48
|
total: ranked.length,
|
|
49
49
|
nextOffset: offset + page.length < ranked.length ? offset + page.length : null,
|
|
50
50
|
skills: page.map(({ skill }) => skill),
|
|
51
|
-
note: '
|
|
51
|
+
note: '宿主提供有效工具集合时已按依赖筛选;目录不授予执行权限。同名项用 sourceId 精确加载,仅加载当前阶段相关技能。',
|
|
52
52
|
}),
|
|
53
53
|
};
|
|
54
54
|
},
|
|
@@ -28,7 +28,7 @@ export const searchSkillsTool: Tool<z.infer<typeof schema>> = {
|
|
|
28
28
|
}),
|
|
29
29
|
callWithContext: async (input, signal, ctx) => {
|
|
30
30
|
signal?.throwIfAborted();
|
|
31
|
-
const skills = visibleSkills(await discoverSkills(ctx.cwd, ctx));
|
|
31
|
+
const skills = visibleSkills(await discoverSkills(ctx.cwd, ctx), ctx.availableToolNames?.());
|
|
32
32
|
signal?.throwIfAborted();
|
|
33
33
|
const terms = (input.query ?? '')
|
|
34
34
|
.toLocaleLowerCase()
|
|
@@ -58,7 +58,7 @@ export const searchSkillsTool: Tool<z.infer<typeof schema>> = {
|
|
|
58
58
|
nextOffset:
|
|
59
59
|
offset + page.length < ranked.length ? offset + page.length : null,
|
|
60
60
|
skills: page.map(({ skill }) => skill),
|
|
61
|
-
note: '
|
|
61
|
+
note: '宿主提供有效工具集合时已按依赖筛选;目录不授予执行权限。同名项用 sourceId 精确加载,仅加载当前阶段相关技能。',
|
|
62
62
|
}),
|
|
63
63
|
};
|
|
64
64
|
},
|
package/src/tools/skill/skill.js
CHANGED
|
@@ -27,6 +27,10 @@ async function call(input, context) {
|
|
|
27
27
|
ok: false,
|
|
28
28
|
error: `Skill 名称有歧义:${input.name}。请选择 sourceId 后重试。\n${JSON.stringify(found.map(({ sourceId, sourceDir }) => ({ sourceId, sourceDir })))}`,
|
|
29
29
|
};
|
|
30
|
+
const available = context.availableToolNames?.();
|
|
31
|
+
const missing = available && found[0].requiresTools?.filter(name => !available.has(name));
|
|
32
|
+
if (missing?.length)
|
|
33
|
+
return { ok: false, error: `Skill ${input.name} 当前缺少工具:${missing.join(', ')}。不能通过技能扩大执行权限。` };
|
|
30
34
|
// Search roots are ordered: project skill intentionally overrides the bundled skill.
|
|
31
35
|
const path = join(found[0].sourceDir, 'SKILL.md');
|
|
32
36
|
const content = await readFile(path);
|
package/src/tools/skill/skill.ts
CHANGED
|
@@ -42,6 +42,9 @@ async function call(
|
|
|
42
42
|
ok: false,
|
|
43
43
|
error: `Skill 名称有歧义:${input.name}。请选择 sourceId 后重试。\n${JSON.stringify(found.map(({ sourceId, sourceDir }) => ({ sourceId, sourceDir })))}`,
|
|
44
44
|
};
|
|
45
|
+
const available = context.availableToolNames?.();
|
|
46
|
+
const missing = available && found[0]!.requiresTools?.filter(name => !available.has(name));
|
|
47
|
+
if (missing?.length) return { ok: false, error: `Skill ${input.name} 当前缺少工具:${missing.join(', ')}。不能通过技能扩大执行权限。` };
|
|
45
48
|
// Search roots are ordered: project skill intentionally overrides the bundled skill.
|
|
46
49
|
const path = join(found[0]!.sourceDir, 'SKILL.md');
|
|
47
50
|
const content = await readFile(path);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AGENT_TOOL_DESCRIPTION } from '../prompts/delegation.js';
|
|
1
2
|
import { bashShellNotice } from './bash/bash.js';
|
|
2
3
|
import { COMPUTER_USE_TOOL_ACTIONS } from './computeruse/protocol.js';
|
|
3
4
|
export const TOOL_CATEGORY_LABELS = {
|
|
@@ -42,7 +43,7 @@ export const BUILTIN_TOOL_PROMPT_GUIDES = {
|
|
|
42
43
|
BrowserCDP: g('web', '需要现有浏览器工具未覆盖的原始 CDP 能力', '发送精确方法并按事件序列读取结果', '常规网页自动化优先 WebBrowser/BrowserBatch', 'last-resort'),
|
|
43
44
|
Download: g('web', '把已知 URL 的原始资源保存到本机', '给出目标路径并校验下载结果', '只阅读正文时用 WebFetch'),
|
|
44
45
|
ComputerUse: g('desktop', '用户明确要求观察或操作原生应用、系统窗口或桌面界面', '观察当前界面,执行一个动作,再验证结果', '文件、静态图片、Shell 和网页任务优先专用工具', 'last-resort'),
|
|
45
|
-
Agent: g('agents', '
|
|
46
|
+
Agent: g('agents', '大型多模块调查优先委派独立只读问题', '先定位入口,再同轮派发 explore,核验整合证据', '少量已知文件或强顺序工作直接处理'),
|
|
46
47
|
AgentMessage: g('agents', '向运行中的子 Agent 补充非触发消息', '按稳定 agent id 发送必要上下文', '需要追加新工作使用 AgentFollowup'),
|
|
47
48
|
AgentList: g('agents', '查看子 Agent 状态和稳定 ID', '按任务路径列出后选择目标', '已知目标 ID 时无需调用'),
|
|
48
49
|
AgentWait: g('agents', '等待指定子 Agent 进展或结果', '优先长等待,收到更新后再整合', '忙轮询'),
|
|
@@ -58,7 +59,7 @@ export const BUILTIN_TOOL_PROMPT_GUIDES = {
|
|
|
58
59
|
RecallMemory: g('memory', '当前回答依赖跨会话稳定偏好、决定或背景', '按主题召回并核对证据', '当前消息已足够时不要装饰性召回'),
|
|
59
60
|
MemoryMaintenance: g('memory', '处理学习队列、项目事实冲突及经验反馈', 'status 定位;summaries 按 query 找近期任务线索、按 turnId 查证据;resolve/resolveKnowledge 结清候选;明确跨项目适用条件后 promoteExperience/promoteKnowledge;adopt 后 feedback', '召回不算使用;失败摘要不是已确认事实;不每轮调用'),
|
|
60
61
|
RememberMemory: g('memory', '用户明确要求记住长期有效事实、偏好或决定', '保存原话证据和适用范围', '普通陈述交由已启用的后台提炼,不重复保存'),
|
|
61
|
-
UpdateMemory: g('memory', '
|
|
62
|
+
UpdateMemory: g('memory', '纠正用户事实', '读版本后 update/review/confirm', '项目知识/经验走 MemoryMaintenance'),
|
|
62
63
|
ListMemories: g('memory', '用户要查看已保存记忆或需要管理总览', '按范围列出后再决定操作', '正常任务无需全量列出'),
|
|
63
64
|
ForgetMemory: g('memory', '用户明确要求忘记某条记忆', '定位准确 ID 后删除', '不得主动清理'),
|
|
64
65
|
SetPersona: g('memory', '用户明确要求改变长期角色或协作风格', '写入清晰、稳定的人格要求', '一次性语气请求不要持久化'),
|
|
@@ -117,7 +118,7 @@ export const CONTRACT_DESCRIPTION_OVERRIDES = {
|
|
|
117
118
|
WebSearch: 'Tavily 搜索返回标题、URL、片段及可选综合答案,不是完整原文或浏览器。重要结论读取来源正文;按子问题查询,重试改变关键词或来源。recency 控制时效,历史范围用 start_date/end_date,none 关闭自动时效;advanced 更慢,news 偏新闻。需配置 Tavily。',
|
|
118
119
|
PowerShell: '运行宿主 PowerShell 独立进程并检查结果;长任务用 TaskOutput/TaskStop。永久删除用 Trash;不可逆操作先确认,非交互时取消。',
|
|
119
120
|
Download: '下载 HTTP(S) 原始字节到指定路径;相对路径基于工作目录,会覆盖已有文件;失败或中断不保留部分下载。',
|
|
120
|
-
Agent:
|
|
121
|
+
Agent: AGENT_TOOL_DESCRIPTION,
|
|
121
122
|
EnterWorktree: '从已提交 HEAD 创建并进入隔离工作树,不自动复制原工作区未提交修改;后续文件工具使用隔离目录,项目记忆仍属于原工作区。',
|
|
122
123
|
ExitWorktree: '退出隔离工作树:apply 检查并应用补丁到原工作区但不自动提交;keep 保留;discard 有改动则归档保留。实际应用成功后的清理告警不表示应重新应用。',
|
|
123
124
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Tool, ToolCategory, ToolPromptGuide } from '../types.js';
|
|
2
|
+
import { AGENT_TOOL_DESCRIPTION } from '../prompts/delegation.js';
|
|
2
3
|
import { bashShellNotice } from './bash/bash.js';
|
|
3
4
|
import { COMPUTER_USE_TOOL_ACTIONS } from './computeruse/protocol.js';
|
|
4
5
|
|
|
@@ -56,7 +57,7 @@ export const BUILTIN_TOOL_PROMPT_GUIDES: Readonly<Record<string, ToolPromptGuide
|
|
|
56
57
|
|
|
57
58
|
ComputerUse: g('desktop', '用户明确要求观察或操作原生应用、系统窗口或桌面界面', '观察当前界面,执行一个动作,再验证结果', '文件、静态图片、Shell 和网页任务优先专用工具', 'last-resort'),
|
|
58
59
|
|
|
59
|
-
Agent: g('agents', '
|
|
60
|
+
Agent: g('agents', '大型多模块调查优先委派独立只读问题', '先定位入口,再同轮派发 explore,核验整合证据', '少量已知文件或强顺序工作直接处理'),
|
|
60
61
|
AgentMessage: g('agents', '向运行中的子 Agent 补充非触发消息', '按稳定 agent id 发送必要上下文', '需要追加新工作使用 AgentFollowup'),
|
|
61
62
|
AgentList: g('agents', '查看子 Agent 状态和稳定 ID', '按任务路径列出后选择目标', '已知目标 ID 时无需调用'),
|
|
62
63
|
AgentWait: g('agents', '等待指定子 Agent 进展或结果', '优先长等待,收到更新后再整合', '忙轮询'),
|
|
@@ -73,7 +74,7 @@ export const BUILTIN_TOOL_PROMPT_GUIDES: Readonly<Record<string, ToolPromptGuide
|
|
|
73
74
|
RecallMemory: g('memory', '当前回答依赖跨会话稳定偏好、决定或背景', '按主题召回并核对证据', '当前消息已足够时不要装饰性召回'),
|
|
74
75
|
MemoryMaintenance: g('memory', '处理学习队列、项目事实冲突及经验反馈', 'status 定位;summaries 按 query 找近期任务线索、按 turnId 查证据;resolve/resolveKnowledge 结清候选;明确跨项目适用条件后 promoteExperience/promoteKnowledge;adopt 后 feedback', '召回不算使用;失败摘要不是已确认事实;不每轮调用'),
|
|
75
76
|
RememberMemory: g('memory', '用户明确要求记住长期有效事实、偏好或决定', '保存原话证据和适用范围', '普通陈述交由已启用的后台提炼,不重复保存'),
|
|
76
|
-
UpdateMemory: g('memory', '
|
|
77
|
+
UpdateMemory: g('memory', '纠正用户事实', '读版本后 update/review/confirm', '项目知识/经验走 MemoryMaintenance'),
|
|
77
78
|
ListMemories: g('memory', '用户要查看已保存记忆或需要管理总览', '按范围列出后再决定操作', '正常任务无需全量列出'),
|
|
78
79
|
ForgetMemory: g('memory', '用户明确要求忘记某条记忆', '定位准确 ID 后删除', '不得主动清理'),
|
|
79
80
|
SetPersona: g('memory', '用户明确要求改变长期角色或协作风格', '写入清晰、稳定的人格要求', '一次性语气请求不要持久化'),
|
|
@@ -135,7 +136,7 @@ export const CONTRACT_DESCRIPTION_OVERRIDES: Readonly<Record<string, string | ((
|
|
|
135
136
|
WebSearch: 'Tavily 搜索返回标题、URL、片段及可选综合答案,不是完整原文或浏览器。重要结论读取来源正文;按子问题查询,重试改变关键词或来源。recency 控制时效,历史范围用 start_date/end_date,none 关闭自动时效;advanced 更慢,news 偏新闻。需配置 Tavily。',
|
|
136
137
|
PowerShell: '运行宿主 PowerShell 独立进程并检查结果;长任务用 TaskOutput/TaskStop。永久删除用 Trash;不可逆操作先确认,非交互时取消。',
|
|
137
138
|
Download: '下载 HTTP(S) 原始字节到指定路径;相对路径基于工作目录,会覆盖已有文件;失败或中断不保留部分下载。',
|
|
138
|
-
Agent:
|
|
139
|
+
Agent: AGENT_TOOL_DESCRIPTION,
|
|
139
140
|
EnterWorktree: '从已提交 HEAD 创建并进入隔离工作树,不自动复制原工作区未提交修改;后续文件工具使用隔离目录,项目记忆仍属于原工作区。',
|
|
140
141
|
ExitWorktree: '退出隔离工作树:apply 检查并应用补丁到原工作区但不自动提交;keep 保留;discard 有改动则归档保留。实际应用成功后的清理告警不表示应重新应用。',
|
|
141
142
|
};
|
package/src/types.ts
CHANGED
|
@@ -570,6 +570,10 @@ export type ToolResult = { problem?: ToolProblemOutcome; observation?: ReadObser
|
|
|
570
570
|
* 避免按工具名写特判分支。
|
|
571
571
|
*/
|
|
572
572
|
export interface ToolCallContext {
|
|
573
|
+
/** Host presentation only; cannot grant tools to a delegated agent. */
|
|
574
|
+
output?: import('./runtime/outputContract.js').OutputContract;
|
|
575
|
+
/** Live effective catalog, including per-turn restrictions. */
|
|
576
|
+
availableToolNames?: () => ReadonlySet<string>;
|
|
573
577
|
messageProvenance?: ConversationProvenance;
|
|
574
578
|
agentHandoff?: {
|
|
575
579
|
questionIds?: readonly string[];
|
package/src/web/protocol.ts
CHANGED
|
@@ -292,6 +292,7 @@ export interface SkillInfo {
|
|
|
292
292
|
name: string;
|
|
293
293
|
description: string;
|
|
294
294
|
source?: 'builtin' | 'workspace';
|
|
295
|
+
unavailableReason?: string;
|
|
295
296
|
}
|
|
296
297
|
|
|
297
298
|
export type ApiFormat = 'openai' | 'anthropic';
|
|
@@ -2195,6 +2196,7 @@ export type ClientFrame =
|
|
|
2195
2196
|
| { t: 'answer'; requestId: string; answers: AskAnswer[] }
|
|
2196
2197
|
| { t: 'cancelAsk'; requestId: string }
|
|
2197
2198
|
| { t: 'abort' }
|
|
2199
|
+
| { t: 'compact/abort'; requestId: string }
|
|
2198
2200
|
| { t: 'new'; dir: string; sessionId?: string }
|
|
2199
2201
|
| { t: 'compact'; dir: string; sessionId?: string; requestId?: string }
|
|
2200
2202
|
| { t: 'listDirs' }
|
|
@@ -3060,7 +3062,7 @@ export type ServerFrame =
|
|
|
3060
3062
|
ok: boolean;
|
|
3061
3063
|
message: string;
|
|
3062
3064
|
}
|
|
3063
|
-
| { t: 'skills'; skills: SkillInfo[] }
|
|
3065
|
+
| { t: 'skills'; skills: SkillInfo[]; dir?: string | null }
|
|
3064
3066
|
| { t: 'server_error'; message: string }
|
|
3065
3067
|
| { t: 'resource/crons/list'; tasks: CronTaskWithRuns[]; missedCount: number }
|
|
3066
3068
|
| { t: 'resource/crons/draft'; draft: CronDraftResult }
|