@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.
@@ -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
- codexTemplate = `${prefix}codex exec${injectedFlags}${suffix}`;
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 (hasPriorConversation && agentMeta.resumeSupported && agentMeta.resumeCommand) {
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
- patchWebAgentSessionState(state.webHistoryDir, sessionRef, {
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',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcanwin/manyoyo",
3
- "version": "5.11.14",
3
+ "version": "5.11.16",
4
4
  "imageVersion": "1.9.1-common",
5
5
  "playwrightCliVersion": "0.1.14",
6
6
  "description": "AI Agent CLI Security Sandbox for Docker and Podman",