@xcanwin/manyoyo 8.6.0 → 8.6.2
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/container-exec.js +20 -1
- package/lib/web/container-manage.js +16 -0
- package/lib/web/index.html +80 -80
- package/lib/web/server.js +91 -7
- package/package.json +1 -1
package/lib/web/server.js
CHANGED
|
@@ -13,7 +13,7 @@ const { buildContainerRunArgs, hasNetworkArg } = require('../container-run');
|
|
|
13
13
|
const { buildContainerIntegration, mergeIntegration } = require('../plugin');
|
|
14
14
|
const { applyAptMirror } = require('../mirrors');
|
|
15
15
|
const containerState = require('../container-state');
|
|
16
|
-
const { buildExecArgs, resolveContainerId } = require('../container-exec');
|
|
16
|
+
const { buildExecArgs, resolveContainerId, readMergedEnv } = require('../container-exec');
|
|
17
17
|
const { createNetworkManager, NETWORK_NAME } = require('../container-network');
|
|
18
18
|
const { normalizePolicy, isUnrestricted: isUnrestrictedPolicy } = require('../network-policy');
|
|
19
19
|
const { withEnvEndpoints } = require('../network-endpoints');
|
|
@@ -829,7 +829,7 @@ function buildCodexAgentExecCommand(template, prompt, options = {}) {
|
|
|
829
829
|
const hasJson = /(?:^|\s)--json(?:\s|$)/.test(suffix);
|
|
830
830
|
const injectedFlags = hasJson ? '' : ' --json';
|
|
831
831
|
const hasModel = /(?:^|\s)(?:-m|--model)(?:\s|$)/.test(suffix);
|
|
832
|
-
const modelFlag = model && !hasModel ? ` --model ${model}` : '';
|
|
832
|
+
const modelFlag = model && !hasModel ? ` --model ${quoteBashSingleValue(model)}` : '';
|
|
833
833
|
if (sessionId) {
|
|
834
834
|
const promptIndex = suffix.indexOf('{prompt}');
|
|
835
835
|
const resumeSuffix = `${suffix.slice(0, promptIndex)}${quoteBashSingleValue(sessionId)} ${suffix.slice(promptIndex)}`;
|
|
@@ -877,7 +877,7 @@ function buildClaudeAgentExecCommand(template, prompt, options = {}) {
|
|
|
877
877
|
}
|
|
878
878
|
const model = options && typeof options.model === 'string' ? options.model.trim() : '';
|
|
879
879
|
if (model) {
|
|
880
|
-
flagSpecs.push({ flag: `--model ${model}`, pattern: /(?:^|\s)--model(?:\s|$)/ });
|
|
880
|
+
flagSpecs.push({ flag: `--model ${quoteBashSingleValue(model)}`, pattern: /(?:^|\s)--model(?:\s|$)/ });
|
|
881
881
|
}
|
|
882
882
|
const claudeTemplate = prependAgentFlags(
|
|
883
883
|
templateText,
|
|
@@ -896,7 +896,7 @@ function buildGeminiAgentExecCommand(template, prompt, options = {}) {
|
|
|
896
896
|
];
|
|
897
897
|
const model = options && typeof options.model === 'string' ? options.model.trim() : '';
|
|
898
898
|
if (model) {
|
|
899
|
-
flagSpecs.push({ flag: `--model ${model}`, pattern: /(?:^|\s)(?:-m|--model)(?:\s|$)/ });
|
|
899
|
+
flagSpecs.push({ flag: `--model ${quoteBashSingleValue(model)}`, pattern: /(?:^|\s)(?:-m|--model)(?:\s|$)/ });
|
|
900
900
|
}
|
|
901
901
|
const geminiTemplate = prependAgentFlags(
|
|
902
902
|
templateText,
|
|
@@ -922,7 +922,7 @@ function buildOpenCodeAgentExecCommand(template, prompt, options = {}) {
|
|
|
922
922
|
}
|
|
923
923
|
const model = options && typeof options.model === 'string' ? options.model.trim() : '';
|
|
924
924
|
if (model) {
|
|
925
|
-
flagSpecs.push({ flag: `--model ${model}`, pattern: /(?:^|\s)(?:-m|--model)(?:\s|$)/ });
|
|
925
|
+
flagSpecs.push({ flag: `--model ${quoteBashSingleValue(model)}`, pattern: /(?:^|\s)(?:-m|--model)(?:\s|$)/ });
|
|
926
926
|
}
|
|
927
927
|
const opencodeTemplate = prependAgentFlags(
|
|
928
928
|
templateText,
|
|
@@ -1319,6 +1319,61 @@ function extractOpenCodeModelsFromOutput(text) {
|
|
|
1319
1319
|
.map(value => ({ value, label: value, description: '' }));
|
|
1320
1320
|
}
|
|
1321
1321
|
|
|
1322
|
+
// 模型目录要在容器里起一个 Agent 进程才能问到,很慢:按“容器 + Agent 程序”缓存,
|
|
1323
|
+
// 并合并同一时刻的重复请求。缓存挂在 ctx 上(随 serve 实例走),不放模块级,避免测试之间串味
|
|
1324
|
+
const MODEL_CATALOG_TTL_MS = 10 * 60 * 1000;
|
|
1325
|
+
const modelCatalogCaches = new WeakMap();
|
|
1326
|
+
|
|
1327
|
+
async function fetchAgentModelCatalogCached(ctx, containerName, agentProgram) {
|
|
1328
|
+
let cache = modelCatalogCaches.get(ctx);
|
|
1329
|
+
if (!cache) {
|
|
1330
|
+
cache = new Map();
|
|
1331
|
+
modelCatalogCaches.set(ctx, cache);
|
|
1332
|
+
}
|
|
1333
|
+
const key = `${containerName}\u0000${agentProgram}`;
|
|
1334
|
+
const hit = cache.get(key);
|
|
1335
|
+
if (hit && (hit.pending || Date.now() - hit.at < MODEL_CATALOG_TTL_MS)) {
|
|
1336
|
+
return hit.pending || hit.models;
|
|
1337
|
+
}
|
|
1338
|
+
const pending = fetchAgentModelCatalog(ctx, containerName, agentProgram).then(models => {
|
|
1339
|
+
// 探测失败(超时 / 容器没起来)返回空数组,不缓存,下次还能重试
|
|
1340
|
+
if (models.length) cache.set(key, { at: Date.now(), models });
|
|
1341
|
+
else cache.delete(key);
|
|
1342
|
+
return models;
|
|
1343
|
+
}, error => {
|
|
1344
|
+
cache.delete(key);
|
|
1345
|
+
throw error;
|
|
1346
|
+
});
|
|
1347
|
+
cache.set(key, { pending });
|
|
1348
|
+
return pending;
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
// 以会话实际配置的 env 生成模型选项。defaultModel 是“跟随默认”实际用的模型;
|
|
1352
|
+
// Claude 另有按档位别名(opus / sonnet / haiku …)一行一个的选项:value 是别名(--model opus 由 CLI 按 env 解析),
|
|
1353
|
+
// label 是该档位对应的真实模型名。不同档位指向同一个模型也各占一行,不合并。
|
|
1354
|
+
// 只读 *_MODEL 这类非敏感键,容器可写的 env 只用来展示,不参与任何安全判断
|
|
1355
|
+
const CLAUDE_TIER_ORDER = ['opus', 'fable', 'sonnet', 'haiku'];
|
|
1356
|
+
|
|
1357
|
+
function buildEnvModelOptions(agentProgram, env) {
|
|
1358
|
+
const source = env && typeof env === 'object' ? env : {};
|
|
1359
|
+
const defaultKey = { claude: 'ANTHROPIC_MODEL', codex: 'OPENAI_MODEL', opencode: 'OPENAI_MODEL', gemini: 'GEMINI_MODEL' }[agentProgram];
|
|
1360
|
+
if (!defaultKey) return { defaultModel: '', options: [] };
|
|
1361
|
+
const defaultModel = normalizeStoredAgentModelValue(source[defaultKey]);
|
|
1362
|
+
if (agentProgram !== 'claude') return { defaultModel, options: [] };
|
|
1363
|
+
const tiers = [];
|
|
1364
|
+
Object.keys(source).forEach(key => {
|
|
1365
|
+
const matched = key.match(/^ANTHROPIC_DEFAULT_([A-Z0-9]+)_MODEL$/);
|
|
1366
|
+
const model = matched ? normalizeStoredAgentModelValue(source[key]) : '';
|
|
1367
|
+
if (model) tiers.push({ alias: matched[1].toLowerCase(), model });
|
|
1368
|
+
});
|
|
1369
|
+
const rank = alias => (CLAUDE_TIER_ORDER.includes(alias) ? CLAUDE_TIER_ORDER.indexOf(alias) : CLAUDE_TIER_ORDER.length);
|
|
1370
|
+
tiers.sort((x, y) => rank(x.alias) - rank(y.alias) || x.alias.localeCompare(y.alias));
|
|
1371
|
+
return {
|
|
1372
|
+
defaultModel,
|
|
1373
|
+
options: tiers.map(tier => ({ value: tier.alias, label: tier.model, description: tier.alias }))
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1322
1377
|
async function fetchAgentModelCatalog(ctx, containerName, agentProgram) {
|
|
1323
1378
|
if (agentProgram === 'claude') {
|
|
1324
1379
|
const requestLine = JSON.stringify({
|
|
@@ -5950,7 +6005,7 @@ async function handleWebApi(req, res, pathname, ctx, state) {
|
|
|
5950
6005
|
containerInfo.defaultCommand
|
|
5951
6006
|
);
|
|
5952
6007
|
const agentProgram = resolveAgentProgram(effectiveTemplate);
|
|
5953
|
-
const models = await
|
|
6008
|
+
const models = await fetchAgentModelCatalogCached(ctx, sessionRef.containerName, agentProgram);
|
|
5954
6009
|
sendJson(res, 200, {
|
|
5955
6010
|
name: buildWebSessionKey(sessionRef.containerName, sessionRef.agentId),
|
|
5956
6011
|
agentProgram,
|
|
@@ -5958,6 +6013,34 @@ async function handleWebApi(req, res, pathname, ctx, state) {
|
|
|
5958
6013
|
});
|
|
5959
6014
|
}
|
|
5960
6015
|
},
|
|
6016
|
+
{
|
|
6017
|
+
// 毫秒级:只读宿主机上的 env 文件,不进容器;慢的 CLI 目录探测走上面的 /models
|
|
6018
|
+
method: 'GET',
|
|
6019
|
+
match: currentPath => currentPath.match(/^\/api\/sessions\/([^/]+)\/model-options$/),
|
|
6020
|
+
handler: async match => {
|
|
6021
|
+
const sessionRef = getValidSessionRef(ctx, res, match[1]);
|
|
6022
|
+
if (!sessionRef) {
|
|
6023
|
+
return;
|
|
6024
|
+
}
|
|
6025
|
+
const containerMap = listWebManyoyoContainers(ctx);
|
|
6026
|
+
const containerInfo = containerMap[sessionRef.containerName] || {};
|
|
6027
|
+
const history = loadWebSessionHistory(state.webHistoryDir, sessionRef.containerName);
|
|
6028
|
+
const agentProgram = resolveAgentProgram(resolveEffectiveAgentPromptCommandForSession(
|
|
6029
|
+
history,
|
|
6030
|
+
sessionRef.agentId,
|
|
6031
|
+
containerInfo.defaultCommand
|
|
6032
|
+
));
|
|
6033
|
+
let env = {};
|
|
6034
|
+
try {
|
|
6035
|
+
env = readMergedEnv({ homeDir: ctx.homeDir || os.homedir(), dockerExecArgs: ctx.dockerExecArgs }, sessionRef.containerName);
|
|
6036
|
+
} catch (e) { /* 读不到 env 就只给空选项 */ }
|
|
6037
|
+
sendJson(res, 200, {
|
|
6038
|
+
name: buildWebSessionKey(sessionRef.containerName, sessionRef.agentId),
|
|
6039
|
+
agentProgram,
|
|
6040
|
+
...buildEnvModelOptions(agentProgram, env)
|
|
6041
|
+
});
|
|
6042
|
+
}
|
|
6043
|
+
},
|
|
5961
6044
|
{
|
|
5962
6045
|
method: 'POST',
|
|
5963
6046
|
match: currentPath => currentPath.match(/^\/api\/sessions\/([^/]+)\/model$/),
|
|
@@ -6872,5 +6955,6 @@ async function startWebServer(options) {
|
|
|
6872
6955
|
}
|
|
6873
6956
|
|
|
6874
6957
|
module.exports = {
|
|
6875
|
-
startWebServer
|
|
6958
|
+
startWebServer,
|
|
6959
|
+
buildEnvModelOptions
|
|
6876
6960
|
};
|