minimal-agent 0.18.5 → 0.18.7
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/plugins/workflow-runner/src/runtime/context.js +5 -17
- package/plugins/workflow-runner/src/runtime/context.ts +4 -15
- package/plugins/workflow-runner/src/runtime/model.js +4 -2
- package/plugins/workflow-runner/src/runtime/model.ts +3 -2
- package/plugins/workflow-runner/test/api.test.ts +4 -2
- package/plugins/workflow-runner/test/embeddedExecution.test.ts +5 -6
- package/plugins/workflow-runner/test/modelOutcome.test.ts +36 -0
- package/skills/dynamic-ui/SKILL.md +38 -278
- package/skills/dynamic-ui/mermaid.md +32 -0
- package/skills/dynamic-ui/scenes/architecture-and-flow.md +4 -1
- package/skills/dynamic-ui/scenes/data-visualization.md +5 -2
- package/skills/dynamic-ui/svg-design.md +251 -0
- package/skills/dynamic-ui/templates/README.md +2 -1
- package/skills/dynamic-ui/tokens/visual-tokens.md +4 -5
- package/src/advisor/README.md +24 -0
- package/src/advisor/advisorCoordinator.js +101 -45
- package/src/advisor/advisorCoordinator.ts +90 -51
- package/src/advisor/deliveryReview.js +35 -0
- package/src/advisor/deliveryReview.ts +30 -0
- package/src/advisor/presentation.js +10 -0
- package/src/advisor/presentation.ts +12 -0
- package/src/advisor/reviewContext.js +60 -0
- package/src/advisor/reviewContext.ts +72 -0
- package/src/context/contextBundle.js +23 -3
- package/src/context/contextBundle.ts +28 -7
- package/src/context/tokenCounter.js +3 -0
- package/src/context/tokenCounter.ts +2 -0
- package/src/llm/messageProvenance.js +1 -1
- package/src/llm/messageProvenance.ts +1 -1
- package/src/loop.js +203 -30
- package/src/loop.ts +183 -30
- package/src/memory/experienceStore.js +1 -0
- package/src/memory/experienceStore.ts +1 -0
- package/src/memory/memoryService.js +3 -2
- package/src/memory/memoryService.ts +3 -0
- package/src/memory/projectKnowledge.js +1 -0
- package/src/memory/projectKnowledge.ts +1 -0
- package/src/memory/retrieval.js +4 -4
- package/src/memory/retrieval.ts +4 -4
- package/src/memory/types.ts +1 -0
- package/src/prompts/policyRegistry.js +31 -17
- package/src/prompts/policyRegistry.ts +31 -16
- package/src/prompts/promptAssembly.js +3 -9
- package/src/prompts/promptAssembly.ts +3 -9
- package/src/runtime/answerPreparation.js +9 -0
- package/src/runtime/answerPreparation.ts +11 -0
- package/src/runtime/eventSemantics.js +2 -0
- package/src/runtime/eventSemantics.ts +2 -0
- package/src/runtime/finalAnswer.js +7 -0
- package/src/runtime/finalAnswer.ts +9 -0
- package/src/runtime/lifecycleTrace.js +16 -0
- package/src/runtime/lifecycleTrace.ts +22 -0
- package/src/session/runTurn.js +15 -1
- package/src/session/runTurn.ts +13 -1
- package/src/tools/advisor/advisor.js +56 -40
- package/src/tools/advisor/advisor.ts +57 -39
- package/src/tools/advisor/advisorDecision.js +6 -3
- package/src/tools/advisor/advisorDecision.ts +6 -3
- package/src/tools/agent/agent.js +7 -1
- package/src/tools/agent/agent.ts +7 -1
- package/src/tools/bash/semantics.js +91 -26
- package/src/tools/bash/semantics.ts +63 -33
- package/src/tools/index.js +4 -1
- package/src/tools/index.ts +4 -1
- package/src/types.ts +15 -6
- package/src/ui/LiveArea.js +2 -1
- package/src/ui/LiveArea.tsx +2 -1
- package/src/ui/MessageList.js +18 -5
- package/src/ui/MessageList.tsx +25 -3
- package/src/ui/draftNotice.js +1 -8
- package/src/ui/draftNotice.ts +1 -10
- package/src/ui/hooks/useChat.js +25 -1
- package/src/ui/hooks/useChat.ts +22 -1
package/src/loop.ts
CHANGED
|
@@ -28,6 +28,9 @@
|
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
30
|
import crypto from 'node:crypto';
|
|
31
|
+
import { answerPreparationNotice } from './runtime/answerPreparation.js';
|
|
32
|
+
import { finalAnswerMessages } from './runtime/finalAnswer.js';
|
|
33
|
+
import { createLifecycleTrace } from './runtime/lifecycleTrace.js';
|
|
31
34
|
import { conciseToolPath } from './runtime/toolDisplay.js';
|
|
32
35
|
import { dirname, resolve } from 'node:path';
|
|
33
36
|
import { loadProjectPromptInstructions } from './prompts/projectInstructions.js';
|
|
@@ -100,6 +103,7 @@ import {
|
|
|
100
103
|
} from './advisor/advisorCoordinator.js';
|
|
101
104
|
import { toolEffectFor } from './tools/capabilities.js';
|
|
102
105
|
import { isReadOnlyShellCommand, isVerificationShellCommand } from './tools/bash/semantics.js';
|
|
106
|
+
import { collectReviewContext } from './advisor/reviewContext.js';
|
|
103
107
|
import {
|
|
104
108
|
createAgentRuntimeContext,
|
|
105
109
|
type AgentRuntimeContext,
|
|
@@ -410,11 +414,13 @@ export async function* runQuery(
|
|
|
410
414
|
if (!computerIntent.needed && sessionState) delete sessionState.computerAutomation;
|
|
411
415
|
// DI fallback:测试可传 stub,生产走真实模块单例
|
|
412
416
|
const chatFn = options.chat ?? defaultChat;
|
|
417
|
+
const trace = createLifecycleTrace('loop');
|
|
413
418
|
const executeToolFn = options.executeTool ?? defaultExecuteTool;
|
|
414
419
|
const explicitFileDeliveryRequested = expectsFormalDelivery(userInput);
|
|
415
420
|
const currentUserMessageId = options.userMessageId ?? crypto.randomUUID();
|
|
416
421
|
|
|
417
422
|
let currentTransientBundle = options.transientBundle;
|
|
423
|
+
let advisorContextBundle = currentTransientBundle;
|
|
418
424
|
let modelEnvironmentCwd = runtimeContext.cwd;
|
|
419
425
|
const observedRuleScopes = new Map<string, string>();
|
|
420
426
|
const pendingRuleScopes = new Set<string>();
|
|
@@ -435,6 +441,7 @@ export async function* runQuery(
|
|
|
435
441
|
const selected = availableForBundle >= bundle.diagnostics.usedTokens
|
|
436
442
|
? bundle
|
|
437
443
|
: resizeContextBundle(bundle, availableForBundle);
|
|
444
|
+
advisorContextBundle = selected;
|
|
438
445
|
return [selected.text, opaque].filter(Boolean).join('\n\n');
|
|
439
446
|
};
|
|
440
447
|
let resultPresented = false;
|
|
@@ -452,6 +459,16 @@ export async function* runQuery(
|
|
|
452
459
|
executionServices: { ...options.executionServices, executeTool: executeToolFn, chat: chatFn, invocationPolicy: options.invocationPolicy },
|
|
453
460
|
history,
|
|
454
461
|
sessionState,
|
|
462
|
+
advisorReviewContext: () => ({
|
|
463
|
+
...collectReviewContext(advisorContextBundle),
|
|
464
|
+
constraints: [...collectReviewContext(advisorContextBundle).constraints,
|
|
465
|
+
{ id: 'execution.boundary', authority: 'host', content: JSON.stringify({ cwd: sessionState?.activeWorktree?.path ?? toolCallContext.cwd,
|
|
466
|
+
conversationMode: toolCallContext.conversationMode, profile: runtimeContext.profile,
|
|
467
|
+
availableTools: [...currentEffectiveToolSet.names], invocation: invocationInstructions(options.invocationPolicy) }) }],
|
|
468
|
+
omittedIds: [...new Set([...(currentTransientBundle?.diagnostics.omittedIds ?? []), ...(advisorContextBundle?.diagnostics.omittedIds ?? [])]),
|
|
469
|
+
...(options.transientContext ? ['legacy.transient-context:not-forwarded'] : [])],
|
|
470
|
+
clippedIds: [...new Set([...(currentTransientBundle?.diagnostics.clippedIds ?? []), ...(advisorContextBundle?.diagnostics.clippedIds ?? [])])],
|
|
471
|
+
}),
|
|
455
472
|
// Statistics only; consultations have no per-turn quota.
|
|
456
473
|
advisorCalls: { used: 0 },
|
|
457
474
|
advisorInvocation: 'agent',
|
|
@@ -910,7 +927,17 @@ export async function* runQuery(
|
|
|
910
927
|
}
|
|
911
928
|
|
|
912
929
|
// 3. 调 LLM 并组装 assistant 消息
|
|
930
|
+
trace('model_start');
|
|
913
931
|
let assistantText = '';
|
|
932
|
+
// Candidate text is private until the delivery gates allow publication.
|
|
933
|
+
// Keep the compatibility event as a phase notice, never as a text retraction.
|
|
934
|
+
function* retireDraft(reason: Extract<LoopEvent, { type: 'assistant_draft_discarded' }>['reason'] | 'interrupted'): Generator<LoopEvent> {
|
|
935
|
+
const message: Message = { role: 'system', content: answerPreparationNotice(reason), id: crypto.randomUUID(),
|
|
936
|
+
timestamp: Date.now(), _ephemeral: true, _uiProgress: true };
|
|
937
|
+
history.push(message);
|
|
938
|
+
yield { type: 'injected_message', message };
|
|
939
|
+
if (reason !== 'interrupted') yield { type: 'assistant_draft_discarded', reason, textLength: 0 };
|
|
940
|
+
}
|
|
914
941
|
/** 按 index 累积 tool_calls(OpenAI 协议下 tool_calls 是分片返回的) */
|
|
915
942
|
const toolCallsByIndex: ToolCall[] = [];
|
|
916
943
|
/**
|
|
@@ -921,9 +948,6 @@ export async function* runQuery(
|
|
|
921
948
|
let reasoningString = '';
|
|
922
949
|
const reasoningDetails: unknown[] = [];
|
|
923
950
|
|
|
924
|
-
/** 首个 text_delta 时切到 streaming,再来不重复 yield */
|
|
925
|
-
let stageStreamingYielded = false;
|
|
926
|
-
|
|
927
951
|
/**
|
|
928
952
|
* R2:本轮 LLM 流的终止原因(来自 client 的 done.stopReason)。
|
|
929
953
|
* 'length' = 被 provider 因 max_tokens 截断 —— 收尾时据此给 warning,
|
|
@@ -956,12 +980,7 @@ export async function* runQuery(
|
|
|
956
980
|
: {}),
|
|
957
981
|
})) {
|
|
958
982
|
if (ev.type === 'text_delta') {
|
|
959
|
-
if (!stageStreamingYielded) {
|
|
960
|
-
stageStreamingYielded = true;
|
|
961
|
-
yield { type: 'stage_change', stage: 'streaming' };
|
|
962
|
-
}
|
|
963
983
|
assistantText += ev.delta;
|
|
964
|
-
yield { type: 'text', delta: ev.delta };
|
|
965
984
|
} else if (ev.type === 'tool_call_delta') {
|
|
966
985
|
mergeToolCallDelta(toolCallsByIndex, ev);
|
|
967
986
|
} else if (ev.type === 'reasoning_delta') {
|
|
@@ -1002,7 +1021,9 @@ export async function* runQuery(
|
|
|
1002
1021
|
}
|
|
1003
1022
|
}
|
|
1004
1023
|
} catch (e) {
|
|
1024
|
+
trace('model_error');
|
|
1005
1025
|
if ((e as Error).name === 'AbortError') {
|
|
1026
|
+
yield* retireDraft('interrupted');
|
|
1006
1027
|
yield interruption();
|
|
1007
1028
|
return;
|
|
1008
1029
|
}
|
|
@@ -1026,6 +1047,7 @@ export async function* runQuery(
|
|
|
1026
1047
|
options.promptCache,
|
|
1027
1048
|
);
|
|
1028
1049
|
} catch (error) {
|
|
1050
|
+
yield* retireDraft('interrupted');
|
|
1029
1051
|
if (signal?.aborted || (error as Error).name === 'AbortError') {
|
|
1030
1052
|
yield interruption();
|
|
1031
1053
|
return;
|
|
@@ -1057,6 +1079,7 @@ export async function* runQuery(
|
|
|
1057
1079
|
}
|
|
1058
1080
|
|
|
1059
1081
|
// code='llm_error':终结性错误,stop_reason='error'
|
|
1082
|
+
yield* retireDraft('interrupted');
|
|
1060
1083
|
yield {
|
|
1061
1084
|
type: 'error',
|
|
1062
1085
|
error: `LLM 调用失败:${(e as Error).message}`,
|
|
@@ -1065,7 +1088,9 @@ export async function* runQuery(
|
|
|
1065
1088
|
return;
|
|
1066
1089
|
}
|
|
1067
1090
|
|
|
1091
|
+
trace('model_end');
|
|
1068
1092
|
if (lastFinishReason === 'aborted' || signal?.aborted || options.hasQueuedInput?.()) {
|
|
1093
|
+
yield* retireDraft('interrupted');
|
|
1069
1094
|
if (options.hasQueuedInput?.() && !signal?.aborted) {
|
|
1070
1095
|
pushQueuedInputMarker(history);
|
|
1071
1096
|
yield { type: 'interrupted', reason: 'queued_input' };
|
|
@@ -1086,6 +1111,19 @@ export async function* runQuery(
|
|
|
1086
1111
|
}
|
|
1087
1112
|
}
|
|
1088
1113
|
|
|
1114
|
+
// A pending review must run before another solving/publishing attempt.
|
|
1115
|
+
// Read-only diagnosis and human questions remain available while stalled.
|
|
1116
|
+
const pendingReview = options.advisorCheckpoints === false ? undefined : advisorCoordinator.pendingReview();
|
|
1117
|
+
if (pendingReview && turn < maxTurns && toolCallsByIndex.length > 0
|
|
1118
|
+
&& !toolCallsByIndex.some(tc => ['Advisor', 'AskUserQuestion'].includes(tc.function.name))
|
|
1119
|
+
&& toolCallsByIndex.some(tc => tc.function.name === 'PresentResult'
|
|
1120
|
+
|| advisorSemanticsFor(tc, toolCallContext, currentEffectiveToolSet.tools.find(t => t.name === tc.function.name), { treatVerificationAsReadOnly: true }).mutatesWorkspace
|
|
1121
|
+
|| stallGuard.shouldReject(tc))) {
|
|
1122
|
+
yield* retireDraft('advisor_checkpoint');
|
|
1123
|
+
toolCallsByIndex.splice(0, toolCallsByIndex.length, { id: crypto.randomUUID(), type: 'function',
|
|
1124
|
+
function: { name: 'Advisor', arguments: JSON.stringify(pendingReview) } });
|
|
1125
|
+
assistantText = '';
|
|
1126
|
+
}
|
|
1089
1127
|
const assistantMsg: Message = {
|
|
1090
1128
|
role: 'assistant',
|
|
1091
1129
|
content: assistantText.length > 0 ? assistantText : null,
|
|
@@ -1098,12 +1136,12 @@ export async function* runQuery(
|
|
|
1098
1136
|
...(options.messageProvenance ? { provenance: options.messageProvenance } : {}),
|
|
1099
1137
|
};
|
|
1100
1138
|
// 4. 没有工具调用 → 整轮交互结束
|
|
1101
|
-
if (toolCallsByIndex.length === 0) {
|
|
1139
|
+
finalResponse: if (toolCallsByIndex.length === 0) {
|
|
1102
1140
|
if (!askCorrectionAttempted && runtimeContext.interactiveAsk
|
|
1103
1141
|
&& currentEffectiveToolSet.names.has('AskUserQuestion')
|
|
1104
1142
|
&& needsInteractiveQuestion(stripThinkBlocks(assistantText)) && turn < maxTurns) {
|
|
1105
1143
|
askCorrectionAttempted = true;
|
|
1106
|
-
yield
|
|
1144
|
+
yield* retireDraft('interactive_question');
|
|
1107
1145
|
continue;
|
|
1108
1146
|
}
|
|
1109
1147
|
const emptyResponse = stripThinkBlocks(assistantText).length === 0;
|
|
@@ -1112,12 +1150,11 @@ export async function* runQuery(
|
|
|
1112
1150
|
if (pendingDelegations.length && !delegationCorrectionAttempted && turn < maxTurns) {
|
|
1113
1151
|
delegationCorrectionAttempted = true;
|
|
1114
1152
|
coordinationText = coordination!.text;
|
|
1115
|
-
yield
|
|
1153
|
+
yield* retireDraft('formal_delivery');
|
|
1116
1154
|
continue;
|
|
1117
1155
|
}
|
|
1118
1156
|
const pendingMessages = [...(options.takePendingMessages?.() ?? []), ...await takeAgentNotifications(toolCallContext)];
|
|
1119
1157
|
if (pendingMessages.length) {
|
|
1120
|
-
if (!emptyResponse) history.push(assistantMsg);
|
|
1121
1158
|
history.push(...pendingMessages);
|
|
1122
1159
|
continue;
|
|
1123
1160
|
}
|
|
@@ -1131,22 +1168,27 @@ export async function* runQuery(
|
|
|
1131
1168
|
&& turn < maxTurns
|
|
1132
1169
|
) {
|
|
1133
1170
|
deliveryCorrectionAttempted = true;
|
|
1134
|
-
yield
|
|
1135
|
-
type: 'assistant_draft_discarded',
|
|
1136
|
-
reason: 'formal_delivery',
|
|
1137
|
-
textLength: assistantText.length,
|
|
1138
|
-
};
|
|
1171
|
+
yield* retireDraft('formal_delivery');
|
|
1139
1172
|
continue;
|
|
1140
1173
|
}
|
|
1141
|
-
const deliveryCheckpoint = emptyResponse
|
|
1174
|
+
const deliveryCheckpoint = emptyResponse || options.advisorCheckpoints === false
|
|
1142
1175
|
? { allow: true as const }
|
|
1143
1176
|
: advisorCoordinator.beforeDelivery(assistantText);
|
|
1177
|
+
const fallbackReview = !emptyResponse && options.advisorCheckpoints !== false
|
|
1178
|
+
? advisorCoordinator.pendingReview(assistantText) : undefined;
|
|
1179
|
+
if (fallbackReview && turn < maxTurns) {
|
|
1180
|
+
yield* retireDraft('advisor_checkpoint');
|
|
1181
|
+
// Reuse the effective tool boundary, permissions, cancellation, events and
|
|
1182
|
+
// persistence. Never call a provider behind the host's tool pipeline.
|
|
1183
|
+
toolCallsByIndex.push({ id: crypto.randomUUID(), type: 'function', function: {
|
|
1184
|
+
name: 'Advisor', arguments: JSON.stringify(fallbackReview),
|
|
1185
|
+
} });
|
|
1186
|
+
assistantMsg.content = null;
|
|
1187
|
+
assistantMsg.tool_calls = toolCallsByIndex;
|
|
1188
|
+
break finalResponse;
|
|
1189
|
+
}
|
|
1144
1190
|
if (!deliveryCheckpoint.allow && turn < maxTurns) {
|
|
1145
|
-
yield
|
|
1146
|
-
type: 'assistant_draft_discarded',
|
|
1147
|
-
reason: 'advisor_checkpoint',
|
|
1148
|
-
textLength: assistantText.length,
|
|
1149
|
-
};
|
|
1191
|
+
yield* retireDraft('advisor_checkpoint');
|
|
1150
1192
|
continue;
|
|
1151
1193
|
}
|
|
1152
1194
|
if (!emptyResponse) {
|
|
@@ -1156,19 +1198,110 @@ export async function* runQuery(
|
|
|
1156
1198
|
if (emptyResponse) {
|
|
1157
1199
|
// A no-tool response is already the model's terminal signal. Do not turn it
|
|
1158
1200
|
// into another model round, and do not persist/broadcast an empty bubble.
|
|
1159
|
-
yield
|
|
1160
|
-
type: 'assistant_draft_discarded',
|
|
1161
|
-
reason: 'empty_response',
|
|
1162
|
-
textLength: assistantText.length,
|
|
1163
|
-
};
|
|
1201
|
+
yield* retireDraft('empty_response');
|
|
1164
1202
|
} else {
|
|
1203
|
+
// Publication is a separate tool-free request. Nothing from preparation
|
|
1204
|
+
// has been streamed, and no gate may retract text after this boundary.
|
|
1205
|
+
let publicationError: unknown;
|
|
1206
|
+
let publicationInterrupted = false;
|
|
1207
|
+
let streamed = false;
|
|
1208
|
+
if (!options.responseFormat && runtimeContext.profile === 'main'
|
|
1209
|
+
&& (runtimeContext.surface === 'desktop' || runtimeContext.surface === 'tui')) {
|
|
1210
|
+
const candidate = assistantText;
|
|
1211
|
+
const candidateFinishReason = lastFinishReason;
|
|
1212
|
+
let publishedText = '';
|
|
1213
|
+
let publicationDone = false;
|
|
1214
|
+
yield { type: 'stage_change', stage: 'streaming' };
|
|
1215
|
+
try {
|
|
1216
|
+
const publicationMessages = finalAnswerMessages(buildModelMessages(history, requestTransientContext, userMessage.id), assistantText);
|
|
1217
|
+
const publicationPlan = buildContextBudgetPlan({ provider, history: publicationMessages, tools: [] });
|
|
1218
|
+
if (publicationPlan.overBudget) throw new Error('最终表达上下文超过模型输入上限,未发送请求。');
|
|
1219
|
+
if (signal?.aborted || options.hasQueuedInput?.()) {
|
|
1220
|
+
publicationInterrupted = true;
|
|
1221
|
+
throw new Error('最终发布已取消。');
|
|
1222
|
+
}
|
|
1223
|
+
turnUsage.totalCalls += 1;
|
|
1224
|
+
for await (const event of chatFn({
|
|
1225
|
+
provider,
|
|
1226
|
+
messages: publicationMessages,
|
|
1227
|
+
tools: [], signal,
|
|
1228
|
+
conversationMode: runtimeContext.conversationMode ?? 'private',
|
|
1229
|
+
promptCache: options.promptCache,
|
|
1230
|
+
})) {
|
|
1231
|
+
if (event.type === 'done' && !publicationDone) {
|
|
1232
|
+
publicationDone = true;
|
|
1233
|
+
if (typeof event.promptTokens === 'number' || typeof event.outputTokens === 'number') {
|
|
1234
|
+
turnUsage.reportedCalls += 1;
|
|
1235
|
+
turnUsage.inputTokens += event.promptTokens ?? 0;
|
|
1236
|
+
turnUsage.outputTokens += event.outputTokens ?? 0;
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
if (signal?.aborted || options.hasQueuedInput?.()) {
|
|
1240
|
+
publicationInterrupted = true;
|
|
1241
|
+
break;
|
|
1242
|
+
}
|
|
1243
|
+
if (event.type === 'text_delta') {
|
|
1244
|
+
// Never expose a rewrite of the reviewed candidate. Previously
|
|
1245
|
+
// published chunks are necessarily an exact prefix of it.
|
|
1246
|
+
if (!candidate.startsWith(publishedText + event.delta)) {
|
|
1247
|
+
throw new Error('发布内容偏离定稿,使用原定稿。');
|
|
1248
|
+
}
|
|
1249
|
+
publishedText += event.delta;
|
|
1250
|
+
streamed = true;
|
|
1251
|
+
yield { type: 'text', delta: event.delta };
|
|
1252
|
+
} else if (event.type === 'tool_call_delta') {
|
|
1253
|
+
throw new Error('最终表达阶段返回了非预期工具调用,未执行该工具。');
|
|
1254
|
+
} else if (event.type === 'done') {
|
|
1255
|
+
publicationDone = true;
|
|
1256
|
+
lastFinishReason = event.stopReason;
|
|
1257
|
+
if (event.stopReason === 'aborted') publicationInterrupted = true;
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
if (!publicationDone && !publicationInterrupted) throw new Error('最终答案流未正常结束。');
|
|
1261
|
+
if (!publicationInterrupted && lastFinishReason !== 'stop' && lastFinishReason !== 'length') throw new Error('最终答案流缺少有效的结束状态。');
|
|
1262
|
+
if (!publishedText.trim() && !publicationInterrupted) throw new Error('最终表达未返回正文。');
|
|
1263
|
+
if (!publicationInterrupted && publishedText !== candidate) throw new Error('最终表达未完整传递定稿。');
|
|
1264
|
+
} catch (error) {
|
|
1265
|
+
publicationInterrupted ||= signal?.aborted === true || options.hasQueuedInput?.() === true || (error as Error)?.name === 'AbortError';
|
|
1266
|
+
publicationError = error;
|
|
1267
|
+
}
|
|
1268
|
+
if (publicationError && !publicationInterrupted) {
|
|
1269
|
+
yield { type: 'warning', message: '流式发布未完成,已使用原定稿补齐;未重新生成或改写答案。' };
|
|
1270
|
+
const remaining = candidate.slice(publishedText.length);
|
|
1271
|
+
if (remaining) {
|
|
1272
|
+
yield { type: 'text', delta: remaining };
|
|
1273
|
+
streamed = true;
|
|
1274
|
+
}
|
|
1275
|
+
publishedText = candidate;
|
|
1276
|
+
publicationError = undefined;
|
|
1277
|
+
}
|
|
1278
|
+
assistantText = publishedText;
|
|
1279
|
+
if (!publicationInterrupted) lastFinishReason = candidateFinishReason;
|
|
1280
|
+
assistantMsg.content = publishedText || null;
|
|
1281
|
+
if (!publishedText) {
|
|
1282
|
+
if (publicationInterrupted) yield { ...interruption(), usage: { ...turnUsage, trustworthy: turnUsage.totalCalls > 0 && turnUsage.reportedCalls === turnUsage.totalCalls } };
|
|
1283
|
+
else yield { type: 'error', code: 'llm_error', error: `最终答案生成失败:${String(publicationError)}`, usage: { ...turnUsage, trustworthy: turnUsage.totalCalls > 0 && turnUsage.reportedCalls === turnUsage.totalCalls } };
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
assistantMsg.presentation = 'final';
|
|
1288
|
+
trace('final_published');
|
|
1165
1289
|
if (assistantMsg.role === 'assistant') assistantMsg.advisorTaskVersion = advisorCoordinator.versions.taskVersion;
|
|
1166
1290
|
assistantMsg.advisorCheckpoints = advisorCoordinator.drainDegradations();
|
|
1167
1291
|
history.push(assistantMsg);
|
|
1292
|
+
// Non-interactive consumers retain atomic output. Interactive hosts
|
|
1293
|
+
// already received real deltas; commit the identical text only once.
|
|
1294
|
+
if (!streamed) yield { type: 'text', delta: assistantText };
|
|
1168
1295
|
yield {
|
|
1169
1296
|
type: 'assistant_message',
|
|
1170
1297
|
message: assistantMsg as Extract<Message, { role: 'assistant' }>,
|
|
1171
1298
|
};
|
|
1299
|
+
if (publicationInterrupted || publicationError) {
|
|
1300
|
+
yield { type: 'warning', message: '最终答案未完整生成,已保留已输出内容;未撤回或重新生成。' };
|
|
1301
|
+
if (publicationInterrupted) yield { ...interruption(), usage: { ...turnUsage, trustworthy: turnUsage.totalCalls > 0 && turnUsage.reportedCalls === turnUsage.totalCalls } };
|
|
1302
|
+
else yield { type: 'error', code: 'llm_error', error: `最终答案流中断:${String(publicationError)}`, usage: { ...turnUsage, trustworthy: turnUsage.totalCalls > 0 && turnUsage.reportedCalls === turnUsage.totalCalls } };
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1172
1305
|
}
|
|
1173
1306
|
// R2:最终回答被 provider 因 max_tokens 截断(finish_reason='length')时,
|
|
1174
1307
|
// 不能静默当正常 end_turn 收尾 —— 否则 -p 宿主会拿到半截答案却以为成功。
|
|
@@ -1220,7 +1353,11 @@ export async function* runQuery(
|
|
|
1220
1353
|
...turnUsage,
|
|
1221
1354
|
trustworthy: turnUsage.totalCalls > 0 && turnUsage.reportedCalls === turnUsage.totalCalls,
|
|
1222
1355
|
};
|
|
1223
|
-
if (
|
|
1356
|
+
if (emptyResponse && !resultPresented) {
|
|
1357
|
+
const warning = '本轮未生成最终答复,已有进展已保留。可以重试上一回合。';
|
|
1358
|
+
yield { type: 'warning', message: warning };
|
|
1359
|
+
yield { type: 'turn_done', completionStatus: 'no_answer', warning, usage };
|
|
1360
|
+
} else if (pendingDelegations.length) {
|
|
1224
1361
|
const warning = `必要子任务尚未收口:${pendingDelegations.map((r) => r.id).join(', ')}。本轮不能视为全部完成。`;
|
|
1225
1362
|
yield { type: 'warning', message: warning };
|
|
1226
1363
|
yield { type: 'turn_done', completionStatus: 'delivery_incomplete', warning, usage };
|
|
@@ -1240,11 +1377,14 @@ export async function* runQuery(
|
|
|
1240
1377
|
usage,
|
|
1241
1378
|
};
|
|
1242
1379
|
} else {
|
|
1243
|
-
yield { type: 'turn_done', completionStatus: 'completed', usage
|
|
1380
|
+
yield { type: 'turn_done', completionStatus: 'completed', usage,
|
|
1381
|
+
...(emptyResponse ? { warning: '成果已提交,未生成文字总结;请查看已提交的成果。' } : {}) };
|
|
1244
1382
|
}
|
|
1245
1383
|
return;
|
|
1246
1384
|
}
|
|
1247
1385
|
|
|
1386
|
+
assistantMsg.presentation = 'progress';
|
|
1387
|
+
trace('progress_published');
|
|
1248
1388
|
history.push(assistantMsg);
|
|
1249
1389
|
yield {
|
|
1250
1390
|
type: 'assistant_message',
|
|
@@ -1471,7 +1611,19 @@ export async function* runQuery(
|
|
|
1471
1611
|
};
|
|
1472
1612
|
|
|
1473
1613
|
const runOne: typeof runOneImpl = async (tc, reportProgress) => {
|
|
1614
|
+
// Also settle denied/invalid/unavailable calls that never reached the
|
|
1615
|
+
// provider adapter. This records a real attempt, not an ignored reminder.
|
|
1616
|
+
let consultation;
|
|
1617
|
+
if (tc.function.name === 'Advisor') {
|
|
1618
|
+
let reason: import('./types.js').AdvisorReason | undefined;
|
|
1619
|
+
try { reason = JSON.parse(tc.function.arguments).reason; } catch { /* validation handles malformed input */ }
|
|
1620
|
+
consultation = advisorCoordinator.beginConsultation(reason);
|
|
1621
|
+
}
|
|
1474
1622
|
const outcome = await runOneImpl(tc, reportProgress);
|
|
1623
|
+
if (consultation && outcome.result.metadata?.kind !== 'advisor_consultation'
|
|
1624
|
+
&& !( !outcome.result.ok && outcome.result.error === 'aborted')) {
|
|
1625
|
+
outcome.result = { ...outcome.result, metadata: advisorCoordinator.finishConsultation(consultation, outcome.result.ok) };
|
|
1626
|
+
}
|
|
1475
1627
|
const metadata = outcome.result.metadata;
|
|
1476
1628
|
if (metadata?.kind === 'advisor_decision' && outcome.result.ok
|
|
1477
1629
|
|| metadata?.kind === 'advisor_consultation') {
|
|
@@ -1788,6 +1940,7 @@ export function buildModelMessages(
|
|
|
1788
1940
|
transientContext?: string,
|
|
1789
1941
|
currentUserMessageId?: string,
|
|
1790
1942
|
): Message[] {
|
|
1943
|
+
history = history.filter(message => !(message.role === 'system' && message._ephemeral));
|
|
1791
1944
|
const transient = transientContext?.trim();
|
|
1792
1945
|
if (!transient) return history;
|
|
1793
1946
|
let index = currentUserMessageId
|
|
@@ -747,7 +747,7 @@ export const memoryService = {
|
|
|
747
747
|
.toLowerCase()
|
|
748
748
|
.includes(term))))
|
|
749
749
|
.slice(0, count(3));
|
|
750
|
-
const pushSection = (id, content, ids = []) => {
|
|
750
|
+
const pushSection = (id, content, ids = [], recordList) => {
|
|
751
751
|
if (countTextTokens([...sections, content].join('\n\n')) > budget)
|
|
752
752
|
return false;
|
|
753
753
|
sections.push(content);
|
|
@@ -757,6 +757,7 @@ export const memoryService = {
|
|
|
757
757
|
content,
|
|
758
758
|
includedIds: ids,
|
|
759
759
|
estimatedTokens: countTextTokens(content),
|
|
760
|
+
...(recordList ? { recordList } : {}),
|
|
760
761
|
});
|
|
761
762
|
return true;
|
|
762
763
|
};
|
|
@@ -778,7 +779,7 @@ export const memoryService = {
|
|
|
778
779
|
selected.push(entry);
|
|
779
780
|
}
|
|
780
781
|
if (selected.length)
|
|
781
|
-
pushSection(id, `${header}\n${selected.map((item) => item.text).join('\n')}`, selected.map((item) => item.id));
|
|
782
|
+
pushSection(id, `${header}\n${selected.map((item) => item.text).join('\n')}`, selected.map((item) => item.id), { header, entries: selected });
|
|
782
783
|
};
|
|
783
784
|
addRecords('preferences', '【响应偏好|personal 全局,workspace 项目】当前要求优先;项目偏好仅在当前项目适用;待复核信息不作为确定约束。', preferences.map((record) => ({ id: record.id, text: describe(record) })), share(550));
|
|
784
785
|
addRecords('relevant', '【相关本机记忆|低优先级、可纠正】当前消息优先;来源见 RecallMemory。', relevant.map((record) => ({ id: record.id, text: describe(record) })), share(850));
|
|
@@ -1110,6 +1110,7 @@ export const memoryService = {
|
|
|
1110
1110
|
id: MemoryContextBundle['sections'][number]['id'],
|
|
1111
1111
|
content: string,
|
|
1112
1112
|
ids: string[] = [],
|
|
1113
|
+
recordList?: MemoryContextBundle['sections'][number]['recordList'],
|
|
1113
1114
|
) => {
|
|
1114
1115
|
if (countTextTokens([...sections, content].join('\n\n')) > budget)
|
|
1115
1116
|
return false;
|
|
@@ -1120,6 +1121,7 @@ export const memoryService = {
|
|
|
1120
1121
|
content,
|
|
1121
1122
|
includedIds: ids,
|
|
1122
1123
|
estimatedTokens: countTextTokens(content),
|
|
1124
|
+
...(recordList ? { recordList } : {}),
|
|
1123
1125
|
});
|
|
1124
1126
|
return true;
|
|
1125
1127
|
};
|
|
@@ -1155,6 +1157,7 @@ export const memoryService = {
|
|
|
1155
1157
|
id,
|
|
1156
1158
|
`${header}\n${selected.map((item) => item.text).join('\n')}`,
|
|
1157
1159
|
selected.map((item) => item.id),
|
|
1160
|
+
{ header, entries: selected },
|
|
1158
1161
|
);
|
|
1159
1162
|
};
|
|
1160
1163
|
addRecords(
|
package/src/memory/retrieval.js
CHANGED
|
@@ -13,10 +13,10 @@ export function isReviewDue(record, settings, now = Date.now()) {
|
|
|
13
13
|
export function scoreMemory(record, query, settings) {
|
|
14
14
|
if (record.status === 'superseded' || (record.validUntil !== undefined && record.validUntil <= Date.now()))
|
|
15
15
|
return Number.NEGATIVE_INFINITY;
|
|
16
|
-
const haystack = `${record.category} ${record.subject} ${record.topicKey} ${record.aliases.join(' ')} ${record.statement}`.toLocaleLowerCase();
|
|
16
|
+
const haystack = `${record.category} ${record.subject} ${record.topicKey} ${record.aliases.join(' ')} ${record.statement}`.normalize('NFKC').toLocaleLowerCase();
|
|
17
17
|
const terms = memoryTerms(query);
|
|
18
18
|
const lexical = terms.reduce((sum, term) => sum + (haystack.includes(term) ? 10 : 0), 0);
|
|
19
|
-
const phrase = query.trim() && haystack.includes(query.trim().toLocaleLowerCase())
|
|
19
|
+
const phrase = query.trim() && haystack.includes(query.trim().normalize('NFKC').toLocaleLowerCase())
|
|
20
20
|
? 25
|
|
21
21
|
: 0;
|
|
22
22
|
const stalePenalty = isReviewDue(record, settings) || record.status === 'needs_review' ? 8 : 0;
|
|
@@ -53,10 +53,10 @@ export function recallFromSnapshot(snapshot, query, settings, limit) {
|
|
|
53
53
|
}
|
|
54
54
|
recordSearchStrategy('bm25', eligible.length);
|
|
55
55
|
const bm25 = index.search(query);
|
|
56
|
-
const normalized = query.trim().toLocaleLowerCase();
|
|
56
|
+
const normalized = query.trim().normalize('NFKC').toLocaleLowerCase();
|
|
57
57
|
return eligible
|
|
58
58
|
.map((record) => {
|
|
59
|
-
const haystack = `${record.category} ${record.subject} ${record.topicKey} ${record.aliases.join(' ')} ${record.statement}`.toLocaleLowerCase();
|
|
59
|
+
const haystack = `${record.category} ${record.subject} ${record.topicKey} ${record.aliases.join(' ')} ${record.statement}`.normalize('NFKC').toLocaleLowerCase();
|
|
60
60
|
const phrase = normalized && haystack.includes(normalized) ? 25 : 0;
|
|
61
61
|
const stalePenalty = isReviewDue(record, settings) || record.status === 'needs_review' ? 8 : 0;
|
|
62
62
|
return {
|
package/src/memory/retrieval.ts
CHANGED
|
@@ -39,14 +39,14 @@ export function scoreMemory(
|
|
|
39
39
|
): number {
|
|
40
40
|
if (record.status === 'superseded' || (record.validUntil !== undefined && record.validUntil <= Date.now())) return Number.NEGATIVE_INFINITY;
|
|
41
41
|
const haystack =
|
|
42
|
-
`${record.category} ${record.subject} ${record.topicKey} ${record.aliases.join(' ')} ${record.statement}`.toLocaleLowerCase();
|
|
42
|
+
`${record.category} ${record.subject} ${record.topicKey} ${record.aliases.join(' ')} ${record.statement}`.normalize('NFKC').toLocaleLowerCase();
|
|
43
43
|
const terms = memoryTerms(query);
|
|
44
44
|
const lexical = terms.reduce(
|
|
45
45
|
(sum, term) => sum + (haystack.includes(term) ? 10 : 0),
|
|
46
46
|
0,
|
|
47
47
|
);
|
|
48
48
|
const phrase =
|
|
49
|
-
query.trim() && haystack.includes(query.trim().toLocaleLowerCase())
|
|
49
|
+
query.trim() && haystack.includes(query.trim().normalize('NFKC').toLocaleLowerCase())
|
|
50
50
|
? 25
|
|
51
51
|
: 0;
|
|
52
52
|
const stalePenalty =
|
|
@@ -93,10 +93,10 @@ export function recallFromSnapshot(
|
|
|
93
93
|
}
|
|
94
94
|
recordSearchStrategy('bm25', eligible.length);
|
|
95
95
|
const bm25 = index.search(query);
|
|
96
|
-
const normalized = query.trim().toLocaleLowerCase();
|
|
96
|
+
const normalized = query.trim().normalize('NFKC').toLocaleLowerCase();
|
|
97
97
|
return eligible
|
|
98
98
|
.map((record) => {
|
|
99
|
-
const haystack = `${record.category} ${record.subject} ${record.topicKey} ${record.aliases.join(' ')} ${record.statement}`.toLocaleLowerCase();
|
|
99
|
+
const haystack = `${record.category} ${record.subject} ${record.topicKey} ${record.aliases.join(' ')} ${record.statement}`.normalize('NFKC').toLocaleLowerCase();
|
|
100
100
|
const phrase = normalized && haystack.includes(normalized) ? 25 : 0;
|
|
101
101
|
const stalePenalty = isReviewDue(record, settings) || record.status === 'needs_review' ? 8 : 0;
|
|
102
102
|
return {
|
package/src/memory/types.ts
CHANGED
|
@@ -38,8 +38,8 @@ ${toolNames.has('TaskCreate') && toolNames.has('TaskUpdate') ? '- 用户已要
|
|
|
38
38
|
id: 'execution.communication', layer: 'execution', profiles: ['main'], source: 'native',
|
|
39
39
|
render: () => `# 结果与沟通
|
|
40
40
|
- 按用途选择对话答案、可编辑文件或可运行成果,不按篇幅自动建文件;要求生成成品时实际产出。
|
|
41
|
-
-
|
|
42
|
-
- Skill
|
|
41
|
+
- 开始执行时简述目标与近期动作;出现实质发现、阶段成果、方向变化或阻塞时更新,并说明下一步。连续相关操作合并汇报,不机械套用“工作进展/下一步”标题,不逐条复述界面已有的工具日志,不输出私有推理;没有新增信息就继续工作,不为汇报暂停执行。
|
|
42
|
+
- 使用 Skill、顾问、浏览器或子 Agent 时说明其目的;返回后核验结果,只汇报影响后续行动的结论,不复述全部过程。
|
|
43
43
|
- 最终答案先给明确结果;复杂任务按“完成内容 / 验证结果 / 未完成与下一步”使用 Markdown 小标题和简短列表,附相关文件或成果入口。简单问题直接回答,不强套空标题。区分已验证、失败与尚未验证,不捏造成功或百分比。代码使用带语言的代码块,比较项用表格,正文不重复粘贴全部工具输出。
|
|
44
44
|
- 可视化须有助理解关系;已有成品不在正文重复。`,
|
|
45
45
|
},
|
|
@@ -60,6 +60,14 @@ ${toolNames.has('TaskCreate') && toolNames.has('TaskUpdate') ? '- 用户已要
|
|
|
60
60
|
- 不经明确授权不得 amend 提交、丢弃工作区修改或强推。Git 使用非交互命令。发现无关他人改动保留并绕开;重叠且无法确定归属时暂停相关修改并询问,不回退他人成果。
|
|
61
61
|
- 测试按风险选择:修改后主动运行能证明行为的最小验证;共享协议、跨模块和用户流程改动应扩大到类型检查、相关测试或构建。不能运行时明确说明。
|
|
62
62
|
- 收尾前审阅本轮 diff,清除自己产生的半成品,确认实现、测试和用户目标一致。`,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: 'execution.follow-through', layer: 'execution', profiles: ['main'], source: 'native',
|
|
66
|
+
render: () => `# 推进与完成
|
|
67
|
+
- 多阶段任务明确完成条件和关键路径,不把计划本身当成交付;小任务直接执行。
|
|
68
|
+
- 在任务范围内持续完成实现、检查和必要返工,不因首次失败提前收尾;受阻时先推进独立工作。
|
|
69
|
+
- 失败后依据回执改变方法,不原样循环或臆测环境故障;真正受外部条件阻塞时说明尝试与所需输入。
|
|
70
|
+
- 完成以成果和验证证据为准,不以操作次数或投入时长为准。`,
|
|
63
71
|
},
|
|
64
72
|
{
|
|
65
73
|
id: 'execution.interruption', layer: 'execution', profiles: ['main'], source: 'native',
|
|
@@ -185,10 +193,18 @@ const CAPABILITY_POLICIES = [
|
|
|
185
193
|
render: ({ toolNames }) => `- 需求匹配已安装 Skill 或用户点名时,${skillInvokeInstruction(toolNames.has('Skill'))};前端开发与交互修复也要主动匹配设计技能,不等用户说“设计”。引用资源按需读取,多阶段按需组合,不预读整个目录或加载无关技能。`,
|
|
186
194
|
},
|
|
187
195
|
{
|
|
188
|
-
id: 'execution.desktop-diagrams', layer: 'execution', profiles:
|
|
189
|
-
render: ({ surface, toolNames }) =>
|
|
190
|
-
|
|
191
|
-
|
|
196
|
+
id: 'execution.desktop-diagrams', layer: 'execution', profiles: ALL_PROFILES, source: 'native',
|
|
197
|
+
render: ({ surface, toolNames }) => [
|
|
198
|
+
'- 按需配图,优先 Mermaid/SVG;用户指定 ASCII、代码/目录树除外。',
|
|
199
|
+
surface === 'desktop'
|
|
200
|
+
? '- Desktop/Web 聊天支持 mermaid 和 svg 围栏,不用裸 HTML。'
|
|
201
|
+
: surface === 'cli' || surface === 'tui'
|
|
202
|
+
? '- CLI/TUI 直接回复用文字/表格/紧凑 ASCII,生成文件仍优先 Mermaid/SVG。'
|
|
203
|
+
: '- 跟随最终阅读载体,不等同于终端:Web 用 mermaid/svg 围栏;保持协议契约,未知不假定可渲染。',
|
|
204
|
+
toolNames.has('Skill') || toolNames.has('Read')
|
|
205
|
+
? '- 加载可用的 dynamic-ui。'
|
|
206
|
+
: '',
|
|
207
|
+
].filter(Boolean).join('\n'),
|
|
192
208
|
},
|
|
193
209
|
{
|
|
194
210
|
id: 'capability.session-worktree', layer: 'capability', profiles: MAIN_PROFILE, source: '06',
|
|
@@ -238,17 +254,17 @@ const CAPABILITY_POLICIES = [
|
|
|
238
254
|
requiresTools: ['Advisor'],
|
|
239
255
|
// 注意:文本内不得硬编码其他工具名(如 PresentResult),否则该工具缺席时这里
|
|
240
256
|
// 成为唯一残留的「幽灵引用」——capability 规则交叉引用必须在 toolNames 条件下拼接。
|
|
241
|
-
render: ({ toolNames }) => `- Advisor
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
257
|
+
render: ({ toolNames }) => `- Advisor(顾问模型)使用规则:提供第二意见,由主 Agent 核实取舍;不授予权限、不替用户验收。
|
|
258
|
+
咨询时机:实质计划执行前(plan_review)、实质交付前(delivery_review)、同一问题两次或以上处理失败后(stalled)、用户明显不满意(user_dissatisfaction)、非常不确定或无法解决(uncertainty)、关键证据冲突无法消解(evidence_conflict)。换工具不清零失败次数,无关失败不合并。
|
|
259
|
+
文字调研、计算、比较和推荐也是交付;自信、没文件不豁免。自动咨询不再问用户;寒暄、进度、拼写修正无需咨询。
|
|
260
|
+
同阶段不重复问;成果、假设实质变化或新冲突才重开。questions/note 指明疑点与证据 ID,历史由宿主附带。缺材料先补查,不算验证通过。读完意见再操作;暂缓操作未执行,提醒不算咨询。
|
|
261
|
+
不设咨询配额,每次咨询只尝试一次;失败、空答或超时自行降级继续,不转成人工审批,也不宣称获得验证。
|
|
262
|
+
以代码、测试和用户要求核实意见,${toolNames.has('AdvisorDecision') ? '可用 AdvisorDecision 记录 accept/partial/reject;登记失败不阻止执行。' : '直接说明采纳判断。'}顾问不是事实来源,冲突以实测为准。`,
|
|
247
263
|
},
|
|
248
264
|
{
|
|
249
265
|
id: 'capability.subagent', layer: 'capability', profiles: MAIN_PROFILE, source: 'native',
|
|
250
266
|
requiresTools: ['Agent'],
|
|
251
|
-
render: () => '-
|
|
267
|
+
render: () => '- 大范围调查先定位入口、模块边界与基线,不穷尽读取。独立且需多轮探索的问题可同轮派发 explore Agent,数量取决于独立工作量和宿主额度,不为并行而拆分;少量已知文件/网址直接批量读,强顺序或协调开销大于收益时自行完成。主 Agent 保留跨模块判断,有独立工作时后台派发并推进,不重复调查或立即轮询。任务书写清问题、范围、停止条件、排除项和已知证据,不复制私有历史。按问题整合结论与阅读证据,区分观察、推断、建议;冲突核验不投票,只补读关键结论、版本变化与修改位置。文件清单不代表全文覆盖。子任务用实际可用的搜索/正文工具,动态浏览器归主 Agent;不得为只读命令升级为 general。',
|
|
252
268
|
},
|
|
253
269
|
{
|
|
254
270
|
id: 'capability.agent-handoff', layer: 'capability', profiles: MAIN_PROFILE, source: 'native',
|
|
@@ -263,7 +279,7 @@ const CAPABILITY_POLICIES = [
|
|
|
263
279
|
{
|
|
264
280
|
id: 'capability.delivery', layer: 'capability', profiles: MAIN_PROFILE, source: '05',
|
|
265
281
|
requiresTools: ['PresentResult'],
|
|
266
|
-
render: () => '- 文件或网站须用 PresentResult
|
|
282
|
+
render: () => '- 文件或网站须用 PresentResult 交付真实成品,路径和中间文件不算交付。交付失败须说明;本机预览不是部署,已提交、检查通过与人类验收是不同状态。',
|
|
267
283
|
},
|
|
268
284
|
{
|
|
269
285
|
id: 'capability.media', layer: 'capability', profiles: MAIN_PROFILE, source: '05',
|
|
@@ -281,9 +297,7 @@ const CAPABILITY_POLICIES = [
|
|
|
281
297
|
if (toolNames.has('RememberMemory'))
|
|
282
298
|
rules.push('RememberMemory 保存明确要求记住的新事实;后台提炼启用时普通陈述交后台处理,不重复写入。自动记忆关闭时仅响应明确保存要求;失败如实说明,不阻断任务。');
|
|
283
299
|
if (toolNames.has('RecallMemory'))
|
|
284
|
-
rules.push('
|
|
285
|
-
if (toolNames.has('RecallMemory'))
|
|
286
|
-
rules.push('自包含的简单任务跳过记忆检索;相关任务先利用已注入记忆,再按关键词少量召回,无相关命中即停止。反复失败或与旧结论冲突时重新查证;未在本轮核验的历史结论须说明来源及可能过时。');
|
|
300
|
+
rules.push('自包含任务无需检索记忆。依赖跨会话用户信息、偏好、决定或项目背景时,先检查已注入证据;缺少相关信息再用 RecallMemory,避免重复检索或直接反问用户。查询无关命中时停止扩大检索;旧结论与现场冲突时重新查证,未在本轮核验的历史结论标明来源及可能过时。');
|
|
287
301
|
if (toolNames.has('RecallMemory') && toolNames.has('UpdateMemory'))
|
|
288
302
|
rules.push('纠正:RecallMemory 定位 → UpdateMemory(expectedVersion);冲突回执充分则合并重试,否则重读。语义未变不重写。');
|
|
289
303
|
if (toolNames.has('ForgetMemory'))
|