@xcanwin/manyoyo 6.0.4 → 6.1.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/bin/manyoyo.js +58 -48
- package/lib/agent-adapters/index.js +47 -0
- package/lib/container-modes.js +37 -0
- package/lib/core/app-error.js +81 -0
- package/lib/core/event-store.js +106 -0
- package/lib/core/events.js +149 -0
- package/lib/doctor.js +84 -0
- package/lib/web/frontend/codemirror.bundle.js +31 -31800
- package/lib/web/server.js +110 -29
- package/package.json +3 -1
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xcanwin/manyoyo",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.1.1",
|
|
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/",
|