@xcanwin/manyoyo 8.6.9 → 8.6.11

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/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(configSnapshot.parseError ? {} : configSnapshot.parsed, runName)
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
- if (typeof payload.custom !== 'string') {
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
- const custom = payload.custom.slice(0, 4000);
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
- if (!custom.trim() && !hasOwn(snapshot.parsed, 'agentInstructions')) {
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
- nextRaw = buildAgentInstructionsConfigRaw(baseRawOf(snapshot), custom);
6420
- parseAndValidateConfigRaw(nextRaw);
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: true, custom });
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
 
@@ -65,8 +65,11 @@
65
65
  "shell": "",
66
66
  "shellSuffix": "",
67
67
  "agentPromptCommand": "",
68
- // 网页里执行 Agent 时追加的说明(接在内置环境说明后面),例如 "回答用中文"
68
+ // 网页里新建容器时的补充系统提示词(接在默认系统提示词后面),例如 "回答用中文"
69
69
  "agentInstructions": "",
70
+ // 默认 / 补充系统提示词的开关,默认都开
71
+ "agentInstructionsBuiltinEnabled": true,
72
+ "agentInstructionsCustomEnabled": true,
70
73
  "yolo": "",
71
74
  "quiet": ["tip", "cmd"],
72
75
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcanwin/manyoyo",
3
- "version": "8.6.9",
3
+ "version": "8.6.11",
4
4
  "imageVersion": "2.2.0-common",
5
5
  "playwrightCliVersion": "0.1.19",
6
6
  "patchrightCoreVersion": "1.63.0",