@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/bin/manyoyo.js +12 -12
- package/lib/agent-adapters/index.js +23 -6
- package/lib/container-network.js +1 -0
- package/lib/init-config.js +4 -1
- package/lib/json5-text-edit.js +51 -0
- package/lib/model-service-containers.js +63 -0
- package/lib/model-service-import.js +98 -0
- package/lib/model-services.js +313 -0
- package/lib/name-template.js +52 -0
- package/lib/provider-presets.js +45 -15
- package/lib/runtime-resolver.js +13 -1
- package/lib/setup-cli.js +18 -5
- package/lib/setup-config.js +6 -1
- package/lib/setup.js +52 -5
- package/lib/web/index.html +87 -85
- package/lib/web/model-services.js +248 -0
- package/lib/web/server.js +274 -84
- package/lib/web/storage.js +444 -0
- package/manyoyo.example.json +3 -1
- package/package.json +3 -1
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
|
-
//
|
|
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
|
-
|
|
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
|