@xcanwin/manyoyo 4.1.10 → 4.2.0

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/README.md CHANGED
@@ -46,7 +46,7 @@
46
46
  ```bash
47
47
  npm install -g @xcanwin/manyoyo # 安装
48
48
  podman pull ubuntu:24.04 # 仅 Podman 需要
49
- manyoyo --ib --iv 1.7.4-common # 构建镜像
49
+ manyoyo --ib --iv 1.8.0-common # 构建镜像
50
50
  manyoyo --init-config all # 从本机 Agent 配置迁移到 ~/.manyoyo
51
51
  manyoyo -r claude # 使用 manyoyo.json 的 runs.claude 启动
52
52
  ```
@@ -97,10 +97,10 @@ npm install -g @xcanwin/manyoyo
97
97
 
98
98
  ```bash
99
99
  # 构建 common 版本(推荐)
100
- manyoyo --ib --iv 1.7.4-common
100
+ manyoyo --ib --iv 1.8.0-common
101
101
 
102
102
  # 构建 full 版本
103
- manyoyo --ib --iv 1.7.4-full
103
+ manyoyo --ib --iv 1.8.0-full
104
104
 
105
105
  # 构建自定义版本
106
106
  manyoyo --ib --iba TOOL=go,codex,java,gemini
package/bin/manyoyo.js CHANGED
@@ -13,7 +13,7 @@ const { startWebServer } = require('../lib/web/server');
13
13
  const { buildContainerRunArgs, buildContainerRunCommand } = require('../lib/container-run');
14
14
  const { initAgentConfigs } = require('../lib/init-config');
15
15
  const { buildImage } = require('../lib/image-build');
16
- const { resolveAgentResumeArg } = require('../lib/agent-resume');
16
+ const { resolveAgentResumeArg, buildAgentResumeCommand } = require('../lib/agent-resume');
17
17
  const { version: BIN_VERSION, imageVersion: IMAGE_VERSION_DEFAULT } = require('../package.json');
18
18
  const IMAGE_VERSION_BASE = String(IMAGE_VERSION_DEFAULT || '1.0.0').split('-')[0];
19
19
  const IMAGE_VERSION_HELP_EXAMPLE = IMAGE_VERSION_DEFAULT || `${IMAGE_VERSION_BASE}-common`;
@@ -1210,8 +1210,12 @@ async function handlePostExit(runtime, defaultCommand) {
1210
1210
 
1211
1211
  getHelloTip(runtime.containerName, defaultCommand, runtime.execCommand);
1212
1212
 
1213
- let tipAskKeep = `❔ 会话已结束。是否保留此后台容器 ${runtime.containerName}? [ y=默认保留, n=删除, 1=首次命令进入, x=执行命令, i=交互式SHELL ]: `;
1214
- if (runtime.quiet.askkeep || runtime.quiet.full) tipAskKeep = `保留容器吗? [y n 1 x i] `;
1213
+ const resumeCommand = buildAgentResumeCommand(defaultCommand);
1214
+ const hasResumeAction = Boolean(resumeCommand);
1215
+ const menuResume = hasResumeAction ? ', r=恢复首次命令会话' : '';
1216
+ const quietResume = hasResumeAction ? ' r' : '';
1217
+ let tipAskKeep = `❔ 会话已结束。是否保留此后台容器 ${runtime.containerName}? [ y=默认保留, n=删除, 1=首次命令进入${menuResume}, x=执行命令, i=交互式SHELL ]: `;
1218
+ if (runtime.quiet.askkeep || runtime.quiet.full) tipAskKeep = `保留容器吗? [y n 1${quietResume} x i] `;
1215
1219
  const reply = await askQuestion(tipAskKeep);
1216
1220
 
1217
1221
  const firstChar = reply.trim().toLowerCase()[0];
@@ -1225,6 +1229,12 @@ async function handlePostExit(runtime, defaultCommand) {
1225
1229
  runtime.execCommandSuffix = "";
1226
1230
  runtime.execCommand = defaultCommand;
1227
1231
  return true;
1232
+ } else if (firstChar === 'r' && hasResumeAction) {
1233
+ if (!(runtime.quiet.full)) console.log(`${GREEN}✅ 离开当前连接,恢复首次命令会话。${NC}`);
1234
+ runtime.execCommandPrefix = "";
1235
+ runtime.execCommandSuffix = "";
1236
+ runtime.execCommand = resumeCommand;
1237
+ return true;
1228
1238
  } else if (firstChar === 'x') {
1229
1239
  const command = await askQuestion('❔ 输入要执行的命令: ');
1230
1240
  if (!(runtime.quiet.cmd || runtime.quiet.full)) console.log(`${GREEN}✅ 离开当前连接,执行命令。${NC}`);
@@ -6,7 +6,7 @@
6
6
  "hostPath": "/path/to/your/project",
7
7
  "containerPath": "/path/to/your/project",
8
8
  "imageName": "localhost/xcanwin/manyoyo",
9
- "imageVersion": "1.7.0-full",
9
+ "imageVersion": "1.8.0-common",
10
10
  "containerMode": "common",
11
11
  "shellPrefix": "",
12
12
  "shell": "",
@@ -67,6 +67,19 @@ function resolveAgentResumeArg(commandText) {
67
67
  return AGENT_RESUME_ARG_MAP[program] || '';
68
68
  }
69
69
 
70
+ function buildAgentResumeCommand(commandText) {
71
+ const baseCommand = String(commandText || '').trim();
72
+ if (!baseCommand) {
73
+ return '';
74
+ }
75
+ const resumeArg = resolveAgentResumeArg(baseCommand);
76
+ if (!resumeArg) {
77
+ return '';
78
+ }
79
+ return `${baseCommand} ${resumeArg}`;
80
+ }
81
+
70
82
  module.exports = {
71
- resolveAgentResumeArg
83
+ resolveAgentResumeArg,
84
+ buildAgentResumeCommand
72
85
  };
@@ -3,6 +3,7 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
  const crypto = require('crypto');
6
+ const { spawn } = require('child_process');
6
7
 
7
8
  function getFileSha256(filePath) {
8
9
  return crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');
@@ -233,6 +234,104 @@ function resolveToolFromBuildArgs(args) {
233
234
  return '';
234
235
  }
235
236
 
237
+ function extractBuildArgValues(args) {
238
+ const values = [];
239
+ for (let i = 0; i < args.length; i += 1) {
240
+ const current = args[i];
241
+ if (current === '--build-arg') {
242
+ const next = args[i + 1];
243
+ if (typeof next === 'string' && next.length > 0) {
244
+ values.push(next);
245
+ i += 1;
246
+ }
247
+ continue;
248
+ }
249
+ if (typeof current === 'string' && current.startsWith('--build-arg=')) {
250
+ values.push(current.slice('--build-arg='.length));
251
+ }
252
+ }
253
+ return values;
254
+ }
255
+
256
+ function buildPodmanBuildkitRunArgs(ctx, dockerfilePath, fullImageTag, imageBuildArgs) {
257
+ const dockerfileRelativePath = path.relative(ctx.rootDir, dockerfilePath).split(path.sep).join('/');
258
+ const buildArgs = extractBuildArgValues(imageBuildArgs);
259
+ const args = [
260
+ 'run', '--rm', '--privileged',
261
+ '--volume', `${ctx.rootDir}:/workspace`,
262
+ '--entrypoint', 'buildctl-daemonless.sh',
263
+ 'moby/buildkit:latest',
264
+ 'build',
265
+ '--frontend', 'dockerfile.v0',
266
+ '--local', 'context=/workspace',
267
+ '--local', 'dockerfile=/workspace',
268
+ '--opt', `filename=${dockerfileRelativePath}`
269
+ ];
270
+
271
+ for (const value of buildArgs) {
272
+ args.push('--opt', `build-arg:${value}`);
273
+ }
274
+
275
+ args.push('--output', `type=docker,name=${fullImageTag},dest=-`);
276
+ return args;
277
+ }
278
+
279
+ function runCmdPipeline(leftCmd, leftArgs, rightCmd, rightArgs, options = {}) {
280
+ const stdio = options.stdio || 'inherit';
281
+
282
+ return new Promise((resolve, reject) => {
283
+ let settled = false;
284
+ const left = spawn(leftCmd, leftArgs, { stdio: ['ignore', 'pipe', 'pipe'] });
285
+ const right = spawn(rightCmd, rightArgs, { stdio: ['pipe', 'pipe', 'pipe'] });
286
+ right.stdin.on('error', () => {});
287
+ left.stdout.pipe(right.stdin);
288
+
289
+ if (stdio === 'inherit') {
290
+ left.stderr.pipe(process.stderr);
291
+ right.stdout.pipe(process.stdout);
292
+ right.stderr.pipe(process.stderr);
293
+ }
294
+
295
+ let leftExited = false;
296
+ let rightExited = false;
297
+ let leftCode = 1;
298
+ let rightCode = 1;
299
+
300
+ const finish = (error) => {
301
+ if (settled) return;
302
+ settled = true;
303
+ if (error) {
304
+ try { left.kill(); } catch (e) {}
305
+ try { right.kill(); } catch (e) {}
306
+ reject(error);
307
+ return;
308
+ }
309
+ if (leftCode === 0 && rightCode === 0) {
310
+ resolve();
311
+ return;
312
+ }
313
+ reject(new Error(`Command pipeline failed: ${leftCmd}(${leftCode}) | ${rightCmd}(${rightCode})`));
314
+ };
315
+
316
+ const onClose = () => {
317
+ if (leftExited && rightExited) finish();
318
+ };
319
+
320
+ left.on('error', finish);
321
+ right.on('error', finish);
322
+ left.on('close', (code) => {
323
+ leftExited = true;
324
+ leftCode = typeof code === 'number' ? code : 1;
325
+ onClose();
326
+ });
327
+ right.on('close', (code) => {
328
+ rightExited = true;
329
+ rightCode = typeof code === 'number' ? code : 1;
330
+ onClose();
331
+ });
332
+ });
333
+ }
334
+
236
335
  async function buildImage(options = {}) {
237
336
  const ctx = {
238
337
  imageBuildArgs: Array.isArray(options.imageBuildArgs) ? [...options.imageBuildArgs] : [],
@@ -247,6 +346,7 @@ async function buildImage(options = {}) {
247
346
  rootDir: options.rootDir || process.cwd(),
248
347
  loadConfig: options.loadConfig || (() => ({})),
249
348
  runCmd: options.runCmd,
349
+ runCmdPipeline: options.runCmdPipeline || runCmdPipeline,
250
350
  askQuestion: options.askQuestion || (async () => ''),
251
351
  pruneDanglingImages: options.pruneDanglingImages || (() => {}),
252
352
  cacheTtlDays: options.cacheTtlDays || 2,
@@ -299,13 +399,40 @@ async function buildImage(options = {}) {
299
399
  ];
300
400
 
301
401
  ctx.log(`${BLUE}准备执行命令:${NC}`);
302
- ctx.log(`${ctx.dockerCmd} ${buildArgs.map(quoteShellArg).join(' ')}\n`);
402
+ const usePodmanBuildkit = ctx.dockerCmd === 'podman';
403
+ const buildkitRunArgs = usePodmanBuildkit
404
+ ? buildPodmanBuildkitRunArgs(ctx, dockerfilePath, fullImageTag, imageBuildArgs)
405
+ : [];
406
+ if (usePodmanBuildkit) {
407
+ ctx.log(`${ctx.dockerCmd} ${buildkitRunArgs.map(quoteShellArg).join(' ')} | ${ctx.dockerCmd} load\n`);
408
+ } else {
409
+ ctx.log(`${ctx.dockerCmd} ${buildArgs.map(quoteShellArg).join(' ')}\n`);
410
+ }
303
411
 
304
412
  if (!ctx.yesMode) {
305
413
  await ctx.askQuestion('❔ 是否继续构建? [ 直接回车=继续, ctrl+c=取消 ]: ');
306
414
  ctx.log('');
307
415
  }
308
416
 
417
+ if (usePodmanBuildkit) {
418
+ try {
419
+ await ctx.runCmdPipeline(ctx.dockerCmd, buildkitRunArgs, ctx.dockerCmd, ['load'], { stdio: 'inherit' });
420
+ ctx.log(`\n${GREEN}✅ 镜像构建成功: ${fullImageTag}${NC}`);
421
+ ctx.log(`${BLUE}使用镜像:${NC}`);
422
+ ctx.log(` ${ctx.manyoyoName} -n test --in ${ctx.imageName} --iv ${version}-${imageTool} -y c`);
423
+ ctx.pruneDanglingImages();
424
+ return;
425
+ } catch (e) {
426
+ ctx.log(`${YELLOW}⚠️ BuildKit 构建失败,回退到 podman build...${NC}`);
427
+ if (e && e.message) {
428
+ ctx.log(`${YELLOW}原因: ${e.message}${NC}`);
429
+ }
430
+ ctx.log('');
431
+ ctx.log(`${BLUE}回退命令:${NC}`);
432
+ ctx.log(`${ctx.dockerCmd} ${buildArgs.map(quoteShellArg).join(' ')}\n`);
433
+ }
434
+ }
435
+
309
436
  try {
310
437
  ctx.runCmd(ctx.dockerCmd, buildArgs, { stdio: 'inherit' });
311
438
  ctx.log(`\n${GREEN}✅ 镜像构建成功: ${fullImageTag}${NC}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcanwin/manyoyo",
3
- "version": "4.1.10",
3
+ "version": "4.2.0",
4
4
  "imageVersion": "1.8.0-common",
5
5
  "description": "AI Agent CLI Security Sandbox for Docker and Podman",
6
6
  "keywords": [