@xcanwin/manyoyo 5.8.6 → 5.8.10

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 CHANGED
@@ -10,6 +10,13 @@ const WebSocket = require('ws');
10
10
  const JSON5 = require('json5');
11
11
  const { buildContainerRunArgs } = require('../container-run');
12
12
  const { extractAgentMessageFromCodexJsonl } = require('../codex-output');
13
+ const { findValueRangeByPath, applyTextReplacements } = require('../json5-text-edit');
14
+ const { resolveRuntimeConfig } = require('../runtime-resolver');
15
+ const {
16
+ parseEnvEntry,
17
+ expandHomeAliasPath,
18
+ normalizeVolume
19
+ } = require('../runtime-normalizers');
13
20
  const {
14
21
  resolveAgentProgram,
15
22
  resolveAgentPromptCommandTemplate,
@@ -1636,226 +1643,6 @@ function isSensitiveConfigKey(key) {
1636
1643
  return Boolean(normalized) && SENSITIVE_CONFIG_KEY_PATTERN.test(normalized);
1637
1644
  }
1638
1645
 
1639
- function readConfigQuotedString(text, startIndex) {
1640
- const quote = text[startIndex];
1641
- let value = '';
1642
-
1643
- for (let i = startIndex + 1; i < text.length; i += 1) {
1644
- const ch = text[i];
1645
- if (ch === '\\') {
1646
- value += ch;
1647
- if (i + 1 < text.length) {
1648
- value += text[i + 1];
1649
- i += 1;
1650
- }
1651
- continue;
1652
- }
1653
- if (ch === quote) {
1654
- return {
1655
- value,
1656
- end: i + 1
1657
- };
1658
- }
1659
- value += ch;
1660
- }
1661
-
1662
- return null;
1663
- }
1664
-
1665
- function isConfigIdentifierStart(ch) {
1666
- return /[A-Za-z_$]/.test(ch);
1667
- }
1668
-
1669
- function isConfigIdentifierPart(ch) {
1670
- return /[A-Za-z0-9_$]/.test(ch);
1671
- }
1672
-
1673
- function skipConfigTrivia(text, index) {
1674
- let cursor = index;
1675
- while (cursor < text.length) {
1676
- const ch = text[cursor];
1677
- const next = text[cursor + 1];
1678
- if (/\s/.test(ch)) {
1679
- cursor += 1;
1680
- continue;
1681
- }
1682
- if (ch === '/' && next === '/') {
1683
- cursor += 2;
1684
- while (cursor < text.length && text[cursor] !== '\n') {
1685
- cursor += 1;
1686
- }
1687
- continue;
1688
- }
1689
- if (ch === '/' && next === '*') {
1690
- cursor += 2;
1691
- while (cursor + 1 < text.length && !(text[cursor] === '*' && text[cursor + 1] === '/')) {
1692
- cursor += 1;
1693
- }
1694
- cursor = cursor + 1 < text.length ? cursor + 2 : text.length;
1695
- continue;
1696
- }
1697
- break;
1698
- }
1699
- return cursor;
1700
- }
1701
-
1702
- function scanConfigValueEnd(text, startIndex) {
1703
- let cursor = startIndex;
1704
- let stringQuote = '';
1705
- let lineComment = false;
1706
- let blockComment = false;
1707
- let depth = 0;
1708
-
1709
- for (; cursor < text.length; cursor += 1) {
1710
- const ch = text[cursor];
1711
- const next = text[cursor + 1];
1712
-
1713
- if (lineComment) {
1714
- if (ch === '\n') {
1715
- lineComment = false;
1716
- }
1717
- continue;
1718
- }
1719
- if (blockComment) {
1720
- if (ch === '*' && next === '/') {
1721
- blockComment = false;
1722
- cursor += 1;
1723
- }
1724
- continue;
1725
- }
1726
- if (stringQuote) {
1727
- if (ch === '\\') {
1728
- cursor += 1;
1729
- continue;
1730
- }
1731
- if (ch === stringQuote) {
1732
- stringQuote = '';
1733
- }
1734
- continue;
1735
- }
1736
-
1737
- if (ch === '/' && next === '/') {
1738
- lineComment = true;
1739
- cursor += 1;
1740
- continue;
1741
- }
1742
- if (ch === '/' && next === '*') {
1743
- blockComment = true;
1744
- cursor += 1;
1745
- continue;
1746
- }
1747
- if (ch === '"' || ch === '\'') {
1748
- stringQuote = ch;
1749
- continue;
1750
- }
1751
- if (ch === '{' || ch === '[' || ch === '(') {
1752
- depth += 1;
1753
- continue;
1754
- }
1755
- if (ch === '}' || ch === ']' || ch === ')') {
1756
- if (depth === 0) {
1757
- break;
1758
- }
1759
- depth -= 1;
1760
- continue;
1761
- }
1762
- if (depth === 0 && ch === ',') {
1763
- break;
1764
- }
1765
- }
1766
-
1767
- let end = cursor;
1768
- while (end > startIndex && /\s/.test(text[end - 1])) {
1769
- end -= 1;
1770
- }
1771
- return end;
1772
- }
1773
-
1774
- function findConfigRootObjectStart(text) {
1775
- const start = skipConfigTrivia(String(text || ''), 0);
1776
- return text[start] === '{' ? start : -1;
1777
- }
1778
-
1779
- function readConfigPropertyToken(text, startIndex) {
1780
- const ch = text[startIndex];
1781
- if (ch === '"' || ch === '\'') {
1782
- const token = readConfigQuotedString(text, startIndex);
1783
- if (!token) {
1784
- return null;
1785
- }
1786
- return token;
1787
- }
1788
- if (!isConfigIdentifierStart(ch)) {
1789
- return null;
1790
- }
1791
- let end = startIndex + 1;
1792
- while (end < text.length && isConfigIdentifierPart(text[end])) {
1793
- end += 1;
1794
- }
1795
- return {
1796
- value: text.slice(startIndex, end),
1797
- end
1798
- };
1799
- }
1800
-
1801
- function findConfigObjectPropertyValueRange(text, objectStartIndex, propertyName) {
1802
- let cursor = skipConfigTrivia(text, objectStartIndex + 1);
1803
- while (cursor < text.length) {
1804
- cursor = skipConfigTrivia(text, cursor);
1805
- if (text[cursor] === '}') {
1806
- return null;
1807
- }
1808
- const token = readConfigPropertyToken(text, cursor);
1809
- if (!token) {
1810
- return null;
1811
- }
1812
- cursor = skipConfigTrivia(text, token.end);
1813
- if (text[cursor] !== ':') {
1814
- return null;
1815
- }
1816
- const valueStart = skipConfigTrivia(text, cursor + 1);
1817
- const valueEnd = scanConfigValueEnd(text, valueStart);
1818
- if (token.value === propertyName) {
1819
- return { start: valueStart, end: valueEnd };
1820
- }
1821
- cursor = skipConfigTrivia(text, valueEnd);
1822
- if (text[cursor] === ',') {
1823
- cursor += 1;
1824
- continue;
1825
- }
1826
- if (text[cursor] === '}') {
1827
- return null;
1828
- }
1829
- }
1830
- return null;
1831
- }
1832
-
1833
- function findConfigValueRangeByPath(text, pathParts) {
1834
- if (!Array.isArray(pathParts) || pathParts.length === 0) {
1835
- return null;
1836
- }
1837
- let objectStart = findConfigRootObjectStart(text);
1838
- if (objectStart === -1) {
1839
- return null;
1840
- }
1841
- let range = null;
1842
- for (let i = 0; i < pathParts.length; i += 1) {
1843
- range = findConfigObjectPropertyValueRange(text, objectStart, pathParts[i]);
1844
- if (!range) {
1845
- return null;
1846
- }
1847
- if (i === pathParts.length - 1) {
1848
- return range;
1849
- }
1850
- const nextObjectStart = skipConfigTrivia(text, range.start);
1851
- if (text[nextObjectStart] !== '{') {
1852
- return null;
1853
- }
1854
- objectStart = nextObjectStart;
1855
- }
1856
- return range;
1857
- }
1858
-
1859
1646
  function collectSensitiveConfigPaths(value, pathParts = []) {
1860
1647
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
1861
1648
  return [];
@@ -1894,13 +1681,6 @@ function collectSensitivePlaceholderPaths(value, pathParts = []) {
1894
1681
  return result;
1895
1682
  }
1896
1683
 
1897
- function applyConfigTextReplacements(text, replacements) {
1898
- return replacements
1899
- .slice()
1900
- .sort((a, b) => b.start - a.start)
1901
- .reduce((result, item) => `${result.slice(0, item.start)}${item.text}${result.slice(item.end)}`, text);
1902
- }
1903
-
1904
1684
  function buildConfigPathLabel(pathParts) {
1905
1685
  return (Array.isArray(pathParts) ? pathParts : []).join('.');
1906
1686
  }
@@ -1908,7 +1688,7 @@ function buildConfigPathLabel(pathParts) {
1908
1688
  function maskWebConfigRaw(raw, parsed) {
1909
1689
  const text = String(raw || '');
1910
1690
  const replacements = collectSensitiveConfigPaths(parsed).map(pathParts => {
1911
- const range = findConfigValueRangeByPath(text, pathParts);
1691
+ const range = findValueRangeByPath(text, pathParts);
1912
1692
  if (!range) {
1913
1693
  throw new Error(`敏感字段定位失败: ${buildConfigPathLabel(pathParts)}`);
1914
1694
  }
@@ -1918,7 +1698,7 @@ function maskWebConfigRaw(raw, parsed) {
1918
1698
  text: JSON.stringify(WEB_CONFIG_KEEP_SECRET_PLACEHOLDER)
1919
1699
  };
1920
1700
  });
1921
- return applyConfigTextReplacements(text, replacements);
1701
+ return applyTextReplacements(text, replacements);
1922
1702
  }
1923
1703
 
1924
1704
  function parseConfigRawObject(raw) {
@@ -1946,8 +1726,8 @@ function restoreWebConfigSecrets(raw, snapshot) {
1946
1726
 
1947
1727
  const currentRaw = String(snapshot.raw || '');
1948
1728
  const replacements = placeholderPaths.map(pathParts => {
1949
- const editedRange = findConfigValueRangeByPath(text, pathParts);
1950
- const currentRange = findConfigValueRangeByPath(currentRaw, pathParts);
1729
+ const editedRange = findValueRangeByPath(text, pathParts);
1730
+ const currentRange = findValueRangeByPath(currentRaw, pathParts);
1951
1731
  if (!editedRange) {
1952
1732
  throw new Error(`敏感字段定位失败: ${buildConfigPathLabel(pathParts)}`);
1953
1733
  }
@@ -1961,7 +1741,7 @@ function restoreWebConfigSecrets(raw, snapshot) {
1961
1741
  };
1962
1742
  });
1963
1743
 
1964
- return applyConfigTextReplacements(text, replacements);
1744
+ return applyTextReplacements(text, replacements);
1965
1745
  }
1966
1746
 
1967
1747
  function redactConfigValue(value) {
@@ -2078,23 +1858,6 @@ function validateWebHostPath(hostPath) {
2078
1858
  }
2079
1859
  }
2080
1860
 
2081
- function parseEnvEntry(entryText) {
2082
- const text = String(entryText || '');
2083
- const idx = text.indexOf('=');
2084
- if (idx <= 0) {
2085
- throw new Error(`env 格式应为 KEY=VALUE: ${text}`);
2086
- }
2087
- const key = text.slice(0, idx);
2088
- const value = text.slice(idx + 1);
2089
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
2090
- throw new Error(`env key 非法: ${key}`);
2091
- }
2092
- if (/[\r\n\0]/.test(value) || /[;&|`$<>]/.test(value)) {
2093
- throw new Error(`env value 含非法字符: ${key}`);
2094
- }
2095
- return { key, value };
2096
- }
2097
-
2098
1861
  function normalizeEnvMap(envConfig, sourceLabel) {
2099
1862
  if (envConfig === undefined || envConfig === null) {
2100
1863
  return {};
@@ -2113,6 +1876,15 @@ function normalizeEnvMap(envConfig, sourceLabel) {
2113
1876
  return result;
2114
1877
  }
2115
1878
 
1879
+ function normalizeCliEnvMap(envList) {
1880
+ const result = {};
1881
+ for (const envText of (envList || [])) {
1882
+ const parsed = parseEnvEntry(envText);
1883
+ result[parsed.key] = parsed.value;
1884
+ }
1885
+ return result;
1886
+ }
1887
+
2116
1888
  function normalizeStringArray(value, sourceLabel) {
2117
1889
  if (value === undefined || value === null) {
2118
1890
  return [];
@@ -2125,41 +1897,6 @@ function normalizeStringArray(value, sourceLabel) {
2125
1897
  .filter(Boolean);
2126
1898
  }
2127
1899
 
2128
- function expandHomeAliasPath(filePath) {
2129
- const text = String(filePath || '').trim();
2130
- if (!text) {
2131
- return text;
2132
- }
2133
- const homeDir = os.homedir();
2134
- if (text === '~') {
2135
- return homeDir;
2136
- }
2137
- if (text.startsWith('~/')) {
2138
- return path.join(homeDir, text.slice(2));
2139
- }
2140
- if (text === '$HOME') {
2141
- return homeDir;
2142
- }
2143
- if (text.startsWith('$HOME/')) {
2144
- return path.join(homeDir, text.slice('$HOME/'.length));
2145
- }
2146
- return text;
2147
- }
2148
-
2149
- function normalizeVolume(volume) {
2150
- const text = String(volume || '').trim();
2151
- if (!text.startsWith('~') && !text.startsWith('$HOME')) {
2152
- return text;
2153
- }
2154
- const separatorIndex = text.indexOf(':');
2155
- if (separatorIndex === -1) {
2156
- return expandHomeAliasPath(text);
2157
- }
2158
- const hostPath = text.slice(0, separatorIndex);
2159
- const rest = text.slice(separatorIndex);
2160
- return `${expandHomeAliasPath(hostPath)}${rest}`;
2161
- }
2162
-
2163
1900
  function parseEnvFileToArgs(filePath) {
2164
1901
  const resolvedPath = expandHomeAliasPath(filePath);
2165
1902
  if (!path.isAbsolute(resolvedPath)) {
@@ -2429,29 +2166,65 @@ function buildCreateRuntime(ctx, state, payload) {
2429
2166
  const hasConfigPorts = hasOwn(config, 'ports');
2430
2167
 
2431
2168
  const requestName = pickFirstString(requestOptions.containerName, body.name);
2432
- let containerName = pickFirstString(requestName, runConfig.containerName, config.containerName);
2433
- if (!containerName) {
2434
- containerName = `my-${ctx.formatDate()}`;
2435
- }
2436
- containerName = resolveNowTemplate(containerName, ctx.formatDate);
2169
+ const requestEnvMap = hasRequestEnv ? normalizeEnvMap(requestOptions.env, 'createOptions.env') : {};
2170
+ const requestEnvList = Object.entries(requestEnvMap).map(([key, value]) => `${key}=${value}`);
2171
+ const requestEnvFileList = hasRequestEnvFile ? normalizeStringArray(requestOptions.envFile, 'createOptions.envFile') : [];
2172
+ const requestVolumeList = hasRequestVolumes ? normalizeStringArray(requestOptions.volumes, 'createOptions.volumes') : [];
2173
+ const requestPortList = hasRequestPorts ? normalizeStringArray(requestOptions.ports, 'createOptions.ports') : [];
2174
+
2175
+ const resolvedBase = resolveRuntimeConfig({
2176
+ cliOptions: {
2177
+ hostPath: requestOptions.hostPath,
2178
+ contName: requestName,
2179
+ contPath: requestOptions.containerPath,
2180
+ imageName: requestOptions.imageName,
2181
+ imageVer: requestOptions.imageVersion,
2182
+ env: requestEnvList,
2183
+ envFile: requestEnvFileList,
2184
+ volume: requestVolumeList,
2185
+ port: requestPortList
2186
+ },
2187
+ globalConfig: config,
2188
+ runConfig,
2189
+ globalFirstConfig: {},
2190
+ runFirstConfig: {},
2191
+ defaults: {
2192
+ hostPath: ctx.hostPath,
2193
+ containerName: `my-${ctx.formatDate()}`,
2194
+ containerPath: ctx.containerPath,
2195
+ imageName: ctx.imageName,
2196
+ imageVersion: ctx.imageVersion
2197
+ },
2198
+ envVars: {},
2199
+ argv: [],
2200
+ isServerMode: false,
2201
+ isServerStopMode: false,
2202
+ pickConfigValue: pickFirstString,
2203
+ resolveContainerNameTemplate: value => resolveNowTemplate(value, ctx.formatDate),
2204
+ normalizeCommandSuffix: value => {
2205
+ const text = String(value || '').trim();
2206
+ return text ? ` ${text}` : '';
2207
+ },
2208
+ normalizeJsonEnvMap: normalizeEnvMap,
2209
+ normalizeCliEnvMap,
2210
+ mergeArrayConfig: (globalValue, runValue, cliValue) => [...(globalValue || []), ...(runValue || []), ...(cliValue || [])],
2211
+ normalizeVolume,
2212
+ parseServerListen: () => ({ host: '', port: 0 })
2213
+ });
2214
+
2215
+ const containerName = resolvedBase.containerName;
2437
2216
  validateContainerNameStrict(containerName);
2438
2217
 
2439
- const hostPath = pickFirstString(requestOptions.hostPath, runConfig.hostPath, config.hostPath, ctx.hostPath);
2218
+ const hostPath = resolvedBase.hostPath;
2440
2219
  if (typeof ctx.validateHostPath === 'function') {
2441
2220
  ctx.validateHostPath(hostPath);
2442
2221
  } else {
2443
2222
  validateWebHostPath(hostPath);
2444
2223
  }
2445
2224
 
2446
- const containerPath = pickFirstString(
2447
- requestOptions.containerPath,
2448
- runConfig.containerPath,
2449
- config.containerPath,
2450
- ctx.containerPath,
2451
- hostPath
2452
- ) || hostPath;
2453
- const imageName = pickFirstString(requestOptions.imageName, runConfig.imageName, config.imageName, ctx.imageName);
2454
- const imageVersion = pickFirstString(requestOptions.imageVersion, runConfig.imageVersion, config.imageVersion, ctx.imageVersion);
2225
+ const containerPath = resolvedBase.containerPath || hostPath;
2226
+ const imageName = resolvedBase.imageName;
2227
+ const imageVersion = resolvedBase.imageVersion;
2455
2228
 
2456
2229
  if (!/^[A-Za-z0-9][A-Za-z0-9._/:-]*$/.test(imageName)) {
2457
2230
  throw new Error(`imageName 非法: ${imageName}`);
@@ -2510,20 +2283,14 @@ function buildCreateRuntime(ctx, state, payload) {
2510
2283
  const hasRunEnv = hasOwn(runConfig, 'env');
2511
2284
  const hasRunEnvFile = hasOwn(runConfig, 'envFile');
2512
2285
  if (hasRequestEnv || hasRequestEnvFile || hasRunEnv || hasRunEnvFile || hasConfigEnv || hasConfigEnvFile) {
2513
- const configEnv = normalizeEnvMap(config.env, 'config.env');
2514
- const runEnv = normalizeEnvMap(runConfig.env, runName ? `runs.${runName}.env` : 'run.env');
2515
- const requestEnv = hasRequestEnv ? normalizeEnvMap(requestOptions.env, 'createOptions.env') : {};
2516
- const mergedEnv = { ...configEnv, ...runEnv, ...requestEnv };
2286
+ const mergedEnv = resolvedBase.env;
2517
2287
  const envArgs = [];
2518
2288
  Object.entries(mergedEnv).forEach(([key, value]) => {
2519
2289
  const parsed = parseEnvEntry(`${key}=${value}`);
2520
2290
  envArgs.push('--env', `${parsed.key}=${parsed.value}`);
2521
2291
  });
2522
2292
 
2523
- const envFileList = []
2524
- .concat(normalizeStringArray(config.envFile, 'config.envFile'))
2525
- .concat(normalizeStringArray(runConfig.envFile, runName ? `runs.${runName}.envFile` : 'run.envFile'))
2526
- .concat(hasRequestEnvFile ? normalizeStringArray(requestOptions.envFile, 'createOptions.envFile') : []);
2293
+ const envFileList = resolvedBase.envFile;
2527
2294
  const envFileArgs = [];
2528
2295
  envFileList.forEach(filePath => {
2529
2296
  envFileArgs.push(...parseEnvFileToArgs(filePath));
@@ -2535,10 +2302,7 @@ function buildCreateRuntime(ctx, state, payload) {
2535
2302
  let containerVolumes = Array.isArray(ctx.containerVolumes) ? ctx.containerVolumes.slice() : [];
2536
2303
  const hasRunVolumes = hasOwn(runConfig, 'volumes');
2537
2304
  if (hasRequestVolumes || hasRunVolumes || hasConfigVolumes) {
2538
- const volumeList = []
2539
- .concat(normalizeStringArray(config.volumes, 'config.volumes'))
2540
- .concat(normalizeStringArray(runConfig.volumes, runName ? `runs.${runName}.volumes` : 'run.volumes'))
2541
- .concat(hasRequestVolumes ? normalizeStringArray(requestOptions.volumes, 'createOptions.volumes') : []);
2305
+ const volumeList = resolvedBase.volumes;
2542
2306
  containerVolumes = [];
2543
2307
  volumeList.forEach(volume => {
2544
2308
  containerVolumes.push('--volume', normalizeVolume(volume));
@@ -2548,10 +2312,7 @@ function buildCreateRuntime(ctx, state, payload) {
2548
2312
  let containerPorts = Array.isArray(ctx.containerPorts) ? ctx.containerPorts.slice() : [];
2549
2313
  const hasRunPorts = hasOwn(runConfig, 'ports');
2550
2314
  if (hasRequestPorts || hasRunPorts || hasConfigPorts) {
2551
- const portList = []
2552
- .concat(normalizeStringArray(config.ports, 'config.ports'))
2553
- .concat(normalizeStringArray(runConfig.ports, runName ? `runs.${runName}.ports` : 'run.ports'))
2554
- .concat(hasRequestPorts ? normalizeStringArray(requestOptions.ports, 'createOptions.ports') : []);
2315
+ const portList = resolvedBase.ports;
2555
2316
  containerPorts = [];
2556
2317
  portList.forEach(port => {
2557
2318
  containerPorts.push('--publish', port);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcanwin/manyoyo",
3
- "version": "5.8.6",
3
+ "version": "5.8.10",
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",