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.
Files changed (174) hide show
  1. package/package.json +1 -3
  2. package/plugins/workflow-runner/src/runtime/context.js +1 -1
  3. package/plugins/workflow-runner/src/runtime/context.ts +1 -1
  4. package/plugins/workflow-runner/test/embeddedExecution.test.ts +2 -5
  5. package/plugins/workflow-runner/test/execLifetime.test.ts +15 -2
  6. package/skills/ssh-remote-dev/SKILL.md +61 -0
  7. package/skills/ssh-remote-dev/agents/openai.yaml +4 -0
  8. package/src/advisor/README.md +62 -24
  9. package/src/advisor/advisorNudge.js +199 -0
  10. package/src/advisor/advisorNudge.ts +232 -0
  11. package/src/advisor/deliveryReview.js +35 -12
  12. package/src/advisor/deliveryReview.ts +35 -11
  13. package/src/advisor/presentation.js +7 -10
  14. package/src/advisor/presentation.ts +10 -13
  15. package/src/advisor/reviewContext.js +22 -58
  16. package/src/advisor/reviewContext.ts +24 -67
  17. package/src/cli/configMenu.js +17 -128
  18. package/src/cli/configMenu.tsx +17 -193
  19. package/src/cli/print.js +2 -0
  20. package/src/cli/print.ts +2 -0
  21. package/src/cli/streamJson.js +2 -0
  22. package/src/cli/streamJson.ts +2 -0
  23. package/src/cli/wizards/advisorWizard.js +9 -7
  24. package/src/cli/wizards/advisorWizard.tsx +9 -7
  25. package/src/cli/wizards/contextAdvisorWizard.js +9 -7
  26. package/src/cli/wizards/contextAdvisorWizard.tsx +9 -7
  27. package/src/cli/wizards/providerPresets.js +27 -1
  28. package/src/cli/wizards/providerPresets.ts +44 -2
  29. package/src/cli/wizards/providerWizard.js +51 -10
  30. package/src/cli/wizards/providerWizard.tsx +59 -9
  31. package/src/config/configDefaults.js +1 -1
  32. package/src/config/configDefaults.ts +1 -1
  33. package/src/config/configDiagnostics.js +1 -21
  34. package/src/config/configDiagnostics.ts +3 -23
  35. package/src/config/configFile.js +11 -4
  36. package/src/config/configFile.ts +27 -7
  37. package/src/config/configManagement.js +1 -0
  38. package/src/config/configManagement.ts +2 -0
  39. package/src/config/configService.js +12 -8
  40. package/src/config/configService.ts +16 -11
  41. package/src/config/numericLimits.js +0 -4
  42. package/src/config/numericLimits.ts +0 -4
  43. package/src/config/reasoningEffort.js +16 -0
  44. package/src/config/reasoningEffort.ts +18 -0
  45. package/src/context/budgetPlan.js +1 -1
  46. package/src/context/budgetPlan.ts +1 -1
  47. package/src/context/optimizeConversation.js +1 -1
  48. package/src/context/optimizeConversation.ts +1 -1
  49. package/src/harness/stallGuard.js +8 -13
  50. package/src/harness/stallGuard.ts +8 -12
  51. package/src/llm/client.js +64 -17
  52. package/src/llm/client.ts +62 -7
  53. package/src/llm/providers/openaiFormat.js +14 -3
  54. package/src/llm/providers/openaiFormat.ts +17 -3
  55. package/src/llm/providers/responsesFormat.js +275 -0
  56. package/src/llm/providers/responsesFormat.ts +306 -0
  57. package/src/loop/README.md +18 -3
  58. package/src/loop/liveDiagramGate.js +63 -0
  59. package/src/loop/liveDiagramGate.ts +61 -0
  60. package/src/loop/messages.js +12 -0
  61. package/src/loop/messages.ts +13 -0
  62. package/src/loop/options.ts +7 -2
  63. package/src/loop/toolBatch.js +0 -1
  64. package/src/loop/toolBatch.ts +0 -1
  65. package/src/loop/toolExecutor.js +11 -46
  66. package/src/loop/toolExecutor.ts +12 -55
  67. package/src/loop/toolPresentation.js +2 -13
  68. package/src/loop/toolPresentation.ts +2 -13
  69. package/src/loop/toolSemantics.js +3 -4
  70. package/src/loop/toolSemantics.ts +4 -6
  71. package/src/loop.js +222 -214
  72. package/src/loop.ts +226 -232
  73. package/src/memory/dependencies.js +11 -3
  74. package/src/memory/dependencies.ts +11 -3
  75. package/src/memory/episodicMemory.js +59 -18
  76. package/src/memory/episodicMemory.ts +58 -18
  77. package/src/memory/experienceStore.js +4 -3
  78. package/src/memory/experienceStore.ts +4 -3
  79. package/src/memory/factCandidates.js +53 -1
  80. package/src/memory/factCandidates.ts +54 -1
  81. package/src/memory/factLearning.js +2 -2
  82. package/src/memory/factLearning.ts +2 -2
  83. package/src/memory/jobPolicy.js +24 -5
  84. package/src/memory/jobPolicy.ts +25 -5
  85. package/src/memory/learning.js +2 -2
  86. package/src/memory/learning.ts +2 -2
  87. package/src/memory/learningModel.js +7 -0
  88. package/src/memory/learningModel.ts +7 -0
  89. package/src/memory/managementService.js +8 -7
  90. package/src/memory/managementService.ts +16 -8
  91. package/src/memory/memoryService.js +104 -33
  92. package/src/memory/memoryService.ts +132 -28
  93. package/src/memory/projectKnowledge.js +39 -3
  94. package/src/memory/projectKnowledge.ts +45 -3
  95. package/src/memory/recallQuery.js +56 -0
  96. package/src/memory/recallQuery.ts +58 -0
  97. package/src/memory/retrieval.js +39 -10
  98. package/src/memory/retrieval.ts +46 -11
  99. package/src/memory/turnJournal.js +75 -10
  100. package/src/memory/turnJournal.ts +66 -10
  101. package/src/memory/types.ts +7 -1
  102. package/src/prompts/delegation.js +2 -1
  103. package/src/prompts/delegation.ts +3 -1
  104. package/src/prompts/policyRegistry.js +9 -12
  105. package/src/prompts/policyRegistry.ts +9 -12
  106. package/src/prompts/promptAssembly.js +6 -5
  107. package/src/prompts/promptAssembly.ts +8 -6
  108. package/src/prompts/visualPresentation.js +39 -10
  109. package/src/prompts/visualPresentation.ts +38 -9
  110. package/src/runtime/answerPreparation.js +1 -1
  111. package/src/runtime/answerPreparation.ts +1 -1
  112. package/src/runtime/fileLock.js +19 -3
  113. package/src/runtime/fileLock.ts +19 -3
  114. package/src/runtime/invocationPolicy.js +1 -1
  115. package/src/runtime/invocationPolicy.ts +1 -1
  116. package/src/runtime/outputContract.js +0 -4
  117. package/src/runtime/outputContract.ts +0 -7
  118. package/src/session/runAgentTurn.js +3 -0
  119. package/src/session/runAgentTurn.ts +3 -0
  120. package/src/team/onlineToolContract.js +0 -1
  121. package/src/team/onlineToolContract.ts +0 -1
  122. package/src/tools/advisor/advisor.js +207 -365
  123. package/src/tools/advisor/advisor.ts +211 -383
  124. package/src/tools/agent/agentTypes.js +0 -1
  125. package/src/tools/agent/agentTypes.ts +0 -1
  126. package/src/tools/agent/coordinator.js +4 -1
  127. package/src/tools/agent/coordinator.ts +4 -1
  128. package/src/tools/bash/semantics.js +4 -5
  129. package/src/tools/bash/semantics.ts +4 -5
  130. package/src/tools/capabilities.js +2 -3
  131. package/src/tools/capabilities.ts +2 -4
  132. package/src/tools/index.js +0 -16
  133. package/src/tools/index.ts +0 -16
  134. package/src/tools/memory/maintenance.js +8 -9
  135. package/src/tools/memory/maintenance.ts +13 -11
  136. package/src/tools/memory/memory.js +5 -1
  137. package/src/tools/memory/memory.ts +5 -0
  138. package/src/tools/optimizeConversation/README.md +4 -6
  139. package/src/tools/toolPromptGuides.js +2 -9
  140. package/src/tools/toolPromptGuides.ts +2 -9
  141. package/src/types.ts +28 -105
  142. package/src/ui/App.js +2 -0
  143. package/src/ui/App.tsx +1 -0
  144. package/src/ui/MessageList.js +8 -44
  145. package/src/ui/MessageList.tsx +3 -81
  146. package/src/ui/hooks/useChat.js +2 -0
  147. package/src/ui/hooks/useChat.ts +2 -0
  148. package/src/ui/toolProgress.js +1 -1
  149. package/src/ui/toolProgress.ts +1 -1
  150. package/src/web/protocol.ts +10 -3
  151. package/src/advisor/advisorCoordinator.js +0 -734
  152. package/src/advisor/advisorCoordinator.ts +0 -825
  153. package/src/cli/wizards/sshHostWizard.js +0 -156
  154. package/src/cli/wizards/sshHostWizard.tsx +0 -214
  155. package/src/context/sshHosts.js +0 -290
  156. package/src/context/sshHosts.ts +0 -312
  157. package/src/loop/publication.js +0 -94
  158. package/src/loop/publication.ts +0 -124
  159. package/src/runtime/finalAnswer.js +0 -9
  160. package/src/runtime/finalAnswer.ts +0 -13
  161. package/src/tools/advisor/advisorDecision.js +0 -79
  162. package/src/tools/advisor/advisorDecision.ts +0 -92
  163. package/src/tools/ssh/ssh.js +0 -152
  164. package/src/tools/ssh/ssh.ts +0 -167
  165. package/src/tools/ssh/sshAvailability.js +0 -35
  166. package/src/tools/ssh/sshAvailability.ts +0 -39
  167. package/src/tools/ssh/sshClient.js +0 -177
  168. package/src/tools/ssh/sshClient.ts +0 -189
  169. package/src/tools/ssh/sshDownload.js +0 -77
  170. package/src/tools/ssh/sshDownload.ts +0 -92
  171. package/src/tools/ssh/sshHostsTool.js +0 -36
  172. package/src/tools/ssh/sshHostsTool.ts +0 -48
  173. package/src/tools/ssh/sshUpload.js +0 -72
  174. 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
- /** One output contract shared by preparation and tool-free publication. */
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。仅用已声明的图形格式替代字符框图;最终表达同样生效。${output.images ? '支持图片。' : ''}${output.attachments ? '附件须真实提交。' : ''}无法可靠画图则用文字/表格。`;
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
- * Require box borders in prose fences; ordinary code, trees and arrows are not diagrams.
33
+ * Live answers use loop/liveDiagramGate.ts, built on the same two predicates.
16
34
  */
17
35
  export function needsDiagramPreparation(candidate, request) {
18
- const rejectsAscii = /(?:不要|不用|避免|别用|without|not|instead of)[^\n。!?]{0,24}(?:ascii|字符图|纯文本图)/i.test(request);
19
- if (!rejectsAscii && /ascii|字符图|纯文本图|原样|不要.*(?:mermaid|svg)/i.test(request))
36
+ if (requestKeepsTextDiagrams(request))
20
37
  return false;
21
- const fences = candidate.matchAll(/^(?:```|~~~)(?:text|plaintext)?[ \t]*\r?\n([\s\S]*?)^(?:```|~~~)[ \t]*$/gm);
22
- for (const [, body] of fences) {
23
- const borders = body.split('\n').filter(line => /[┌┐└┘┏┓┗┛+][─━\-]{3,}[┌┐└┘┏┓┗┛+]/.test(line));
24
- if (borders.length >= 2 && /[│┃|].*\S.*[│┃|]/.test(body))
25
- return true;
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
- /** One output contract shared by preparation and tool-free publication. */
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。仅用已声明的图形格式替代字符框图;最终表达同样生效。${output.images ? '支持图片。' : ''}${output.attachments ? '附件须真实提交。' : ''}无法可靠画图则用文字/表格。`;
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
- * Require box borders in prose fences; ordinary code, trees and arrows are not diagrams.
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
- const rejectsAscii = /(?:不要|不用|避免|别用|without|not|instead of)[^\n。!?]{0,24}(?:ascii|字符图|纯文本图)/i.test(request);
19
- if (!rejectsAscii && /ascii|字符图|纯文本图|原样|不要.*(?:mermaid|svg)/i.test(request)) return false;
20
- const fences = candidate.matchAll(/^(?:```|~~~)(?:text|plaintext)?[ \t]*\r?\n([\s\S]*?)^(?:```|~~~)[ \t]*$/gm);
21
- for (const [, body] of fences) {
22
- const borders = body!.split('\n').filter(line => /[┌┐└┘┏┓┗┛+][─━\-]{3,}[┌┐└┘┏┓┗┛+]/.test(line));
23
- if (borders.length >= 2 && /[│┃|].*\S.*[│┃|]/.test(body!)) return true;
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
  }
@@ -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
- throw new Error(`${label}_LEGACY_LOCK:请停止旧版本宿主并检查遗留锁:${target}.lock`);
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;
@@ -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
- throw new Error(`${label}_LEGACY_LOCK:请停止旧版本宿主并检查遗留锁:${target}.lock`);
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
- !['Advisor', 'AdvisorDecision'].includes(name));
9
+ name !== 'Advisor');
10
10
  }
11
11
  export function invocationInstructions(policy) {
12
12
  if (!policy)
@@ -16,7 +16,7 @@ export function invocationToolAllowed(
16
16
  if (!policy.interactiveAsk && name === 'AskUserQuestion') return false;
17
17
  return (
18
18
  policy.consultation !== 'disabled' ||
19
- !['Advisor', 'AdvisorDecision'].includes(name)
19
+ name !== 'Advisor'
20
20
  );
21
21
  }
22
22
 
@@ -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);
@@ -55,7 +55,6 @@ export const ONLINE_TOOL_CATEGORY_BY_NAME = {
55
55
  AgentInterrupt: 'subagents',
56
56
  AgentFollowup: 'subagents',
57
57
  Advisor: 'subagents',
58
- AdvisorDecision: 'subagents',
59
58
  GenerateImage: 'integrations',
60
59
  GenerateSpeech: 'integrations',
61
60
  };
@@ -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>>;