@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/frontend/app.css +182 -1
- package/lib/web/frontend/app.html +4 -0
- package/lib/web/frontend/app.js +186 -9
- package/lib/web/frontend/codemirror-entry.js +98 -0
- package/lib/web/frontend/codemirror.bundle.js +31648 -0
- package/lib/web/frontend/file-browser.js +406 -0
- package/lib/web/server.js +374 -12
- package/package.json +15 -1
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
|
-
|
|
376
|
-
id:
|
|
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
|
-
|
|
3618
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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'
|
|
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.
|
|
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
|
},
|