minimal-agent 0.18.13 → 0.18.14
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 -3
- package/plugins/workflow-runner/src/runtime/context.js +1 -1
- package/plugins/workflow-runner/src/runtime/context.ts +1 -1
- package/plugins/workflow-runner/test/embeddedExecution.test.ts +2 -5
- package/plugins/workflow-runner/test/execLifetime.test.ts +15 -2
- package/skills/ssh-remote-dev/SKILL.md +61 -0
- package/skills/ssh-remote-dev/agents/openai.yaml +4 -0
- package/src/advisor/README.md +62 -24
- package/src/advisor/advisorNudge.js +199 -0
- package/src/advisor/advisorNudge.ts +232 -0
- package/src/advisor/deliveryReview.js +35 -12
- package/src/advisor/deliveryReview.ts +35 -11
- package/src/advisor/presentation.js +7 -10
- package/src/advisor/presentation.ts +10 -13
- package/src/advisor/reviewContext.js +22 -58
- package/src/advisor/reviewContext.ts +24 -67
- package/src/cli/configMenu.js +17 -128
- package/src/cli/configMenu.tsx +17 -193
- package/src/cli/print.js +2 -0
- package/src/cli/print.ts +2 -0
- package/src/cli/streamJson.js +2 -0
- package/src/cli/streamJson.ts +2 -0
- package/src/cli/wizards/advisorWizard.js +9 -7
- package/src/cli/wizards/advisorWizard.tsx +9 -7
- package/src/cli/wizards/contextAdvisorWizard.js +9 -7
- package/src/cli/wizards/contextAdvisorWizard.tsx +9 -7
- package/src/cli/wizards/providerPresets.js +27 -1
- package/src/cli/wizards/providerPresets.ts +44 -2
- package/src/cli/wizards/providerWizard.js +51 -10
- package/src/cli/wizards/providerWizard.tsx +59 -9
- package/src/config/configDefaults.js +1 -1
- package/src/config/configDefaults.ts +1 -1
- package/src/config/configDiagnostics.js +1 -21
- package/src/config/configDiagnostics.ts +3 -23
- package/src/config/configFile.js +11 -4
- package/src/config/configFile.ts +27 -7
- package/src/config/configManagement.js +1 -0
- package/src/config/configManagement.ts +2 -0
- package/src/config/configService.js +12 -8
- package/src/config/configService.ts +16 -11
- package/src/config/numericLimits.js +0 -4
- package/src/config/numericLimits.ts +0 -4
- package/src/config/reasoningEffort.js +16 -0
- package/src/config/reasoningEffort.ts +18 -0
- package/src/context/budgetPlan.js +1 -1
- package/src/context/budgetPlan.ts +1 -1
- package/src/context/optimizeConversation.js +1 -1
- package/src/context/optimizeConversation.ts +1 -1
- package/src/harness/stallGuard.js +8 -13
- package/src/harness/stallGuard.ts +8 -12
- package/src/llm/client.js +64 -17
- package/src/llm/client.ts +62 -7
- package/src/llm/providers/openaiFormat.js +14 -3
- package/src/llm/providers/openaiFormat.ts +17 -3
- package/src/llm/providers/responsesFormat.js +275 -0
- package/src/llm/providers/responsesFormat.ts +306 -0
- package/src/loop/README.md +18 -3
- package/src/loop/liveDiagramGate.js +63 -0
- package/src/loop/liveDiagramGate.ts +61 -0
- package/src/loop/messages.js +12 -0
- package/src/loop/messages.ts +13 -0
- package/src/loop/options.ts +7 -2
- package/src/loop/toolBatch.js +0 -1
- package/src/loop/toolBatch.ts +0 -1
- package/src/loop/toolExecutor.js +11 -46
- package/src/loop/toolExecutor.ts +12 -55
- package/src/loop/toolPresentation.js +2 -13
- package/src/loop/toolPresentation.ts +2 -13
- package/src/loop/toolSemantics.js +3 -4
- package/src/loop/toolSemantics.ts +4 -6
- package/src/loop.js +222 -214
- package/src/loop.ts +226 -232
- package/src/memory/dependencies.js +11 -3
- package/src/memory/dependencies.ts +11 -3
- package/src/memory/episodicMemory.js +59 -18
- package/src/memory/episodicMemory.ts +58 -18
- package/src/memory/experienceStore.js +4 -3
- package/src/memory/experienceStore.ts +4 -3
- package/src/memory/factCandidates.js +53 -1
- package/src/memory/factCandidates.ts +54 -1
- package/src/memory/factLearning.js +2 -2
- package/src/memory/factLearning.ts +2 -2
- package/src/memory/jobPolicy.js +24 -5
- package/src/memory/jobPolicy.ts +25 -5
- package/src/memory/learning.js +2 -2
- package/src/memory/learning.ts +2 -2
- package/src/memory/learningModel.js +7 -0
- package/src/memory/learningModel.ts +7 -0
- package/src/memory/managementService.js +8 -7
- package/src/memory/managementService.ts +16 -8
- package/src/memory/memoryService.js +104 -33
- package/src/memory/memoryService.ts +132 -28
- package/src/memory/projectKnowledge.js +39 -3
- package/src/memory/projectKnowledge.ts +45 -3
- package/src/memory/recallQuery.js +56 -0
- package/src/memory/recallQuery.ts +58 -0
- package/src/memory/retrieval.js +39 -10
- package/src/memory/retrieval.ts +46 -11
- package/src/memory/turnJournal.js +75 -10
- package/src/memory/turnJournal.ts +66 -10
- package/src/memory/types.ts +7 -1
- package/src/prompts/delegation.js +2 -1
- package/src/prompts/delegation.ts +3 -1
- package/src/prompts/policyRegistry.js +9 -12
- package/src/prompts/policyRegistry.ts +9 -12
- package/src/prompts/promptAssembly.js +6 -5
- package/src/prompts/promptAssembly.ts +8 -6
- package/src/prompts/visualPresentation.js +39 -10
- package/src/prompts/visualPresentation.ts +38 -9
- package/src/runtime/answerPreparation.js +1 -1
- package/src/runtime/answerPreparation.ts +1 -1
- package/src/runtime/fileLock.js +19 -3
- package/src/runtime/fileLock.ts +19 -3
- package/src/runtime/invocationPolicy.js +1 -1
- package/src/runtime/invocationPolicy.ts +1 -1
- package/src/runtime/outputContract.js +0 -4
- package/src/runtime/outputContract.ts +0 -7
- package/src/session/runAgentTurn.js +3 -0
- package/src/session/runAgentTurn.ts +3 -0
- package/src/team/onlineToolContract.js +0 -1
- package/src/team/onlineToolContract.ts +0 -1
- package/src/tools/advisor/advisor.js +207 -365
- package/src/tools/advisor/advisor.ts +211 -383
- package/src/tools/agent/agentTypes.js +0 -1
- package/src/tools/agent/agentTypes.ts +0 -1
- package/src/tools/agent/coordinator.js +4 -1
- package/src/tools/agent/coordinator.ts +4 -1
- package/src/tools/bash/semantics.js +4 -5
- package/src/tools/bash/semantics.ts +4 -5
- package/src/tools/capabilities.js +2 -3
- package/src/tools/capabilities.ts +2 -4
- package/src/tools/index.js +0 -16
- package/src/tools/index.ts +0 -16
- package/src/tools/memory/maintenance.js +8 -9
- package/src/tools/memory/maintenance.ts +13 -11
- package/src/tools/memory/memory.js +5 -1
- package/src/tools/memory/memory.ts +5 -0
- package/src/tools/optimizeConversation/README.md +4 -6
- package/src/tools/toolPromptGuides.js +2 -9
- package/src/tools/toolPromptGuides.ts +2 -9
- package/src/types.ts +28 -105
- package/src/ui/App.js +2 -0
- package/src/ui/App.tsx +1 -0
- package/src/ui/MessageList.js +8 -44
- package/src/ui/MessageList.tsx +3 -81
- package/src/ui/hooks/useChat.js +2 -0
- package/src/ui/hooks/useChat.ts +2 -0
- package/src/ui/toolProgress.js +1 -1
- package/src/ui/toolProgress.ts +1 -1
- package/src/web/protocol.ts +10 -3
- package/src/advisor/advisorCoordinator.js +0 -734
- package/src/advisor/advisorCoordinator.ts +0 -825
- package/src/cli/wizards/sshHostWizard.js +0 -156
- package/src/cli/wizards/sshHostWizard.tsx +0 -214
- package/src/context/sshHosts.js +0 -290
- package/src/context/sshHosts.ts +0 -312
- package/src/loop/publication.js +0 -94
- package/src/loop/publication.ts +0 -124
- package/src/runtime/finalAnswer.js +0 -9
- package/src/runtime/finalAnswer.ts +0 -13
- package/src/tools/advisor/advisorDecision.js +0 -79
- package/src/tools/advisor/advisorDecision.ts +0 -92
- package/src/tools/ssh/ssh.js +0 -152
- package/src/tools/ssh/ssh.ts +0 -167
- package/src/tools/ssh/sshAvailability.js +0 -35
- package/src/tools/ssh/sshAvailability.ts +0 -39
- package/src/tools/ssh/sshClient.js +0 -177
- package/src/tools/ssh/sshClient.ts +0 -189
- package/src/tools/ssh/sshDownload.js +0 -77
- package/src/tools/ssh/sshDownload.ts +0 -92
- package/src/tools/ssh/sshHostsTool.js +0 -36
- package/src/tools/ssh/sshHostsTool.ts +0 -48
- package/src/tools/ssh/sshUpload.js +0 -72
- package/src/tools/ssh/sshUpload.ts +0 -85
|
@@ -1,734 +0,0 @@
|
|
|
1
|
-
import crypto from 'node:crypto';
|
|
2
|
-
import { needsTextDeliveryReview, reviewSignal, isCosmeticRequest, isReviewProcessQuestion, isRoutineMaintenanceRequest, isRoutineMaintenancePath } from './deliveryReview.js';
|
|
3
|
-
import { expandAdvisorHistory } from '../context/advisorHistory.js';
|
|
4
|
-
import { provenanceLabel } from '../llm/messageProvenance.js';
|
|
5
|
-
/**
|
|
6
|
-
* contextPacket() 证据条目的数量与单条截断上限。
|
|
7
|
-
* 优先分配给近期、相关证据,同时保留早期依据。最终请求预算仍可裁剪本包。
|
|
8
|
-
*/
|
|
9
|
-
const EVIDENCE_MAX_ITEMS = 16;
|
|
10
|
-
const EVIDENCE_ITEM_MAX_CHARS = 800;
|
|
11
|
-
const INVESTIGATION_TOOLS = new Set([
|
|
12
|
-
'Read',
|
|
13
|
-
'Glob',
|
|
14
|
-
'Grep',
|
|
15
|
-
'LSP',
|
|
16
|
-
'WebSearch',
|
|
17
|
-
'WebFetch',
|
|
18
|
-
'WebBrowser',
|
|
19
|
-
'TerminalCapture',
|
|
20
|
-
'RecallMemory',
|
|
21
|
-
'SearchConversations',
|
|
22
|
-
]);
|
|
23
|
-
function textContent(message) {
|
|
24
|
-
if (!message)
|
|
25
|
-
return '';
|
|
26
|
-
if (typeof message.content === 'string')
|
|
27
|
-
return message.content;
|
|
28
|
-
if (Array.isArray(message.content)) {
|
|
29
|
-
return message.content
|
|
30
|
-
.filter((part) => part.type === 'text')
|
|
31
|
-
.map((part) => part.text)
|
|
32
|
-
.join('\n');
|
|
33
|
-
}
|
|
34
|
-
return '';
|
|
35
|
-
}
|
|
36
|
-
function manifestFingerprint(raw) {
|
|
37
|
-
try {
|
|
38
|
-
return JSON.stringify(JSON.parse(raw));
|
|
39
|
-
}
|
|
40
|
-
catch {
|
|
41
|
-
return raw;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
function toolNamesByCallId(history) {
|
|
45
|
-
const names = new Map();
|
|
46
|
-
for (const message of history) {
|
|
47
|
-
if (message.role !== 'assistant')
|
|
48
|
-
continue;
|
|
49
|
-
for (const call of message.tool_calls ?? [])
|
|
50
|
-
names.set(call.id, call.function.name);
|
|
51
|
-
}
|
|
52
|
-
return names;
|
|
53
|
-
}
|
|
54
|
-
function advisorMetadata(metadata) {
|
|
55
|
-
if (metadata?.kind === 'advisor_checkpoint' ||
|
|
56
|
-
metadata?.kind === 'advisor_consultation' ||
|
|
57
|
-
metadata?.kind === 'advisor_decision')
|
|
58
|
-
return metadata;
|
|
59
|
-
return undefined;
|
|
60
|
-
}
|
|
61
|
-
function versionsOf(metadata) {
|
|
62
|
-
return {
|
|
63
|
-
taskVersion: metadata.taskVersion,
|
|
64
|
-
workVersion: metadata.workVersion,
|
|
65
|
-
evidenceVersion: metadata.evidenceVersion,
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
function checkpointKey(checkpoint, versions, signature) {
|
|
69
|
-
// pre-execution/pre-delivery 的意图是"本任务版本内只问一次"(见 beforeTool()
|
|
70
|
-
// 的执行方案复核)。旧实现把 workVersion/
|
|
71
|
-
// evidenceVersion 也编进 key,而这两个计数器在几乎每次成功的工具调用(包括
|
|
72
|
-
// 纯 Read/Grep,见 observeToolResult)后都会变化——"首次"因此从未真正生效,
|
|
73
|
-
// 任务里的每一次不同写入都会重新触发一轮完整 Advisor+AdvisorDecision,同时
|
|
74
|
-
// 让 MAX_CONSECUTIVE_DEFERRALS 的连续暂缓降级网因为 key 很少连续重复而基本
|
|
75
|
-
// 失效。改成只用 taskVersion,让这两个检查点在一个任务版本内真正只问一次。
|
|
76
|
-
if (checkpoint === 'pre-execution' || checkpoint === 'pre-delivery') {
|
|
77
|
-
return `${versions.taskVersion}:${checkpoint}`;
|
|
78
|
-
}
|
|
79
|
-
if (checkpoint === 'stalled' && signature)
|
|
80
|
-
return `${versions.taskVersion}:stalled:${signature}`;
|
|
81
|
-
return `${versions.taskVersion}:${checkpoint}:${versions.workVersion}:${versions.evidenceVersion}`;
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Run-local controller whose durable source of truth is Advisor metadata already
|
|
85
|
-
* stored on tool messages. It owns checkpoint de-duplication and versioning;
|
|
86
|
-
* provider calls remain in tools/advisor/advisor.ts.
|
|
87
|
-
*/
|
|
88
|
-
export class AdvisorCoordinator {
|
|
89
|
-
#history;
|
|
90
|
-
#currentRequest;
|
|
91
|
-
#objective;
|
|
92
|
-
#advisorAvailable;
|
|
93
|
-
#completed = new Set();
|
|
94
|
-
#observedConsultations = new Set();
|
|
95
|
-
#unavailableTransitions = [];
|
|
96
|
-
#softAlerts = new Set();
|
|
97
|
-
#taskVersion;
|
|
98
|
-
#taskCommitted = false;
|
|
99
|
-
#workVersion = 0;
|
|
100
|
-
#evidenceVersion = 0;
|
|
101
|
-
#investigationEvidence = 0;
|
|
102
|
-
#mutations = 0;
|
|
103
|
-
#nonRoutineMutation = false;
|
|
104
|
-
#verifications = 0;
|
|
105
|
-
#deliveryProduced = false;
|
|
106
|
-
#deliveryDraft;
|
|
107
|
-
#deliveryReviewedWork = -1;
|
|
108
|
-
#deliveryManifest;
|
|
109
|
-
#reviewedManifest;
|
|
110
|
-
#required;
|
|
111
|
-
#softCheckpoint;
|
|
112
|
-
#pending;
|
|
113
|
-
#lastDecision;
|
|
114
|
-
#reminders = [];
|
|
115
|
-
#degradationWarnings = new Set();
|
|
116
|
-
#failureReviews = new Map();
|
|
117
|
-
requireFailureReview(signature, attempts) {
|
|
118
|
-
if (!this.#advisorAvailable || this.#failureReviews.has(signature))
|
|
119
|
-
return;
|
|
120
|
-
this.#failureReviews.set(signature, [...attempts]);
|
|
121
|
-
const key = checkpointKey('stalled', this.versions, signature);
|
|
122
|
-
if (this.#completed.has(key))
|
|
123
|
-
return;
|
|
124
|
-
if (!this.#required)
|
|
125
|
-
this.#required = { checkpoint: 'stalled', key, reason: '同一问题已经尝试解决两次仍失败' };
|
|
126
|
-
this.#reminders.push('同一问题已连续两次尝试失败。下一次解决尝试前咨询 Advisor(reason=stalled)一次;失败即自行继续,成功后自行判断采纳,可记录采纳依据。可继续只读诊断。');
|
|
127
|
-
}
|
|
128
|
-
#checkFailureReview() {
|
|
129
|
-
for (const signature of this.#failureReviews.keys()) {
|
|
130
|
-
if (this.#completed.has(checkpointKey('stalled', this.versions, signature)))
|
|
131
|
-
continue;
|
|
132
|
-
return this.requestCheckpoint('stalled', '同一问题两次解决失败,继续执行或交付前必须先咨询顾问', { signature });
|
|
133
|
-
}
|
|
134
|
-
return undefined;
|
|
135
|
-
}
|
|
136
|
-
constructor(input) {
|
|
137
|
-
this.#history = input.history;
|
|
138
|
-
input = { ...input, history: expandAdvisorHistory(input.history) };
|
|
139
|
-
this.#currentRequest = input.currentRequest;
|
|
140
|
-
this.#advisorAvailable = input.advisorAvailable;
|
|
141
|
-
const names = toolNamesByCallId(input.history);
|
|
142
|
-
let maxTaskVersion = 0;
|
|
143
|
-
let maxWorkVersion = 0;
|
|
144
|
-
let maxEvidenceVersion = 0;
|
|
145
|
-
// ⚠️ 只有「上一个任务版本确实交付过」才允许 taskVersion 再往前走一格;
|
|
146
|
-
// 见构造函数末尾的赋值处的详细说明(真实事故 K2,已用跨回合重放脚本复现)。
|
|
147
|
-
let deliveredSinceMaxTaskVersion = false;
|
|
148
|
-
const currentRequestIndex = input.currentRequestMessageId
|
|
149
|
-
? input.history.findIndex((message) => message.role === 'user' && message.id === input.currentRequestMessageId)
|
|
150
|
-
: -1;
|
|
151
|
-
for (let index = 0; index < input.history.length; index += 1) {
|
|
152
|
-
const message = input.history[index];
|
|
153
|
-
if (message.role === 'assistant') {
|
|
154
|
-
const submission = message.tool_calls?.find(call => call.function.name === 'PresentResult');
|
|
155
|
-
if (submission)
|
|
156
|
-
this.#deliveryManifest = manifestFingerprint(submission.function.arguments);
|
|
157
|
-
}
|
|
158
|
-
// Text-only work has a delivery boundary too. Only answers preceding the
|
|
159
|
-
// current request start a new review cycle; replay inside a request does not.
|
|
160
|
-
if (message.role === 'assistant' && message.advisorTaskVersion !== undefined
|
|
161
|
-
&& (currentRequestIndex < 0 || index < currentRequestIndex)) {
|
|
162
|
-
maxTaskVersion = Math.max(maxTaskVersion, message.advisorTaskVersion);
|
|
163
|
-
deliveredSinceMaxTaskVersion = true;
|
|
164
|
-
}
|
|
165
|
-
if (message.role !== 'tool') {
|
|
166
|
-
for (const transition of message.advisorCheckpoints ?? [])
|
|
167
|
-
this.#applyReviewEvent(transition);
|
|
168
|
-
continue;
|
|
169
|
-
}
|
|
170
|
-
const metadata = advisorMetadata(message.display_metadata);
|
|
171
|
-
if (metadata) {
|
|
172
|
-
// 进入了一个更新的任务版本:清空「自那以来是否交付过」的标记,重新计数。
|
|
173
|
-
if (metadata.taskVersion > maxTaskVersion)
|
|
174
|
-
deliveredSinceMaxTaskVersion = false;
|
|
175
|
-
maxTaskVersion = Math.max(maxTaskVersion, metadata.taskVersion);
|
|
176
|
-
maxWorkVersion = Math.max(maxWorkVersion, metadata.workVersion);
|
|
177
|
-
maxEvidenceVersion = Math.max(maxEvidenceVersion, metadata.evidenceVersion);
|
|
178
|
-
this.#applyReviewEvent(metadata);
|
|
179
|
-
}
|
|
180
|
-
for (const transition of message.advisorCheckpoints ?? [])
|
|
181
|
-
this.#applyReviewEvent(transition);
|
|
182
|
-
const toolName = names.get(message.tool_call_id);
|
|
183
|
-
const display = message.display_metadata;
|
|
184
|
-
if (display?.kind === 'result_manifest' && (currentRequestIndex < 0 || index < currentRequestIndex))
|
|
185
|
-
deliveredSinceMaxTaskVersion = true;
|
|
186
|
-
if (display?.kind === 'file_activity') {
|
|
187
|
-
const mutationCount = display.files.filter((file) => ['created', 'modified', 'generated', 'deleted'].includes(file.activity)).length;
|
|
188
|
-
this.#workVersion += mutationCount;
|
|
189
|
-
}
|
|
190
|
-
else if (message.advisorMutatesWorkspace && message.toolResultDisposition !== 'deferred'
|
|
191
|
-
&& !message.content.startsWith('Error: ')) {
|
|
192
|
-
this.#workVersion += 1;
|
|
193
|
-
}
|
|
194
|
-
const failed = message.content.startsWith('Error: ');
|
|
195
|
-
if (toolName &&
|
|
196
|
-
toolName !== 'Advisor' &&
|
|
197
|
-
toolName !== 'AdvisorDecision' &&
|
|
198
|
-
toolName !== 'OptimizeConversation' &&
|
|
199
|
-
message.toolResultDisposition !== 'deferred' &&
|
|
200
|
-
display?.kind !== 'advisor_checkpoint' &&
|
|
201
|
-
(!failed || display?.kind === 'process_execution')) {
|
|
202
|
-
this.#evidenceVersion += 1;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
this.#workVersion = Math.max(this.#workVersion, maxWorkVersion);
|
|
206
|
-
this.#evidenceVersion = Math.max(this.#evidenceVersion, maxEvidenceVersion);
|
|
207
|
-
// Review cycles are not Goal lifecycle. Unfinished turns reuse the cycle;
|
|
208
|
-
// delivered artifacts OR a host-marked text answer end it, without requiring
|
|
209
|
-
// PresentResult for ordinary analysis/document-free work.
|
|
210
|
-
this.#taskVersion = maxTaskVersion === 0 || deliveredSinceMaxTaskVersion
|
|
211
|
-
? maxTaskVersion + 1
|
|
212
|
-
: maxTaskVersion;
|
|
213
|
-
// Keep the initiating request for unfinished work, not the first user message
|
|
214
|
-
// of an arbitrarily long Session. A completed cycle starts at the new request.
|
|
215
|
-
let boundary = -1;
|
|
216
|
-
for (let i = 0; i < input.history.length; i++) {
|
|
217
|
-
const message = input.history[i];
|
|
218
|
-
if ((currentRequestIndex < 0 || i < currentRequestIndex) &&
|
|
219
|
-
((message.role === 'assistant' && message.advisorTaskVersion !== undefined)
|
|
220
|
-
|| (message.role === 'tool' && message.display_metadata?.kind === 'result_manifest')))
|
|
221
|
-
boundary = i;
|
|
222
|
-
}
|
|
223
|
-
const initiatingUser = input.history.slice(boundary + 1).find((message) => message.role === 'user');
|
|
224
|
-
this.#objective = deliveredSinceMaxTaskVersion
|
|
225
|
-
? input.currentRequest : textContent(initiatingUser) || input.currentRequest;
|
|
226
|
-
// New requests must not inherit unresolved consultations from a prior request.
|
|
227
|
-
if (input.currentRequestMessageId !== undefined && (currentRequestIndex < 0 || !input.history.slice(currentRequestIndex + 1)
|
|
228
|
-
.some(message => message.role === 'tool' && message.display_metadata?.kind === 'advisor_consultation'
|
|
229
|
-
&& message.display_metadata.consultationId === this.#pending?.consultationId)))
|
|
230
|
-
this.#pending = undefined;
|
|
231
|
-
// Restore delivery facts only after the previous delivered cycle, not across
|
|
232
|
-
// the entire Session. Version counters alone cannot answer shouldCheckDelivery.
|
|
233
|
-
for (const message of input.history.slice(input.currentRequestMessageId !== undefined && currentRequestIndex < 0
|
|
234
|
-
? input.history.length : boundary + 1)) {
|
|
235
|
-
for (const transition of message.advisorCheckpoints ?? [])
|
|
236
|
-
this.#restoreWarning(transition);
|
|
237
|
-
if (message.role !== 'tool' || message.toolResultDisposition === 'deferred')
|
|
238
|
-
continue;
|
|
239
|
-
const review = advisorMetadata(message.display_metadata);
|
|
240
|
-
if (review)
|
|
241
|
-
this.#restoreWarning(review);
|
|
242
|
-
const name = names.get(message.tool_call_id) ?? '';
|
|
243
|
-
this.#observeDeliveryFacts(name, {
|
|
244
|
-
ok: !message.content.startsWith('Error: '),
|
|
245
|
-
content: message.content, error: message.content,
|
|
246
|
-
metadata: message.display_metadata,
|
|
247
|
-
}, { mutatesWorkspace: message.advisorMutatesWorkspace === true, highRisk: false });
|
|
248
|
-
}
|
|
249
|
-
if (this.#required && (!this.#required.key.startsWith(`${this.#taskVersion}:`)
|
|
250
|
-
|| this.#completed.has(this.#required.key)))
|
|
251
|
-
this.#required = undefined;
|
|
252
|
-
this.#requireSignal(input.currentRequest);
|
|
253
|
-
}
|
|
254
|
-
#requireSignal(text, delivery = false) {
|
|
255
|
-
const reason = reviewSignal(text);
|
|
256
|
-
if (!reason || !this.#advisorAvailable || this.#required)
|
|
257
|
-
return;
|
|
258
|
-
const key = `${this.#taskVersion}:signal:${reason}:${this.#evidenceVersion}`;
|
|
259
|
-
if (this.#completed.has(key))
|
|
260
|
-
return;
|
|
261
|
-
this.#required = { checkpoint: delivery ? 'pre-delivery' : 'pre-execution', key, reviewReason: reason,
|
|
262
|
-
reason: `针对 ${reason} 复核用户反馈或证据;先核查已有材料,不把顾问意见当作事实。` };
|
|
263
|
-
this.#reminders.push(this.#required.reason);
|
|
264
|
-
}
|
|
265
|
-
#applyReviewEvent(metadata) {
|
|
266
|
-
if (!(metadata.kind === 'advisor_checkpoint' && metadata.status === 'required')
|
|
267
|
-
&& this.#required?.key === metadata.checkpointKey)
|
|
268
|
-
this.#required = undefined;
|
|
269
|
-
if (metadata.checkpoint === 'pre-delivery' && metadata.kind === 'advisor_consultation') {
|
|
270
|
-
this.#deliveryReviewedWork = metadata.workVersion;
|
|
271
|
-
this.#reviewedManifest = this.#deliveryManifest;
|
|
272
|
-
this.#completed.add(`${metadata.taskVersion}:pre-delivery`);
|
|
273
|
-
}
|
|
274
|
-
if (metadata.kind === 'advisor_consultation' && metadata.status === 'completed') {
|
|
275
|
-
if (this.#observedConsultations.has(metadata.consultationId))
|
|
276
|
-
return;
|
|
277
|
-
this.#observedConsultations.add(metadata.consultationId);
|
|
278
|
-
this.#completed.add(metadata.checkpointKey);
|
|
279
|
-
if (metadata.checkpoint === 'stalled')
|
|
280
|
-
this.#completed.add(`${metadata.taskVersion}:pre-execution`);
|
|
281
|
-
this.#pending = metadata;
|
|
282
|
-
return;
|
|
283
|
-
}
|
|
284
|
-
if (metadata.kind === 'advisor_checkpoint' && metadata.status === 'required') {
|
|
285
|
-
const signal = metadata.checkpointKey.match(/:signal:(user_dissatisfaction|evidence_conflict|uncertainty):/)?.[1];
|
|
286
|
-
this.#required = { checkpoint: metadata.checkpoint, key: metadata.checkpointKey, reviewReason: signal, reason: '恢复尚未完成的顾问检查' };
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
this.#completed.add(metadata.checkpointKey);
|
|
290
|
-
if (metadata.checkpoint === 'stalled')
|
|
291
|
-
this.#completed.add(`${metadata.taskVersion}:pre-execution`);
|
|
292
|
-
if (this.#pending?.checkpointKey === metadata.checkpointKey)
|
|
293
|
-
this.#pending = undefined;
|
|
294
|
-
if (metadata.kind === 'advisor_decision')
|
|
295
|
-
this.#lastDecision = metadata;
|
|
296
|
-
this.#restoreWarning(metadata);
|
|
297
|
-
}
|
|
298
|
-
#restoreWarning(metadata) {
|
|
299
|
-
if (metadata.taskVersion === this.#taskVersion && (metadata.kind === 'advisor_consultation' && metadata.status === 'failed'
|
|
300
|
-
|| metadata.kind === 'advisor_checkpoint' && metadata.status === 'attempted-unavailable'))
|
|
301
|
-
this.#degradationWarnings.add(metadata.kind === 'advisor_consultation'
|
|
302
|
-
? `顾问检查 ${metadata.checkpoint} 咨询未成功,已降级继续;该次咨询不构成验证证据。`
|
|
303
|
-
: `顾问检查 ${metadata.checkpoint} 未完成,已降级继续;该次检查不构成验证证据。`);
|
|
304
|
-
}
|
|
305
|
-
// Retain the public method name for existing callers. Pending checks also need
|
|
306
|
-
// persistence when a turn ends at its limit before consultation can run.
|
|
307
|
-
drainDegradations() {
|
|
308
|
-
return [...this.#unavailableTransitions.splice(0), ...(this.#required ? [{ kind: 'advisor_checkpoint', checkpoint: this.#required.checkpoint,
|
|
309
|
-
checkpointKey: this.#required.key, status: 'required', ...this.versions }] : [])];
|
|
310
|
-
}
|
|
311
|
-
get versions() {
|
|
312
|
-
return {
|
|
313
|
-
taskVersion: this.#taskVersion,
|
|
314
|
-
workVersion: this.#workVersion,
|
|
315
|
-
evidenceVersion: this.#evidenceVersion,
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
get pendingConsultationId() {
|
|
319
|
-
return this.#pending?.consultationId;
|
|
320
|
-
}
|
|
321
|
-
setAvailable(available) {
|
|
322
|
-
const pending = this.#required ?? this.#softCheckpoint;
|
|
323
|
-
if (!available && this.#advisorAvailable && pending && !this.#completed.has(pending.key)) {
|
|
324
|
-
const event = { kind: 'advisor_checkpoint', checkpoint: pending.checkpoint,
|
|
325
|
-
checkpointKey: pending.key, status: 'attempted-unavailable', ...this.versions };
|
|
326
|
-
this.#applyReviewEvent(event);
|
|
327
|
-
this.#unavailableTransitions.push(event);
|
|
328
|
-
this.#softCheckpoint = undefined;
|
|
329
|
-
}
|
|
330
|
-
this.#advisorAvailable = available;
|
|
331
|
-
}
|
|
332
|
-
usageLine(used) {
|
|
333
|
-
const pending = this.#pending
|
|
334
|
-
? `;顾问意见 ${this.#pending.consultationId} 由主 Agent 自行判断采纳,可记录采纳依据`
|
|
335
|
-
: '';
|
|
336
|
-
return used === 0
|
|
337
|
-
? `Advisor 动态:本回合尚未咨询顾问(无调用配额)${pending}。`
|
|
338
|
-
: `Advisor 动态:本回合已咨询 ${used} 次(无调用配额)${pending}。`;
|
|
339
|
-
}
|
|
340
|
-
transientReminder() {
|
|
341
|
-
return this.#reminders.join('\n\n');
|
|
342
|
-
}
|
|
343
|
-
markTransientDelivered() {
|
|
344
|
-
this.#reminders = [];
|
|
345
|
-
}
|
|
346
|
-
/** User-visible review limitations, without another approval/consultation loop. */
|
|
347
|
-
deliveryWarnings() {
|
|
348
|
-
const warnings = [...this.#degradationWarnings];
|
|
349
|
-
if (this.#lastDecision?.taskVersion === this.#taskVersion
|
|
350
|
-
&& this.#lastDecision.verificationStatus !== 'evidence_linked') {
|
|
351
|
-
warnings.push('顾问意见已记录,但主 Agent 尚未关联一手核验证据;这不是已验证通过的结论。');
|
|
352
|
-
}
|
|
353
|
-
return warnings;
|
|
354
|
-
}
|
|
355
|
-
beforeTool(call, semantics) {
|
|
356
|
-
if (!this.#advisorAvailable ||
|
|
357
|
-
call.function.name === 'Advisor' ||
|
|
358
|
-
call.function.name === 'AdvisorDecision') {
|
|
359
|
-
return { allow: true };
|
|
360
|
-
}
|
|
361
|
-
if (call.function.name === 'PresentResult') {
|
|
362
|
-
// Review the actual submission, not an earlier manifest with different
|
|
363
|
-
// files/claims. Whitespace-only JSON changes are not a new revision.
|
|
364
|
-
const manifest = manifestFingerprint(call.function.arguments);
|
|
365
|
-
this.#deliveryManifest = manifest;
|
|
366
|
-
if (this.#deliveryReviewedWork >= 0 && this.#reviewedManifest !== manifest) {
|
|
367
|
-
this.#completed.delete(checkpointKey('pre-delivery', this.versions));
|
|
368
|
-
}
|
|
369
|
-
this.#deliveryDraft = `待提交成果(尚未发布;文件内容必须以读取/渲染证据核实):\n${call.function.arguments}`;
|
|
370
|
-
return this.beforeDelivery(undefined, true);
|
|
371
|
-
}
|
|
372
|
-
if (semantics.mutatesWorkspace || semantics.retriesFailure) {
|
|
373
|
-
const failure = this.#checkFailureReview();
|
|
374
|
-
if (failure && !failure.allow)
|
|
375
|
-
return failure;
|
|
376
|
-
}
|
|
377
|
-
if (semantics.mutatesWorkspace) {
|
|
378
|
-
if (this.#required?.reviewReason)
|
|
379
|
-
return this.#defer(this.#required.checkpoint, this.#required.reason, this.#required.key);
|
|
380
|
-
if (isCosmeticRequest(this.#currentRequest) && !semantics.highRisk)
|
|
381
|
-
return { allow: true };
|
|
382
|
-
if (isRoutineMaintenanceRequest(this.#currentRequest) && !semantics.highRisk
|
|
383
|
-
&& ['Edit', 'MultiEdit', 'Write'].includes(call.function.name)) {
|
|
384
|
-
try {
|
|
385
|
-
const path = JSON.parse(call.function.arguments).file_path;
|
|
386
|
-
if (typeof path === 'string' && isRoutineMaintenancePath(path))
|
|
387
|
-
return { allow: true };
|
|
388
|
-
}
|
|
389
|
-
catch { /* Unknown scope retains review. */ }
|
|
390
|
-
}
|
|
391
|
-
return this.requestCheckpoint('pre-execution', '即将执行可能修改工作区的操作,先复核执行方案');
|
|
392
|
-
}
|
|
393
|
-
return { allow: true };
|
|
394
|
-
}
|
|
395
|
-
requestCheckpoint(checkpoint, reason, options = {}) {
|
|
396
|
-
if (!this.#advisorAvailable || checkpoint === 'high-risk')
|
|
397
|
-
return { allow: true };
|
|
398
|
-
this.#commitTask();
|
|
399
|
-
const key = checkpointKey(checkpoint, this.versions, options.signature);
|
|
400
|
-
if (this.#completed.has(key))
|
|
401
|
-
return { allow: true };
|
|
402
|
-
// A reminder is not an attempt. The host must execute the pending review,
|
|
403
|
-
// or record its actual failure/unavailability before releasing this boundary.
|
|
404
|
-
this.#required = { checkpoint, key, reason };
|
|
405
|
-
return this.#defer(checkpoint, reason, key);
|
|
406
|
-
}
|
|
407
|
-
notifyStalled(signature) {
|
|
408
|
-
if (!this.#advisorAvailable)
|
|
409
|
-
return;
|
|
410
|
-
this.#commitTask();
|
|
411
|
-
const key = checkpointKey('stalled', this.versions, signature ?? 'unknown');
|
|
412
|
-
if (this.#softAlerts.has(key) || this.#completed.has(key))
|
|
413
|
-
return;
|
|
414
|
-
this.#softAlerts.add(key);
|
|
415
|
-
this.#softCheckpoint = {
|
|
416
|
-
checkpoint: 'stalled',
|
|
417
|
-
key,
|
|
418
|
-
};
|
|
419
|
-
this.#reminders.push('检测到连续同类失败或执行没有进展。继续重试前请优先调用 Advisor 获取第二意见;这是提醒,不阻止只读诊断或切换路径。');
|
|
420
|
-
}
|
|
421
|
-
shouldCheckDelivery() {
|
|
422
|
-
// Only exempt a process explanation when no real work or delivery occurred.
|
|
423
|
-
if (isReviewProcessQuestion(this.#currentRequest) && this.#mutations === 0 && !this.#deliveryProduced)
|
|
424
|
-
return false;
|
|
425
|
-
if (isRoutineMaintenanceRequest(this.#currentRequest) && !this.#nonRoutineMutation && !this.#deliveryProduced)
|
|
426
|
-
return false;
|
|
427
|
-
return ((this.#mutations > 0 && !isCosmeticRequest(this.#currentRequest)) ||
|
|
428
|
-
this.#verifications > 0 ||
|
|
429
|
-
this.#deliveryProduced ||
|
|
430
|
-
needsTextDeliveryReview(this.#currentRequest, this.#deliveryDraft ?? '') ||
|
|
431
|
-
this.#investigationEvidence >= 3);
|
|
432
|
-
}
|
|
433
|
-
/** After one ignored reminder, the host executes the consultation through its
|
|
434
|
-
* normal tool pipeline instead of treating a missing attempt as a failed one. */
|
|
435
|
-
pendingReview(draft) {
|
|
436
|
-
if (draft !== undefined)
|
|
437
|
-
this.#deliveryDraft = draft;
|
|
438
|
-
const active = this.#required ?? this.#softCheckpoint;
|
|
439
|
-
if (!this.#advisorAvailable || !active || this.#completed.has(active.key))
|
|
440
|
-
return;
|
|
441
|
-
return { reason: active.reviewReason ?? (active.checkpoint === 'pre-delivery' ? 'delivery_review'
|
|
442
|
-
: active.checkpoint === 'stalled' ? 'stalled' : 'plan_review'),
|
|
443
|
-
note: active.reason ?? '核实当前问题、待执行方案或待交付草稿,主 Agent 读取第二意见后自行判断继续。' };
|
|
444
|
-
}
|
|
445
|
-
beforeDelivery(draft, force = false) {
|
|
446
|
-
if (draft !== undefined)
|
|
447
|
-
this.#deliveryDraft = draft;
|
|
448
|
-
if (!this.#advisorAvailable)
|
|
449
|
-
return { allow: true };
|
|
450
|
-
if (!isReviewProcessQuestion(this.#currentRequest) || this.#mutations > 0 || this.#deliveryProduced)
|
|
451
|
-
this.#requireSignal(draft ?? '', true);
|
|
452
|
-
if (this.#required?.reviewReason)
|
|
453
|
-
return this.#defer(this.#required.checkpoint, this.#required.reason, this.#required.key);
|
|
454
|
-
// A later successful mutation changes the reviewed artifact. Ordinary reads
|
|
455
|
-
// and wording changes do not reopen the checkpoint.
|
|
456
|
-
if (this.#deliveryReviewedWork >= 0 && this.#workVersion > this.#deliveryReviewedWork) {
|
|
457
|
-
this.#completed.delete(checkpointKey('pre-delivery', this.versions));
|
|
458
|
-
}
|
|
459
|
-
const failure = this.#checkFailureReview();
|
|
460
|
-
if (failure && !failure.allow) {
|
|
461
|
-
if (failure.deferred && !failure.deferred.ok)
|
|
462
|
-
this.#reminders.push(failure.deferred.error);
|
|
463
|
-
return failure;
|
|
464
|
-
}
|
|
465
|
-
if (!force && !this.shouldCheckDelivery())
|
|
466
|
-
return { allow: true };
|
|
467
|
-
const decision = this.requestCheckpoint('pre-delivery', '当前回合已产生实质成果或证据,提交最终结论前进行一次第二意见复核');
|
|
468
|
-
if (decision.deferred &&
|
|
469
|
-
!decision.deferred.ok &&
|
|
470
|
-
!this.#reminders.includes(decision.deferred.error)) {
|
|
471
|
-
this.#reminders.push(decision.deferred.error);
|
|
472
|
-
}
|
|
473
|
-
return decision;
|
|
474
|
-
}
|
|
475
|
-
beginConsultation(reason) {
|
|
476
|
-
this.#commitTask();
|
|
477
|
-
const trigger = this.#required ?? this.#softCheckpoint;
|
|
478
|
-
// Explicit phase reviews settle that phase, even when initiated before a host reminder.
|
|
479
|
-
const explicit = reason === 'plan_review' ? 'pre-execution' : reason === 'delivery_review' ? 'pre-delivery' : undefined;
|
|
480
|
-
const active = explicit
|
|
481
|
-
? { checkpoint: explicit, key: checkpointKey(explicit, this.versions) }
|
|
482
|
-
: this.#required ?? ((reason === 'evidence_conflict' && this.#pending)
|
|
483
|
-
? { checkpoint: this.#pending.checkpoint, key: this.#pending.checkpointKey }
|
|
484
|
-
: this.#softCheckpoint ?? {
|
|
485
|
-
checkpoint: 'pre-execution',
|
|
486
|
-
key: `${this.versions.taskVersion}:voluntary`,
|
|
487
|
-
});
|
|
488
|
-
this.#softCheckpoint = { checkpoint: active.checkpoint, key: active.key };
|
|
489
|
-
return {
|
|
490
|
-
kind: 'advisor_consultation',
|
|
491
|
-
triggerSource: trigger ? 'checkpoint' : 'model',
|
|
492
|
-
triggerReason: this.#required?.reason ?? (trigger ? '运行时复核提醒' : `模型主动咨询:${reason ?? '未指定原因'}`),
|
|
493
|
-
consultationId: crypto.randomUUID(),
|
|
494
|
-
checkpoint: active.checkpoint,
|
|
495
|
-
checkpointKey: active.key,
|
|
496
|
-
status: 'completed',
|
|
497
|
-
...this.versions,
|
|
498
|
-
};
|
|
499
|
-
}
|
|
500
|
-
finishConsultation(metadata, ok) {
|
|
501
|
-
const finished = {
|
|
502
|
-
...metadata,
|
|
503
|
-
status: ok ? 'completed' : 'failed',
|
|
504
|
-
};
|
|
505
|
-
this.#applyReviewEvent(finished);
|
|
506
|
-
this.#required = undefined;
|
|
507
|
-
this.#softCheckpoint = undefined;
|
|
508
|
-
return finished;
|
|
509
|
-
}
|
|
510
|
-
recordDecision(input) {
|
|
511
|
-
if (!this.#pending ||
|
|
512
|
-
this.#pending.consultationId !== input.consultationId) {
|
|
513
|
-
const expected = this.#pending?.consultationId;
|
|
514
|
-
return {
|
|
515
|
-
ok: false,
|
|
516
|
-
error: expected
|
|
517
|
-
? `顾问咨询 ID 不匹配:收到 ${input.consultationId},当前待处理的完整 consultationId 是 ${expected}。请原样使用该完整 ID 重试一次 AdvisorDecision。`
|
|
518
|
-
: `没有找到待处理的顾问咨询 ${input.consultationId};不要继续重试 AdvisorDecision。`,
|
|
519
|
-
recovery: expected ? 'change-input' : 'none',
|
|
520
|
-
};
|
|
521
|
-
}
|
|
522
|
-
const evidenceRefs = [...new Set(input.evidenceRefs ?? [])];
|
|
523
|
-
const history = expandAdvisorHistory(this.#history);
|
|
524
|
-
const names = toolNamesByCallId(history);
|
|
525
|
-
const validEvidence = new Set(history.filter((message) => message.role === 'tool' && !advisorMetadata(message.display_metadata)
|
|
526
|
-
&& message.toolResultDisposition !== 'deferred'
|
|
527
|
-
&& !['Advisor', 'AdvisorDecision', 'OptimizeConversation'].includes(names.get(message.tool_call_id) ?? '')
|
|
528
|
-
&& message.content.trim().length > 0).map((message) => message.tool_call_id));
|
|
529
|
-
const invalid = evidenceRefs.filter((ref) => !validEvidence.has(ref));
|
|
530
|
-
if (invalid.length)
|
|
531
|
-
return { ok: false, error: `证据引用不存在或不是一手工具结果:${invalid.join(', ')}。请引用已有 tool_call_id,或省略引用并记录待验证计划。`, recovery: 'change-input' };
|
|
532
|
-
const metadata = {
|
|
533
|
-
kind: 'advisor_decision',
|
|
534
|
-
consultationId: input.consultationId,
|
|
535
|
-
checkpoint: this.#pending.checkpoint,
|
|
536
|
-
checkpointKey: this.#pending.checkpointKey,
|
|
537
|
-
decision: input.decision,
|
|
538
|
-
rationale: input.rationale,
|
|
539
|
-
acceptedPoints: input.acceptedPoints ?? [],
|
|
540
|
-
rejectedPoints: input.rejectedPoints ?? [],
|
|
541
|
-
verificationPlan: input.verificationPlan ?? [],
|
|
542
|
-
evidenceRefs,
|
|
543
|
-
verificationStatus: evidenceRefs.length ? 'evidence_linked' : 'pending',
|
|
544
|
-
...versionsOf(this.#pending),
|
|
545
|
-
};
|
|
546
|
-
this.#applyReviewEvent(metadata);
|
|
547
|
-
return {
|
|
548
|
-
ok: true,
|
|
549
|
-
content: `已记录顾问意见处理决定:${input.decision}。理由:${input.rationale}\n${evidenceRefs.length ? '已关联一手证据;关联不等于自动验证正确性,请逐条核对建议与证据。' : '待验证:没有关联一手证据。可继续执行核验,不要求再次审批;交付时如实说明未验证项。'}`,
|
|
550
|
-
metadata,
|
|
551
|
-
};
|
|
552
|
-
}
|
|
553
|
-
observeToolResult(call, result, semantics) {
|
|
554
|
-
if (call.function.name === 'OptimizeConversation')
|
|
555
|
-
return;
|
|
556
|
-
if (!result.ok && result.disposition === 'deferred')
|
|
557
|
-
return;
|
|
558
|
-
const review = advisorMetadata(result.metadata);
|
|
559
|
-
if (review) {
|
|
560
|
-
this.#applyReviewEvent(review);
|
|
561
|
-
return;
|
|
562
|
-
}
|
|
563
|
-
this.#observeDeliveryFacts(call.function.name, result, semantics);
|
|
564
|
-
const metadata = result.metadata;
|
|
565
|
-
const verificationResult = Boolean(metadata?.kind === 'process_execution' &&
|
|
566
|
-
metadata.commandKind !== 'other' &&
|
|
567
|
-
['completed', 'failed'].includes(metadata.status));
|
|
568
|
-
if (verificationResult) {
|
|
569
|
-
this.#evidenceVersion += 1;
|
|
570
|
-
if (!result.ok)
|
|
571
|
-
return;
|
|
572
|
-
}
|
|
573
|
-
if (!result.ok)
|
|
574
|
-
return;
|
|
575
|
-
if (metadata?.kind === 'file_activity') {
|
|
576
|
-
const mutations = metadata.files.filter((file) => ['created', 'modified', 'generated', 'deleted'].includes(file.activity)).length;
|
|
577
|
-
this.#workVersion += mutations;
|
|
578
|
-
}
|
|
579
|
-
else if (semantics?.mutatesWorkspace) {
|
|
580
|
-
// Scripts/generation/browser tools need not emit file_activity. Record
|
|
581
|
-
// their declared effect too, so subsequent delivery review cannot vanish.
|
|
582
|
-
this.#workVersion += 1;
|
|
583
|
-
}
|
|
584
|
-
if (call.function.name !== 'Advisor' &&
|
|
585
|
-
call.function.name !== 'AdvisorDecision' &&
|
|
586
|
-
!verificationResult) {
|
|
587
|
-
this.#evidenceVersion += 1;
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
#observeDeliveryFacts(name, result, semantics) {
|
|
591
|
-
const metadata = result.metadata;
|
|
592
|
-
if (advisorMetadata(metadata))
|
|
593
|
-
return;
|
|
594
|
-
if (metadata?.kind === 'process_execution' && metadata.commandKind !== 'other'
|
|
595
|
-
&& ['completed', 'failed'].includes(metadata.status))
|
|
596
|
-
this.#verifications++;
|
|
597
|
-
if (!result.ok)
|
|
598
|
-
return;
|
|
599
|
-
if (metadata?.kind === 'file_activity') {
|
|
600
|
-
const changes = metadata.files.filter(file => ['created', 'modified', 'generated', 'deleted'].includes(file.activity));
|
|
601
|
-
this.#mutations += changes.length;
|
|
602
|
-
this.#nonRoutineMutation ||= changes.some(file => file.activity === 'deleted' || !isRoutineMaintenancePath(file.relativePath));
|
|
603
|
-
}
|
|
604
|
-
else if (semantics?.mutatesWorkspace) {
|
|
605
|
-
this.#mutations++;
|
|
606
|
-
this.#nonRoutineMutation = true;
|
|
607
|
-
}
|
|
608
|
-
if (metadata?.kind === 'result_manifest')
|
|
609
|
-
this.#deliveryProduced = true;
|
|
610
|
-
if (INVESTIGATION_TOOLS.has(name))
|
|
611
|
-
this.#investigationEvidence++;
|
|
612
|
-
}
|
|
613
|
-
contextPacket(note) {
|
|
614
|
-
const history = expandAdvisorHistory(this.#history);
|
|
615
|
-
const names = toolNamesByCallId(history);
|
|
616
|
-
const changedFiles = new Set();
|
|
617
|
-
const candidates = [];
|
|
618
|
-
let previousAdvice;
|
|
619
|
-
let lastAdvisorIndex = -1;
|
|
620
|
-
const terms = [...new Set(`${this.#currentRequest} ${note ?? ''}`
|
|
621
|
-
.toLowerCase().match(/[\p{L}\p{N}_./-]{3,}/gu) ?? [])].slice(0, 64);
|
|
622
|
-
for (let index = 0; index < history.length; index += 1) {
|
|
623
|
-
const message = history[index];
|
|
624
|
-
if (message.role !== 'tool')
|
|
625
|
-
continue;
|
|
626
|
-
if (message.toolResultDisposition === 'deferred')
|
|
627
|
-
continue;
|
|
628
|
-
const toolName = names.get(message.tool_call_id) ?? 'Tool';
|
|
629
|
-
const sourceLabel = provenanceLabel(message.provenance ?? { mode: 'private', visibility: 'owner-private' });
|
|
630
|
-
if (toolName === 'OptimizeConversation')
|
|
631
|
-
continue;
|
|
632
|
-
if (message.display_metadata?.kind === 'file_activity') {
|
|
633
|
-
for (const file of message.display_metadata.files)
|
|
634
|
-
changedFiles.add(file.relativePath);
|
|
635
|
-
}
|
|
636
|
-
if (message.display_metadata?.kind === 'process_execution') {
|
|
637
|
-
candidates.push(`[${message.tool_call_id}] ${toolName}: ${sourceLabel} ${message.display_metadata.commandKind}/${message.display_metadata.status}\n${message.content.slice(-EVIDENCE_ITEM_MAX_CHARS)}`);
|
|
638
|
-
}
|
|
639
|
-
else if (message.display_metadata?.kind === 'result_manifest') {
|
|
640
|
-
candidates.push(`[${message.tool_call_id}] ${toolName}: ${sourceLabel} 已生成 ${message.display_metadata.manifest.files.length} 个交付文件\n${message.content.slice(-EVIDENCE_ITEM_MAX_CHARS)}`);
|
|
641
|
-
}
|
|
642
|
-
else if (toolName !== 'Advisor' &&
|
|
643
|
-
toolName !== 'AdvisorDecision' &&
|
|
644
|
-
!advisorMetadata(message.display_metadata)) {
|
|
645
|
-
const relevantAt = terms.map((term) => message.content.toLowerCase().indexOf(term)).find((index) => index >= 0) ?? -1;
|
|
646
|
-
const excerpt = message.content.length <= EVIDENCE_ITEM_MAX_CHARS
|
|
647
|
-
? message.content
|
|
648
|
-
: relevantAt >= 0
|
|
649
|
-
? `${message.content.slice(0, 180)}\n[相关片段]\n${message.content.slice(Math.max(0, relevantAt - 80), Math.max(0, relevantAt - 80) + 380)}\n[尾部]\n${message.content.slice(-180)}`
|
|
650
|
-
: `${message.content.slice(0, 380)}\n[中段省略]\n${message.content.slice(-380)}`;
|
|
651
|
-
candidates.push(`[${message.tool_call_id}] ${toolName}: ${sourceLabel}\n${excerpt}`);
|
|
652
|
-
}
|
|
653
|
-
if (toolName === 'Advisor' &&
|
|
654
|
-
message.display_metadata?.kind === 'advisor_consultation' &&
|
|
655
|
-
message.display_metadata.status === 'completed') {
|
|
656
|
-
previousAdvice = `${sourceLabel}\n${message.content.slice(0, 4_000)}`;
|
|
657
|
-
lastAdvisorIndex = index;
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
const actionsAfterPreviousAdvice = lastAdvisorIndex < 0
|
|
661
|
-
? []
|
|
662
|
-
: history
|
|
663
|
-
.slice(lastAdvisorIndex + 1)
|
|
664
|
-
.filter((message) => message.role === 'tool' && message.toolResultDisposition !== 'deferred')
|
|
665
|
-
.map((message) => names.get(message.tool_call_id) ?? 'Tool')
|
|
666
|
-
.filter((name) => !['AdvisorDecision', 'OptimizeConversation'].includes(name));
|
|
667
|
-
const indices = new Set();
|
|
668
|
-
// Reserve recent results first; then relevant older evidence and early context.
|
|
669
|
-
for (let i = Math.max(0, candidates.length - 10); i < candidates.length; i++)
|
|
670
|
-
indices.add(i);
|
|
671
|
-
candidates.map((value, index) => ({ index, score: terms.reduce((sum, term) => sum + Number(value.toLowerCase().includes(term)), 0) }))
|
|
672
|
-
.filter(({ index, score }) => score > 0 && !indices.has(index))
|
|
673
|
-
.sort((a, b) => b.score - a.score || b.index - a.index)
|
|
674
|
-
.slice(0, 4).forEach(({ index }) => indices.add(index));
|
|
675
|
-
for (let i = 0; i < candidates.length && indices.size < EVIDENCE_MAX_ITEMS; i++)
|
|
676
|
-
indices.add(i);
|
|
677
|
-
const evidence = [...indices].sort((a, b) => a - b).map((index) => candidates[index]);
|
|
678
|
-
const active = this.#required ?? this.#softCheckpoint;
|
|
679
|
-
return {
|
|
680
|
-
failureAttempts: [...this.#failureReviews.entries()]
|
|
681
|
-
.filter(([signature]) => !this.#completed.has(checkpointKey('stalled', this.versions, signature)))
|
|
682
|
-
.flatMap(([, attempts]) => attempts).slice(-4),
|
|
683
|
-
...(this.#deliveryDraft !== undefined ? { deliveryDraft: this.#deliveryDraft } : {}),
|
|
684
|
-
objective: this.#objective.slice(0, 8_000),
|
|
685
|
-
currentRequest: this.#currentRequest.slice(0, 8_000),
|
|
686
|
-
checkpoint: active?.checkpoint ?? 'pre-execution',
|
|
687
|
-
versions: this.versions,
|
|
688
|
-
changedFiles: [...changedFiles].slice(-40),
|
|
689
|
-
evidence,
|
|
690
|
-
evidenceCoverage: { available: candidates.length, included: evidence.length,
|
|
691
|
-
omitted: candidates.length - evidence.length, excerpts: true },
|
|
692
|
-
...(previousAdvice ? { previousAdvice } : {}),
|
|
693
|
-
actionsAfterPreviousAdvice: actionsAfterPreviousAdvice.slice(-40),
|
|
694
|
-
openQuestions: [this.#required?.reason, note].filter((value) => !!value),
|
|
695
|
-
...(this.#lastDecision?.taskVersion === this.#taskVersion ? { decisionVerification: {
|
|
696
|
-
consultationId: this.#lastDecision.consultationId,
|
|
697
|
-
decision: this.#lastDecision.decision,
|
|
698
|
-
rationale: this.#lastDecision.rationale,
|
|
699
|
-
acceptedPoints: this.#lastDecision.acceptedPoints,
|
|
700
|
-
rejectedPoints: this.#lastDecision.rejectedPoints,
|
|
701
|
-
verificationStatus: this.#lastDecision.verificationStatus ?? 'pending',
|
|
702
|
-
evidenceRefs: this.#lastDecision.evidenceRefs ?? [],
|
|
703
|
-
verificationPlan: this.#lastDecision.verificationPlan,
|
|
704
|
-
} } : {}),
|
|
705
|
-
};
|
|
706
|
-
}
|
|
707
|
-
#commitTask() {
|
|
708
|
-
if (this.#taskCommitted)
|
|
709
|
-
return;
|
|
710
|
-
this.#taskCommitted = true;
|
|
711
|
-
}
|
|
712
|
-
#defer(checkpoint, reason, key) {
|
|
713
|
-
this.#commitTask();
|
|
714
|
-
const actualKey = key ?? checkpointKey(checkpoint, this.versions);
|
|
715
|
-
const metadata = {
|
|
716
|
-
kind: 'advisor_checkpoint',
|
|
717
|
-
checkpoint,
|
|
718
|
-
checkpointKey: actualKey,
|
|
719
|
-
status: 'required',
|
|
720
|
-
...this.versions,
|
|
721
|
-
};
|
|
722
|
-
return {
|
|
723
|
-
allow: false,
|
|
724
|
-
deferred: {
|
|
725
|
-
ok: false,
|
|
726
|
-
disposition: 'deferred',
|
|
727
|
-
recovery: 'change-input',
|
|
728
|
-
sensitivity: 'public',
|
|
729
|
-
metadata,
|
|
730
|
-
error: `Advisor 检查点 ${checkpoint} 暂缓了这一步:${reason}\n请先调用 Advisor 获取第二意见;成功后自行决定采纳、部分采纳或拒绝,可记录采纳依据,再继续原操作。本次咨询只尝试一次;失败或超时就由主 Agent 自行决定并继续。同一检查点不会因命令改写再次要求咨询。`,
|
|
731
|
-
},
|
|
732
|
-
};
|
|
733
|
-
}
|
|
734
|
-
}
|