@xcanwin/manyoyo 5.11.15 → 6.0.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 +115 -28
- package/package.json +1 -1
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
|
+
// 支持按 Web Agent 保存的引擎会话 ID 原生续接,避免历史文本被截断。
|
|
38
|
+
const NATIVE_SESSION_RESUME_PROGRAMS = new Set(['claude', 'codex', 'opencode']);
|
|
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)
|
|
@@ -747,30 +763,38 @@ function buildGeminiAgentExecCommand(template, prompt) {
|
|
|
747
763
|
: renderAgentPromptCommand(geminiTemplate, prompt);
|
|
748
764
|
}
|
|
749
765
|
|
|
750
|
-
function buildOpenCodeAgentExecCommand(template, prompt) {
|
|
766
|
+
function buildOpenCodeAgentExecCommand(template, prompt, options = {}) {
|
|
751
767
|
const templateText = normalizeAgentPromptCommandTemplate(template, 'agentPromptCommand');
|
|
768
|
+
const sessionId = options && typeof options.sessionId === 'string' ? options.sessionId.trim() : '';
|
|
769
|
+
const flagSpecs = [
|
|
770
|
+
{ flag: '--format json', pattern: /(?:^|\s)--format(?:\s|$)/ }
|
|
771
|
+
];
|
|
772
|
+
if (sessionId) {
|
|
773
|
+
flagSpecs.push({
|
|
774
|
+
flag: `--session ${quoteBashSingleValue(sessionId)}`,
|
|
775
|
+
pattern: /(?:^|\s)(?:--session|-s)(?:\s|$)/
|
|
776
|
+
});
|
|
777
|
+
}
|
|
752
778
|
const opencodeTemplate = prependAgentFlags(
|
|
753
779
|
templateText,
|
|
754
|
-
/^(((?:(?:[A-Za-z_][A-Za-z0-9_]*=)(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*'|[^\s]+)\s+)*)opencode\s+run\b)(.*)$/,
|
|
755
|
-
|
|
756
|
-
{ flag: '--format json', pattern: /(?:^|\s)--format(?:\s|$)/ }
|
|
757
|
-
]
|
|
780
|
+
/^(((?:(?:[A-Za-z_][A-Za-z0-9_]*=)(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*'|[^\s]+)\s+)*)opencode\b.*?\s+run\b)(.*)$/,
|
|
781
|
+
flagSpecs
|
|
758
782
|
);
|
|
759
783
|
return opencodeTemplate === templateText
|
|
760
784
|
? renderAgentPromptCommand(templateText, prompt)
|
|
761
785
|
: renderAgentPromptCommand(opencodeTemplate, prompt);
|
|
762
786
|
}
|
|
763
787
|
|
|
764
|
-
function buildWebAgentExecCommand(template, prompt, agentProgram) {
|
|
788
|
+
function buildWebAgentExecCommand(template, prompt, agentProgram, options = {}) {
|
|
765
789
|
switch (agentProgram) {
|
|
766
790
|
case 'claude':
|
|
767
|
-
return buildClaudeAgentExecCommand(template, prompt);
|
|
791
|
+
return buildClaudeAgentExecCommand(template, prompt, options);
|
|
768
792
|
case 'gemini':
|
|
769
793
|
return buildGeminiAgentExecCommand(template, prompt);
|
|
770
794
|
case 'codex':
|
|
771
|
-
return buildCodexAgentExecCommand(template, prompt);
|
|
795
|
+
return buildCodexAgentExecCommand(template, prompt, options);
|
|
772
796
|
case 'opencode':
|
|
773
|
-
return buildOpenCodeAgentExecCommand(template, prompt);
|
|
797
|
+
return buildOpenCodeAgentExecCommand(template, prompt, options);
|
|
774
798
|
default:
|
|
775
799
|
break;
|
|
776
800
|
}
|
|
@@ -874,7 +898,7 @@ function extractOpenCodeAgentMessage(text) {
|
|
|
874
898
|
if (role && role !== 'assistant') {
|
|
875
899
|
continue;
|
|
876
900
|
}
|
|
877
|
-
const content = collectStructuredText(message.content || payload.content || payload.text || payload);
|
|
901
|
+
const content = collectStructuredText(message.content || payload.content || payload.text || payload.part || payload);
|
|
878
902
|
if (!content) {
|
|
879
903
|
continue;
|
|
880
904
|
}
|
|
@@ -905,6 +929,50 @@ function extractAgentMessageFromStructuredOutput(agentProgram, text) {
|
|
|
905
929
|
return '';
|
|
906
930
|
}
|
|
907
931
|
|
|
932
|
+
function extractCodexThreadId(text) {
|
|
933
|
+
for (const rawLine of String(text || '').split('\n')) {
|
|
934
|
+
const payload = parseJsonObjectLine(rawLine);
|
|
935
|
+
if (payload && payload.type === 'thread.started' && typeof payload.thread_id === 'string') {
|
|
936
|
+
return payload.thread_id.trim();
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
return '';
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
function extractClaudeSessionId(text) {
|
|
943
|
+
for (const rawLine of String(text || '').split('\n')) {
|
|
944
|
+
const payload = parseJsonObjectLine(rawLine);
|
|
945
|
+
if (payload && payload.type === 'system' && payload.subtype === 'init' && typeof payload.session_id === 'string') {
|
|
946
|
+
return payload.session_id.trim();
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
return '';
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
function extractOpenCodeSessionId(text) {
|
|
953
|
+
for (const rawLine of String(text || '').split('\n')) {
|
|
954
|
+
const payload = parseJsonObjectLine(rawLine);
|
|
955
|
+
const sessionId = payload && pickFirstString(payload.sessionID, payload.session_id);
|
|
956
|
+
if (sessionId) {
|
|
957
|
+
return sessionId;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
return '';
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
function extractEngineSessionId(agentProgram, text) {
|
|
964
|
+
if (agentProgram === 'codex') {
|
|
965
|
+
return extractCodexThreadId(text);
|
|
966
|
+
}
|
|
967
|
+
if (agentProgram === 'claude') {
|
|
968
|
+
return extractClaudeSessionId(text);
|
|
969
|
+
}
|
|
970
|
+
if (agentProgram === 'opencode') {
|
|
971
|
+
return extractOpenCodeSessionId(text);
|
|
972
|
+
}
|
|
973
|
+
return '';
|
|
974
|
+
}
|
|
975
|
+
|
|
908
976
|
function getAgentRuntimeMeta(template) {
|
|
909
977
|
const normalizedTemplate = normalizeAgentPromptCommandTemplate(template, 'agentPromptCommand');
|
|
910
978
|
const agentProgram = resolveAgentProgram(normalizedTemplate);
|
|
@@ -1603,11 +1671,19 @@ async function prepareWebAgentExecution(ctx, state, sessionRef, prompt) {
|
|
|
1603
1671
|
await ensureWebContainer(ctx, state, sessionRef.containerName, sessionRef);
|
|
1604
1672
|
const agentMeta = getAgentRuntimeMeta(effectiveTemplate);
|
|
1605
1673
|
const hasPriorConversation = hasAgentConversationHistory(agentSession);
|
|
1674
|
+
const useNativeSessionResume = NATIVE_SESSION_RESUME_PROGRAMS.has(agentMeta.agentProgram);
|
|
1606
1675
|
let resumeAttempted = false;
|
|
1607
1676
|
let resumeSucceeded = false;
|
|
1608
1677
|
let resumeError = '';
|
|
1678
|
+
let sessionIdForRun = '';
|
|
1609
1679
|
|
|
1610
|
-
if (
|
|
1680
|
+
if (useNativeSessionResume && hasPriorConversation && agentSession.engineSessionId) {
|
|
1681
|
+
// 已有引擎会话 ID 时直接续接;旧 Web 会话缺少 ID 时注入一次历史来建立新会话,
|
|
1682
|
+
// 会话 ID 由引擎自己生成,执行结束后从输出中提取(见 finalizeWebAgentExecution)。
|
|
1683
|
+
sessionIdForRun = agentSession.engineSessionId;
|
|
1684
|
+
resumeAttempted = true;
|
|
1685
|
+
resumeSucceeded = true;
|
|
1686
|
+
} else if (!useNativeSessionResume && hasPriorConversation && agentMeta.resumeSupported && agentMeta.resumeCommand) {
|
|
1611
1687
|
resumeAttempted = true;
|
|
1612
1688
|
const resumeResult = await execCommandInWebContainer(ctx, sessionRef.containerName, agentMeta.resumeCommand);
|
|
1613
1689
|
if (resumeResult.exitCode === 0) {
|
|
@@ -1620,7 +1696,9 @@ async function prepareWebAgentExecution(ctx, state, sessionRef, prompt) {
|
|
|
1620
1696
|
const effectivePrompt = resumeSucceeded
|
|
1621
1697
|
? prompt
|
|
1622
1698
|
: buildAgentPromptWithHistory(agentSession, prompt);
|
|
1623
|
-
const command = buildWebAgentExecCommand(effectiveTemplate, effectivePrompt, agentMeta.agentProgram
|
|
1699
|
+
const command = buildWebAgentExecCommand(effectiveTemplate, effectivePrompt, agentMeta.agentProgram, {
|
|
1700
|
+
sessionId: sessionIdForRun
|
|
1701
|
+
});
|
|
1624
1702
|
const contextMode = resumeSucceeded ? 'resume' : (hasPriorConversation ? 'history-injected' : 'first-turn');
|
|
1625
1703
|
|
|
1626
1704
|
return {
|
|
@@ -1631,7 +1709,8 @@ async function prepareWebAgentExecution(ctx, state, sessionRef, prompt) {
|
|
|
1631
1709
|
contextMode,
|
|
1632
1710
|
resumeAttempted,
|
|
1633
1711
|
resumeSucceeded,
|
|
1634
|
-
resumeError
|
|
1712
|
+
resumeError,
|
|
1713
|
+
engineSessionId: sessionIdForRun
|
|
1635
1714
|
};
|
|
1636
1715
|
}
|
|
1637
1716
|
|
|
@@ -1644,11 +1723,16 @@ function finalizeWebAgentExecution(state, sessionRef, agentSession, agentMeta, m
|
|
|
1644
1723
|
resumeSucceeded: meta.resumeSucceeded,
|
|
1645
1724
|
interrupted: result.interrupted === true
|
|
1646
1725
|
});
|
|
1647
|
-
|
|
1726
|
+
const patch = {
|
|
1648
1727
|
lastResumeAt: meta.resumeAttempted ? new Date().toISOString() : (agentSession.lastResumeAt || null),
|
|
1649
1728
|
lastResumeOk: meta.resumeAttempted ? meta.resumeSucceeded : agentSession.lastResumeOk,
|
|
1650
1729
|
lastResumeError: meta.resumeAttempted ? (meta.resumeSucceeded ? '' : meta.resumeError) : (agentSession.lastResumeError || '')
|
|
1651
|
-
}
|
|
1730
|
+
};
|
|
1731
|
+
const engineSessionId = meta.engineSessionId || extractEngineSessionId(agentMeta.agentProgram, result.stdout);
|
|
1732
|
+
if (engineSessionId) {
|
|
1733
|
+
patch.engineSessionId = engineSessionId;
|
|
1734
|
+
}
|
|
1735
|
+
patchWebAgentSessionState(state.webHistoryDir, sessionRef, patch);
|
|
1652
1736
|
}
|
|
1653
1737
|
|
|
1654
1738
|
function appendWebAgentTraceMessage(webHistoryDir, sessionRefOrContainerName, content, extra = {}) {
|
|
@@ -3044,6 +3128,7 @@ async function execAgentInWebContainerStream(ctx, state, sessionRefOrContainerNa
|
|
|
3044
3128
|
resolve({
|
|
3045
3129
|
exitCode,
|
|
3046
3130
|
output,
|
|
3131
|
+
stdout: clippedStdout,
|
|
3047
3132
|
interrupted: exitCode !== 0 && runState.stopping === true
|
|
3048
3133
|
});
|
|
3049
3134
|
});
|
|
@@ -4056,7 +4141,7 @@ async function handleWebApi(req, res, pathname, ctx, state) {
|
|
|
4056
4141
|
return;
|
|
4057
4142
|
}
|
|
4058
4143
|
|
|
4059
|
-
const { agentSession, agentMeta, command, contextMode, resumeAttempted, resumeSucceeded, resumeError } = prepared;
|
|
4144
|
+
const { agentSession, agentMeta, command, contextMode, resumeAttempted, resumeSucceeded, resumeError, engineSessionId } = prepared;
|
|
4060
4145
|
appendWebSessionMessage(state.webHistoryDir, sessionRef, 'user', prompt, {
|
|
4061
4146
|
mode: 'agent',
|
|
4062
4147
|
contextMode
|
|
@@ -4068,7 +4153,8 @@ async function handleWebApi(req, res, pathname, ctx, state) {
|
|
|
4068
4153
|
contextMode,
|
|
4069
4154
|
resumeAttempted,
|
|
4070
4155
|
resumeSucceeded,
|
|
4071
|
-
resumeError
|
|
4156
|
+
resumeError,
|
|
4157
|
+
engineSessionId
|
|
4072
4158
|
}, result);
|
|
4073
4159
|
sendJson(res, 200, {
|
|
4074
4160
|
exitCode: result.exitCode,
|
|
@@ -4124,7 +4210,7 @@ async function handleWebApi(req, res, pathname, ctx, state) {
|
|
|
4124
4210
|
return;
|
|
4125
4211
|
}
|
|
4126
4212
|
|
|
4127
|
-
const { agentSession, agentMeta, command, contextMode, resumeAttempted, resumeSucceeded, resumeError } = prepared;
|
|
4213
|
+
const { agentSession, agentMeta, command, contextMode, resumeAttempted, resumeSucceeded, resumeError, engineSessionId } = prepared;
|
|
4128
4214
|
const traceLines = ['[执行过程]'];
|
|
4129
4215
|
const traceEvents = [];
|
|
4130
4216
|
let streamingReplyMessageId = '';
|
|
@@ -4226,7 +4312,8 @@ async function handleWebApi(req, res, pathname, ctx, state) {
|
|
|
4226
4312
|
contextMode,
|
|
4227
4313
|
resumeAttempted,
|
|
4228
4314
|
resumeSucceeded,
|
|
4229
|
-
resumeError
|
|
4315
|
+
resumeError,
|
|
4316
|
+
engineSessionId
|
|
4230
4317
|
}, result);
|
|
4231
4318
|
sendNdjson(res, {
|
|
4232
4319
|
type: 'result',
|