@xcanwin/manyoyo 5.8.11 → 5.9.1

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
@@ -34,6 +34,7 @@ const WEB_TERMINAL_MIN_ROWS = 12;
34
34
  const WEB_AGENT_CONTEXT_MAX_MESSAGES = 24;
35
35
  const WEB_AGENT_CONTEXT_MAX_CHARS = 6000;
36
36
  const WEB_AGENT_CONTEXT_PER_MESSAGE_MAX_CHARS = 600;
37
+ const WEB_FILE_PREVIEW_MAX_BYTES = 256 * 1024;
37
38
  const WEB_AUTH_COOKIE_NAME = 'manyoyo_web_auth';
38
39
  const WEB_AUTH_TTL_SECONDS = 12 * 60 * 60;
39
40
  const WEB_SESSION_KEY_SEPARATOR = '~';
@@ -109,6 +110,24 @@ const MIME_TYPES = {
109
110
  '.js': 'application/javascript; charset=utf-8',
110
111
  '.html': 'text/html; charset=utf-8'
111
112
  };
113
+ const FILE_LANGUAGE_MAP = {
114
+ '.cjs': 'javascript',
115
+ '.css': 'css',
116
+ '.htm': 'html',
117
+ '.html': 'html',
118
+ '.java': 'javascript',
119
+ '.js': 'javascript',
120
+ '.json': 'json',
121
+ '.jsx': 'javascript',
122
+ '.md': 'markdown',
123
+ '.markdown': 'markdown',
124
+ '.mjs': 'javascript',
125
+ '.py': 'python',
126
+ '.ts': 'javascript',
127
+ '.tsx': 'javascript',
128
+ '.yaml': 'yaml',
129
+ '.yml': 'yaml'
130
+ };
112
131
 
113
132
  function formatUrlHost(host) {
114
133
  if (typeof host !== 'string' || !host) return '127.0.0.1';
@@ -365,6 +384,10 @@ function listWebAgentSessions(history, options = {}) {
365
384
  });
366
385
  }
367
386
 
387
+ function createWebSessionMessageId() {
388
+ return `${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
389
+ }
390
+
368
391
  function appendWebSessionMessage(webHistoryDir, sessionRefOrContainerName, role, content, extra = {}) {
369
392
  const sessionRef = typeof sessionRefOrContainerName === 'string'
370
393
  ? { containerName: sessionRefOrContainerName, agentId: WEB_DEFAULT_AGENT_ID }
@@ -372,13 +395,14 @@ function appendWebSessionMessage(webHistoryDir, sessionRefOrContainerName, role,
372
395
  const history = loadWebSessionHistory(webHistoryDir, sessionRef.containerName);
373
396
  const agentSession = getWebAgentSession(history, sessionRef.agentId, { create: true });
374
397
  const timestamp = new Date().toISOString();
375
- agentSession.messages.push({
376
- id: `${Date.now()}-${Math.random().toString(16).slice(2, 8)}`,
398
+ const message = {
399
+ id: createWebSessionMessageId(),
377
400
  role,
378
401
  content,
379
402
  timestamp,
380
403
  ...extra
381
- });
404
+ };
405
+ agentSession.messages.push(message);
382
406
 
383
407
  if (agentSession.messages.length > WEB_HISTORY_MAX_MESSAGES) {
384
408
  agentSession.messages = agentSession.messages.slice(-WEB_HISTORY_MAX_MESSAGES);
@@ -387,6 +411,57 @@ function appendWebSessionMessage(webHistoryDir, sessionRefOrContainerName, role,
387
411
  agentSession.updatedAt = timestamp;
388
412
  history.updatedAt = timestamp;
389
413
  saveWebSessionHistory(webHistoryDir, sessionRef.containerName, history);
414
+ return message;
415
+ }
416
+
417
+ function patchWebSessionMessage(webHistoryDir, sessionRefOrContainerName, messageId, patch) {
418
+ const sessionRef = typeof sessionRefOrContainerName === 'string'
419
+ ? { containerName: sessionRefOrContainerName, agentId: WEB_DEFAULT_AGENT_ID }
420
+ : sessionRefOrContainerName;
421
+ const targetId = String(messageId || '').trim();
422
+ if (!targetId) {
423
+ return null;
424
+ }
425
+ const history = loadWebSessionHistory(webHistoryDir, sessionRef.containerName);
426
+ const agentSession = getWebAgentSession(history, sessionRef.agentId, { create: true });
427
+ const nextPatch = patch && typeof patch === 'object' ? patch : {};
428
+ const message = agentSession.messages.find(item => item && String(item.id || '') === targetId);
429
+ if (!message) {
430
+ return null;
431
+ }
432
+ Object.keys(nextPatch).forEach(key => {
433
+ if (key === 'id') {
434
+ return;
435
+ }
436
+ message[key] = nextPatch[key];
437
+ });
438
+ const timestamp = new Date().toISOString();
439
+ agentSession.updatedAt = timestamp;
440
+ history.updatedAt = timestamp;
441
+ saveWebSessionHistory(webHistoryDir, sessionRef.containerName, history);
442
+ return message;
443
+ }
444
+
445
+ function removeWebSessionMessage(webHistoryDir, sessionRefOrContainerName, messageId) {
446
+ const sessionRef = typeof sessionRefOrContainerName === 'string'
447
+ ? { containerName: sessionRefOrContainerName, agentId: WEB_DEFAULT_AGENT_ID }
448
+ : sessionRefOrContainerName;
449
+ const targetId = String(messageId || '').trim();
450
+ if (!targetId) {
451
+ return null;
452
+ }
453
+ const history = loadWebSessionHistory(webHistoryDir, sessionRef.containerName);
454
+ const agentSession = getWebAgentSession(history, sessionRef.agentId, { create: true });
455
+ const index = agentSession.messages.findIndex(item => item && String(item.id || '') === targetId);
456
+ if (index === -1) {
457
+ return null;
458
+ }
459
+ const removed = agentSession.messages.splice(index, 1)[0] || null;
460
+ const timestamp = new Date().toISOString();
461
+ agentSession.updatedAt = timestamp;
462
+ history.updatedAt = timestamp;
463
+ saveWebSessionHistory(webHistoryDir, sessionRef.containerName, history);
464
+ return removed;
390
465
  }
391
466
 
392
467
  function setWebSessionAgentPromptCommand(webHistoryDir, containerName, agentPromptCommand) {
@@ -797,6 +872,7 @@ function hasAgentConversationHistory(history) {
797
872
  for (const message of messages) {
798
873
  if (!message || typeof message !== 'object') continue;
799
874
  if (message.mode !== 'agent') continue;
875
+ if (message.pending === true) continue;
800
876
  if (message.streamTrace === true) continue;
801
877
  if (message.role === 'user' || message.role === 'assistant') {
802
878
  return true;
@@ -819,6 +895,7 @@ function buildAgentPromptWithHistory(history, prompt) {
819
895
  .filter(message => (
820
896
  message
821
897
  && message.mode === 'agent'
898
+ && message.pending !== true
822
899
  && message.streamTrace !== true
823
900
  && (message.role === 'user' || message.role === 'assistant')
824
901
  ))
@@ -1528,9 +1605,9 @@ function finalizeWebAgentExecution(state, sessionRef, agentSession, agentMeta, m
1528
1605
  function appendWebAgentTraceMessage(webHistoryDir, sessionRefOrContainerName, content, extra = {}) {
1529
1606
  const text = String(content || '').trim();
1530
1607
  if (!text) {
1531
- return;
1608
+ return null;
1532
1609
  }
1533
- appendWebSessionMessage(webHistoryDir, sessionRefOrContainerName, 'assistant', text, {
1610
+ return appendWebSessionMessage(webHistoryDir, sessionRefOrContainerName, 'assistant', text, {
1534
1611
  mode: 'agent',
1535
1612
  streamTrace: true,
1536
1613
  ...extra
@@ -2537,6 +2614,156 @@ async function execCommandInWebContainer(ctx, containerName, command, options =
2537
2614
  });
2538
2615
  }
2539
2616
 
2617
+ function buildWebContainerNodeCommand(scriptSource) {
2618
+ return `node <<'__MANYOYO_NODE__'
2619
+ ${scriptSource}
2620
+ __MANYOYO_NODE__`;
2621
+ }
2622
+
2623
+ function inferFileLanguage(filePath) {
2624
+ const ext = path.extname(String(filePath || '')).toLowerCase();
2625
+ return FILE_LANGUAGE_MAP[ext] || 'text';
2626
+ }
2627
+
2628
+ async function execJsonCommandInWebContainer(ctx, containerName, command) {
2629
+ const result = await execCommandInWebContainer(ctx, containerName, command);
2630
+ if (result.exitCode !== 0) {
2631
+ throw new Error(result.output || '容器命令执行失败');
2632
+ }
2633
+ try {
2634
+ return JSON.parse(String(result.output || '{}'));
2635
+ } catch (e) {
2636
+ throw new Error('容器返回了无法解析的 JSON');
2637
+ }
2638
+ }
2639
+
2640
+ function buildContainerFileListCommand(requestedPath) {
2641
+ return buildWebContainerNodeCommand(`
2642
+ // __MANYOYO_FS_LIST__
2643
+ const fs = require('fs');
2644
+ const path = require('path');
2645
+
2646
+ const requestedPath = ${JSON.stringify(String(requestedPath || '/'))};
2647
+
2648
+ try {
2649
+ const realPath = fs.realpathSync(requestedPath);
2650
+ const stat = fs.statSync(realPath);
2651
+ if (!stat.isDirectory()) {
2652
+ throw new Error('目标不是目录: ' + realPath);
2653
+ }
2654
+
2655
+ const root = path.parse(realPath).root;
2656
+ const parentPath = realPath === root ? '' : path.dirname(realPath);
2657
+ const entries = fs.readdirSync(realPath, { withFileTypes: true })
2658
+ .map(entry => {
2659
+ const fullPath = path.join(realPath, entry.name);
2660
+ let itemStat = null;
2661
+ try {
2662
+ itemStat = fs.lstatSync(fullPath);
2663
+ } catch (e) {
2664
+ itemStat = null;
2665
+ }
2666
+ let kind = 'other';
2667
+ if (entry.isDirectory()) {
2668
+ kind = 'directory';
2669
+ } else if (entry.isFile()) {
2670
+ kind = 'file';
2671
+ } else if (entry.isSymbolicLink()) {
2672
+ kind = 'symlink';
2673
+ }
2674
+ return {
2675
+ name: entry.name,
2676
+ path: fullPath,
2677
+ kind,
2678
+ size: itemStat && typeof itemStat.size === 'number' ? itemStat.size : 0,
2679
+ mtimeMs: itemStat && typeof itemStat.mtimeMs === 'number' ? Math.floor(itemStat.mtimeMs) : 0
2680
+ };
2681
+ })
2682
+ .sort((a, b) => {
2683
+ if (a.kind !== b.kind) {
2684
+ if (a.kind === 'directory') return -1;
2685
+ if (b.kind === 'directory') return 1;
2686
+ }
2687
+ return a.name.localeCompare(b.name, 'zh-CN');
2688
+ });
2689
+
2690
+ process.stdout.write(JSON.stringify({
2691
+ path: realPath,
2692
+ parentPath,
2693
+ entries
2694
+ }));
2695
+ } catch (e) {
2696
+ process.stdout.write(JSON.stringify({
2697
+ error: e && e.message ? e.message : '读取目录失败'
2698
+ }));
2699
+ }
2700
+ `);
2701
+ }
2702
+
2703
+ function buildContainerFileReadCommand(requestedPath) {
2704
+ return buildWebContainerNodeCommand(`
2705
+ // __MANYOYO_FS_READ__
2706
+ const fs = require('fs');
2707
+
2708
+ const requestedPath = ${JSON.stringify(String(requestedPath || ''))};
2709
+ const maxBytes = ${String(WEB_FILE_PREVIEW_MAX_BYTES)};
2710
+
2711
+ function looksBinary(buffer) {
2712
+ const length = Math.min(buffer.length, 4096);
2713
+ let suspicious = 0;
2714
+ for (let i = 0; i < length; i += 1) {
2715
+ const byte = buffer[i];
2716
+ if (byte === 0) {
2717
+ return true;
2718
+ }
2719
+ if (byte < 7 || (byte > 13 && byte < 32)) {
2720
+ suspicious += 1;
2721
+ }
2722
+ }
2723
+ return length > 0 && (suspicious / length) > 0.12;
2724
+ }
2725
+
2726
+ try {
2727
+ const realPath = fs.realpathSync(requestedPath);
2728
+ const stat = fs.statSync(realPath);
2729
+ if (!stat.isFile()) {
2730
+ throw new Error('目标不是文件: ' + realPath);
2731
+ }
2732
+
2733
+ const size = stat.size;
2734
+ const readBytes = Math.min(size, maxBytes);
2735
+ const buffer = Buffer.alloc(readBytes);
2736
+ const fd = fs.openSync(realPath, 'r');
2737
+ try {
2738
+ fs.readSync(fd, buffer, 0, readBytes, 0);
2739
+ } finally {
2740
+ fs.closeSync(fd);
2741
+ }
2742
+
2743
+ if (looksBinary(buffer)) {
2744
+ process.stdout.write(JSON.stringify({
2745
+ path: realPath,
2746
+ kind: 'binary',
2747
+ size,
2748
+ truncated: size > maxBytes
2749
+ }));
2750
+ } else {
2751
+ process.stdout.write(JSON.stringify({
2752
+ path: realPath,
2753
+ kind: 'text',
2754
+ size,
2755
+ truncated: size > maxBytes,
2756
+ content: buffer.toString('utf8')
2757
+ }));
2758
+ }
2759
+ } catch (e) {
2760
+ process.stdout.write(JSON.stringify({
2761
+ error: e && e.message ? e.message : '读取文件失败'
2762
+ }));
2763
+ }
2764
+ `);
2765
+ }
2766
+
2540
2767
  async function execAgentInWebContainerStream(ctx, state, sessionRefOrContainerName, command, options = {}) {
2541
2768
  const opts = options && typeof options === 'object' ? options : {};
2542
2769
  const sessionRef = typeof sessionRefOrContainerName === 'string'
@@ -3435,6 +3662,61 @@ async function handleWebApi(req, res, pathname, ctx, state) {
3435
3662
  });
3436
3663
  }
3437
3664
  },
3665
+ {
3666
+ method: 'GET',
3667
+ match: currentPath => currentPath.match(/^\/api\/sessions\/([^/]+)\/fs\/list$/),
3668
+ handler: async match => {
3669
+ const sessionRef = getValidSessionRef(ctx, res, match[1]);
3670
+ if (!sessionRef) {
3671
+ return;
3672
+ }
3673
+ const requestUrl = new URL(req.url || '/api/sessions/x/fs/list', 'http://localhost');
3674
+ const targetPath = String(requestUrl.searchParams.get('path') || '/').trim() || '/';
3675
+
3676
+ await ensureWebContainer(ctx, state, sessionRef.containerName, sessionRef);
3677
+ const payload = await execJsonCommandInWebContainer(
3678
+ ctx,
3679
+ sessionRef.containerName,
3680
+ buildContainerFileListCommand(targetPath)
3681
+ );
3682
+ if (payload && payload.error) {
3683
+ sendJson(res, 400, { error: payload.error });
3684
+ return;
3685
+ }
3686
+ sendJson(res, 200, payload);
3687
+ }
3688
+ },
3689
+ {
3690
+ method: 'GET',
3691
+ match: currentPath => currentPath.match(/^\/api\/sessions\/([^/]+)\/fs\/read$/),
3692
+ handler: async match => {
3693
+ const sessionRef = getValidSessionRef(ctx, res, match[1]);
3694
+ if (!sessionRef) {
3695
+ return;
3696
+ }
3697
+ const requestUrl = new URL(req.url || '/api/sessions/x/fs/read', 'http://localhost');
3698
+ const targetPath = String(requestUrl.searchParams.get('path') || '').trim();
3699
+ if (!targetPath) {
3700
+ sendJson(res, 400, { error: 'path 不能为空' });
3701
+ return;
3702
+ }
3703
+
3704
+ await ensureWebContainer(ctx, state, sessionRef.containerName, sessionRef);
3705
+ const payload = await execJsonCommandInWebContainer(
3706
+ ctx,
3707
+ sessionRef.containerName,
3708
+ buildContainerFileReadCommand(targetPath)
3709
+ );
3710
+ if (payload && payload.error) {
3711
+ sendJson(res, 400, { error: payload.error });
3712
+ return;
3713
+ }
3714
+ if (payload && payload.kind === 'text') {
3715
+ payload.language = inferFileLanguage(payload.path);
3716
+ }
3717
+ sendJson(res, 200, payload);
3718
+ }
3719
+ },
3438
3720
  {
3439
3721
  method: 'GET',
3440
3722
  match: currentPath => currentPath.match(/^\/api\/sessions\/([^/]+)\/detail$/),
@@ -3603,10 +3885,26 @@ async function handleWebApi(req, res, pathname, ctx, state) {
3603
3885
  return;
3604
3886
  }
3605
3887
 
3888
+ const userMessage = appendWebSessionMessage(state.webHistoryDir, sessionRef, 'user', prompt, {
3889
+ mode: 'agent',
3890
+ pending: true
3891
+ });
3892
+ const traceMessage = appendWebAgentTraceMessage(
3893
+ state.webHistoryDir,
3894
+ sessionRef,
3895
+ '[执行过程]\n等待 Agent 启动…',
3896
+ {
3897
+ traceEvents: [],
3898
+ pending: true
3899
+ }
3900
+ );
3901
+
3606
3902
  let prepared = null;
3607
3903
  try {
3608
3904
  prepared = await prepareWebAgentExecution(ctx, state, sessionRef, prompt);
3609
3905
  } catch (e) {
3906
+ removeWebSessionMessage(state.webHistoryDir, sessionRef, traceMessage && traceMessage.id);
3907
+ removeWebSessionMessage(state.webHistoryDir, sessionRef, userMessage && userMessage.id);
3610
3908
  sendJson(res, 400, { error: e && e.message ? e.message : 'Agent 执行准备失败' });
3611
3909
  return;
3612
3910
  }
@@ -3614,8 +3912,9 @@ async function handleWebApi(req, res, pathname, ctx, state) {
3614
3912
  const { agentSession, agentMeta, command, contextMode, resumeAttempted, resumeSucceeded, resumeError } = prepared;
3615
3913
  const traceLines = ['[执行过程]'];
3616
3914
  const traceEvents = [];
3617
- appendWebSessionMessage(state.webHistoryDir, sessionRef, 'user', prompt, {
3618
- mode: 'agent',
3915
+ let streamingReplyMessageId = '';
3916
+ patchWebSessionMessage(state.webHistoryDir, sessionRef, userMessage && userMessage.id, {
3917
+ pending: true,
3619
3918
  contextMode
3620
3919
  });
3621
3920
 
@@ -3639,6 +3938,14 @@ async function handleWebApi(req, res, pathname, ctx, state) {
3639
3938
  if (resumeAttempted) {
3640
3939
  traceLines.push(resumeSucceeded ? '会话恢复成功' : '会话恢复失败,已回退到历史注入');
3641
3940
  }
3941
+ patchWebSessionMessage(state.webHistoryDir, sessionRef, traceMessage && traceMessage.id, {
3942
+ content: traceLines.join('\n'),
3943
+ traceEvents: traceEvents.slice(),
3944
+ contextMode,
3945
+ resumeAttempted,
3946
+ resumeSucceeded,
3947
+ pending: true
3948
+ });
3642
3949
 
3643
3950
  try {
3644
3951
  const result = await execAgentInWebContainerStream(ctx, state, sessionRef, command, {
@@ -3649,18 +3956,57 @@ async function handleWebApi(req, res, pathname, ctx, state) {
3649
3956
  if (event.traceEvent && typeof event.traceEvent === 'object') {
3650
3957
  traceEvents.push(event.traceEvent);
3651
3958
  }
3959
+ patchWebSessionMessage(state.webHistoryDir, sessionRef, traceMessage && traceMessage.id, {
3960
+ content: traceLines.join('\n'),
3961
+ traceEvents: traceEvents.slice(),
3962
+ pending: true
3963
+ });
3964
+ }
3965
+ if (event && event.type === 'content_delta' && typeof event.content === 'string') {
3966
+ if (!streamingReplyMessageId) {
3967
+ const streamingReplyMessage = appendWebSessionMessage(
3968
+ state.webHistoryDir,
3969
+ sessionRef,
3970
+ 'assistant',
3971
+ event.content,
3972
+ {
3973
+ mode: 'agent',
3974
+ streamingReply: true,
3975
+ pending: true
3976
+ }
3977
+ );
3978
+ streamingReplyMessageId = streamingReplyMessage && streamingReplyMessage.id
3979
+ ? streamingReplyMessage.id
3980
+ : '';
3981
+ } else {
3982
+ patchWebSessionMessage(state.webHistoryDir, sessionRef, streamingReplyMessageId, {
3983
+ content: event.content,
3984
+ pending: true
3985
+ });
3986
+ }
3652
3987
  }
3653
3988
  sendNdjson(res, event);
3654
3989
  }
3655
3990
  });
3656
3991
  traceLines.push(result.interrupted === true ? '[任务] 已停止' : '[任务] 已完成');
3657
- appendWebAgentTraceMessage(state.webHistoryDir, sessionRef, traceLines.join('\n'), {
3992
+ patchWebSessionMessage(state.webHistoryDir, sessionRef, userMessage && userMessage.id, {
3993
+ pending: false,
3994
+ contextMode,
3995
+ resumeAttempted,
3996
+ resumeSucceeded
3997
+ });
3998
+ patchWebSessionMessage(state.webHistoryDir, sessionRef, traceMessage && traceMessage.id, {
3999
+ content: traceLines.join('\n'),
3658
4000
  traceEvents,
3659
4001
  contextMode,
3660
4002
  resumeAttempted,
3661
4003
  resumeSucceeded,
3662
- interrupted: result.interrupted === true
4004
+ interrupted: result.interrupted === true,
4005
+ pending: false
3663
4006
  });
4007
+ if (streamingReplyMessageId) {
4008
+ removeWebSessionMessage(state.webHistoryDir, sessionRef, streamingReplyMessageId);
4009
+ }
3664
4010
  finalizeWebAgentExecution(state, sessionRef, agentSession, agentMeta, {
3665
4011
  contextMode,
3666
4012
  resumeAttempted,
@@ -3678,13 +4024,24 @@ async function handleWebApi(req, res, pathname, ctx, state) {
3678
4024
  });
3679
4025
  } catch (e) {
3680
4026
  traceLines.push(`[错误] ${e && e.message ? e.message : 'Agent 执行失败'}`);
3681
- appendWebAgentTraceMessage(state.webHistoryDir, sessionRef, traceLines.join('\n'), {
4027
+ patchWebSessionMessage(state.webHistoryDir, sessionRef, userMessage && userMessage.id, {
4028
+ pending: false,
4029
+ contextMode,
4030
+ resumeAttempted,
4031
+ resumeSucceeded
4032
+ });
4033
+ patchWebSessionMessage(state.webHistoryDir, sessionRef, traceMessage && traceMessage.id, {
4034
+ content: traceLines.join('\n'),
3682
4035
  traceEvents,
3683
4036
  contextMode,
3684
4037
  resumeAttempted,
3685
4038
  resumeSucceeded,
3686
- interrupted: true
4039
+ interrupted: true,
4040
+ pending: false
3687
4041
  });
4042
+ if (streamingReplyMessageId) {
4043
+ removeWebSessionMessage(state.webHistoryDir, sessionRef, streamingReplyMessageId);
4044
+ }
3688
4045
  sendNdjson(res, {
3689
4046
  type: 'error',
3690
4047
  error: e && e.message ? e.message : 'Agent 执行失败'
@@ -3872,7 +4229,12 @@ async function startWebServer(options) {
3872
4229
  const appFrontendMatch = pathname.match(/^\/app\/frontend\/([A-Za-z0-9._-]+)$/);
3873
4230
  if (req.method === 'GET' && appFrontendMatch) {
3874
4231
  const assetName = appFrontendMatch[1];
3875
- if (!(assetName === 'app.css' || assetName === 'app.js' || assetName === 'markdown.css' || assetName === 'markdown-renderer.js')) {
4232
+ if (!(assetName === 'app.css'
4233
+ || assetName === 'app.js'
4234
+ || assetName === 'markdown.css'
4235
+ || assetName === 'markdown-renderer.js'
4236
+ || assetName === 'file-browser.js'
4237
+ || assetName === 'codemirror.bundle.js')) {
3876
4238
  sendHtml(res, 404, '<h1>404 Not Found</h1>');
3877
4239
  return;
3878
4240
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcanwin/manyoyo",
3
- "version": "5.8.11",
3
+ "version": "5.9.1",
4
4
  "imageVersion": "1.9.0-common",
5
5
  "playwrightCliVersion": "0.1.1",
6
6
  "description": "AI Agent CLI Security Sandbox for Docker and Podman",
@@ -31,6 +31,7 @@
31
31
  },
32
32
  "scripts": {
33
33
  "dev:release": "node scripts/dev-release.js",
34
+ "build:web-editor": "node scripts/build-web-code-editor.js",
34
35
  "install-link": "npm link",
35
36
  "test": "jest --coverage",
36
37
  "test:unit": "jest test/",
@@ -66,6 +67,19 @@
66
67
  "ws": "^8.20.0"
67
68
  },
68
69
  "devDependencies": {
70
+ "@codemirror/commands": "^6.10.3",
71
+ "@codemirror/lang-css": "^6.3.1",
72
+ "@codemirror/lang-html": "^6.4.11",
73
+ "@codemirror/lang-javascript": "^6.2.5",
74
+ "@codemirror/lang-json": "^6.0.2",
75
+ "@codemirror/lang-markdown": "^6.5.0",
76
+ "@codemirror/lang-python": "^6.2.1",
77
+ "@codemirror/lang-yaml": "^6.1.3",
78
+ "@codemirror/language": "^6.12.3",
79
+ "@codemirror/search": "^6.6.0",
80
+ "@codemirror/state": "^6.6.0",
81
+ "@codemirror/view": "^6.41.0",
82
+ "codemirror": "^6.0.2",
69
83
  "jest": "^30.3.0",
70
84
  "vitepress": "^1.6.4"
71
85
  },