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
package/src/memory/retrieval.js
CHANGED
|
@@ -5,6 +5,23 @@ const snapshotIndexes = new WeakMap();
|
|
|
5
5
|
export function memoryTerms(query) {
|
|
6
6
|
return searchTerms(query);
|
|
7
7
|
}
|
|
8
|
+
/**
|
|
9
|
+
* Stable personal facts keep their force however old they are. A preference
|
|
10
|
+
* like "always answer in Chinese" does not expire by calendar: age used to
|
|
11
|
+
* relabel it "待复核,不能当作当前已确认事实" after the review window, and
|
|
12
|
+
* restating it never refreshed the clock, so a standing preference silently
|
|
13
|
+
* stopped binding after six months. Project/decision/topic records change with
|
|
14
|
+
* the work, so for them age is a real signal. Only the injected label and the
|
|
15
|
+
* ranking penalty use this; `isReviewDue` still feeds the user's review queue.
|
|
16
|
+
*/
|
|
17
|
+
export function agingDowngrades(record) {
|
|
18
|
+
return record.category !== 'preference' && record.category !== 'profile';
|
|
19
|
+
}
|
|
20
|
+
/** The label and ranking view of staleness: explicit marks always count, age only where it is evidence. */
|
|
21
|
+
export function isUnconfirmed(record, settings, now = Date.now()) {
|
|
22
|
+
return record.status === 'needs_review'
|
|
23
|
+
|| (agingDowngrades(record) && isReviewDue(record, settings, now));
|
|
24
|
+
}
|
|
8
25
|
export function isReviewDue(record, settings, now = Date.now()) {
|
|
9
26
|
const days = settings.reviewAfterDays?.[record.category] ??
|
|
10
27
|
DEFAULT_REVIEW_WINDOWS[record.category];
|
|
@@ -13,14 +30,18 @@ export function isReviewDue(record, settings, now = Date.now()) {
|
|
|
13
30
|
export function scoreMemory(record, query, settings) {
|
|
14
31
|
if (record.status === 'superseded' || (record.validUntil !== undefined && record.validUntil <= Date.now()))
|
|
15
32
|
return Number.NEGATIVE_INFINITY;
|
|
33
|
+
const stalePenalty = isUnconfirmed(record, settings) ? 8 : 0;
|
|
34
|
+
const { terms, phrase } = substringParts(record, query);
|
|
35
|
+
return terms + phrase - stalePenalty + record.lastConfirmedAt / 1e13;
|
|
36
|
+
}
|
|
37
|
+
/** Substring evidence: 10 per matching query term, 25 for the whole phrase. */
|
|
38
|
+
function substringParts(record, query) {
|
|
16
39
|
const haystack = `${record.category} ${record.subject} ${record.topicKey} ${record.aliases.join(' ')} ${record.statement}`.normalize('NFKC').toLocaleLowerCase();
|
|
17
|
-
const
|
|
18
|
-
const lexical = terms.reduce((sum, term) => sum + (haystack.includes(term) ? 10 : 0), 0);
|
|
40
|
+
const lexical = memoryTerms(query).reduce((sum, term) => sum + (haystack.includes(term) ? 10 : 0), 0);
|
|
19
41
|
const phrase = query.trim() && haystack.includes(query.trim().normalize('NFKC').toLocaleLowerCase())
|
|
20
42
|
? 25
|
|
21
43
|
: 0;
|
|
22
|
-
|
|
23
|
-
return lexical + phrase - stalePenalty + record.lastConfirmedAt / 1e13;
|
|
44
|
+
return { terms: lexical, phrase };
|
|
24
45
|
}
|
|
25
46
|
export function recallFromSnapshot(snapshot, query, settings, limit) {
|
|
26
47
|
const eligible = snapshot.records.filter((record) => record.status !== 'superseded' && (record.validUntil === undefined || record.validUntil > Date.now()));
|
|
@@ -53,18 +74,26 @@ export function recallFromSnapshot(snapshot, query, settings, limit) {
|
|
|
53
74
|
}
|
|
54
75
|
recordSearchStrategy('bm25', eligible.length);
|
|
55
76
|
const bm25 = index.search(query);
|
|
56
|
-
|
|
77
|
+
// BM25 only knows whole tokens, so `postgres` never reaches a record that
|
|
78
|
+
// says `PostgreSQL`; the small-corpus path finds it by substring. Growing
|
|
79
|
+
// past the threshold must not make a findable record vanish, so substring
|
|
80
|
+
// hits stay candidates. A token hit keeps its original score (BM25's IDF
|
|
81
|
+
// is what puts a rare word above two common ones, and the flat per-term
|
|
82
|
+
// substring bonus would flatten it); substring evidence only scores the
|
|
83
|
+
// records BM25 could not see.
|
|
57
84
|
return eligible
|
|
58
85
|
.map((record) => {
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
const stalePenalty =
|
|
86
|
+
const { terms, phrase } = substringParts(record, query);
|
|
87
|
+
const tokens = bm25.get(record.id) ?? 0;
|
|
88
|
+
const stalePenalty = isUnconfirmed(record, settings) ? 8 : 0;
|
|
62
89
|
return {
|
|
63
90
|
record,
|
|
64
|
-
|
|
91
|
+
hit: tokens > 0 || terms + phrase >= 2,
|
|
92
|
+
score: (tokens > 0 ? tokens * 10 + phrase : terms + phrase)
|
|
93
|
+
- stalePenalty + record.lastConfirmedAt / 1e13,
|
|
65
94
|
};
|
|
66
95
|
})
|
|
67
|
-
.filter((item) =>
|
|
96
|
+
.filter((item) => item.hit)
|
|
68
97
|
.sort((a, b) => b.score - a.score)
|
|
69
98
|
.slice(0, Math.min(100, Math.max(1, limit)))
|
|
70
99
|
.map((item) => item.record);
|
package/src/memory/retrieval.ts
CHANGED
|
@@ -21,6 +21,29 @@ export function memoryTerms(query: string): string[] {
|
|
|
21
21
|
return searchTerms(query);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Stable personal facts keep their force however old they are. A preference
|
|
26
|
+
* like "always answer in Chinese" does not expire by calendar: age used to
|
|
27
|
+
* relabel it "待复核,不能当作当前已确认事实" after the review window, and
|
|
28
|
+
* restating it never refreshed the clock, so a standing preference silently
|
|
29
|
+
* stopped binding after six months. Project/decision/topic records change with
|
|
30
|
+
* the work, so for them age is a real signal. Only the injected label and the
|
|
31
|
+
* ranking penalty use this; `isReviewDue` still feeds the user's review queue.
|
|
32
|
+
*/
|
|
33
|
+
export function agingDowngrades(record: MemoryRecordV2): boolean {
|
|
34
|
+
return record.category !== 'preference' && record.category !== 'profile';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** The label and ranking view of staleness: explicit marks always count, age only where it is evidence. */
|
|
38
|
+
export function isUnconfirmed(
|
|
39
|
+
record: MemoryRecordV2,
|
|
40
|
+
settings: MemorySettings,
|
|
41
|
+
now = Date.now(),
|
|
42
|
+
): boolean {
|
|
43
|
+
return record.status === 'needs_review'
|
|
44
|
+
|| (agingDowngrades(record) && isReviewDue(record, settings, now));
|
|
45
|
+
}
|
|
46
|
+
|
|
24
47
|
export function isReviewDue(
|
|
25
48
|
record: MemoryRecordV2,
|
|
26
49
|
settings: MemorySettings,
|
|
@@ -38,10 +61,16 @@ export function scoreMemory(
|
|
|
38
61
|
settings: MemorySettings,
|
|
39
62
|
): number {
|
|
40
63
|
if (record.status === 'superseded' || (record.validUntil !== undefined && record.validUntil <= Date.now())) return Number.NEGATIVE_INFINITY;
|
|
64
|
+
const stalePenalty = isUnconfirmed(record, settings) ? 8 : 0;
|
|
65
|
+
const { terms, phrase } = substringParts(record, query);
|
|
66
|
+
return terms + phrase - stalePenalty + record.lastConfirmedAt / 1e13;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Substring evidence: 10 per matching query term, 25 for the whole phrase. */
|
|
70
|
+
function substringParts(record: MemoryRecordV2, query: string): { terms: number; phrase: number } {
|
|
41
71
|
const haystack =
|
|
42
72
|
`${record.category} ${record.subject} ${record.topicKey} ${record.aliases.join(' ')} ${record.statement}`.normalize('NFKC').toLocaleLowerCase();
|
|
43
|
-
const
|
|
44
|
-
const lexical = terms.reduce(
|
|
73
|
+
const lexical = memoryTerms(query).reduce(
|
|
45
74
|
(sum, term) => sum + (haystack.includes(term) ? 10 : 0),
|
|
46
75
|
0,
|
|
47
76
|
);
|
|
@@ -49,9 +78,7 @@ export function scoreMemory(
|
|
|
49
78
|
query.trim() && haystack.includes(query.trim().normalize('NFKC').toLocaleLowerCase())
|
|
50
79
|
? 25
|
|
51
80
|
: 0;
|
|
52
|
-
|
|
53
|
-
isReviewDue(record, settings) || record.status === 'needs_review' ? 8 : 0;
|
|
54
|
-
return lexical + phrase - stalePenalty + record.lastConfirmedAt / 1e13;
|
|
81
|
+
return { terms: lexical, phrase };
|
|
55
82
|
}
|
|
56
83
|
|
|
57
84
|
export function recallFromSnapshot(
|
|
@@ -93,18 +120,26 @@ export function recallFromSnapshot(
|
|
|
93
120
|
}
|
|
94
121
|
recordSearchStrategy('bm25', eligible.length);
|
|
95
122
|
const bm25 = index.search(query);
|
|
96
|
-
|
|
123
|
+
// BM25 only knows whole tokens, so `postgres` never reaches a record that
|
|
124
|
+
// says `PostgreSQL`; the small-corpus path finds it by substring. Growing
|
|
125
|
+
// past the threshold must not make a findable record vanish, so substring
|
|
126
|
+
// hits stay candidates. A token hit keeps its original score (BM25's IDF
|
|
127
|
+
// is what puts a rare word above two common ones, and the flat per-term
|
|
128
|
+
// substring bonus would flatten it); substring evidence only scores the
|
|
129
|
+
// records BM25 could not see.
|
|
97
130
|
return eligible
|
|
98
131
|
.map((record) => {
|
|
99
|
-
const
|
|
100
|
-
const
|
|
101
|
-
const stalePenalty =
|
|
132
|
+
const { terms, phrase } = substringParts(record, query);
|
|
133
|
+
const tokens = bm25.get(record.id) ?? 0;
|
|
134
|
+
const stalePenalty = isUnconfirmed(record, settings) ? 8 : 0;
|
|
102
135
|
return {
|
|
103
136
|
record,
|
|
104
|
-
|
|
137
|
+
hit: tokens > 0 || terms + phrase >= 2,
|
|
138
|
+
score: (tokens > 0 ? tokens * 10 + phrase : terms + phrase)
|
|
139
|
+
- stalePenalty + record.lastConfirmedAt / 1e13,
|
|
105
140
|
};
|
|
106
141
|
})
|
|
107
|
-
.filter((item) =>
|
|
142
|
+
.filter((item) => item.hit)
|
|
108
143
|
.sort((a, b) => b.score - a.score)
|
|
109
144
|
.slice(0, Math.min(100, Math.max(1, limit)))
|
|
110
145
|
.map((item) => item.record);
|
|
@@ -30,6 +30,7 @@ import { scheduleMemoryMaintenance } from './learning.js';
|
|
|
30
30
|
import { enqueueFactTurn, factLearningStatus, scheduleFactLearning } from './factLearning.js';
|
|
31
31
|
import { captureDependencies } from './dependencies.js';
|
|
32
32
|
import { getWorkingDir } from '../bootstrap/workingDir.js';
|
|
33
|
+
import { FENCE_CLOSE, FENCE_OPEN } from '../prompts/visualPresentation.js';
|
|
33
34
|
import { isReadOnlyShellCommand } from '../tools/bash/semantics.js';
|
|
34
35
|
function messageText(message) {
|
|
35
36
|
if (typeof message.content === 'string')
|
|
@@ -48,6 +49,31 @@ function lastAssistantMessage(messages, start) {
|
|
|
48
49
|
}
|
|
49
50
|
return undefined;
|
|
50
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* The turn's answer as the user read it. When a live answer paused before a
|
|
54
|
+
* text-drawn diagram, the part already shown was committed as its own
|
|
55
|
+
* text-only progress message and the model continued in the final one; both
|
|
56
|
+
* halves are the answer. Only contiguous text-only assistant messages right
|
|
57
|
+
* before the last one join it (ephemeral notices in between are skipped); tool
|
|
58
|
+
* rounds and user messages end the run.
|
|
59
|
+
*/
|
|
60
|
+
function turnAnswerText(messages, start) {
|
|
61
|
+
let index = messages.length - 1;
|
|
62
|
+
while (index >= start && messages[index]?.role !== 'assistant')
|
|
63
|
+
index -= 1;
|
|
64
|
+
if (index < start)
|
|
65
|
+
return '';
|
|
66
|
+
const parts = [messageText(messages[index])];
|
|
67
|
+
for (let cursor = index - 1; cursor >= start; cursor -= 1) {
|
|
68
|
+
const message = messages[cursor];
|
|
69
|
+
if (message.role === 'system' && message._ephemeral)
|
|
70
|
+
continue;
|
|
71
|
+
if (message.role !== 'assistant' || message.tool_calls?.length || message.presentation !== 'progress')
|
|
72
|
+
break;
|
|
73
|
+
parts.unshift(messageText(message));
|
|
74
|
+
}
|
|
75
|
+
return parts.join('');
|
|
76
|
+
}
|
|
51
77
|
const PURE_CONTINUATION = /^(?:继续|接着(?:来|做)?|好的?|行|嗯+|ok(?:ay)?|go on|continue|next|下一步|开始吧?|谢谢|thanks?)[。.!!~\s]*$/i;
|
|
52
78
|
/** Both current events and legacy previews use the same minimal evidence fields. */
|
|
53
79
|
function evidenceArguments(raw, toolName) {
|
|
@@ -84,18 +110,56 @@ function evidenceArguments(raw, toolName) {
|
|
|
84
110
|
* - 斜杠命令:是给宿主的指令,不是陈述;
|
|
85
111
|
* - 纯续接词(「继续」「好的」):提示词明确要求不展开成事实。明确确认(「是的」
|
|
86
112
|
* 「按这个」)不在此列——它们命中下面的旧信号,是确认前文方案的唯一入口;
|
|
87
|
-
* -
|
|
113
|
+
* - 长输入且无明确信号:粘进来的邮件/文档里的第一人称句子能通过逐字引用闸门,
|
|
88
114
|
* 「引述他人不算」只靠提示词挡。这条是为准确性,不是为省调用。
|
|
115
|
+
* 曾经整条丢弃,于是 2300 字的消息末尾那句「以后回答简洁点」永远进不了队列。
|
|
116
|
+
* 现在只交出**用户自己写的部分**:去掉围栏代码块和 `>` 引用行(那是贴进来的资料,
|
|
117
|
+
* 不是陈述);仍超长就取末尾——长消息里的诉求几乎总在最后。不新增任何模型判断,
|
|
118
|
+
* 逐字证据闸门不变,且交出去的文本就是证据来源,引用只能出自这一段。
|
|
89
119
|
*/
|
|
90
|
-
|
|
120
|
+
const FACT_TEXT_MAX_CHARS = 2_000;
|
|
121
|
+
export function factTurnText(userInput, explicitSignal) {
|
|
91
122
|
const value = userInput.trim();
|
|
92
123
|
if (!value || value.startsWith('/'))
|
|
93
|
-
return
|
|
124
|
+
return undefined;
|
|
94
125
|
if (explicitSignal)
|
|
95
|
-
return
|
|
126
|
+
return userInput;
|
|
96
127
|
if (PURE_CONTINUATION.test(value))
|
|
97
|
-
return
|
|
98
|
-
|
|
128
|
+
return undefined;
|
|
129
|
+
if (value.length <= FACT_TEXT_MAX_CHARS)
|
|
130
|
+
return userInput;
|
|
131
|
+
const written = userWrittenLines(value).join('\n').trim();
|
|
132
|
+
if (!written)
|
|
133
|
+
return undefined;
|
|
134
|
+
if (written.length <= FACT_TEXT_MAX_CHARS)
|
|
135
|
+
return written;
|
|
136
|
+
const tail = written.slice(-FACT_TEXT_MAX_CHARS);
|
|
137
|
+
// Start at a line boundary so the first sentence is not cut mid-way.
|
|
138
|
+
const boundary = tail.indexOf('\n');
|
|
139
|
+
return boundary >= 0 && boundary < tail.length - 200 ? tail.slice(boundary + 1) : tail;
|
|
140
|
+
}
|
|
141
|
+
/** Lines outside fenced blocks and `>` quotes: what the user actually wrote. */
|
|
142
|
+
function userWrittenLines(text) {
|
|
143
|
+
const kept = [];
|
|
144
|
+
let marker;
|
|
145
|
+
for (const raw of text.split('\n')) {
|
|
146
|
+
const line = raw.replace(/\r$/, '');
|
|
147
|
+
if (marker) {
|
|
148
|
+
const close = FENCE_CLOSE.exec(line);
|
|
149
|
+
if (close && close[1][0] === marker[0] && close[1].length >= marker.length)
|
|
150
|
+
marker = undefined;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const open = FENCE_OPEN.exec(line);
|
|
154
|
+
if (open) {
|
|
155
|
+
marker = open[1];
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (/^\s*>/.test(line))
|
|
159
|
+
continue;
|
|
160
|
+
kept.push(line);
|
|
161
|
+
}
|
|
162
|
+
return kept;
|
|
99
163
|
}
|
|
100
164
|
/**
|
|
101
165
|
* 会话原文的保留清理:节流后台执行,绝不阻塞本回合。
|
|
@@ -278,8 +342,7 @@ export async function startTurnJournal(input) {
|
|
|
278
342
|
return;
|
|
279
343
|
finished = true;
|
|
280
344
|
structuredInputs.clear();
|
|
281
|
-
const
|
|
282
|
-
const content = assistant ? messageText(assistant) : '';
|
|
345
|
+
const content = turnAnswerText(history, startIndex);
|
|
283
346
|
if (content)
|
|
284
347
|
await episodicMemoryService.append({
|
|
285
348
|
...target,
|
|
@@ -314,11 +377,13 @@ export async function startTurnJournal(input) {
|
|
|
314
377
|
// "记住我喜欢简洁回答" answered with "好的,已记住" but no tool call landed
|
|
315
378
|
// as claims_unverified and the fact was silently dropped — the loop broke
|
|
316
379
|
// in exactly the case memory exists for.
|
|
317
|
-
|
|
380
|
+
const factText = settings.factLearning && !userInterrupted
|
|
381
|
+
? factTurnText(input.userInput, userSignalsChange) : undefined;
|
|
382
|
+
if (factText) {
|
|
318
383
|
// Separate stores, separate failures: a corrupt fact queue must not
|
|
319
384
|
// also cost this turn its experience source below.
|
|
320
385
|
const queued = await enqueueFactTurn(target.workspaceId, {
|
|
321
|
-
id: turnId, sessionId: target.sessionId, text:
|
|
386
|
+
id: turnId, sessionId: target.sessionId, text: factText, createdAt,
|
|
322
387
|
handledQuotes: [...handledQuotes],
|
|
323
388
|
handledTopics: [...handledTopics.values()],
|
|
324
389
|
confirmationContext: priorAssistant,
|
|
@@ -37,6 +37,7 @@ import type { Message } from '../types.js';
|
|
|
37
37
|
import { enqueueFactTurn, factLearningStatus, scheduleFactLearning } from './factLearning.js';
|
|
38
38
|
import { captureDependencies } from './dependencies.js';
|
|
39
39
|
import { getWorkingDir } from '../bootstrap/workingDir.js';
|
|
40
|
+
import { FENCE_CLOSE, FENCE_OPEN } from '../prompts/visualPresentation.js';
|
|
40
41
|
import { isReadOnlyShellCommand } from '../tools/bash/semantics.js';
|
|
41
42
|
|
|
42
43
|
export interface TurnJournalInput {
|
|
@@ -79,6 +80,28 @@ function lastAssistantMessage(
|
|
|
79
80
|
return undefined;
|
|
80
81
|
}
|
|
81
82
|
|
|
83
|
+
/**
|
|
84
|
+
* The turn's answer as the user read it. When a live answer paused before a
|
|
85
|
+
* text-drawn diagram, the part already shown was committed as its own
|
|
86
|
+
* text-only progress message and the model continued in the final one; both
|
|
87
|
+
* halves are the answer. Only contiguous text-only assistant messages right
|
|
88
|
+
* before the last one join it (ephemeral notices in between are skipped); tool
|
|
89
|
+
* rounds and user messages end the run.
|
|
90
|
+
*/
|
|
91
|
+
function turnAnswerText(messages: readonly Message[], start: number): string {
|
|
92
|
+
let index = messages.length - 1;
|
|
93
|
+
while (index >= start && messages[index]?.role !== 'assistant') index -= 1;
|
|
94
|
+
if (index < start) return '';
|
|
95
|
+
const parts = [messageText(messages[index]!)];
|
|
96
|
+
for (let cursor = index - 1; cursor >= start; cursor -= 1) {
|
|
97
|
+
const message = messages[cursor]!;
|
|
98
|
+
if (message.role === 'system' && message._ephemeral) continue;
|
|
99
|
+
if (message.role !== 'assistant' || message.tool_calls?.length || message.presentation !== 'progress') break;
|
|
100
|
+
parts.unshift(messageText(message));
|
|
101
|
+
}
|
|
102
|
+
return parts.join('');
|
|
103
|
+
}
|
|
104
|
+
|
|
82
105
|
const PURE_CONTINUATION =
|
|
83
106
|
/^(?:继续|接着(?:来|做)?|好的?|行|嗯+|ok(?:ay)?|go on|continue|next|下一步|开始吧?|谢谢|thanks?)[。.!!~\s]*$/i;
|
|
84
107
|
|
|
@@ -114,15 +137,47 @@ function evidenceArguments(raw: unknown, toolName: string): Record<string, unkno
|
|
|
114
137
|
* - 斜杠命令:是给宿主的指令,不是陈述;
|
|
115
138
|
* - 纯续接词(「继续」「好的」):提示词明确要求不展开成事实。明确确认(「是的」
|
|
116
139
|
* 「按这个」)不在此列——它们命中下面的旧信号,是确认前文方案的唯一入口;
|
|
117
|
-
* -
|
|
140
|
+
* - 长输入且无明确信号:粘进来的邮件/文档里的第一人称句子能通过逐字引用闸门,
|
|
118
141
|
* 「引述他人不算」只靠提示词挡。这条是为准确性,不是为省调用。
|
|
142
|
+
* 曾经整条丢弃,于是 2300 字的消息末尾那句「以后回答简洁点」永远进不了队列。
|
|
143
|
+
* 现在只交出**用户自己写的部分**:去掉围栏代码块和 `>` 引用行(那是贴进来的资料,
|
|
144
|
+
* 不是陈述);仍超长就取末尾——长消息里的诉求几乎总在最后。不新增任何模型判断,
|
|
145
|
+
* 逐字证据闸门不变,且交出去的文本就是证据来源,引用只能出自这一段。
|
|
119
146
|
*/
|
|
120
|
-
|
|
147
|
+
const FACT_TEXT_MAX_CHARS = 2_000;
|
|
148
|
+
|
|
149
|
+
export function factTurnText(userInput: string, explicitSignal: boolean): string | undefined {
|
|
121
150
|
const value = userInput.trim();
|
|
122
|
-
if (!value || value.startsWith('/')) return
|
|
123
|
-
if (explicitSignal) return
|
|
124
|
-
if (PURE_CONTINUATION.test(value)) return
|
|
125
|
-
|
|
151
|
+
if (!value || value.startsWith('/')) return undefined;
|
|
152
|
+
if (explicitSignal) return userInput;
|
|
153
|
+
if (PURE_CONTINUATION.test(value)) return undefined;
|
|
154
|
+
if (value.length <= FACT_TEXT_MAX_CHARS) return userInput;
|
|
155
|
+
const written = userWrittenLines(value).join('\n').trim();
|
|
156
|
+
if (!written) return undefined;
|
|
157
|
+
if (written.length <= FACT_TEXT_MAX_CHARS) return written;
|
|
158
|
+
const tail = written.slice(-FACT_TEXT_MAX_CHARS);
|
|
159
|
+
// Start at a line boundary so the first sentence is not cut mid-way.
|
|
160
|
+
const boundary = tail.indexOf('\n');
|
|
161
|
+
return boundary >= 0 && boundary < tail.length - 200 ? tail.slice(boundary + 1) : tail;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Lines outside fenced blocks and `>` quotes: what the user actually wrote. */
|
|
165
|
+
function userWrittenLines(text: string): string[] {
|
|
166
|
+
const kept: string[] = [];
|
|
167
|
+
let marker: string | undefined;
|
|
168
|
+
for (const raw of text.split('\n')) {
|
|
169
|
+
const line = raw.replace(/\r$/, '');
|
|
170
|
+
if (marker) {
|
|
171
|
+
const close = FENCE_CLOSE.exec(line);
|
|
172
|
+
if (close && close[1]![0] === marker[0] && close[1]!.length >= marker.length) marker = undefined;
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
const open = FENCE_OPEN.exec(line);
|
|
176
|
+
if (open) { marker = open[1]!; continue; }
|
|
177
|
+
if (/^\s*>/.test(line)) continue;
|
|
178
|
+
kept.push(line);
|
|
179
|
+
}
|
|
180
|
+
return kept;
|
|
126
181
|
}
|
|
127
182
|
|
|
128
183
|
/**
|
|
@@ -284,8 +339,7 @@ export async function startTurnJournal(
|
|
|
284
339
|
if (finished) return;
|
|
285
340
|
finished = true;
|
|
286
341
|
structuredInputs.clear();
|
|
287
|
-
const
|
|
288
|
-
const content = assistant ? messageText(assistant) : '';
|
|
342
|
+
const content = turnAnswerText(history, startIndex);
|
|
289
343
|
if (content) await episodicMemoryService.append({
|
|
290
344
|
...target,
|
|
291
345
|
role: 'assistant',
|
|
@@ -315,11 +369,13 @@ export async function startTurnJournal(
|
|
|
315
369
|
// "记住我喜欢简洁回答" answered with "好的,已记住" but no tool call landed
|
|
316
370
|
// as claims_unverified and the fact was silently dropped — the loop broke
|
|
317
371
|
// in exactly the case memory exists for.
|
|
318
|
-
|
|
372
|
+
const factText = settings.factLearning && !userInterrupted
|
|
373
|
+
? factTurnText(input.userInput, userSignalsChange) : undefined;
|
|
374
|
+
if (factText) {
|
|
319
375
|
// Separate stores, separate failures: a corrupt fact queue must not
|
|
320
376
|
// also cost this turn its experience source below.
|
|
321
377
|
const queued = await enqueueFactTurn(target.workspaceId, {
|
|
322
|
-
id: turnId, sessionId: target.sessionId, text:
|
|
378
|
+
id: turnId, sessionId: target.sessionId, text: factText, createdAt,
|
|
323
379
|
handledQuotes: [...handledQuotes],
|
|
324
380
|
handledTopics: [...handledTopics.values()],
|
|
325
381
|
confirmationContext: priorAssistant,
|
package/src/memory/types.ts
CHANGED
|
@@ -172,7 +172,7 @@ export interface MemoryContextBundle {
|
|
|
172
172
|
includedIds: string[];
|
|
173
173
|
estimatedTokens: number;
|
|
174
174
|
sections: Array<{
|
|
175
|
-
id: 'persona' | 'preferences' | 'relevant' | 'projectKnowledge' | 'experiences' | 'health';
|
|
175
|
+
id: 'persona' | 'preferences' | 'profile' | 'relevant' | 'projectKnowledge' | 'experiences' | 'index' | 'health';
|
|
176
176
|
content: string;
|
|
177
177
|
includedIds: string[];
|
|
178
178
|
estimatedTokens: number;
|
|
@@ -189,6 +189,12 @@ export interface ConversationEpisode {
|
|
|
189
189
|
content: string;
|
|
190
190
|
createdAt: number;
|
|
191
191
|
contentHash: string;
|
|
192
|
+
/**
|
|
193
|
+
* Set when one message was stored as several consecutive episodes sharing a
|
|
194
|
+
* `messageId` (each ≤ the per-episode cap). `omittedChars` marks a message so
|
|
195
|
+
* long that its middle was dropped, so a reader knows the text is not whole.
|
|
196
|
+
*/
|
|
197
|
+
part?: { index: number; total: number; omittedChars?: number };
|
|
192
198
|
}
|
|
193
199
|
|
|
194
200
|
export interface ConversationSearchOptions {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CONFIG_LIMITS } from '../config/numericLimits.js';
|
|
1
2
|
/** One delegation contract shared by the main prompt, tool discovery and runtime. */
|
|
2
3
|
export const RESEARCH_DELEGATION_POLICY = `# 大范围阅读与并行调研
|
|
3
4
|
- 先读项目说明、目录、入口与基线,建立问题地图,不先串行读完整仓库。批量阅读涉及多个独立模块、各需多轮探索时,优先同轮派发多个 Agent(agent_type="explore");数量取决于独立工作量和宿主额度,不按文件数量机械切块。
|
|
@@ -11,5 +12,5 @@ export function delegationRuntimeGuidance(input) {
|
|
|
11
12
|
const remaining = Math.max(0, input.maxCalls - input.callsUsed);
|
|
12
13
|
if (!remaining)
|
|
13
14
|
return '本轮子任务派发次数已用完:收口已有子任务,其余调查由主 Agent 使用当前工具完成,不重复尝试派发。';
|
|
14
|
-
return `本轮可使用 Agent 进行隔离委派,剩余派发次数 ${remaining},配置并发上限 ${Math.max(1, Math.min(
|
|
15
|
+
return `本轮可使用 Agent 进行隔离委派,剩余派发次数 ${remaining},配置并发上限 ${Math.max(1, Math.min(CONFIG_LIMITS.subagentMaxConcurrent.max, input.maxConcurrent))}(实际槽位由调度器协调)。大型批量阅读先建立问题地图,再优先并行派发独立 explore 调查;不要把所有模块留在主上下文串行阅读。少量已知文件仍直接批量读。`;
|
|
15
16
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { CONFIG_LIMITS } from '../config/numericLimits.js';
|
|
2
|
+
|
|
1
3
|
/** One delegation contract shared by the main prompt, tool discovery and runtime. */
|
|
2
4
|
export const RESEARCH_DELEGATION_POLICY = `# 大范围阅读与并行调研
|
|
3
5
|
- 先读项目说明、目录、入口与基线,建立问题地图,不先串行读完整仓库。批量阅读涉及多个独立模块、各需多轮探索时,优先同轮派发多个 Agent(agent_type="explore");数量取决于独立工作量和宿主额度,不按文件数量机械切块。
|
|
@@ -16,5 +18,5 @@ export function delegationRuntimeGuidance(input: {
|
|
|
16
18
|
if (!input.available) return '';
|
|
17
19
|
const remaining = Math.max(0, input.maxCalls - input.callsUsed);
|
|
18
20
|
if (!remaining) return '本轮子任务派发次数已用完:收口已有子任务,其余调查由主 Agent 使用当前工具完成,不重复尝试派发。';
|
|
19
|
-
return `本轮可使用 Agent 进行隔离委派,剩余派发次数 ${remaining},配置并发上限 ${Math.max(1, Math.min(
|
|
21
|
+
return `本轮可使用 Agent 进行隔离委派,剩余派发次数 ${remaining},配置并发上限 ${Math.max(1, Math.min(CONFIG_LIMITS.subagentMaxConcurrent.max, input.maxConcurrent))}(实际槽位由调度器协调)。大型批量阅读先建立问题地图,再优先并行派发独立 explore 调查;不要把所有模块留在主上下文串行阅读。少量已知文件仍直接批量读。`;
|
|
20
22
|
}
|
|
@@ -118,7 +118,7 @@ const ROUTABLE_TOOLS = [
|
|
|
118
118
|
'EnterWorktree', 'ExitWorktree', 'TerminalCapture', 'Snip', 'OptimizeConversation',
|
|
119
119
|
];
|
|
120
120
|
const PARAMETERIZED_TOOLS = [
|
|
121
|
-
...ROUTABLE_TOOLS, ...MEMORY_TOOLS, 'AskUserQuestion', 'Advisor', '
|
|
121
|
+
...ROUTABLE_TOOLS, ...MEMORY_TOOLS, 'AskUserQuestion', 'Advisor', 'Agent',
|
|
122
122
|
'PresentResult', 'GenerateImage', 'GenerateSpeech',
|
|
123
123
|
];
|
|
124
124
|
const CAPABILITY_POLICIES = [
|
|
@@ -199,7 +199,7 @@ const CAPABILITY_POLICIES = [
|
|
|
199
199
|
render: ({ surface, output, toolNames }) => [
|
|
200
200
|
visualPresentation(output ?? surface),
|
|
201
201
|
toolNames.has('Skill') || toolNames.has('Read')
|
|
202
|
-
? '-
|
|
202
|
+
? '- 需要配图时从技能目录加载匹配的图形技能。'
|
|
203
203
|
: '',
|
|
204
204
|
].filter(Boolean).join('\n'),
|
|
205
205
|
},
|
|
@@ -249,14 +249,11 @@ const CAPABILITY_POLICIES = [
|
|
|
249
249
|
{
|
|
250
250
|
id: 'capability.advisor', layer: 'capability', profiles: MAIN_PROFILE, source: 'native',
|
|
251
251
|
requiresTools: ['Advisor'],
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
同阶段不重复问;成果、假设实质变化或新冲突才重开。questions/note 指明疑点与证据 ID,历史由宿主附带。缺材料先补查,不算验证通过。读完意见再操作;暂缓操作未执行,提醒不算咨询。
|
|
258
|
-
不设咨询配额,每次咨询只尝试一次;失败、空答或超时自行降级继续,不转成人工审批,也不宣称获得验证。
|
|
259
|
-
以代码、测试和用户要求核实意见,${toolNames.has('AdvisorDecision') ? '可用 AdvisorDecision 记录 accept/partial/reject;登记失败不阻止执行。' : '直接说明采纳判断。'}顾问不是事实来源,冲突以实测为准。`,
|
|
252
|
+
render: () => `- Advisor(顾问模型)使用规则:独立的第二意见,由主 Agent 核实取舍;不授予权限、不替用户验收。它能看到整段对话(含你的思考和全部工具结果),无需复述;没有工具,不能替你验证。
|
|
253
|
+
咨询时机:实质工作之前(先调查清楚,再在写、改、定方案之前问);认为完成时(实质交付或下结论之前);同一问题两次或以上处理失败后;想改变既定方案或方向前;非常不确定、关键证据冲突无法消解、用户明显不满意时。换工具不清零失败次数,无关失败不合并。
|
|
254
|
+
文字调研、计算、比较和推荐也是交付,自信、没文件不豁免;简短即时问答、寒暄、进度、拼写修正无需咨询;自动咨询不问用户。
|
|
255
|
+
宿主只会在动态上下文里提醒你还没问,不拦截工具也不替你调用;是否咨询由你判断。每次咨询只尝试一次,不设配额;失败、空答或超时即自行继续,不转成人工审批,也不宣称获得验证。note 只写想被挑战的疑点或关键原文;缺材料先补查,不算验证通过。读完意见再操作。
|
|
256
|
+
以代码、测试和用户要求核实意见:一手证据或用户明确要求否定才拒绝;自测通过不能证明意见错误,它可能没测到所指问题。已有证据与意见方向不同又无法判定时,不要默默改道,写明冲突再咨询一次。顾问不是事实来源。`,
|
|
260
257
|
},
|
|
261
258
|
{
|
|
262
259
|
id: 'capability.subagent', layer: 'capability', profiles: MAIN_PROFILE, source: 'native',
|
|
@@ -271,7 +268,7 @@ const CAPABILITY_POLICIES = [
|
|
|
271
268
|
{
|
|
272
269
|
id: 'capability.agent-write-plan', layer: 'capability', profiles: MAIN_PROFILE, source: 'native',
|
|
273
270
|
requiresTools: ['AgentResolve'],
|
|
274
|
-
render: ({ toolNames }) => `- 并行写任务先由主 Agent 铺路:确认需求和验收,检查当前改动,统一共享接口、文件归属、依赖与基线。${toolNames.has('Advisor') ? '复杂/高风险设计按顾问规则咨询 Advisor
|
|
271
|
+
render: ({ toolNames }) => `- 并行写任务先由主 Agent 铺路:确认需求和验收,检查当前改动,统一共享接口、文件归属、依赖与基线。${toolNames.has('Advisor') ? '复杂/高风险设计按顾问规则咨询 Advisor;不可用或仅手动模式时自行核实,不等待顾问。' : '复杂设计由主 Agent 核实证据和取舍,不等待未提供的顾问能力。'}小型独立修改不强制走大流程。先完成前置接口再派发依赖实现,每份任务书携带 context.baseline/acceptance/interfaces/verification、决定及证据来源和 write_scopes。共享目录仅并行不重叠的局部编辑,构建与跨文件脚本由主 Agent 在子任务退出后统一执行;需独立构建用 worktree,但它不含未提交的铺路代码,不能为方便擅自提交用户改动。共享接口、锁文件、迁移与最终集成归主 Agent 单一负责。收到求助先决定接口或范围,必要时停止旧任务重新派发;不要让多个子 Agent 自行协调扩大范围。子任务结果是候选改动:检查差异、集成到目标工作区、处理冲突并验证组合行为后,才 AgentResolve accept/complete,写任务必须填写 writeReview 及主 Agent 的真实检查证据。接管 worktree 任务先检查其分支、未提交改动和后台进程,不把失败现场当主工作区已修改。最终只汇总设计决定、实际集成、验证证据、风险与未完成项,不拼接完整子历史。`,
|
|
275
272
|
},
|
|
276
273
|
{
|
|
277
274
|
id: 'capability.delivery', layer: 'capability', profiles: MAIN_PROFILE, source: '05',
|
|
@@ -308,7 +305,7 @@ const CAPABILITY_POLICIES = [
|
|
|
308
305
|
if (toolNames.has('SearchConversations'))
|
|
309
306
|
rules.push('提及共享历史时用 SearchConversations 按内容关键词定位,片段不足才 read 和展开邻接消息;证据充分即答。无命中可换关键词,不能声称从未讨论;原文缺失须说明。');
|
|
310
307
|
if (toolNames.has('MemoryMaintenance')) {
|
|
311
|
-
rules.push('项目经验须核对环境、失效条件和依赖;召回不等于采用,不为记忆重复任务。具体维护动作以 MemoryMaintenance 工具说明为准。', '
|
|
308
|
+
rules.push('项目经验须核对环境、失效条件和依赖;召回不等于采用,不为记忆重复任务。具体维护动作以 MemoryMaintenance 工具说明为准。', '事实候选经用户确认后 resolve 结清;UpdateMemory 改写记录即失效。后台失败不等于已保存,遗忘条目不等于删除原文。');
|
|
312
309
|
}
|
|
313
310
|
return rules.map((rule) => `- ${rule}`).join('\n');
|
|
314
311
|
},
|
|
@@ -170,7 +170,7 @@ const ROUTABLE_TOOLS = [
|
|
|
170
170
|
];
|
|
171
171
|
|
|
172
172
|
const PARAMETERIZED_TOOLS = [
|
|
173
|
-
...ROUTABLE_TOOLS, ...MEMORY_TOOLS, 'AskUserQuestion', 'Advisor', '
|
|
173
|
+
...ROUTABLE_TOOLS, ...MEMORY_TOOLS, 'AskUserQuestion', 'Advisor', 'Agent',
|
|
174
174
|
'PresentResult', 'GenerateImage', 'GenerateSpeech',
|
|
175
175
|
];
|
|
176
176
|
|
|
@@ -248,7 +248,7 @@ const CAPABILITY_POLICIES: PromptPolicySpec[] = [
|
|
|
248
248
|
render: ({ surface, output, toolNames }) => [
|
|
249
249
|
visualPresentation(output ?? surface),
|
|
250
250
|
toolNames.has('Skill') || toolNames.has('Read')
|
|
251
|
-
? '-
|
|
251
|
+
? '- 需要配图时从技能目录加载匹配的图形技能。'
|
|
252
252
|
: '',
|
|
253
253
|
].filter(Boolean).join('\n'),
|
|
254
254
|
},
|
|
@@ -298,14 +298,11 @@ const CAPABILITY_POLICIES: PromptPolicySpec[] = [
|
|
|
298
298
|
{
|
|
299
299
|
id: 'capability.advisor', layer: 'capability', profiles: MAIN_PROFILE, source: 'native',
|
|
300
300
|
requiresTools: ['Advisor'],
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
同阶段不重复问;成果、假设实质变化或新冲突才重开。questions/note 指明疑点与证据 ID,历史由宿主附带。缺材料先补查,不算验证通过。读完意见再操作;暂缓操作未执行,提醒不算咨询。
|
|
307
|
-
不设咨询配额,每次咨询只尝试一次;失败、空答或超时自行降级继续,不转成人工审批,也不宣称获得验证。
|
|
308
|
-
以代码、测试和用户要求核实意见,${toolNames.has('AdvisorDecision') ? '可用 AdvisorDecision 记录 accept/partial/reject;登记失败不阻止执行。' : '直接说明采纳判断。'}顾问不是事实来源,冲突以实测为准。`,
|
|
301
|
+
render: () => `- Advisor(顾问模型)使用规则:独立的第二意见,由主 Agent 核实取舍;不授予权限、不替用户验收。它能看到整段对话(含你的思考和全部工具结果),无需复述;没有工具,不能替你验证。
|
|
302
|
+
咨询时机:实质工作之前(先调查清楚,再在写、改、定方案之前问);认为完成时(实质交付或下结论之前);同一问题两次或以上处理失败后;想改变既定方案或方向前;非常不确定、关键证据冲突无法消解、用户明显不满意时。换工具不清零失败次数,无关失败不合并。
|
|
303
|
+
文字调研、计算、比较和推荐也是交付,自信、没文件不豁免;简短即时问答、寒暄、进度、拼写修正无需咨询;自动咨询不问用户。
|
|
304
|
+
宿主只会在动态上下文里提醒你还没问,不拦截工具也不替你调用;是否咨询由你判断。每次咨询只尝试一次,不设配额;失败、空答或超时即自行继续,不转成人工审批,也不宣称获得验证。note 只写想被挑战的疑点或关键原文;缺材料先补查,不算验证通过。读完意见再操作。
|
|
305
|
+
以代码、测试和用户要求核实意见:一手证据或用户明确要求否定才拒绝;自测通过不能证明意见错误,它可能没测到所指问题。已有证据与意见方向不同又无法判定时,不要默默改道,写明冲突再咨询一次。顾问不是事实来源。`,
|
|
309
306
|
},
|
|
310
307
|
{
|
|
311
308
|
id: 'capability.subagent', layer: 'capability', profiles: MAIN_PROFILE, source: 'native',
|
|
@@ -320,7 +317,7 @@ const CAPABILITY_POLICIES: PromptPolicySpec[] = [
|
|
|
320
317
|
{
|
|
321
318
|
id: 'capability.agent-write-plan', layer: 'capability', profiles: MAIN_PROFILE, source: 'native',
|
|
322
319
|
requiresTools: ['AgentResolve'],
|
|
323
|
-
render: ({ toolNames }) => `- 并行写任务先由主 Agent 铺路:确认需求和验收,检查当前改动,统一共享接口、文件归属、依赖与基线。${toolNames.has('Advisor') ? '复杂/高风险设计按顾问规则咨询 Advisor
|
|
320
|
+
render: ({ toolNames }) => `- 并行写任务先由主 Agent 铺路:确认需求和验收,检查当前改动,统一共享接口、文件归属、依赖与基线。${toolNames.has('Advisor') ? '复杂/高风险设计按顾问规则咨询 Advisor;不可用或仅手动模式时自行核实,不等待顾问。' : '复杂设计由主 Agent 核实证据和取舍,不等待未提供的顾问能力。'}小型独立修改不强制走大流程。先完成前置接口再派发依赖实现,每份任务书携带 context.baseline/acceptance/interfaces/verification、决定及证据来源和 write_scopes。共享目录仅并行不重叠的局部编辑,构建与跨文件脚本由主 Agent 在子任务退出后统一执行;需独立构建用 worktree,但它不含未提交的铺路代码,不能为方便擅自提交用户改动。共享接口、锁文件、迁移与最终集成归主 Agent 单一负责。收到求助先决定接口或范围,必要时停止旧任务重新派发;不要让多个子 Agent 自行协调扩大范围。子任务结果是候选改动:检查差异、集成到目标工作区、处理冲突并验证组合行为后,才 AgentResolve accept/complete,写任务必须填写 writeReview 及主 Agent 的真实检查证据。接管 worktree 任务先检查其分支、未提交改动和后台进程,不把失败现场当主工作区已修改。最终只汇总设计决定、实际集成、验证证据、风险与未完成项,不拼接完整子历史。`,
|
|
324
321
|
},
|
|
325
322
|
{
|
|
326
323
|
id: 'capability.delivery', layer: 'capability', profiles: MAIN_PROFILE, source: '05',
|
|
@@ -351,7 +348,7 @@ const CAPABILITY_POLICIES: PromptPolicySpec[] = [
|
|
|
351
348
|
if (toolNames.has('MemoryMaintenance')) {
|
|
352
349
|
rules.push(
|
|
353
350
|
'项目经验须核对环境、失效条件和依赖;召回不等于采用,不为记忆重复任务。具体维护动作以 MemoryMaintenance 工具说明为准。',
|
|
354
|
-
'
|
|
351
|
+
'事实候选经用户确认后 resolve 结清;UpdateMemory 改写记录即失效。后台失败不等于已保存,遗忘条目不等于删除原文。',
|
|
355
352
|
);
|
|
356
353
|
}
|
|
357
354
|
return rules.map((rule) => `- ${rule}`).join('\n');
|
|
@@ -142,11 +142,7 @@ async function buildAdvisorStatusLine() {
|
|
|
142
142
|
if (!advisor)
|
|
143
143
|
return 'Advisor 状态:未配置。';
|
|
144
144
|
const mode = advisor.mode === 'auto' ? '自动' : '仅手动';
|
|
145
|
-
const vision = advisor.supportsVision
|
|
146
|
-
? '允许图片'
|
|
147
|
-
: advisor.supportsVision
|
|
148
|
-
? '支持图片但敏感共享关闭'
|
|
149
|
-
: '不支持图片';
|
|
145
|
+
const vision = advisor.supportsVision ? '支持图片' : '不支持图片';
|
|
150
146
|
return `Advisor 状态:${advisor.enabled ? '已启用' : '已关闭'},${advisor.provider.name}/${advisor.provider.model},${mode},${vision},超时 ${Math.round(advisor.timeoutMs / 1000)} 秒。`;
|
|
151
147
|
}
|
|
152
148
|
catch {
|
|
@@ -219,6 +215,7 @@ async function buildMemoryTransient(runtime) {
|
|
|
219
215
|
return memoryService.buildContextBundle(runtime.currentUserInput ?? '', runtime.workspaceId, {
|
|
220
216
|
includePersona: runtime.inheritPersona !== false,
|
|
221
217
|
budgetTokens: memoryBudgetTokens(runtime),
|
|
218
|
+
previousInput: runtime.previousUserInput,
|
|
222
219
|
});
|
|
223
220
|
}
|
|
224
221
|
catch {
|
|
@@ -385,9 +382,13 @@ export async function buildTransientBundle(runtime, profile, tools, options = {}
|
|
|
385
382
|
const priorities = {
|
|
386
383
|
persona: 82,
|
|
387
384
|
preferences: 78,
|
|
385
|
+
profile: 76,
|
|
388
386
|
relevant: 70,
|
|
389
387
|
projectKnowledge: 68,
|
|
390
388
|
experiences: 65,
|
|
389
|
+
// The subject index only helps the model choose what to Recall; below
|
|
390
|
+
// skills (45) so a tight budget drops it before anything the model acts on.
|
|
391
|
+
index: 40,
|
|
391
392
|
health: 10,
|
|
392
393
|
};
|
|
393
394
|
for (const section of memory.sections) {
|