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
@@ -92,6 +92,14 @@ export function AdvisorWizard({ existing, models, onDone, onCancel }) {
92
92
  default: existing?.mode ?? 'auto',
93
93
  hint: 'auto=主循环自主判断时机调用 · manual=仅 /advisor ask 手动触发',
94
94
  },
95
+ {
96
+ key: 'enforcement',
97
+ label: '补问方式',
98
+ type: 'select',
99
+ options: ['remind', 'followup'],
100
+ default: existing?.enforcement ?? 'remind',
101
+ hint: 'remind=只提醒 · followup=实质回答未复核时再发一次请求,让模型自己补问顾问(弱基座模型用)',
102
+ },
95
103
  {
96
104
  key: 'timeoutSeconds',
97
105
  label: '最长等待(秒)',
@@ -99,12 +107,6 @@ export function AdvisorWizard({ existing, models, onDone, onCancel }) {
99
107
  default: String(Math.round((existing?.timeoutMs ?? DEFAULT_ADVISOR_CONFIG.timeoutMs) / 1000)),
100
108
  optional: true,
101
109
  },
102
- {
103
- key: 'shareSensitive',
104
- label: '是否允许把敏感内容一并发给顾问',
105
- type: 'boolean',
106
- default: existing?.shareSensitive ?? false,
107
- },
108
110
  ], onSubmit: async (values) => {
109
111
  // 只在这最后一步才真正落盘——ESC 在任何前置阶段取消都不留半成品模型。
110
112
  // newModelDraft 在 add 成功后立即清空 + 把新 id 记入 chosenModelId:若紧接着
@@ -140,8 +142,8 @@ export function AdvisorWizard({ existing, models, onDone, onCancel }) {
140
142
  advisor: {
141
143
  enabled: values.enabled,
142
144
  mode: values.mode === 'manual' ? 'manual' : 'auto',
145
+ enforcement: values.enforcement === 'followup' ? 'followup' : 'remind',
143
146
  modelId,
144
- shareSensitive: values.shareSensitive,
145
147
  timeoutMs: normalizeLimited(parsePositiveInt(values.timeoutSeconds, DEFAULT_ADVISOR_CONFIG.timeoutMs / 1000) * 1000, CONFIG_LIMITS.advisorTimeoutMs, DEFAULT_ADVISOR_CONFIG.timeoutMs),
146
148
  },
147
149
  });
@@ -136,6 +136,14 @@ export function AdvisorWizard({ existing, models, onDone, onCancel }: Props): JS
136
136
  default: existing?.mode ?? 'auto',
137
137
  hint: 'auto=主循环自主判断时机调用 · manual=仅 /advisor ask 手动触发',
138
138
  },
139
+ {
140
+ key: 'enforcement',
141
+ label: '补问方式',
142
+ type: 'select',
143
+ options: ['remind', 'followup'],
144
+ default: existing?.enforcement ?? 'remind',
145
+ hint: 'remind=只提醒 · followup=实质回答未复核时再发一次请求,让模型自己补问顾问(弱基座模型用)',
146
+ },
139
147
  {
140
148
  key: 'timeoutSeconds',
141
149
  label: '最长等待(秒)',
@@ -143,12 +151,6 @@ export function AdvisorWizard({ existing, models, onDone, onCancel }: Props): JS
143
151
  default: String(Math.round((existing?.timeoutMs ?? DEFAULT_ADVISOR_CONFIG.timeoutMs) / 1000)),
144
152
  optional: true,
145
153
  },
146
- {
147
- key: 'shareSensitive',
148
- label: '是否允许把敏感内容一并发给顾问',
149
- type: 'boolean',
150
- default: existing?.shareSensitive ?? false,
151
- },
152
154
  ]}
153
155
  onSubmit={async (values) => {
154
156
  // 只在这最后一步才真正落盘——ESC 在任何前置阶段取消都不留半成品模型。
@@ -184,8 +186,8 @@ export function AdvisorWizard({ existing, models, onDone, onCancel }: Props): JS
184
186
  advisor: {
185
187
  enabled: values.enabled as boolean,
186
188
  mode: values.mode === 'manual' ? 'manual' : 'auto',
189
+ enforcement: values.enforcement === 'followup' ? 'followup' : 'remind',
187
190
  modelId,
188
- shareSensitive: values.shareSensitive as boolean,
189
191
  timeoutMs: normalizeLimited(
190
192
  parsePositiveInt(values.timeoutSeconds, DEFAULT_ADVISOR_CONFIG.timeoutMs / 1000) * 1000,
191
193
  CONFIG_LIMITS.advisorTimeoutMs,
@@ -45,6 +45,14 @@ export function ContextAdvisorWizard({ compactRatio, advisor, models, onDone, on
45
45
  options: ['auto', 'manual'],
46
46
  default: advisor?.mode ?? 'auto',
47
47
  },
48
+ {
49
+ key: 'enforcement',
50
+ label: '补问方式',
51
+ type: 'select',
52
+ options: ['remind', 'followup'],
53
+ default: advisor?.enforcement ?? 'remind',
54
+ hint: 'remind=只提醒 · followup=实质回答未复核时再发一次请求,让模型自己补问顾问(弱基座模型用)',
55
+ },
48
56
  {
49
57
  key: 'inputTokenBudget',
50
58
  label: '顾问输入 Token 预算',
@@ -57,12 +65,6 @@ export function ContextAdvisorWizard({ compactRatio, advisor, models, onDone, on
57
65
  type: 'text',
58
66
  default: String(Math.round((advisor?.timeoutMs ?? DEFAULT_ADVISOR_CONFIG.timeoutMs) / 1000)),
59
67
  },
60
- {
61
- key: 'shareSensitive',
62
- label: '允许向顾问发送敏感内容',
63
- type: 'boolean',
64
- default: advisor?.shareSensitive ?? false,
65
- },
66
68
  ], onSubmit: async (values) => {
67
69
  const ratio = normalizeLimited(numberValue(values.compactPercent, 85) / 100, CONFIG_LIMITS.compactRatio, 0.85);
68
70
  const modelIndex = options.indexOf(String(values.advisorModel));
@@ -73,10 +75,10 @@ export function ContextAdvisorWizard({ compactRatio, advisor, models, onDone, on
73
75
  ? {
74
76
  enabled: values.enabled === true,
75
77
  mode: values.mode === 'manual' ? 'manual' : 'auto',
78
+ enforcement: values.enforcement === 'followup' ? 'followup' : 'remind',
76
79
  modelId,
77
80
  inputTokenBudget: normalizeLimited(numberValue(values.inputTokenBudget, DEFAULT_ADVISOR_CONFIG.inputTokenBudget), CONFIG_LIMITS.advisorInputTokenBudget, DEFAULT_ADVISOR_CONFIG.inputTokenBudget),
78
81
  timeoutMs: normalizeLimited(numberValue(values.timeoutSeconds, DEFAULT_ADVISOR_CONFIG.timeoutMs / 1000) * 1000, CONFIG_LIMITS.advisorTimeoutMs, DEFAULT_ADVISOR_CONFIG.timeoutMs),
79
- shareSensitive: values.shareSensitive === true,
80
82
  }
81
83
  : { clear: true },
82
84
  });
@@ -69,6 +69,14 @@ export function ContextAdvisorWizard({
69
69
  options: ['auto', 'manual'],
70
70
  default: advisor?.mode ?? 'auto',
71
71
  },
72
+ {
73
+ key: 'enforcement',
74
+ label: '补问方式',
75
+ type: 'select',
76
+ options: ['remind', 'followup'],
77
+ default: advisor?.enforcement ?? 'remind',
78
+ hint: 'remind=只提醒 · followup=实质回答未复核时再发一次请求,让模型自己补问顾问(弱基座模型用)',
79
+ },
72
80
  {
73
81
  key: 'inputTokenBudget',
74
82
  label: '顾问输入 Token 预算',
@@ -81,12 +89,6 @@ export function ContextAdvisorWizard({
81
89
  type: 'text',
82
90
  default: String(Math.round((advisor?.timeoutMs ?? DEFAULT_ADVISOR_CONFIG.timeoutMs) / 1000)),
83
91
  },
84
- {
85
- key: 'shareSensitive',
86
- label: '允许向顾问发送敏感内容',
87
- type: 'boolean',
88
- default: advisor?.shareSensitive ?? false,
89
- },
90
92
  ]}
91
93
  onSubmit={async (values) => {
92
94
  const ratio = normalizeLimited(
@@ -102,6 +104,7 @@ export function ContextAdvisorWizard({
102
104
  ? {
103
105
  enabled: values.enabled === true,
104
106
  mode: values.mode === 'manual' ? 'manual' : 'auto',
107
+ enforcement: values.enforcement === 'followup' ? 'followup' : 'remind',
105
108
  modelId,
106
109
  inputTokenBudget: normalizeLimited(
107
110
  numberValue(values.inputTokenBudget, DEFAULT_ADVISOR_CONFIG.inputTokenBudget),
@@ -113,7 +116,6 @@ export function ContextAdvisorWizard({
113
116
  CONFIG_LIMITS.advisorTimeoutMs,
114
117
  DEFAULT_ADVISOR_CONFIG.timeoutMs,
115
118
  ),
116
- shareSensitive: values.shareSensitive === true,
117
119
  }
118
120
  : { clear: true },
119
121
  });
@@ -7,6 +7,7 @@
7
7
  * baseURL 预设表。
8
8
  * ============================================================
9
9
  */
10
+ import { REASONING_EFFORTS, normalizeReasoningEffort, } from '../../config/reasoningEffort.js';
10
11
  // 兜底值:custom 或表外 model 都退到这里。
11
12
  // 与 src/config.ts 的同名常量含义对齐("既没 env、saved 也没填 contextWindow" 的最终兜底)。
12
13
  export const FALLBACK_CONTEXT_WINDOW = 128_000;
@@ -57,11 +58,25 @@ export const PRESETS = [
57
58
  contextWindow: 128_000,
58
59
  },
59
60
  {
61
+ // OpenAI 当前主推的 Responses API(POST /v1/responses)。模型与推理强度都在请求体里,
62
+ // 不换端点。模型 ID 以官方模型页为准——这里只是常用项,向导里可直接改。
63
+ name: 'openai-responses',
64
+ label: 'OpenAI(Responses API,推荐)',
65
+ baseURL: 'https://api.openai.com/v1/responses',
66
+ models: ['gpt-6.1-terra', 'gpt-5.6-luna'],
67
+ contextWindow: 128_000,
68
+ apiFormat: 'openai-responses',
69
+ supportsReasoningEffort: true,
70
+ reasoningEffort: 'medium',
71
+ },
72
+ {
73
+ // 仍然可用的 Chat Completions;OpenAI 官方端点认顶层 reasoning_effort。
60
74
  name: 'openai',
61
- label: 'OpenAI',
75
+ label: 'OpenAI(Chat Completions)',
62
76
  baseURL: 'https://api.openai.com/v1/chat/completions',
63
77
  models: ['gpt-4o', 'gpt-4o-mini'],
64
78
  contextWindow: 128_000,
79
+ supportsReasoningEffort: true,
65
80
  },
66
81
  {
67
82
  name: 'openrouter',
@@ -97,6 +112,17 @@ export const PRESETS = [
97
112
  contextWindow: FALLBACK_CONTEXT_WINDOW,
98
113
  },
99
114
  ];
115
+ /** 向导里「不设置」的显示文案:不发送该参数,沿用服务端对该模型的默认强度。 */
116
+ export const REASONING_EFFORT_UNSET = '不设置(沿用模型默认)';
117
+ export const REASONING_EFFORT_CHOICES = [REASONING_EFFORT_UNSET, ...REASONING_EFFORTS];
118
+ export function reasoningEffortFromChoice(choice) {
119
+ return normalizeReasoningEffort(choice);
120
+ }
121
+ export function reasoningEffortToChoice(effort) {
122
+ return effort ?? REASONING_EFFORT_UNSET;
123
+ }
124
+ /** 选「自定义」时可手选的协议;已有模型沿用它自己的协议。 */
125
+ export const API_FORMAT_CHOICES = ['openai', 'openai-responses', 'anthropic'];
100
126
  export function resolveContextWindow(preset, model) {
101
127
  return (preset.modelContextWindows?.[model] ??
102
128
  preset.contextWindow ??
@@ -8,6 +8,15 @@
8
8
  * ============================================================
9
9
  */
10
10
 
11
+ // 只引纯逻辑模块:Desktop 浏览器端经 `minimal-agent/provider-presets` 引用本文件,
12
+ // 不能把 configFile.ts(node:fs)拖进前端包。ApiFormat 仅类型引用,编译后抹掉。
13
+ import type { ApiFormat } from '../../config/configFile.js';
14
+ import {
15
+ REASONING_EFFORTS,
16
+ type ReasoningEffort,
17
+ normalizeReasoningEffort,
18
+ } from '../../config/reasoningEffort.js';
19
+
11
20
  // 兜底值:custom 或表外 model 都退到这里。
12
21
  // 与 src/config.ts 的同名常量含义对齐("既没 env、saved 也没填 contextWindow" 的最终兜底)。
13
22
  export const FALLBACK_CONTEXT_WINDOW = 128_000;
@@ -19,7 +28,11 @@ export interface ProviderPreset {
19
28
  models: string[];
20
29
  /** 该 provider 的默认 context window(token 数) */
21
30
  contextWindow: number;
22
- apiFormat?: 'openai' | 'anthropic';
31
+ apiFormat?: ApiFormat;
32
+ /** 该服务商的模型有「推理强度」档位:向导的快速配置也会问。 */
33
+ supportsReasoningEffort?: boolean;
34
+ /** 向导里推理强度的预选值。 */
35
+ reasoningEffort?: ReasoningEffort;
23
36
  promptCacheMode?: 'auto' | 'session-key' | 'disabled';
24
37
  /** Default reasoning switch for providers that expose an OpenAI-compatible extension. */
25
38
  reasoning?: boolean;
@@ -74,11 +87,25 @@ export const PRESETS: ProviderPreset[] = [
74
87
  contextWindow: 128_000,
75
88
  },
76
89
  {
90
+ // OpenAI 当前主推的 Responses API(POST /v1/responses)。模型与推理强度都在请求体里,
91
+ // 不换端点。模型 ID 以官方模型页为准——这里只是常用项,向导里可直接改。
92
+ name: 'openai-responses',
93
+ label: 'OpenAI(Responses API,推荐)',
94
+ baseURL: 'https://api.openai.com/v1/responses',
95
+ models: ['gpt-6.1-terra', 'gpt-5.6-luna'],
96
+ contextWindow: 128_000,
97
+ apiFormat: 'openai-responses',
98
+ supportsReasoningEffort: true,
99
+ reasoningEffort: 'medium',
100
+ },
101
+ {
102
+ // 仍然可用的 Chat Completions;OpenAI 官方端点认顶层 reasoning_effort。
77
103
  name: 'openai',
78
- label: 'OpenAI',
104
+ label: 'OpenAI(Chat Completions)',
79
105
  baseURL: 'https://api.openai.com/v1/chat/completions',
80
106
  models: ['gpt-4o', 'gpt-4o-mini'],
81
107
  contextWindow: 128_000,
108
+ supportsReasoningEffort: true,
82
109
  },
83
110
  {
84
111
  name: 'openrouter',
@@ -115,6 +142,21 @@ export const PRESETS: ProviderPreset[] = [
115
142
  },
116
143
  ];
117
144
 
145
+ /** 向导里「不设置」的显示文案:不发送该参数,沿用服务端对该模型的默认强度。 */
146
+ export const REASONING_EFFORT_UNSET = '不设置(沿用模型默认)';
147
+ export const REASONING_EFFORT_CHOICES: string[] = [REASONING_EFFORT_UNSET, ...REASONING_EFFORTS];
148
+
149
+ export function reasoningEffortFromChoice(choice: unknown): ReasoningEffort | undefined {
150
+ return normalizeReasoningEffort(choice);
151
+ }
152
+
153
+ export function reasoningEffortToChoice(effort: ReasoningEffort | undefined): string {
154
+ return effort ?? REASONING_EFFORT_UNSET;
155
+ }
156
+
157
+ /** 选「自定义」时可手选的协议;已有模型沿用它自己的协议。 */
158
+ export const API_FORMAT_CHOICES: ApiFormat[] = ['openai', 'openai-responses', 'anthropic'];
159
+
118
160
  export function resolveContextWindow(preset: ProviderPreset, model: string): number {
119
161
  return (
120
162
  preset.modelContextWindows?.[model] ??
@@ -6,7 +6,7 @@ import { defaultMaxTokens } from '../../config/configFile.js';
6
6
  import { configService } from '../../config/configService.js';
7
7
  import { CONFIG_LIMITS, normalizeLimited } from '../../config/numericLimits.js';
8
8
  import { FieldForm } from './fieldForm.js';
9
- import { PRESETS, findPresetIndex, resolveContextWindow, } from './providerPresets.js';
9
+ import { API_FORMAT_CHOICES, PRESETS, REASONING_EFFORT_CHOICES, findPresetIndex, reasoningEffortFromChoice, reasoningEffortToChoice, resolveContextWindow, } from './providerPresets.js';
10
10
  const numberValue = (value, fallback) => {
11
11
  const parsed = Number(String(value ?? '').trim());
12
12
  return Number.isFinite(parsed) ? parsed : fallback;
@@ -52,6 +52,18 @@ export function ProviderWizard({ existing, setAsBase = true, quick = false, onDo
52
52
  : '添加模型' }), _jsx(Text, { color: "gray", children: "\u2191\u2193 \u9009\u62E9\u670D\u52A1\u5546 \u00B7 Enter \u7EE7\u7EED \u00B7 ESC \u8FD4\u56DE" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: PRESETS.map((item, index) => (_jsxs(Text, { color: index === presetIndex ? 'cyan' : undefined, children: [index === presetIndex ? '> ' : ' ', item.label] }, item.name))) })] }));
53
53
  if (phase === 'quick-choice')
54
54
  return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(Text, { color: "cyan", bold: true, children: "\u9009\u62E9\u914D\u7F6E\u6DF1\u5EA6" }), _jsx(Text, { color: "gray", children: "\u5FEB\u901F\u914D\u7F6E\u4F7F\u7528\u670D\u52A1\u5546\u63A8\u8350\u7684\u7A97\u53E3\u3001\u8F93\u51FA\u4E0A\u9650\u548C\u7F13\u5B58\u7B56\u7565\u3002" }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Text, { color: !advanced ? 'cyan' : undefined, children: [!advanced ? '> ' : ' ', "\u5FEB\u901F\u914D\u7F6E\uFF08\u63A8\u8350\uFF09"] }), _jsxs(Text, { color: advanced ? 'cyan' : undefined, children: [advanced ? '> ' : ' ', "\u9AD8\u7EA7\u914D\u7F6E"] })] })] }));
55
+ // 协议:选了具体服务商就跟服务商走(编辑时换服务商可以顺带换协议);
56
+ // 「自定义」没有预设,沿用已有模型自己的协议,新建时在完整设置里让用户选。
57
+ const presetFormat = preset.name === 'custom' ? existing?.apiFormat : (preset.apiFormat ?? 'openai');
58
+ const askFormat = preset.name === 'custom' && (!quick || advanced);
59
+ const knownFormat = presetFormat ?? 'openai';
60
+ // 推理强度是 OpenAI 系模型最常调的一项:快速配置也要问,不藏进高级里。
61
+ const showEffort = knownFormat !== 'anthropic' &&
62
+ (preset.supportsReasoningEffort === true ||
63
+ knownFormat === 'openai-responses' ||
64
+ !!existing?.reasoningEffort ||
65
+ !quick ||
66
+ advanced);
55
67
  const defaultModel = existing?.model ?? preset.models[0] ?? '';
56
68
  const defaultContext = existing?.contextWindow ?? resolveContextWindow(preset, defaultModel);
57
69
  const fields = [
@@ -81,6 +93,24 @@ export function ProviderWizard({ existing, setAsBase = true, quick = false, onDo
81
93
  : undefined,
82
94
  },
83
95
  ];
96
+ if (askFormat)
97
+ fields.push({
98
+ key: 'apiFormat',
99
+ label: '接口协议',
100
+ type: 'select',
101
+ options: API_FORMAT_CHOICES,
102
+ default: existing?.apiFormat ?? 'openai',
103
+ hint: 'openai = Chat Completions(多数国内服务商);openai-responses = OpenAI Responses API;anthropic = Messages API(未经真实端点验证)',
104
+ });
105
+ if (showEffort)
106
+ fields.push({
107
+ key: 'reasoningEffort',
108
+ label: '推理强度',
109
+ type: 'select',
110
+ options: REASONING_EFFORT_CHOICES,
111
+ default: reasoningEffortToChoice(existing ? existing.reasoningEffort : preset.reasoningEffort),
112
+ hint: '越高越慢越贵(推理 Token 照常计费)。各模型支持的档位不同;服务端不接受时会自动去掉该参数重发并告警。',
113
+ });
84
114
  if (!quick || advanced)
85
115
  fields.push({
86
116
  key: 'displayName',
@@ -105,13 +135,17 @@ export function ProviderWizard({ existing, setAsBase = true, quick = false, onDo
105
135
  type: 'select',
106
136
  options: ['auto', 'session-key', 'disabled'],
107
137
  default: existing?.promptCache?.mode ?? preset.promptCacheMode ?? 'auto',
108
- }, {
109
- key: 'reasoning',
110
- label: '启用 Reasoning(Provider 支持时)',
111
- type: 'boolean',
112
- default: existing?.reasoning ?? preset.reasoning ?? false,
113
- hint: '仅显式启用时发送 reasoning.enabled=true;普通 OpenAI 端点保持默认行为',
114
- });
138
+ }, ...(knownFormat === 'openai-responses'
139
+ ? []
140
+ : [
141
+ {
142
+ key: 'reasoning',
143
+ label: '启用 Reasoning(Provider 支持时)',
144
+ type: 'boolean',
145
+ default: existing?.reasoning ?? preset.reasoning ?? false,
146
+ hint: '仅显式启用时发送 reasoning.enabled=true;普通 OpenAI 端点保持默认行为',
147
+ },
148
+ ]));
115
149
  return (_jsx(FieldForm, { title: quick
116
150
  ? '快速模型配置'
117
151
  : existing
@@ -129,7 +163,12 @@ export function ProviderWizard({ existing, setAsBase = true, quick = false, onDo
129
163
  const maxTokens = rawMax
130
164
  ? normalizeLimited(numberValue(rawMax, defaultMaxTokens(contextWindow)), CONFIG_LIMITS.modelMaxTokens, defaultMaxTokens(contextWindow))
131
165
  : undefined;
132
- const apiFormat = existing?.apiFormat ?? 'openai';
166
+ const apiFormat = askFormat
167
+ ? (API_FORMAT_CHOICES.find((item) => item === values.apiFormat) ?? knownFormat)
168
+ : knownFormat;
169
+ const reasoningEffort = showEffort
170
+ ? reasoningEffortFromChoice(values.reasoningEffort)
171
+ : existing?.reasoningEffort;
133
172
  const draft = {
134
173
  apiFormat,
135
174
  displayName: quick && !advanced
@@ -141,6 +180,7 @@ export function ProviderWizard({ existing, setAsBase = true, quick = false, onDo
141
180
  contextWindow,
142
181
  ...(maxTokens ? { maxTokens } : {}),
143
182
  supportsVision: existing?.supportsVision !== false,
183
+ ...(reasoningEffort ? { reasoningEffort } : {}),
144
184
  reasoning: quick && !advanced
145
185
  ? (preset.reasoning ?? false)
146
186
  : values.reasoning === true,
@@ -155,7 +195,8 @@ export function ProviderWizard({ existing, setAsBase = true, quick = false, onDo
155
195
  throw new Error(diagnostic.message);
156
196
  if (existing) {
157
197
  await configService.savePatch({
158
- models: [{ op: 'update', id: existing.id, patch: draft }],
198
+ // null = 清除之前设的强度(选了「不设置」);不带这个字段则保留旧值。
199
+ models: [{ op: 'update', id: existing.id, patch: { ...draft, reasoningEffort: reasoningEffort ?? null } }],
159
200
  ...(setAsBase ? { baseModelId: existing.id } : {}),
160
201
  });
161
202
  }
@@ -8,8 +8,12 @@ import { configService } from '../../config/configService.js';
8
8
  import { CONFIG_LIMITS, normalizeLimited } from '../../config/numericLimits.js';
9
9
  import { type FieldDef, FieldForm } from './fieldForm.js';
10
10
  import {
11
+ API_FORMAT_CHOICES,
11
12
  PRESETS,
13
+ REASONING_EFFORT_CHOICES,
12
14
  findPresetIndex,
15
+ reasoningEffortFromChoice,
16
+ reasoningEffortToChoice,
13
17
  resolveContextWindow,
14
18
  } from './providerPresets.js';
15
19
 
@@ -26,6 +30,7 @@ export type ExistingProvider = Pick<
26
30
  | 'supportsVision'
27
31
  | 'promptCache'
28
32
  | 'reasoning'
33
+ | 'reasoningEffort'
29
34
  >;
30
35
 
31
36
  interface Props {
@@ -124,6 +129,20 @@ export function ProviderWizard({
124
129
  </Box>
125
130
  );
126
131
 
132
+ // 协议:选了具体服务商就跟服务商走(编辑时换服务商可以顺带换协议);
133
+ // 「自定义」没有预设,沿用已有模型自己的协议,新建时在完整设置里让用户选。
134
+ const presetFormat: ApiFormat | undefined =
135
+ preset.name === 'custom' ? existing?.apiFormat : (preset.apiFormat ?? 'openai');
136
+ const askFormat = preset.name === 'custom' && (!quick || advanced);
137
+ const knownFormat: ApiFormat = presetFormat ?? 'openai';
138
+ // 推理强度是 OpenAI 系模型最常调的一项:快速配置也要问,不藏进高级里。
139
+ const showEffort =
140
+ knownFormat !== 'anthropic' &&
141
+ (preset.supportsReasoningEffort === true ||
142
+ knownFormat === 'openai-responses' ||
143
+ !!existing?.reasoningEffort ||
144
+ !quick ||
145
+ advanced);
127
146
  const defaultModel = existing?.model ?? preset.models[0] ?? '';
128
147
  const defaultContext =
129
148
  existing?.contextWindow ?? resolveContextWindow(preset, defaultModel);
@@ -154,6 +173,26 @@ export function ProviderWizard({
154
173
  : undefined,
155
174
  },
156
175
  ];
176
+ if (askFormat)
177
+ fields.push({
178
+ key: 'apiFormat',
179
+ label: '接口协议',
180
+ type: 'select',
181
+ options: API_FORMAT_CHOICES,
182
+ default: existing?.apiFormat ?? 'openai',
183
+ hint: 'openai = Chat Completions(多数国内服务商);openai-responses = OpenAI Responses API;anthropic = Messages API(未经真实端点验证)',
184
+ });
185
+ if (showEffort)
186
+ fields.push({
187
+ key: 'reasoningEffort',
188
+ label: '推理强度',
189
+ type: 'select',
190
+ options: REASONING_EFFORT_CHOICES,
191
+ default: reasoningEffortToChoice(
192
+ existing ? existing.reasoningEffort : preset.reasoningEffort,
193
+ ),
194
+ hint: '越高越慢越贵(推理 Token 照常计费)。各模型支持的档位不同;服务端不接受时会自动去掉该参数重发并告警。',
195
+ });
157
196
  if (!quick || advanced)
158
197
  fields.push(
159
198
  {
@@ -184,13 +223,17 @@ export function ProviderWizard({
184
223
  default:
185
224
  existing?.promptCache?.mode ?? preset.promptCacheMode ?? 'auto',
186
225
  },
187
- {
188
- key: 'reasoning',
189
- label: '启用 Reasoning(Provider 支持时)',
190
- type: 'boolean',
191
- default: existing?.reasoning ?? preset.reasoning ?? false,
192
- hint: '仅显式启用时发送 reasoning.enabled=true;普通 OpenAI 端点保持默认行为',
193
- },
226
+ ...(knownFormat === 'openai-responses'
227
+ ? []
228
+ : [
229
+ {
230
+ key: 'reasoning',
231
+ label: '启用 Reasoning(Provider 支持时)',
232
+ type: 'boolean' as const,
233
+ default: existing?.reasoning ?? preset.reasoning ?? false,
234
+ hint: '仅显式启用时发送 reasoning.enabled=true;普通 OpenAI 端点保持默认行为',
235
+ },
236
+ ]),
194
237
  );
195
238
 
196
239
  return (
@@ -225,7 +268,12 @@ export function ProviderWizard({
225
268
  defaultMaxTokens(contextWindow),
226
269
  )
227
270
  : undefined;
228
- const apiFormat: ApiFormat = existing?.apiFormat ?? 'openai';
271
+ const apiFormat: ApiFormat = askFormat
272
+ ? (API_FORMAT_CHOICES.find((item) => item === values.apiFormat) ?? knownFormat)
273
+ : knownFormat;
274
+ const reasoningEffort = showEffort
275
+ ? reasoningEffortFromChoice(values.reasoningEffort)
276
+ : existing?.reasoningEffort;
229
277
  const draft = {
230
278
  apiFormat,
231
279
  displayName:
@@ -238,6 +286,7 @@ export function ProviderWizard({
238
286
  contextWindow,
239
287
  ...(maxTokens ? { maxTokens } : {}),
240
288
  supportsVision: existing?.supportsVision !== false,
289
+ ...(reasoningEffort ? { reasoningEffort } : {}),
241
290
  reasoning:
242
291
  quick && !advanced
243
292
  ? (preset.reasoning ?? false)
@@ -253,7 +302,8 @@ export function ProviderWizard({
253
302
  if (!diagnostic.ok) throw new Error(diagnostic.message);
254
303
  if (existing) {
255
304
  await configService.savePatch({
256
- models: [{ op: 'update', id: existing.id, patch: draft }],
305
+ // null = 清除之前设的强度(选了「不设置」);不带这个字段则保留旧值。
306
+ models: [{ op: 'update', id: existing.id, patch: { ...draft, reasoningEffort: reasoningEffort ?? null } }],
257
307
  ...(setAsBase ? { baseModelId: existing.id } : {}),
258
308
  });
259
309
  } else {
@@ -61,9 +61,9 @@ export const DEFAULT_ADVISOR_CONFIG = {
61
61
  // 顾问会增加额外模型调用和延迟;必须由用户显式开启。
62
62
  enabled: false,
63
63
  mode: 'auto',
64
+ enforcement: 'remind',
64
65
  modelId: null,
65
66
  contextPolicy: 'full-relevant',
66
- shareSensitive: false,
67
67
  inputTokenBudget: 2_000_000,
68
68
  timeoutMs: 120_000,
69
69
  };
@@ -72,9 +72,9 @@ export const DEFAULT_ADVISOR_CONFIG: AdvisorConfig = {
72
72
  // 顾问会增加额外模型调用和延迟;必须由用户显式开启。
73
73
  enabled: false,
74
74
  mode: 'auto',
75
+ enforcement: 'remind',
75
76
  modelId: null,
76
77
  contextPolicy: 'full-relevant',
77
- shareSensitive: false,
78
78
  inputTokenBudget: 2_000_000,
79
79
  timeoutMs: 120_000,
80
80
  };
@@ -1,5 +1,3 @@
1
- import { resolveSshHost } from '../context/sshHosts.js';
2
- import { defaultSshConnect } from '../tools/ssh/sshClient.js';
3
1
  import { configService } from './configService.js';
4
2
  function providerFromModel(model) {
5
3
  return {
@@ -13,6 +11,7 @@ function providerFromModel(model) {
13
11
  supportsVision: model.supportsVision !== false,
14
12
  promptCache: model.promptCache ?? { mode: 'auto' },
15
13
  ...(model.reasoning === true ? { reasoning: true } : {}),
14
+ ...(model.reasoningEffort ? { reasoningEffort: model.reasoningEffort } : {}),
16
15
  };
17
16
  }
18
17
  export async function testModelConnection(model, timeoutMs = 45_000) {
@@ -119,22 +118,3 @@ export async function testToolConnection(tool, service = configService) {
119
118
  clearTimeout(timer);
120
119
  }
121
120
  }
122
- /** 测试当前工作区某个 SSH 主机是否能建立连接(只连不执行任何命令,连上即断开)。 */
123
- export async function testSshHostConnection(workspaceId, alias) {
124
- const resolved = await resolveSshHost(workspaceId, alias);
125
- if (!resolved.ok)
126
- return { ok: false, code: 'missing-config', message: resolved.error };
127
- try {
128
- const connection = await defaultSshConnect(resolved.host, 10_000);
129
- connection.end();
130
- return { ok: true, code: 'ok', message: `已成功连接 ${resolved.host.username}@${resolved.host.host}:${resolved.host.port}` };
131
- }
132
- catch (error) {
133
- const message = error instanceof Error ? error.message : String(error);
134
- return {
135
- ok: false,
136
- code: /timeout|ETIMEDOUT/i.test(message) ? 'timeout' : 'network',
137
- message: `连接失败:${message}`,
138
- };
139
- }
140
- }
@@ -1,6 +1,4 @@
1
1
  import type { LocalProvider } from '../types.js';
2
- import { resolveSshHost } from '../context/sshHosts.js';
3
- import { defaultSshConnect } from '../tools/ssh/sshClient.js';
4
2
  import type { ModelConfig } from './configFile.js';
5
3
  import { type ConfigService, configService } from './configService.js';
6
4
 
@@ -29,6 +27,7 @@ function providerFromModel(
29
27
  | 'supportsVision'
30
28
  | 'promptCache'
31
29
  | 'reasoning'
30
+ | 'reasoningEffort'
32
31
  >,
33
32
  ): LocalProvider {
34
33
  return {
@@ -42,6 +41,7 @@ function providerFromModel(
42
41
  supportsVision: model.supportsVision !== false,
43
42
  promptCache: model.promptCache ?? { mode: 'auto' },
44
43
  ...(model.reasoning === true ? { reasoning: true } : {}),
44
+ ...(model.reasoningEffort ? { reasoningEffort: model.reasoningEffort } : {}),
45
45
  };
46
46
  }
47
47
 
@@ -58,6 +58,7 @@ export async function testModelConnection(
58
58
  | 'supportsVision'
59
59
  | 'promptCache'
60
60
  | 'reasoning'
61
+ | 'reasoningEffort'
61
62
  >,
62
63
  timeoutMs = 45_000,
63
64
  ): Promise<ConfigDiagnosticResult> {
@@ -170,24 +171,3 @@ export async function testToolConnection(
170
171
  clearTimeout(timer);
171
172
  }
172
173
  }
173
-
174
- /** 测试当前工作区某个 SSH 主机是否能建立连接(只连不执行任何命令,连上即断开)。 */
175
- export async function testSshHostConnection(
176
- workspaceId: string,
177
- alias: string,
178
- ): Promise<ConfigDiagnosticResult> {
179
- const resolved = await resolveSshHost(workspaceId, alias);
180
- if (!resolved.ok) return { ok: false, code: 'missing-config', message: resolved.error };
181
- try {
182
- const connection = await defaultSshConnect(resolved.host, 10_000);
183
- connection.end();
184
- return { ok: true, code: 'ok', message: `已成功连接 ${resolved.host.username}@${resolved.host.host}:${resolved.host.port}` };
185
- } catch (error) {
186
- const message = error instanceof Error ? error.message : String(error);
187
- return {
188
- ok: false,
189
- code: /timeout|ETIMEDOUT/i.test(message) ? 'timeout' : 'network',
190
- message: `连接失败:${message}`,
191
- };
192
- }
193
- }