@xcanwin/manyoyo 8.6.9 → 8.6.10
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 +41 -4
- package/lib/agent-instructions.js +30 -7
- package/lib/app-update.js +20 -8
- package/lib/code-update.js +165 -0
- package/lib/container-state.js +21 -1
- package/lib/setup-config.js +5 -1
- package/lib/web/AGENTS.md +1 -1
- package/lib/web/container-manage.js +33 -0
- package/lib/web/index.html +91 -91
- package/lib/web/server.js +106 -17
- package/manyoyo.example.json +4 -1
- package/package.json +1 -1
package/lib/web/server.js
CHANGED
|
@@ -12,7 +12,7 @@ const JSON5 = require('json5');
|
|
|
12
12
|
const { buildContainerRunArgs, hasNetworkArg } = require('../container-run');
|
|
13
13
|
const { buildContainerIntegration, mergeIntegration } = require('../plugin');
|
|
14
14
|
const { applyAptMirror } = require('../mirrors');
|
|
15
|
-
const { BUILTIN_AGENT_INSTRUCTIONS, buildAgentInstructions, toTomlBasicString } = require('../agent-instructions');
|
|
15
|
+
const { BUILTIN_AGENT_INSTRUCTIONS, buildAgentInstructions, composeSystemPrompt, SYSTEM_PROMPT_MAX_CHARS, toTomlBasicString } = require('../agent-instructions');
|
|
16
16
|
const containerState = require('../container-state');
|
|
17
17
|
const sessionMeta = require('./session-meta');
|
|
18
18
|
const gitChanges = require('./git-changes');
|
|
@@ -37,7 +37,7 @@ const { resolveRuntimeConfig } = require('../runtime-resolver');
|
|
|
37
37
|
const { AGENT_ENV_SCHEMA } = require('../init-config');
|
|
38
38
|
const { ensureImagePresent, pullImageProcess } = require('../image-pull');
|
|
39
39
|
const { waitForImport } = require('../offline-import');
|
|
40
|
-
const { baseRawOf, buildAgentConfigRaw, buildPasswordConfigRaw, shouldWriteMirrors, buildMirrorsConfigRaw, buildAgentInstructionsConfigRaw } = require('../setup-config');
|
|
40
|
+
const { baseRawOf, buildAgentConfigRaw, buildPasswordConfigRaw, shouldWriteMirrors, buildMirrorsConfigRaw, buildAgentInstructionsConfigRaw, buildBooleanConfigRaw } = require('../setup-config');
|
|
41
41
|
const { listSetupAgents, getConfiguredAgents, buildAgentRunProfile, classifyConnectionResult, redactSecrets, validateSetupPassword, listMirrorPresets } = require('../setup');
|
|
42
42
|
const { applyDoctorFixes } = require('../doctor');
|
|
43
43
|
const { writeConfigFileSecure } = require('../secure-file');
|
|
@@ -2387,7 +2387,11 @@ async function prepareWebAgentExecution(ctx, state, sessionRef, prompt) {
|
|
|
2387
2387
|
sessionId: sessionIdForRun,
|
|
2388
2388
|
model: agentSession.model || '',
|
|
2389
2389
|
forwardSubagentText: agentMeta.agentProgram === 'claude' ? 'auto' : false,
|
|
2390
|
-
instructions: buildAgentInstructions(
|
|
2390
|
+
instructions: buildAgentInstructions(
|
|
2391
|
+
configSnapshot.parseError ? {} : configSnapshot.parsed,
|
|
2392
|
+
runName,
|
|
2393
|
+
readContainerSystemPrompt(ctx, sessionRef.containerName)
|
|
2394
|
+
)
|
|
2391
2395
|
});
|
|
2392
2396
|
const contextMode = resumeSucceeded ? 'resume' : (hasPriorConversation ? 'history-injected' : 'first-turn');
|
|
2393
2397
|
|
|
@@ -2971,6 +2975,11 @@ function validateWebConfigShape(configObject) {
|
|
|
2971
2975
|
if (hasOwn(config, 'agentInstructions') && typeof config.agentInstructions !== 'string') {
|
|
2972
2976
|
throw new Error('agentInstructions 必须是字符串');
|
|
2973
2977
|
}
|
|
2978
|
+
['agentInstructionsBuiltinEnabled', 'agentInstructionsCustomEnabled'].forEach(key => {
|
|
2979
|
+
if (hasOwn(config, key) && typeof config[key] !== 'boolean') {
|
|
2980
|
+
throw new Error(`${key} 必须是布尔值`);
|
|
2981
|
+
}
|
|
2982
|
+
});
|
|
2974
2983
|
if (hasOwn(config, 'runs')) {
|
|
2975
2984
|
const runs = config.runs;
|
|
2976
2985
|
if (runs !== undefined && (typeof runs !== 'object' || runs === null || Array.isArray(runs))) {
|
|
@@ -3196,6 +3205,10 @@ function buildCreateRuntime(ctx, state, payload) {
|
|
|
3196
3205
|
throw new Error('该网络设置会放开限制(自定义模式、很宽的允许规则或绑定非本机地址),需要确认(confirmRisk)');
|
|
3197
3206
|
}
|
|
3198
3207
|
|
|
3208
|
+
// 系统提示词:请求里填了就用它(创建时定下),否则按系统设置里的默认 + 补充(各自的开关生效)
|
|
3209
|
+
const systemPrompt = normalizeSystemPromptInput(requestOptions.systemPrompt)
|
|
3210
|
+
|| { enabled: true, text: composeSystemPrompt(config, runName) };
|
|
3211
|
+
|
|
3199
3212
|
const containerName = resolvedBase.containerName;
|
|
3200
3213
|
validateContainerNameStrict(containerName);
|
|
3201
3214
|
|
|
@@ -3313,6 +3326,7 @@ function buildCreateRuntime(ctx, state, payload) {
|
|
|
3313
3326
|
containerPorts,
|
|
3314
3327
|
autostart: manage.autostart,
|
|
3315
3328
|
network: manage.network,
|
|
3329
|
+
systemPrompt,
|
|
3316
3330
|
agentPromptCommand,
|
|
3317
3331
|
defaultCommand: buildDefaultCommand(shellPrefix, shell, shellSuffix) || '/bin/bash',
|
|
3318
3332
|
// 仅供服务端内部复用(如"创建相同配置容器"/"复制容器"),绝不能透传给任何对外响应字段,
|
|
@@ -3334,6 +3348,7 @@ function buildCreateRuntime(ctx, state, payload) {
|
|
|
3334
3348
|
volumes: resolvedBase.volumes || [],
|
|
3335
3349
|
ports: resolvedBase.ports || [],
|
|
3336
3350
|
autostart: manage.autostart,
|
|
3351
|
+
systemPrompt,
|
|
3337
3352
|
// 复制容器时端口暴露(宿主机端口会冲突)与 peers(按 id 引用别的容器)不带过去
|
|
3338
3353
|
network: { ...manage.network, expose: [], peers: { inbound: [] } }
|
|
3339
3354
|
},
|
|
@@ -3358,6 +3373,27 @@ function buildCreateRuntime(ctx, state, payload) {
|
|
|
3358
3373
|
};
|
|
3359
3374
|
}
|
|
3360
3375
|
|
|
3376
|
+
// 容器自己存的系统提示词;旧容器(没有 id 或没存过)返回 null,按系统设置现算
|
|
3377
|
+
function readContainerSystemPrompt(ctx, containerName) {
|
|
3378
|
+
try {
|
|
3379
|
+
const homeDir = ctx.homeDir || os.homedir();
|
|
3380
|
+
const id = resolveContainerId(ctx.dockerExecArgs, containerName);
|
|
3381
|
+
return id && containerState.stateExists(homeDir, id) ? containerState.readSystemPrompt(homeDir, id) : null;
|
|
3382
|
+
} catch (e) {
|
|
3383
|
+
return null;
|
|
3384
|
+
}
|
|
3385
|
+
}
|
|
3386
|
+
|
|
3387
|
+
// 请求里的系统提示词 {enabled, text};没给返回 null,给了但格式不对抛错
|
|
3388
|
+
function normalizeSystemPromptInput(value) {
|
|
3389
|
+
if (value === undefined || value === null) return null;
|
|
3390
|
+
if (typeof value !== 'object' || Array.isArray(value) || typeof value.text !== 'string'
|
|
3391
|
+
|| (value.enabled !== undefined && typeof value.enabled !== 'boolean')) {
|
|
3392
|
+
throw new Error('systemPrompt 必须是 {enabled, text}');
|
|
3393
|
+
}
|
|
3394
|
+
return { enabled: value.enabled !== false, text: value.text.slice(0, SYSTEM_PROMPT_MAX_CHARS) };
|
|
3395
|
+
}
|
|
3396
|
+
|
|
3361
3397
|
function resolveUniqueContainerName(ctx, state, baseName) {
|
|
3362
3398
|
const containerMap = listWebManyoyoContainers(ctx);
|
|
3363
3399
|
const historyNames = listWebHistorySessionNames(state.webHistoryDir, ctx.isValidContainerName);
|
|
@@ -3397,6 +3433,7 @@ function buildCloneCreateOptionsFromSnapshot(runtimeSnapshot, fallbackHistory) {
|
|
|
3397
3433
|
volumes: runtimeSnapshot.volumes || [],
|
|
3398
3434
|
ports: runtimeSnapshot.ports || [],
|
|
3399
3435
|
autostart: runtimeSnapshot.autostart || '',
|
|
3436
|
+
systemPrompt: runtimeSnapshot.systemPrompt || undefined,
|
|
3400
3437
|
network: runtimeSnapshot.network || undefined,
|
|
3401
3438
|
// 快照来自服务端、原容器创建时已确认过,复制不必再确认
|
|
3402
3439
|
confirmRisk: runtimeSnapshot.network ? true : undefined
|
|
@@ -3877,6 +3914,7 @@ async function ensureWebContainer(ctx, state, containerInput, messageSessionRef
|
|
|
3877
3914
|
envLines: userEnvLines,
|
|
3878
3915
|
envFiles: runtime.envFiles,
|
|
3879
3916
|
autostart: runtime.autostart,
|
|
3917
|
+
systemPrompt: runtime.systemPrompt,
|
|
3880
3918
|
network: networkPolicy,
|
|
3881
3919
|
netRequired: !isUnrestrictedPolicy(networkPolicy),
|
|
3882
3920
|
meta: { name: runtime.containerName }
|
|
@@ -6390,7 +6428,11 @@ async function handleWebApi(req, res, pathname, ctx, state) {
|
|
|
6390
6428
|
const config = snapshot.parseError ? {} : snapshot.parsed;
|
|
6391
6429
|
sendJson(res, 200, {
|
|
6392
6430
|
builtin: BUILTIN_AGENT_INSTRUCTIONS,
|
|
6393
|
-
custom: typeof config.agentInstructions === 'string' ? config.agentInstructions : ''
|
|
6431
|
+
custom: typeof config.agentInstructions === 'string' ? config.agentInstructions : '',
|
|
6432
|
+
builtinEnabled: config.agentInstructionsBuiltinEnabled !== false,
|
|
6433
|
+
customEnabled: config.agentInstructionsCustomEnabled !== false,
|
|
6434
|
+
// 新建容器时系统提示词的初始内容
|
|
6435
|
+
composed: composeSystemPrompt(config, '')
|
|
6394
6436
|
});
|
|
6395
6437
|
}
|
|
6396
6438
|
},
|
|
@@ -6399,31 +6441,41 @@ async function handleWebApi(req, res, pathname, ctx, state) {
|
|
|
6399
6441
|
match: currentPath => currentPath === '/api/agent-instructions' ? [] : null,
|
|
6400
6442
|
handler: async () => {
|
|
6401
6443
|
const payload = toPlainObject(await readJsonBody(req));
|
|
6402
|
-
|
|
6444
|
+
const toggles = [['builtinEnabled', 'agentInstructionsBuiltinEnabled'], ['customEnabled', 'agentInstructionsCustomEnabled']];
|
|
6445
|
+
if (payload.custom !== undefined && typeof payload.custom !== 'string') {
|
|
6403
6446
|
sendJson(res, 400, { error: 'custom 必须是字符串' });
|
|
6404
6447
|
return;
|
|
6405
6448
|
}
|
|
6406
|
-
|
|
6449
|
+
if (toggles.some(([field]) => payload[field] !== undefined && typeof payload[field] !== 'boolean')) {
|
|
6450
|
+
sendJson(res, 400, { error: '开关必须是布尔值' });
|
|
6451
|
+
return;
|
|
6452
|
+
}
|
|
6407
6453
|
const snapshot = readWebConfigSnapshot(state.webConfigPath);
|
|
6408
6454
|
if (snapshot.parseError) {
|
|
6409
6455
|
sendJson(res, 400, { error: '当前配置解析失败,无法继续', detail: snapshot.parseError });
|
|
6410
6456
|
return;
|
|
6411
6457
|
}
|
|
6412
|
-
|
|
6413
|
-
|
|
6414
|
-
sendJson(res, 200, { saved: false, custom });
|
|
6415
|
-
return;
|
|
6416
|
-
}
|
|
6417
|
-
let nextRaw;
|
|
6458
|
+
let nextRaw = baseRawOf(snapshot);
|
|
6459
|
+
let changed = false;
|
|
6418
6460
|
try {
|
|
6419
|
-
|
|
6420
|
-
|
|
6461
|
+
// 没写过、也没填内容时不动配置文件;开关保持默认(开)且没写过时同理
|
|
6462
|
+
if (typeof payload.custom === 'string' && (payload.custom.trim() || hasOwn(snapshot.parsed, 'agentInstructions'))) {
|
|
6463
|
+
nextRaw = buildAgentInstructionsConfigRaw(nextRaw, payload.custom.slice(0, 4000));
|
|
6464
|
+
changed = true;
|
|
6465
|
+
}
|
|
6466
|
+
toggles.forEach(([field, key]) => {
|
|
6467
|
+
if (typeof payload[field] === 'boolean' && (payload[field] === false || hasOwn(snapshot.parsed, key))) {
|
|
6468
|
+
nextRaw = buildBooleanConfigRaw(nextRaw, key, payload[field]);
|
|
6469
|
+
changed = true;
|
|
6470
|
+
}
|
|
6471
|
+
});
|
|
6472
|
+
if (changed) parseAndValidateConfigRaw(nextRaw);
|
|
6421
6473
|
} catch (e) {
|
|
6422
6474
|
sendJson(res, 400, { error: '配置写入失败' });
|
|
6423
6475
|
return;
|
|
6424
6476
|
}
|
|
6425
|
-
writeConfigFileSecure(path.resolve(state.webConfigPath), nextRaw);
|
|
6426
|
-
sendJson(res, 200, { saved:
|
|
6477
|
+
if (changed) writeConfigFileSecure(path.resolve(state.webConfigPath), nextRaw);
|
|
6478
|
+
sendJson(res, 200, { saved: changed });
|
|
6427
6479
|
}
|
|
6428
6480
|
},
|
|
6429
6481
|
{
|
|
@@ -7724,6 +7776,39 @@ async function handleWebApi(req, res, pathname, ctx, state) {
|
|
|
7724
7776
|
sendJson(res, 200, { ok: true, stopping: true });
|
|
7725
7777
|
}
|
|
7726
7778
|
},
|
|
7779
|
+
{
|
|
7780
|
+
// 容器菜单里的“启动容器”:与对话前的拉起同一条路径(含网络规则下发)
|
|
7781
|
+
method: 'POST',
|
|
7782
|
+
match: currentPath => currentPath.match(/^\/api\/sessions\/([^/]+)\/container\/start$/),
|
|
7783
|
+
handler: async match => {
|
|
7784
|
+
const sessionRef = getValidSessionRef(ctx, res, match[1]);
|
|
7785
|
+
if (!sessionRef) return;
|
|
7786
|
+
if (!ctx.containerExists(sessionRef.containerName)) {
|
|
7787
|
+
sendJson(res, 404, { error: `容器不存在: ${sessionRef.containerName}` });
|
|
7788
|
+
return;
|
|
7789
|
+
}
|
|
7790
|
+
await ensureWebContainer(ctx, state, sessionRef.containerName, sessionRef);
|
|
7791
|
+
sendJson(res, 200, { ok: true });
|
|
7792
|
+
}
|
|
7793
|
+
},
|
|
7794
|
+
{
|
|
7795
|
+
method: 'POST',
|
|
7796
|
+
match: currentPath => currentPath.match(/^\/api\/sessions\/([^/]+)\/container\/stop$/),
|
|
7797
|
+
handler: async match => {
|
|
7798
|
+
const sessionRef = getValidSessionRef(ctx, res, match[1]);
|
|
7799
|
+
if (!sessionRef) return;
|
|
7800
|
+
if (!ctx.containerExists(sessionRef.containerName)) {
|
|
7801
|
+
sendJson(res, 404, { error: `容器不存在: ${sessionRef.containerName}` });
|
|
7802
|
+
return;
|
|
7803
|
+
}
|
|
7804
|
+
if (state.agentRuns && state.agentRuns.has(sessionRef.containerName)) {
|
|
7805
|
+
sendJson(res, 409, { error: '容器里有 AGENT 正在执行,请先停止任务' });
|
|
7806
|
+
return;
|
|
7807
|
+
}
|
|
7808
|
+
await ctx.dockerExecAsync(['stop', '-t', '10', sessionRef.containerName], { timeout: 60000 });
|
|
7809
|
+
sendJson(res, 200, { ok: true });
|
|
7810
|
+
}
|
|
7811
|
+
},
|
|
7727
7812
|
{
|
|
7728
7813
|
method: 'POST',
|
|
7729
7814
|
match: currentPath => currentPath.match(/^\/api\/sessions\/([^/]+)\/remove$/),
|
|
@@ -7803,7 +7888,11 @@ async function handleWebApi(req, res, pathname, ctx, state) {
|
|
|
7803
7888
|
state.containerNames.set(id, name);
|
|
7804
7889
|
},
|
|
7805
7890
|
ensureWebContainer,
|
|
7806
|
-
execCommandInWebContainer
|
|
7891
|
+
execCommandInWebContainer,
|
|
7892
|
+
readConfig: () => {
|
|
7893
|
+
const snapshot = readWebConfigSnapshot(state.webConfigPath);
|
|
7894
|
+
return snapshot.parseError ? {} : snapshot.parsed;
|
|
7895
|
+
}
|
|
7807
7896
|
}
|
|
7808
7897
|
}));
|
|
7809
7898
|
|
package/manyoyo.example.json
CHANGED
|
@@ -65,8 +65,11 @@
|
|
|
65
65
|
"shell": "",
|
|
66
66
|
"shellSuffix": "",
|
|
67
67
|
"agentPromptCommand": "",
|
|
68
|
-
//
|
|
68
|
+
// 网页里新建容器时的补充系统提示词(接在默认系统提示词后面),例如 "回答用中文"
|
|
69
69
|
"agentInstructions": "",
|
|
70
|
+
// 默认 / 补充系统提示词的开关,默认都开
|
|
71
|
+
"agentInstructionsBuiltinEnabled": true,
|
|
72
|
+
"agentInstructionsCustomEnabled": true,
|
|
70
73
|
"yolo": "",
|
|
71
74
|
"quiet": ["tip", "cmd"],
|
|
72
75
|
|