@xcanwin/manyoyo 5.11.14 → 5.11.16
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/docker/manyoyo.Dockerfile +1 -0
- package/lib/web/server.js +86 -21
- package/package.json +1 -1
|
@@ -139,6 +139,7 @@ RUN <<EOX
|
|
|
139
139
|
# 配置 node.js
|
|
140
140
|
set -eu
|
|
141
141
|
npm config set registry=${NPM_REGISTRY}
|
|
142
|
+
npm install -g npm
|
|
142
143
|
npm config set allow-scripts=@anthropic-ai/claude-code,@openai/codex,@google/gemini-cli,opencode-ai,@playwright/cli,pyright,typescript-language-server,typescript --location=user
|
|
143
144
|
|
|
144
145
|
export GIT_SSL_NO_VERIFY=$GIT_SSL_NO_VERIFY
|
package/lib/web/server.js
CHANGED
|
@@ -34,6 +34,8 @@ 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
|
+
// Claude 与 Codex 都按 Web Agent 保存的引擎会话 ID 原生续接,避免历史文本被截断。
|
|
38
|
+
const NATIVE_SESSION_RESUME_PROGRAMS = new Set(['claude', 'codex']);
|
|
37
39
|
const WEB_FILE_PREVIEW_MAX_BYTES = 512 * 1024;
|
|
38
40
|
const WEB_FILE_EDIT_MAX_BYTES = 2 * 1024 * 1024;
|
|
39
41
|
const WEB_AUTH_COOKIE_NAME = 'manyoyo_web_auth';
|
|
@@ -171,7 +173,8 @@ function createEmptyWebAgentSession(agentId, agentName) {
|
|
|
171
173
|
messages: [],
|
|
172
174
|
lastResumeAt: null,
|
|
173
175
|
lastResumeOk: null,
|
|
174
|
-
lastResumeError: ''
|
|
176
|
+
lastResumeError: '',
|
|
177
|
+
engineSessionId: ''
|
|
175
178
|
};
|
|
176
179
|
}
|
|
177
180
|
|
|
@@ -188,7 +191,8 @@ function normalizeWebAgentSessionRecord(agentId, rawAgent) {
|
|
|
188
191
|
messages: Array.isArray(source.messages) ? source.messages : [],
|
|
189
192
|
lastResumeAt: typeof source.lastResumeAt === 'string' ? source.lastResumeAt : null,
|
|
190
193
|
lastResumeOk: typeof source.lastResumeOk === 'boolean' ? source.lastResumeOk : null,
|
|
191
|
-
lastResumeError: typeof source.lastResumeError === 'string' ? source.lastResumeError : ''
|
|
194
|
+
lastResumeError: typeof source.lastResumeError === 'string' ? source.lastResumeError : '',
|
|
195
|
+
engineSessionId: typeof source.engineSessionId === 'string' ? source.engineSessionId : ''
|
|
192
196
|
};
|
|
193
197
|
}
|
|
194
198
|
|
|
@@ -683,8 +687,9 @@ function renderAgentPromptCommand(template, prompt) {
|
|
|
683
687
|
return templateText.replace(/\{prompt\}/g, safePrompt);
|
|
684
688
|
}
|
|
685
689
|
|
|
686
|
-
function buildCodexAgentExecCommand(template, prompt) {
|
|
690
|
+
function buildCodexAgentExecCommand(template, prompt, options = {}) {
|
|
687
691
|
const templateText = normalizeAgentPromptCommandTemplate(template, 'agentPromptCommand');
|
|
692
|
+
const sessionId = options && typeof options.sessionId === 'string' ? options.sessionId.trim() : '';
|
|
688
693
|
const execMatch = templateText.match(
|
|
689
694
|
/^((?:(?:[A-Za-z_][A-Za-z0-9_]*=)(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*'|[^\s]+)\s+)*)codex\s+exec\b/
|
|
690
695
|
);
|
|
@@ -694,7 +699,13 @@ function buildCodexAgentExecCommand(template, prompt) {
|
|
|
694
699
|
const suffix = templateText.slice(execMatch[0].length);
|
|
695
700
|
const hasJson = /(?:^|\s)--json(?:\s|$)/.test(suffix);
|
|
696
701
|
const injectedFlags = hasJson ? '' : ' --json';
|
|
697
|
-
|
|
702
|
+
if (sessionId) {
|
|
703
|
+
const promptIndex = suffix.indexOf('{prompt}');
|
|
704
|
+
const resumeSuffix = `${suffix.slice(0, promptIndex)}${quoteBashSingleValue(sessionId)} ${suffix.slice(promptIndex)}`;
|
|
705
|
+
codexTemplate = `${prefix}codex exec resume${injectedFlags}${resumeSuffix}`;
|
|
706
|
+
} else {
|
|
707
|
+
codexTemplate = `${prefix}codex exec${injectedFlags}${suffix}`;
|
|
708
|
+
}
|
|
698
709
|
}
|
|
699
710
|
return codexTemplate === templateText
|
|
700
711
|
? renderAgentPromptCommand(templateText, prompt)
|
|
@@ -718,15 +729,20 @@ function prependAgentFlags(commandText, matchPattern, flagSpecs) {
|
|
|
718
729
|
return `${prefix}${suffix}`;
|
|
719
730
|
}
|
|
720
731
|
|
|
721
|
-
function buildClaudeAgentExecCommand(template, prompt) {
|
|
732
|
+
function buildClaudeAgentExecCommand(template, prompt, options = {}) {
|
|
722
733
|
const templateText = normalizeAgentPromptCommandTemplate(template, 'agentPromptCommand');
|
|
734
|
+
const flagSpecs = [
|
|
735
|
+
{ flag: '--verbose', pattern: /(?:^|\s)--verbose(?:\s|$)/ },
|
|
736
|
+
{ flag: '--output-format stream-json', pattern: /(?:^|\s)--output-format(?:\s|$)/ }
|
|
737
|
+
];
|
|
738
|
+
const sessionId = options && typeof options.sessionId === 'string' ? options.sessionId.trim() : '';
|
|
739
|
+
if (sessionId) {
|
|
740
|
+
flagSpecs.push({ flag: `-r ${sessionId}`, pattern: /(?:^|\s)-r(?:\s|$)/ });
|
|
741
|
+
}
|
|
723
742
|
const claudeTemplate = prependAgentFlags(
|
|
724
743
|
templateText,
|
|
725
744
|
/^(((?:(?:[A-Za-z_][A-Za-z0-9_]*=)(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*'|[^\s]+)\s+)*)claude\b)(.*)$/,
|
|
726
|
-
|
|
727
|
-
{ flag: '--verbose', pattern: /(?:^|\s)--verbose(?:\s|$)/ },
|
|
728
|
-
{ flag: '--output-format stream-json', pattern: /(?:^|\s)--output-format(?:\s|$)/ }
|
|
729
|
-
]
|
|
745
|
+
flagSpecs
|
|
730
746
|
);
|
|
731
747
|
return claudeTemplate === templateText
|
|
732
748
|
? renderAgentPromptCommand(templateText, prompt)
|
|
@@ -761,14 +777,14 @@ function buildOpenCodeAgentExecCommand(template, prompt) {
|
|
|
761
777
|
: renderAgentPromptCommand(opencodeTemplate, prompt);
|
|
762
778
|
}
|
|
763
779
|
|
|
764
|
-
function buildWebAgentExecCommand(template, prompt, agentProgram) {
|
|
780
|
+
function buildWebAgentExecCommand(template, prompt, agentProgram, options = {}) {
|
|
765
781
|
switch (agentProgram) {
|
|
766
782
|
case 'claude':
|
|
767
|
-
return buildClaudeAgentExecCommand(template, prompt);
|
|
783
|
+
return buildClaudeAgentExecCommand(template, prompt, options);
|
|
768
784
|
case 'gemini':
|
|
769
785
|
return buildGeminiAgentExecCommand(template, prompt);
|
|
770
786
|
case 'codex':
|
|
771
|
-
return buildCodexAgentExecCommand(template, prompt);
|
|
787
|
+
return buildCodexAgentExecCommand(template, prompt, options);
|
|
772
788
|
case 'opencode':
|
|
773
789
|
return buildOpenCodeAgentExecCommand(template, prompt);
|
|
774
790
|
default:
|
|
@@ -905,6 +921,36 @@ function extractAgentMessageFromStructuredOutput(agentProgram, text) {
|
|
|
905
921
|
return '';
|
|
906
922
|
}
|
|
907
923
|
|
|
924
|
+
function extractCodexThreadId(text) {
|
|
925
|
+
for (const rawLine of String(text || '').split('\n')) {
|
|
926
|
+
const payload = parseJsonObjectLine(rawLine);
|
|
927
|
+
if (payload && payload.type === 'thread.started' && typeof payload.thread_id === 'string') {
|
|
928
|
+
return payload.thread_id.trim();
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
return '';
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
function extractClaudeSessionId(text) {
|
|
935
|
+
for (const rawLine of String(text || '').split('\n')) {
|
|
936
|
+
const payload = parseJsonObjectLine(rawLine);
|
|
937
|
+
if (payload && payload.type === 'system' && payload.subtype === 'init' && typeof payload.session_id === 'string') {
|
|
938
|
+
return payload.session_id.trim();
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
return '';
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
function extractEngineSessionId(agentProgram, text) {
|
|
945
|
+
if (agentProgram === 'codex') {
|
|
946
|
+
return extractCodexThreadId(text);
|
|
947
|
+
}
|
|
948
|
+
if (agentProgram === 'claude') {
|
|
949
|
+
return extractClaudeSessionId(text);
|
|
950
|
+
}
|
|
951
|
+
return '';
|
|
952
|
+
}
|
|
953
|
+
|
|
908
954
|
function getAgentRuntimeMeta(template) {
|
|
909
955
|
const normalizedTemplate = normalizeAgentPromptCommandTemplate(template, 'agentPromptCommand');
|
|
910
956
|
const agentProgram = resolveAgentProgram(normalizedTemplate);
|
|
@@ -1603,11 +1649,19 @@ async function prepareWebAgentExecution(ctx, state, sessionRef, prompt) {
|
|
|
1603
1649
|
await ensureWebContainer(ctx, state, sessionRef.containerName, sessionRef);
|
|
1604
1650
|
const agentMeta = getAgentRuntimeMeta(effectiveTemplate);
|
|
1605
1651
|
const hasPriorConversation = hasAgentConversationHistory(agentSession);
|
|
1652
|
+
const useNativeSessionResume = NATIVE_SESSION_RESUME_PROGRAMS.has(agentMeta.agentProgram);
|
|
1606
1653
|
let resumeAttempted = false;
|
|
1607
1654
|
let resumeSucceeded = false;
|
|
1608
1655
|
let resumeError = '';
|
|
1656
|
+
let sessionIdForRun = '';
|
|
1609
1657
|
|
|
1610
|
-
if (
|
|
1658
|
+
if (useNativeSessionResume && hasPriorConversation && agentSession.engineSessionId) {
|
|
1659
|
+
// 已有引擎会话 ID 时直接续接;旧 Web 会话缺少 ID 时注入一次历史来建立新会话,
|
|
1660
|
+
// 会话 ID 由引擎自己生成,执行结束后从输出中提取(见 finalizeWebAgentExecution)。
|
|
1661
|
+
sessionIdForRun = agentSession.engineSessionId;
|
|
1662
|
+
resumeAttempted = true;
|
|
1663
|
+
resumeSucceeded = true;
|
|
1664
|
+
} else if (!useNativeSessionResume && hasPriorConversation && agentMeta.resumeSupported && agentMeta.resumeCommand) {
|
|
1611
1665
|
resumeAttempted = true;
|
|
1612
1666
|
const resumeResult = await execCommandInWebContainer(ctx, sessionRef.containerName, agentMeta.resumeCommand);
|
|
1613
1667
|
if (resumeResult.exitCode === 0) {
|
|
@@ -1620,7 +1674,9 @@ async function prepareWebAgentExecution(ctx, state, sessionRef, prompt) {
|
|
|
1620
1674
|
const effectivePrompt = resumeSucceeded
|
|
1621
1675
|
? prompt
|
|
1622
1676
|
: buildAgentPromptWithHistory(agentSession, prompt);
|
|
1623
|
-
const command = buildWebAgentExecCommand(effectiveTemplate, effectivePrompt, agentMeta.agentProgram
|
|
1677
|
+
const command = buildWebAgentExecCommand(effectiveTemplate, effectivePrompt, agentMeta.agentProgram, {
|
|
1678
|
+
sessionId: sessionIdForRun
|
|
1679
|
+
});
|
|
1624
1680
|
const contextMode = resumeSucceeded ? 'resume' : (hasPriorConversation ? 'history-injected' : 'first-turn');
|
|
1625
1681
|
|
|
1626
1682
|
return {
|
|
@@ -1631,7 +1687,8 @@ async function prepareWebAgentExecution(ctx, state, sessionRef, prompt) {
|
|
|
1631
1687
|
contextMode,
|
|
1632
1688
|
resumeAttempted,
|
|
1633
1689
|
resumeSucceeded,
|
|
1634
|
-
resumeError
|
|
1690
|
+
resumeError,
|
|
1691
|
+
engineSessionId: sessionIdForRun
|
|
1635
1692
|
};
|
|
1636
1693
|
}
|
|
1637
1694
|
|
|
@@ -1644,11 +1701,16 @@ function finalizeWebAgentExecution(state, sessionRef, agentSession, agentMeta, m
|
|
|
1644
1701
|
resumeSucceeded: meta.resumeSucceeded,
|
|
1645
1702
|
interrupted: result.interrupted === true
|
|
1646
1703
|
});
|
|
1647
|
-
|
|
1704
|
+
const patch = {
|
|
1648
1705
|
lastResumeAt: meta.resumeAttempted ? new Date().toISOString() : (agentSession.lastResumeAt || null),
|
|
1649
1706
|
lastResumeOk: meta.resumeAttempted ? meta.resumeSucceeded : agentSession.lastResumeOk,
|
|
1650
1707
|
lastResumeError: meta.resumeAttempted ? (meta.resumeSucceeded ? '' : meta.resumeError) : (agentSession.lastResumeError || '')
|
|
1651
|
-
}
|
|
1708
|
+
};
|
|
1709
|
+
const engineSessionId = meta.engineSessionId || extractEngineSessionId(agentMeta.agentProgram, result.stdout);
|
|
1710
|
+
if (engineSessionId) {
|
|
1711
|
+
patch.engineSessionId = engineSessionId;
|
|
1712
|
+
}
|
|
1713
|
+
patchWebAgentSessionState(state.webHistoryDir, sessionRef, patch);
|
|
1652
1714
|
}
|
|
1653
1715
|
|
|
1654
1716
|
function appendWebAgentTraceMessage(webHistoryDir, sessionRefOrContainerName, content, extra = {}) {
|
|
@@ -3044,6 +3106,7 @@ async function execAgentInWebContainerStream(ctx, state, sessionRefOrContainerNa
|
|
|
3044
3106
|
resolve({
|
|
3045
3107
|
exitCode,
|
|
3046
3108
|
output,
|
|
3109
|
+
stdout: clippedStdout,
|
|
3047
3110
|
interrupted: exitCode !== 0 && runState.stopping === true
|
|
3048
3111
|
});
|
|
3049
3112
|
});
|
|
@@ -4056,7 +4119,7 @@ async function handleWebApi(req, res, pathname, ctx, state) {
|
|
|
4056
4119
|
return;
|
|
4057
4120
|
}
|
|
4058
4121
|
|
|
4059
|
-
const { agentSession, agentMeta, command, contextMode, resumeAttempted, resumeSucceeded, resumeError } = prepared;
|
|
4122
|
+
const { agentSession, agentMeta, command, contextMode, resumeAttempted, resumeSucceeded, resumeError, engineSessionId } = prepared;
|
|
4060
4123
|
appendWebSessionMessage(state.webHistoryDir, sessionRef, 'user', prompt, {
|
|
4061
4124
|
mode: 'agent',
|
|
4062
4125
|
contextMode
|
|
@@ -4068,7 +4131,8 @@ async function handleWebApi(req, res, pathname, ctx, state) {
|
|
|
4068
4131
|
contextMode,
|
|
4069
4132
|
resumeAttempted,
|
|
4070
4133
|
resumeSucceeded,
|
|
4071
|
-
resumeError
|
|
4134
|
+
resumeError,
|
|
4135
|
+
engineSessionId
|
|
4072
4136
|
}, result);
|
|
4073
4137
|
sendJson(res, 200, {
|
|
4074
4138
|
exitCode: result.exitCode,
|
|
@@ -4124,7 +4188,7 @@ async function handleWebApi(req, res, pathname, ctx, state) {
|
|
|
4124
4188
|
return;
|
|
4125
4189
|
}
|
|
4126
4190
|
|
|
4127
|
-
const { agentSession, agentMeta, command, contextMode, resumeAttempted, resumeSucceeded, resumeError } = prepared;
|
|
4191
|
+
const { agentSession, agentMeta, command, contextMode, resumeAttempted, resumeSucceeded, resumeError, engineSessionId } = prepared;
|
|
4128
4192
|
const traceLines = ['[执行过程]'];
|
|
4129
4193
|
const traceEvents = [];
|
|
4130
4194
|
let streamingReplyMessageId = '';
|
|
@@ -4226,7 +4290,8 @@ async function handleWebApi(req, res, pathname, ctx, state) {
|
|
|
4226
4290
|
contextMode,
|
|
4227
4291
|
resumeAttempted,
|
|
4228
4292
|
resumeSucceeded,
|
|
4229
|
-
resumeError
|
|
4293
|
+
resumeError,
|
|
4294
|
+
engineSessionId
|
|
4230
4295
|
}, result);
|
|
4231
4296
|
sendNdjson(res, {
|
|
4232
4297
|
type: 'result',
|