@xcanwin/manyoyo 6.0.5 → 6.1.3

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
@@ -22,6 +22,13 @@ const {
22
22
  resolveAgentPromptCommandTemplate,
23
23
  buildAgentResumeCommand
24
24
  } = require('../agent-resume');
25
+ const { resolveYoloCommand: resolveYoloCommandFromAdapters } = require('../agent-adapters');
26
+ const {
27
+ createControlEvent,
28
+ validateControlEvent,
29
+ projectSessionEvents
30
+ } = require('../core/events');
31
+ const { FileEventStore } = require('../core/event-store');
25
32
 
26
33
  const WEB_HISTORY_MAX_MESSAGES = 500;
27
34
  const WEB_OUTPUT_MAX_CHARS = 16000;
@@ -50,19 +57,6 @@ const IMAGE_VERSION_TAG_PATTERN = /^(\d+\.\d+\.\d+)-([A-Za-z0-9][A-Za-z0-9_.-]*)
50
57
  const SENSITIVE_CONFIG_KEY_PATTERN = /(pass(word)?|passwd|secret|token|api(?:_|-)?key|auth(?:_|-)?token|oauth(?:_|-)?token)$/i;
51
58
  const REDACTED_CONFIG_VALUE = '***';
52
59
 
53
- const YOLO_COMMAND_MAP = {
54
- claude: 'IS_SANDBOX=1 claude --dangerously-skip-permissions',
55
- cc: 'IS_SANDBOX=1 claude --dangerously-skip-permissions',
56
- c: 'IS_SANDBOX=1 claude --dangerously-skip-permissions',
57
- gemini: 'gemini --yolo',
58
- gm: 'gemini --yolo',
59
- g: 'gemini --yolo',
60
- codex: 'codex --dangerously-bypass-approvals-and-sandbox',
61
- cx: 'codex --dangerously-bypass-approvals-and-sandbox',
62
- opencode: 'OPENCODE_PERMISSION=\'{"*":"allow"}\' opencode',
63
- oc: 'OPENCODE_PERMISSION=\'{"*":"allow"}\' opencode'
64
- };
65
-
66
60
  const DEFAULT_WEB_CONFIG_TEMPLATE = `{
67
61
  // MANYOYO 全局配置文件(JSON5)
68
62
  "containerName": "my-dev",
@@ -171,6 +165,7 @@ function createEmptyWebAgentSession(agentId, agentName) {
171
165
  createdAt: null,
172
166
  updatedAt: null,
173
167
  messages: [],
168
+ events: [],
174
169
  lastResumeAt: null,
175
170
  lastResumeOk: null,
176
171
  lastResumeError: '',
@@ -189,6 +184,16 @@ function normalizeWebAgentSessionRecord(agentId, rawAgent) {
189
184
  createdAt: typeof source.createdAt === 'string' ? source.createdAt : null,
190
185
  updatedAt: typeof source.updatedAt === 'string' ? source.updatedAt : null,
191
186
  messages: Array.isArray(source.messages) ? source.messages : [],
187
+ events: Array.isArray(source.events)
188
+ ? source.events.filter(event => {
189
+ try {
190
+ validateControlEvent(event);
191
+ return true;
192
+ } catch (e) {
193
+ return false;
194
+ }
195
+ })
196
+ : [],
192
197
  lastResumeAt: typeof source.lastResumeAt === 'string' ? source.lastResumeAt : null,
193
198
  lastResumeOk: typeof source.lastResumeOk === 'boolean' ? source.lastResumeOk : null,
194
199
  lastResumeError: typeof source.lastResumeError === 'string' ? source.lastResumeError : '',
@@ -463,6 +468,40 @@ function appendWebSessionMessage(webHistoryDir, sessionRefOrContainerName, role,
463
468
  return message;
464
469
  }
465
470
 
471
+ function appendWebSessionControlEvent(webHistoryDir, sessionRef, type, data = {}) {
472
+ const history = loadWebSessionHistory(webHistoryDir, sessionRef.containerName);
473
+ const agentSession = getWebAgentSession(history, sessionRef.agentId, { create: true });
474
+ const events = Array.isArray(agentSession.events) ? agentSession.events : [];
475
+ const lastEvent = events.length ? events[events.length - 1] : null;
476
+ const event = createControlEvent({
477
+ type,
478
+ aggregateId: buildWebSessionKey(sessionRef.containerName, sessionRef.agentId),
479
+ seq: lastEvent ? lastEvent.seq + 1 : 1,
480
+ data
481
+ });
482
+ const eventStore = new FileEventStore(webHistoryDir);
483
+ eventStore.append(event);
484
+ agentSession.events = [...events, event].slice(-WEB_HISTORY_MAX_MESSAGES);
485
+ agentSession.updatedAt = event.timestamp;
486
+ history.updatedAt = event.timestamp;
487
+ saveWebSessionHistory(webHistoryDir, sessionRef.containerName, history);
488
+ return event;
489
+ }
490
+
491
+ function loadWebSessionControlEvents(webHistoryDir, sessionRef, fallbackEvents = []) {
492
+ try {
493
+ const events = new FileEventStore(webHistoryDir).read(
494
+ buildWebSessionKey(sessionRef.containerName, sessionRef.agentId)
495
+ );
496
+ if (events.length) {
497
+ return events;
498
+ }
499
+ } catch (error) {
500
+ // 旧 history 快照仍可作为只读恢复源;写入路径始终以日志为准。
501
+ }
502
+ return Array.isArray(fallbackEvents) ? fallbackEvents : [];
503
+ }
504
+
466
505
  function patchWebSessionMessage(webHistoryDir, sessionRefOrContainerName, messageId, patch) {
467
506
  const sessionRef = typeof sessionRefOrContainerName === 'string'
468
507
  ? { containerName: sessionRefOrContainerName, agentId: WEB_DEFAULT_AGENT_ID }
@@ -2149,15 +2188,7 @@ function resolveContainerModeArgs(mode) {
2149
2188
  }
2150
2189
 
2151
2190
  function resolveYoloCommand(yolo) {
2152
- const key = String(yolo || '').trim().toLowerCase();
2153
- if (!key) {
2154
- return '';
2155
- }
2156
- const mapped = YOLO_COMMAND_MAP[key];
2157
- if (!mapped) {
2158
- throw new Error(`未知 yolo 值: ${yolo}`);
2159
- }
2160
- return mapped;
2191
+ return resolveYoloCommandFromAdapters(yolo);
2161
2192
  }
2162
2193
 
2163
2194
  function buildDefaultCommand(shellPrefix, shell, shellSuffix) {
@@ -3149,6 +3180,23 @@ function sendNdjson(res, payload) {
3149
3180
  res.write(`${JSON.stringify(payload)}\n`);
3150
3181
  }
3151
3182
 
3183
+ function createWebStreamEmitter(res, webHistoryDir, sessionRef) {
3184
+ return (payload, type, data = {}) => {
3185
+ const controlEvent = appendWebSessionControlEvent(webHistoryDir, sessionRef, type, data);
3186
+ sendNdjson(res, { ...payload, controlEvent });
3187
+ };
3188
+ }
3189
+
3190
+ function resolveWebStreamControlEventType(event) {
3191
+ if (event && event.type === 'content_delta') {
3192
+ return 'agent.turn.delta';
3193
+ }
3194
+ if (event && event.type === 'trace' && event.traceEvent) {
3195
+ return 'agent.tool.observed';
3196
+ }
3197
+ return 'process.stdout';
3198
+ }
3199
+
3152
3200
  function stopWebAgentRun(state, containerName) {
3153
3201
  const runState = state.agentRuns.get(containerName);
3154
3202
  if (!runState || !runState.process || runState.process.killed) {
@@ -3332,6 +3380,21 @@ function buildSessionDetail(ctx, state, containerMap, name) {
3332
3380
  };
3333
3381
  }
3334
3382
 
3383
+ function buildSessionAudit(ctx, state, sessionRef) {
3384
+ const history = loadWebSessionHistory(state.webHistoryDir, sessionRef.containerName);
3385
+ const agentSession = getWebAgentSession(history, sessionRef.agentId)
3386
+ || createEmptyWebAgentSession(sessionRef.agentId);
3387
+ const events = loadWebSessionControlEvents(state.webHistoryDir, sessionRef, agentSession.events);
3388
+ const applied = history.applied && typeof history.applied === 'object' && !Array.isArray(history.applied)
3389
+ ? history.applied
3390
+ : {};
3391
+ return {
3392
+ runSpec: applied.runSpec ? redactConfigObject(applied.runSpec) : null,
3393
+ events,
3394
+ projection: projectSessionEvents(events)
3395
+ };
3396
+ }
3397
+
3335
3398
  function isSafeStaticAssetName(name) {
3336
3399
  return /^[A-Za-z0-9._-]+$/.test(name);
3337
3400
  }
@@ -4005,6 +4068,19 @@ async function handleWebApi(req, res, pathname, ctx, state) {
4005
4068
  sendJson(res, 200, { name: buildWebSessionKey(sessionRef.containerName, sessionRef.agentId), detail });
4006
4069
  }
4007
4070
  },
4071
+ {
4072
+ method: 'GET',
4073
+ match: currentPath => currentPath.match(/^\/api\/sessions\/([^/]+)\/audit$/),
4074
+ handler: async match => {
4075
+ const sessionRef = getValidSessionRef(ctx, res, match[1]);
4076
+ if (!sessionRef) {
4077
+ return;
4078
+ }
4079
+
4080
+ const audit = buildSessionAudit(ctx, state, sessionRef);
4081
+ sendJson(res, 200, { name: buildWebSessionKey(sessionRef.containerName, sessionRef.agentId), audit });
4082
+ }
4083
+ },
4008
4084
  {
4009
4085
  method: 'PUT',
4010
4086
  match: currentPath => currentPath.match(/^\/api\/sessions\/([^/]+)\/agent-template$/),
@@ -4198,7 +4274,8 @@ async function handleWebApi(req, res, pathname, ctx, state) {
4198
4274
  'Cache-Control': 'no-store',
4199
4275
  'X-Accel-Buffering': 'no'
4200
4276
  });
4201
- sendNdjson(res, {
4277
+ const emitStreamEvent = createWebStreamEmitter(res, state.webHistoryDir, sessionRef);
4278
+ emitStreamEvent({
4202
4279
  type: 'meta',
4203
4280
  containerName: sessionRef.containerName,
4204
4281
  sessionName: buildWebSessionKey(sessionRef.containerName, sessionRef.agentId),
@@ -4206,7 +4283,7 @@ async function handleWebApi(req, res, pathname, ctx, state) {
4206
4283
  resumeAttempted,
4207
4284
  resumeSucceeded,
4208
4285
  agentProgram: agentMeta.agentProgram
4209
- });
4286
+ }, 'session.ready', { agentProgram: agentMeta.agentProgram });
4210
4287
  if (contextMode) {
4211
4288
  traceLines.push(`上下文模式: ${contextMode}`);
4212
4289
  }
@@ -4260,7 +4337,10 @@ async function handleWebApi(req, res, pathname, ctx, state) {
4260
4337
  });
4261
4338
  }
4262
4339
  }
4263
- sendNdjson(res, event);
4340
+ emitStreamEvent(event, resolveWebStreamControlEventType(event), {
4341
+ transportType: event && event.type ? event.type : '',
4342
+ text: event && (event.text || event.content) ? String(event.text || event.content) : ''
4343
+ });
4264
4344
  }
4265
4345
  });
4266
4346
  traceLines.push(result.interrupted === true ? '[任务] 已停止' : '[任务] 已完成');
@@ -4289,7 +4369,7 @@ async function handleWebApi(req, res, pathname, ctx, state) {
4289
4369
  resumeError,
4290
4370
  engineSessionId
4291
4371
  }, result);
4292
- sendNdjson(res, {
4372
+ emitStreamEvent({
4293
4373
  type: 'result',
4294
4374
  exitCode: result.exitCode,
4295
4375
  output: result.output,
@@ -4297,7 +4377,7 @@ async function handleWebApi(req, res, pathname, ctx, state) {
4297
4377
  resumeAttempted,
4298
4378
  resumeSucceeded,
4299
4379
  interrupted: result.interrupted === true
4300
- });
4380
+ }, result.interrupted === true ? 'process.interrupted' : 'process.exited', { exitCode: result.exitCode });
4301
4381
  } catch (e) {
4302
4382
  traceLines.push(`[错误] ${e && e.message ? e.message : 'Agent 执行失败'}`);
4303
4383
  patchWebSessionMessage(state.webHistoryDir, sessionRef, userMessage && userMessage.id, {
@@ -4318,10 +4398,10 @@ async function handleWebApi(req, res, pathname, ctx, state) {
4318
4398
  if (streamingReplyMessageId) {
4319
4399
  removeWebSessionMessage(state.webHistoryDir, sessionRef, streamingReplyMessageId);
4320
4400
  }
4321
- sendNdjson(res, {
4401
+ emitStreamEvent({
4322
4402
  type: 'error',
4323
4403
  error: e && e.message ? e.message : 'Agent 执行失败'
4324
- });
4404
+ }, 'agent.turn.failed', { error: e && e.message ? e.message : 'Agent 执行失败' });
4325
4405
  } finally {
4326
4406
  res.end();
4327
4407
  }
@@ -4340,6 +4420,7 @@ async function handleWebApi(req, res, pathname, ctx, state) {
4340
4420
  sendJson(res, 404, { error: '当前会话没有运行中的 agent 任务' });
4341
4421
  return;
4342
4422
  }
4423
+ appendWebSessionControlEvent(state.webHistoryDir, sessionRef, 'session.stopping');
4343
4424
  sendJson(res, 200, { ok: true, stopping: true });
4344
4425
  }
4345
4426
  },
@@ -4509,6 +4590,7 @@ async function startWebServer(options) {
4509
4590
  || assetName === 'app.js'
4510
4591
  || assetName === 'markdown.css'
4511
4592
  || assetName === 'markdown-renderer.js'
4593
+ || assetName === 'chat-behavior.js'
4512
4594
  || assetName === 'file-browser.js'
4513
4595
  || assetName === 'codemirror.bundle.js')) {
4514
4596
  sendHtml(res, 404, '<h1>404 Not Found</h1>');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcanwin/manyoyo",
3
- "version": "6.0.5",
3
+ "version": "6.1.3",
4
4
  "imageVersion": "1.9.1-common",
5
5
  "playwrightCliVersion": "0.1.18",
6
6
  "description": "AI Agent CLI Security Sandbox for Docker and Podman",
@@ -32,6 +32,8 @@
32
32
  "scripts": {
33
33
  "dev:release": "node scripts/dev-release.js",
34
34
  "build:web-editor": "node scripts/build-web-code-editor.js",
35
+ "prepack": "npm run build:web-editor",
36
+ "prepare": "npm run build:web-editor",
35
37
  "install-link": "npm link",
36
38
  "test": "jest --coverage",
37
39
  "test:unit": "jest test/",