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
|
@@ -113,6 +113,8 @@ export interface PromptRuntimeContext {
|
|
|
113
113
|
/** @deprecated Use memoryAccess. Kept for source compatibility. */
|
|
114
114
|
ownerLocalSession: boolean;
|
|
115
115
|
currentUserInput?: string;
|
|
116
|
+
/** The user request before this one; lets a bare follow-up recall by topic (see memory/recallQuery.ts). */
|
|
117
|
+
previousUserInput?: string;
|
|
116
118
|
/** Host policy, independent of the tools exposed to this invocation. */
|
|
117
119
|
inheritPersona?: boolean;
|
|
118
120
|
transientBudgetTokens?: number;
|
|
@@ -248,12 +250,7 @@ async function buildAdvisorStatusLine(): Promise<string> {
|
|
|
248
250
|
const advisor = await configService.advisor();
|
|
249
251
|
if (!advisor) return 'Advisor 状态:未配置。';
|
|
250
252
|
const mode = advisor.mode === 'auto' ? '自动' : '仅手动';
|
|
251
|
-
const vision =
|
|
252
|
-
advisor.supportsVision && advisor.shareSensitive
|
|
253
|
-
? '允许图片'
|
|
254
|
-
: advisor.supportsVision
|
|
255
|
-
? '支持图片但敏感共享关闭'
|
|
256
|
-
: '不支持图片';
|
|
253
|
+
const vision = advisor.supportsVision ? '支持图片' : '不支持图片';
|
|
257
254
|
return `Advisor 状态:${advisor.enabled ? '已启用' : '已关闭'},${advisor.provider.name}/${advisor.provider.model},${mode},${vision},超时 ${Math.round(advisor.timeoutMs / 1000)} 秒。`;
|
|
258
255
|
} catch {
|
|
259
256
|
return 'Advisor 状态:暂时无法读取。';
|
|
@@ -357,6 +354,7 @@ async function buildMemoryTransient(
|
|
|
357
354
|
{
|
|
358
355
|
includePersona: runtime.inheritPersona !== false,
|
|
359
356
|
budgetTokens: memoryBudgetTokens(runtime),
|
|
357
|
+
previousInput: runtime.previousUserInput,
|
|
360
358
|
},
|
|
361
359
|
);
|
|
362
360
|
} catch {
|
|
@@ -586,9 +584,13 @@ export async function buildTransientBundle(
|
|
|
586
584
|
const priorities = {
|
|
587
585
|
persona: 82,
|
|
588
586
|
preferences: 78,
|
|
587
|
+
profile: 76,
|
|
589
588
|
relevant: 70,
|
|
590
589
|
projectKnowledge: 68,
|
|
591
590
|
experiences: 65,
|
|
591
|
+
// The subject index only helps the model choose what to Recall; below
|
|
592
|
+
// skills (45) so a tight budget drops it before anything the model acts on.
|
|
593
|
+
index: 40,
|
|
592
594
|
health: 10,
|
|
593
595
|
} as const;
|
|
594
596
|
for (const section of memory.sections) {
|
|
@@ -1,28 +1,57 @@
|
|
|
1
1
|
import { resolveOutputContract } from '../runtime/outputContract.js';
|
|
2
|
-
/**
|
|
2
|
+
/** Presentation guidance derived from the host's declared output contract. */
|
|
3
3
|
export function visualPresentation(target) {
|
|
4
4
|
const output = typeof target === 'string' ? resolveOutputContract(target) : target;
|
|
5
5
|
const common = '按需配图,优先 Mermaid/SVG;用户指定 ASCII、代码/目录树除外。';
|
|
6
6
|
if (output.format === 'rich-markdown')
|
|
7
|
-
return `${common}\nDesktop/Web 聊天支持 ${output.diagrams.length ? output.diagrams.join(' 和 ') + ' 围栏' : 'Markdown,未声明图形渲染'},不用裸 HTML
|
|
7
|
+
return `${common}\nDesktop/Web 聊天支持 ${output.diagrams.length ? output.diagrams.join(' 和 ') + ' 围栏' : 'Markdown,未声明图形渲染'},不用裸 HTML。仅用已声明的图形格式替代字符框图。${output.images ? '支持图片。' : ''}${output.attachments ? '附件须真实提交。' : ''}无法可靠画图则用文字/表格。`;
|
|
8
8
|
if (output.format === 'terminal')
|
|
9
9
|
return `${common}\nCLI/TUI 直接回复用文字/表格/紧凑 ASCII,生成文件仍优先 Mermaid/SVG。`;
|
|
10
10
|
if (output.format === 'structured')
|
|
11
11
|
return '仅按调用方结构化协议输出,不插入 Markdown 围栏或图形源码。';
|
|
12
12
|
return `${common}\n跟随最终阅读载体,不等同于终端:Web 用 mermaid/svg 围栏;保持协议契约,未知不假定可渲染。`;
|
|
13
13
|
}
|
|
14
|
+
/** The user explicitly wants text drawings (ASCII, "原样", no Mermaid/SVG). */
|
|
15
|
+
export function requestKeepsTextDiagrams(request) {
|
|
16
|
+
const rejectsAscii = /(?:不要|不用|避免|别用|without|not|instead of)[^\n。!?]{0,24}(?:ascii|字符图|纯文本图)/i.test(request);
|
|
17
|
+
return !rejectsAscii && /ascii|字符图|纯文本图|原样|不要.*(?:mermaid|svg)/i.test(request);
|
|
18
|
+
}
|
|
19
|
+
/** Only these fence info words can hold a prose diagram; a language tag means code. */
|
|
20
|
+
export const DIAGRAM_FENCE_INFO = /^(?:text|plaintext)?$/i;
|
|
21
|
+
/** Fence lines as Markdown renders them: up to 3 spaces of indentation (list
|
|
22
|
+
* items), and an info string whose first word is the language — anything after
|
|
23
|
+
* it (`title=…`) is attributes. Group 1 = marker, group 2 = first info word. */
|
|
24
|
+
export const FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})[ \t]*([^\s`]*)[^`\n]*$/;
|
|
25
|
+
export const FENCE_CLOSE = /^ {0,3}(`{3,}|~{3,})[ \t]*$/;
|
|
26
|
+
/** A fenced body drawn as boxes: at least two border lines and a walled row.
|
|
27
|
+
* Ordinary code, directory trees and arrows are not diagrams. */
|
|
28
|
+
export function isBoxDiagram(body) {
|
|
29
|
+
const borders = body.split('\n').filter(line => /[┌┐└┘┏┓┗┛+][─━\-]{3,}[┌┐└┘┏┓┗┛+]/.test(line));
|
|
30
|
+
return borders.length >= 2 && /[│┃|].*\S.*[│┃|]/.test(body);
|
|
31
|
+
}
|
|
14
32
|
/** Conservative signal on PRIVATE candidates only, never a frontend converter.
|
|
15
|
-
*
|
|
33
|
+
* Live answers use loop/liveDiagramGate.ts, built on the same two predicates.
|
|
16
34
|
*/
|
|
17
35
|
export function needsDiagramPreparation(candidate, request) {
|
|
18
|
-
|
|
19
|
-
if (!rejectsAscii && /ascii|字符图|纯文本图|原样|不要.*(?:mermaid|svg)/i.test(request))
|
|
36
|
+
if (requestKeepsTextDiagrams(request))
|
|
20
37
|
return false;
|
|
21
|
-
|
|
22
|
-
for (const
|
|
23
|
-
const
|
|
24
|
-
if (
|
|
25
|
-
|
|
38
|
+
let fence;
|
|
39
|
+
for (const raw of candidate.split('\n')) {
|
|
40
|
+
const line = raw.replace(/\r$/, '');
|
|
41
|
+
if (!fence) {
|
|
42
|
+
const open = FENCE_OPEN.exec(line);
|
|
43
|
+
if (open)
|
|
44
|
+
fence = { marker: open[1], candidate: DIAGRAM_FENCE_INFO.test(open[2]), body: [] };
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const close = FENCE_CLOSE.exec(line);
|
|
48
|
+
if (close && close[1][0] === fence.marker[0] && close[1].length >= fence.marker.length) {
|
|
49
|
+
if (fence.candidate && isBoxDiagram(fence.body.join('\n')))
|
|
50
|
+
return true;
|
|
51
|
+
fence = undefined;
|
|
52
|
+
}
|
|
53
|
+
else
|
|
54
|
+
fence.body.push(line);
|
|
26
55
|
}
|
|
27
56
|
return false;
|
|
28
57
|
}
|
|
@@ -1,26 +1,55 @@
|
|
|
1
1
|
import type { AgentSurface } from '../runtime/agentRuntimeContext.js';
|
|
2
2
|
import { resolveOutputContract, type OutputContract } from '../runtime/outputContract.js';
|
|
3
3
|
|
|
4
|
-
/**
|
|
4
|
+
/** Presentation guidance derived from the host's declared output contract. */
|
|
5
5
|
export function visualPresentation(target: AgentSurface | OutputContract): string {
|
|
6
6
|
const output = typeof target === 'string' ? resolveOutputContract(target) : target;
|
|
7
7
|
const common = '按需配图,优先 Mermaid/SVG;用户指定 ASCII、代码/目录树除外。';
|
|
8
|
-
if (output.format === 'rich-markdown') return `${common}\nDesktop/Web 聊天支持 ${output.diagrams.length ? output.diagrams.join(' 和 ') + ' 围栏' : 'Markdown,未声明图形渲染'},不用裸 HTML
|
|
8
|
+
if (output.format === 'rich-markdown') return `${common}\nDesktop/Web 聊天支持 ${output.diagrams.length ? output.diagrams.join(' 和 ') + ' 围栏' : 'Markdown,未声明图形渲染'},不用裸 HTML。仅用已声明的图形格式替代字符框图。${output.images ? '支持图片。' : ''}${output.attachments ? '附件须真实提交。' : ''}无法可靠画图则用文字/表格。`;
|
|
9
9
|
if (output.format === 'terminal') return `${common}\nCLI/TUI 直接回复用文字/表格/紧凑 ASCII,生成文件仍优先 Mermaid/SVG。`;
|
|
10
10
|
if (output.format === 'structured') return '仅按调用方结构化协议输出,不插入 Markdown 围栏或图形源码。';
|
|
11
11
|
return `${common}\n跟随最终阅读载体,不等同于终端:Web 用 mermaid/svg 围栏;保持协议契约,未知不假定可渲染。`;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
/** The user explicitly wants text drawings (ASCII, "原样", no Mermaid/SVG). */
|
|
15
|
+
export function requestKeepsTextDiagrams(request: string): boolean {
|
|
16
|
+
const rejectsAscii = /(?:不要|不用|避免|别用|without|not|instead of)[^\n。!?]{0,24}(?:ascii|字符图|纯文本图)/i.test(request);
|
|
17
|
+
return !rejectsAscii && /ascii|字符图|纯文本图|原样|不要.*(?:mermaid|svg)/i.test(request);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Only these fence info words can hold a prose diagram; a language tag means code. */
|
|
21
|
+
export const DIAGRAM_FENCE_INFO = /^(?:text|plaintext)?$/i;
|
|
22
|
+
/** Fence lines as Markdown renders them: up to 3 spaces of indentation (list
|
|
23
|
+
* items), and an info string whose first word is the language — anything after
|
|
24
|
+
* it (`title=…`) is attributes. Group 1 = marker, group 2 = first info word. */
|
|
25
|
+
export const FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})[ \t]*([^\s`]*)[^`\n]*$/;
|
|
26
|
+
export const FENCE_CLOSE = /^ {0,3}(`{3,}|~{3,})[ \t]*$/;
|
|
27
|
+
|
|
28
|
+
/** A fenced body drawn as boxes: at least two border lines and a walled row.
|
|
29
|
+
* Ordinary code, directory trees and arrows are not diagrams. */
|
|
30
|
+
export function isBoxDiagram(body: string): boolean {
|
|
31
|
+
const borders = body.split('\n').filter(line => /[┌┐└┘┏┓┗┛+][─━\-]{3,}[┌┐└┘┏┓┗┛+]/.test(line));
|
|
32
|
+
return borders.length >= 2 && /[│┃|].*\S.*[│┃|]/.test(body);
|
|
33
|
+
}
|
|
34
|
+
|
|
14
35
|
/** Conservative signal on PRIVATE candidates only, never a frontend converter.
|
|
15
|
-
*
|
|
36
|
+
* Live answers use loop/liveDiagramGate.ts, built on the same two predicates.
|
|
16
37
|
*/
|
|
17
38
|
export function needsDiagramPreparation(candidate: string, request: string): boolean {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
39
|
+
if (requestKeepsTextDiagrams(request)) return false;
|
|
40
|
+
let fence: { marker: string; candidate: boolean; body: string[] } | undefined;
|
|
41
|
+
for (const raw of candidate.split('\n')) {
|
|
42
|
+
const line = raw.replace(/\r$/, '');
|
|
43
|
+
if (!fence) {
|
|
44
|
+
const open = FENCE_OPEN.exec(line);
|
|
45
|
+
if (open) fence = { marker: open[1]!, candidate: DIAGRAM_FENCE_INFO.test(open[2]!), body: [] };
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const close = FENCE_CLOSE.exec(line);
|
|
49
|
+
if (close && close[1]![0] === fence.marker[0] && close[1]!.length >= fence.marker.length) {
|
|
50
|
+
if (fence.candidate && isBoxDiagram(fence.body.join('\n'))) return true;
|
|
51
|
+
fence = undefined;
|
|
52
|
+
} else fence.body.push(line);
|
|
24
53
|
}
|
|
25
54
|
return false;
|
|
26
55
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export function answerPreparationNotice(reason) {
|
|
2
2
|
switch (reason) {
|
|
3
|
-
case 'advisor_checkpoint': return '交付前复核:正在核查内部候选结论,完成后发布最终答案。';
|
|
4
3
|
case 'formal_delivery': return '整理交付:正在核对委派结果或补齐正式成果提交。';
|
|
5
4
|
case 'interactive_question': return '等待关键决策:接下来通过交互问题确认,请在输入区作答。';
|
|
6
5
|
case 'empty_response': return '本轮未产生可显示的正文,未生成新的最终答复。';
|
|
6
|
+
case 'diagram_preparation': return '整理配图:正在把字符框图改为可渲染的图形。';
|
|
7
7
|
case 'interrupted': return '生成未完成,未发布最终答案。';
|
|
8
8
|
}
|
|
9
9
|
}
|
|
@@ -2,10 +2,10 @@ import type { LoopEvent } from '../types.js';
|
|
|
2
2
|
|
|
3
3
|
export function answerPreparationNotice(reason: Extract<LoopEvent, { type: 'assistant_draft_discarded' }>['reason'] | 'interrupted'): string {
|
|
4
4
|
switch (reason) {
|
|
5
|
-
case 'advisor_checkpoint': return '交付前复核:正在核查内部候选结论,完成后发布最终答案。';
|
|
6
5
|
case 'formal_delivery': return '整理交付:正在核对委派结果或补齐正式成果提交。';
|
|
7
6
|
case 'interactive_question': return '等待关键决策:接下来通过交互问题确认,请在输入区作答。';
|
|
8
7
|
case 'empty_response': return '本轮未产生可显示的正文,未生成新的最终答复。';
|
|
8
|
+
case 'diagram_preparation': return '整理配图:正在把字符框图改为可渲染的图形。';
|
|
9
9
|
case 'interrupted': return '生成未完成,未发布最终答案。';
|
|
10
10
|
}
|
|
11
11
|
}
|
package/src/runtime/fileLock.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
|
-
import { mkdir, stat } from 'node:fs/promises';
|
|
1
|
+
import { mkdir, stat, unlink } from 'node:fs/promises';
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
3
|
import { dirname, resolve } from 'node:path';
|
|
4
4
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
5
5
|
import lockfile from 'proper-lockfile';
|
|
6
|
+
/** Hosts before this protocol wrote a plain `.lock` file without heartbeat and
|
|
7
|
+
* reclaimed each other's after 120s (memory) / 30s (runtime). A file older than
|
|
8
|
+
* that is abandoned by the old protocol's own rule. Refusing it forever turned a
|
|
9
|
+
* crash of the previous version into a permanent memory/runtime outage that only
|
|
10
|
+
* a manual delete could end. A younger one is a live old host: wait, never steal.
|
|
11
|
+
*/
|
|
12
|
+
const LEGACY_LOCK_STALE_MS = 120_000;
|
|
6
13
|
/** Shared heartbeat protocol for local file-backed stores, not a database lock.
|
|
7
|
-
* Never reclaim legacy regular-file locks: old hosts do not speak this protocol.
|
|
8
14
|
* proper-lockfile fails closed on compromised ownership (its default handler).
|
|
9
15
|
*/
|
|
10
16
|
export async function acquireStateFileLock(path, options = {}) {
|
|
@@ -21,7 +27,17 @@ export async function acquireStateFileLock(path, options = {}) {
|
|
|
21
27
|
throw error;
|
|
22
28
|
});
|
|
23
29
|
if (legacy?.isFile()) {
|
|
24
|
-
|
|
30
|
+
if (Date.now() - legacy.mtimeMs > LEGACY_LOCK_STALE_MS) {
|
|
31
|
+
await unlink(`${target}.lock`).catch((error) => {
|
|
32
|
+
if (error.code !== 'ENOENT')
|
|
33
|
+
throw error;
|
|
34
|
+
});
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (Date.now() >= deadline)
|
|
38
|
+
throw new Error(`${label}_LEGACY_LOCK:旧版本宿主正持有锁,请停止旧版本后重试:${target}.lock`);
|
|
39
|
+
await delay(40, undefined, { signal });
|
|
40
|
+
continue;
|
|
25
41
|
}
|
|
26
42
|
try {
|
|
27
43
|
let owned = false;
|
package/src/runtime/fileLock.ts
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
|
-
import { mkdir, stat } from 'node:fs/promises';
|
|
1
|
+
import { mkdir, stat, unlink } from 'node:fs/promises';
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
3
|
import { dirname, resolve } from 'node:path';
|
|
4
4
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
5
5
|
import lockfile from 'proper-lockfile';
|
|
6
6
|
|
|
7
|
+
/** Hosts before this protocol wrote a plain `.lock` file without heartbeat and
|
|
8
|
+
* reclaimed each other's after 120s (memory) / 30s (runtime). A file older than
|
|
9
|
+
* that is abandoned by the old protocol's own rule. Refusing it forever turned a
|
|
10
|
+
* crash of the previous version into a permanent memory/runtime outage that only
|
|
11
|
+
* a manual delete could end. A younger one is a live old host: wait, never steal.
|
|
12
|
+
*/
|
|
13
|
+
const LEGACY_LOCK_STALE_MS = 120_000;
|
|
14
|
+
|
|
7
15
|
/** Shared heartbeat protocol for local file-backed stores, not a database lock.
|
|
8
|
-
* Never reclaim legacy regular-file locks: old hosts do not speak this protocol.
|
|
9
16
|
* proper-lockfile fails closed on compromised ownership (its default handler).
|
|
10
17
|
*/
|
|
11
18
|
export async function acquireStateFileLock(
|
|
@@ -24,7 +31,16 @@ export async function acquireStateFileLock(
|
|
|
24
31
|
throw error;
|
|
25
32
|
});
|
|
26
33
|
if (legacy?.isFile()) {
|
|
27
|
-
|
|
34
|
+
if (Date.now() - legacy.mtimeMs > LEGACY_LOCK_STALE_MS) {
|
|
35
|
+
await unlink(`${target}.lock`).catch((error) => {
|
|
36
|
+
if (error.code !== 'ENOENT') throw error;
|
|
37
|
+
});
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (Date.now() >= deadline)
|
|
41
|
+
throw new Error(`${label}_LEGACY_LOCK:旧版本宿主正持有锁,请停止旧版本后重试:${target}.lock`);
|
|
42
|
+
await delay(40, undefined, { signal });
|
|
43
|
+
continue;
|
|
28
44
|
}
|
|
29
45
|
try {
|
|
30
46
|
let owned = false;
|
|
@@ -6,7 +6,7 @@ export function invocationToolAllowed(name, policy) {
|
|
|
6
6
|
if (!policy.interactiveAsk && name === 'AskUserQuestion')
|
|
7
7
|
return false;
|
|
8
8
|
return (policy.consultation !== 'disabled' ||
|
|
9
|
-
|
|
9
|
+
name !== 'Advisor');
|
|
10
10
|
}
|
|
11
11
|
export function invocationInstructions(policy) {
|
|
12
12
|
if (!policy)
|
|
@@ -11,7 +11,3 @@ export function resolveOutputContract(surface, output) {
|
|
|
11
11
|
});
|
|
12
12
|
return Object.freeze({ ...value, diagrams: Object.freeze(value.format === 'rich-markdown' ? [...new Set(value.diagrams)] : []) });
|
|
13
13
|
}
|
|
14
|
-
/** Publication changes tool access, not the renderer or local execution rights. */
|
|
15
|
-
export function toolsForResponsePhase(tools, phase) {
|
|
16
|
-
return phase === 'publication' ? [] : [...tools];
|
|
17
|
-
}
|
|
@@ -25,10 +25,3 @@ export function resolveOutputContract(surface: AgentSurface, output?: OutputCont
|
|
|
25
25
|
value.format === 'rich-markdown' ? [...new Set(value.diagrams)] : [],
|
|
26
26
|
) });
|
|
27
27
|
}
|
|
28
|
-
|
|
29
|
-
export type ResponsePhase = 'preparation' | 'publication';
|
|
30
|
-
|
|
31
|
-
/** Publication changes tool access, not the renderer or local execution rights. */
|
|
32
|
-
export function toolsForResponsePhase<T>(tools: readonly T[], phase: ResponsePhase): T[] {
|
|
33
|
-
return phase === 'publication' ? [] : [...tools];
|
|
34
|
-
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { previousUserRequest } from '../memory/recallQuery.js';
|
|
2
|
+
import { loadContext } from '../context/persistContext.js';
|
|
1
3
|
import { withWorkingDir } from '../bootstrap/workingDir.js';
|
|
2
4
|
import { configService } from '../config/configService.js';
|
|
3
5
|
import { sessionFileFor } from '../context/sessionPath.js';
|
|
@@ -164,6 +166,7 @@ deps = {}) {
|
|
|
164
166
|
memoryAccess: runtimeContext.memoryAccess,
|
|
165
167
|
browser: browserPromptRuntime(runtimeContext.browser, provider.supportsVision),
|
|
166
168
|
currentUserInput: opts.input,
|
|
169
|
+
previousUserInput: previousUserRequest(await loadContext(sessionFile).catch(() => null) ?? undefined),
|
|
167
170
|
contextWindow: turnProvider.contextWindow,
|
|
168
171
|
},
|
|
169
172
|
}).catch(() => undefined);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { previousUserRequest } from '../memory/recallQuery.js';
|
|
2
|
+
import { loadContext } from '../context/persistContext.js';
|
|
1
3
|
import { withWorkingDir } from '../bootstrap/workingDir.js';
|
|
2
4
|
import { configService } from '../config/configService.js';
|
|
3
5
|
import { sessionFileFor } from '../context/sessionPath.js';
|
|
@@ -234,6 +236,7 @@ export async function runAgentTurn(
|
|
|
234
236
|
memoryAccess: runtimeContext.memoryAccess,
|
|
235
237
|
browser: browserPromptRuntime(runtimeContext.browser, provider.supportsVision),
|
|
236
238
|
currentUserInput: opts.input,
|
|
239
|
+
previousUserInput: previousUserRequest(await loadContext(sessionFile).catch(() => null) ?? undefined),
|
|
237
240
|
contextWindow: turnProvider.contextWindow,
|
|
238
241
|
},
|
|
239
242
|
}).catch(() => undefined);
|
|
@@ -95,7 +95,6 @@ export const ONLINE_TOOL_CATEGORY_BY_NAME = {
|
|
|
95
95
|
AgentInterrupt: 'subagents',
|
|
96
96
|
AgentFollowup: 'subagents',
|
|
97
97
|
Advisor: 'subagents',
|
|
98
|
-
AdvisorDecision: 'subagents',
|
|
99
98
|
GenerateImage: 'integrations',
|
|
100
99
|
GenerateSpeech: 'integrations',
|
|
101
100
|
} as const satisfies Readonly<Record<string, OnlineToolCategory>>;
|