@xcanwin/manyoyo 8.6.12 → 8.7.1

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/lib/setup.js CHANGED
@@ -3,6 +3,8 @@
3
3
  const { AGENT_ENV_SCHEMA } = require('./init-config');
4
4
  const { parseEnvEntry } = require('./runtime-normalizers');
5
5
  const { providersForAgent } = require('./provider-presets');
6
+ const { findAdapterByAlias } = require('./agent-adapters');
7
+ const { resolveAgentProgram } = require('./agent-resume');
6
8
 
7
9
  const SETUP_AGENTS = Object.keys(AGENT_ENV_SCHEMA);
8
10
 
@@ -32,10 +34,31 @@ function hasNonEmptyRequiredKey(spec, env) {
32
34
  return spec.requiredAnyOf.some(key => typeof source[key] === 'string' && source[key].trim() !== '');
33
35
  }
34
36
 
35
- // 已有可用 runs.<agent>:run 存在,且必填变量(任意一个)非空
37
+ // 运行配置属于哪个 Agent:yolo → shell / agentPromptCommand 里的程序 → 名字前缀;认不出返回空串
38
+ function identifyRunAgent(name, run) {
39
+ if (!isPlainObject(run)) return '';
40
+ const profile = run;
41
+ const byYolo = findAdapterByAlias(profile.yolo);
42
+ if (byYolo && AGENT_ENV_SCHEMA[byYolo.id]) return byYolo.id;
43
+ for (const text of [profile.shell, profile.agentPromptCommand]) {
44
+ if (typeof text !== 'string' || !text.trim()) continue;
45
+ const program = resolveAgentProgram(text);
46
+ if (AGENT_ENV_SCHEMA[program]) return program;
47
+ }
48
+ const prefix = String(name || '').toLowerCase().split(/[-_.\s]/)[0];
49
+ const byName = findAdapterByAlias(prefix);
50
+ return byName && AGENT_ENV_SCHEMA[byName.id] ? byName.id : '';
51
+ }
52
+
53
+ // 已有可用的运行配置:不论叫什么名字,能认出 Agent 且必填变量(任意一个)非空
36
54
  function getConfiguredAgents(config) {
37
55
  const runs = isPlainObject(config && config.runs) ? config.runs : {};
38
- return SETUP_AGENTS.filter(id => isPlainObject(runs[id]) && hasNonEmptyRequiredKey(AGENT_ENV_SCHEMA[id], runs[id].env));
56
+ const found = new Set();
57
+ for (const [name, run] of Object.entries(runs)) {
58
+ const agent = identifyRunAgent(name, run);
59
+ if (agent && hasNonEmptyRequiredKey(AGENT_ENV_SCHEMA[agent], run.env)) found.add(agent);
60
+ }
61
+ return SETUP_AGENTS.filter(id => found.has(id));
39
62
  }
40
63
 
41
64
  /**
@@ -70,13 +93,18 @@ function buildAgentRunProfile(agent, envInput, options = {}) {
70
93
  const existingEnv = isPlainObject(existing.env) ? existing.env : {};
71
94
  const profile = { ...existing };
72
95
  profile.env = { ...existingEnv, ...provided };
96
+ // 换成订阅令牌时,同一个运行配置里的 API Key 类变量和 Base URL 要删掉:Claude Code 同时有 Key 时优先用 Key,订阅就被绕过了
97
+ if (agent === 'claude' && provided.CLAUDE_CODE_OAUTH_TOKEN && !['ANTHROPIC_AUTH_TOKEN', 'ANTHROPIC_API_KEY'].some(key => provided[key])) {
98
+ for (const key of ['ANTHROPIC_AUTH_TOKEN', 'ANTHROPIC_API_KEY', 'ANTHROPIC_BASE_URL']) delete profile.env[key];
99
+ }
73
100
  if (!hasNonEmptyRequiredKey(spec, profile.env)) {
74
101
  throw new Error(`请填写 ${spec.requiredAnyOf.join(' 或 ')}`);
75
102
  }
76
103
  if (profile.containerName === undefined) profile.containerName = `my-${agent}-{now}`;
77
104
  if (profile.yolo === undefined) profile.yolo = spec.yolo;
105
+ // 只写用户选定的目录。默认的工作目录根不写:新建容器按 {quickChatPath}/{now} 各建一个子目录,
106
+ // 写了根目录,对话框里目录是空白、每个新容器都会把整个工作目录根挂进去
78
107
  if (options.hostPath) profile.hostPath = options.hostPath;
79
- else if (profile.hostPath === undefined && options.defaultHostPath) profile.hostPath = options.defaultHostPath;
80
108
  return profile;
81
109
  }
82
110
 
@@ -86,6 +114,16 @@ const CONNECTION_RULES = [
86
114
  pattern: /\b(401|403)\b|unauthori[sz]ed|invalid[ _-]?(x-)?api[ _-]?key|incorrect api key|authentication[ _](failed|error)|forbidden|permission_denied|invalid[ _]token/i,
87
115
  message: '认证失败:Key 或令牌无效,请检查后重新填写'
88
116
  },
117
+ {
118
+ category: 'quota',
119
+ pattern: /\b429\b|rate[ _-]?limit|too many requests|insufficient[ _](quota|balance)|quota exceeded|余额不足|无可用资源包|\b1113\b/i,
120
+ message: '额度不足或请求太频繁:请检查账户余额 / 套餐额度;套餐 Key 不能用在按量计费的地址上,反过来也一样'
121
+ },
122
+ {
123
+ category: 'model',
124
+ pattern: /model(?! metadata)[^\n]{0,80}(not found|does not exist|not exist|not supported|unknown)|(unknown|invalid|unsupported) model|no such model|模型[^\n]{0,20}(不存在|不支持)/i,
125
+ message: '模型名不存在或该 Key 不能用这个模型,请检查模型名'
126
+ },
89
127
  {
90
128
  category: 'network',
91
129
  pattern: /ENOTFOUND|ECONNREFUSED|ECONNRESET|ETIMEDOUT|EAI_AGAIN|EHOSTUNREACH|ENETUNREACH|fetch failed|getaddrinfo|could not resolve|network (is )?unreachable|connection (refused|reset|timed out)|socket hang up|unable to connect/i,
@@ -94,13 +132,21 @@ const CONNECTION_RULES = [
94
132
  ];
95
133
 
96
134
  /**
97
- * 把容器内最小请求的结果归类为 success / network / auth / missing_cli / other。
135
+ * 把容器内最小请求的结果归类为 success / no_tools / quota / model / network / auth / missing_cli / other。
98
136
  * 返回值只含固定文案和经过脱敏的尾部片段,不回传完整输出。
99
137
  */
100
- function classifyConnectionResult(result, secrets = []) {
138
+ function classifyConnectionResult(result, secrets = [], options = {}) {
101
139
  const exitCode = result && Number.isInteger(result.exitCode) ? result.exitCode : 1;
102
140
  const output = String(result && result.output || '');
103
141
  if (exitCode === 0) {
142
+ // 只回一句 OK 发现不了“能连通但调不了工具”,测试要求执行一条命令并读回结果
143
+ if (options.expectOutput && !output.includes(options.expectOutput)) {
144
+ return {
145
+ category: 'no_tools',
146
+ message: '能连通,但模型没有执行命令:Agent 在这个服务上可能调不了工具,换个模型或服务商再试',
147
+ detail: redactSecrets(output.trim().slice(-300), secrets)
148
+ };
149
+ }
104
150
  return { category: 'success', message: '连接成功', detail: '' };
105
151
  }
106
152
  const detail = redactSecrets(output.trim().slice(-300), secrets);
@@ -188,6 +234,7 @@ module.exports = {
188
234
  SETUP_AGENTS,
189
235
  listSetupAgents,
190
236
  getConfiguredAgents,
237
+ identifyRunAgent,
191
238
  buildAgentRunProfile,
192
239
  classifyConnectionResult,
193
240
  redactSecrets