@xcanwin/manyoyo 5.8.6 → 5.8.9
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 +121 -173
- package/lib/global-config.js +1 -198
- package/lib/image-build.js +20 -4
- package/lib/init-config.js +22 -10
- package/lib/json5-text-edit.js +238 -0
- package/lib/plugin/playwright-bootstrap.js +116 -0
- package/lib/plugin/playwright-command-output.js +95 -0
- package/lib/plugin/playwright-container-runtime.js +94 -0
- package/lib/plugin/playwright-extension-manager.js +265 -0
- package/lib/plugin/playwright-extension-paths.js +98 -0
- package/lib/plugin/playwright-host-runtime.js +114 -0
- package/lib/plugin/playwright-scene-config.js +137 -0
- package/lib/plugin/playwright-scene-drivers.js +285 -0
- package/lib/plugin/playwright-scene-state.js +80 -0
- package/lib/plugin/playwright.js +169 -1049
- package/lib/runtime-normalizers.js +65 -0
- package/lib/runtime-resolver.js +195 -0
- package/lib/web/agent-command.js +153 -0
- package/lib/web/api-route-helpers.js +88 -0
- package/lib/web/container-exec.js +215 -0
- package/lib/web/http-handlers.js +163 -0
- package/lib/web/runtime-state.js +50 -0
- package/lib/web/server-context.js +71 -0
- package/lib/web/server-lifecycle.js +129 -0
- package/lib/web/server.js +293 -2496
- package/lib/web/session-api-routes.js +390 -0
- package/lib/web/structured-output.js +149 -0
- package/lib/web/structured-trace.js +603 -0
- package/lib/web/system-api-routes.js +114 -0
- package/lib/web/terminal-session.js +205 -0
- package/lib/web/upgrade-handler.js +94 -0
- package/package.json +1 -1
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
function parseEnvEntry(entryText) {
|
|
7
|
+
const text = String(entryText || '');
|
|
8
|
+
const idx = text.indexOf('=');
|
|
9
|
+
if (idx <= 0) {
|
|
10
|
+
throw new Error(`env 格式应为 KEY=VALUE: ${text}`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const key = text.slice(0, idx);
|
|
14
|
+
const value = text.slice(idx + 1);
|
|
15
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
|
|
16
|
+
throw new Error(`env key 非法: ${key}`);
|
|
17
|
+
}
|
|
18
|
+
if (/[\r\n\0]/.test(value) || /[;&|`$<>]/.test(value)) {
|
|
19
|
+
throw new Error(`env value 含非法字符: ${key}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return { key, value };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function expandHomeAliasPath(filePath, homeDir = os.homedir()) {
|
|
26
|
+
const text = String(filePath || '').trim();
|
|
27
|
+
if (!text) {
|
|
28
|
+
return text;
|
|
29
|
+
}
|
|
30
|
+
if (text === '~') {
|
|
31
|
+
return homeDir;
|
|
32
|
+
}
|
|
33
|
+
if (text.startsWith('~/')) {
|
|
34
|
+
return path.join(homeDir, text.slice(2));
|
|
35
|
+
}
|
|
36
|
+
if (text === '$HOME') {
|
|
37
|
+
return homeDir;
|
|
38
|
+
}
|
|
39
|
+
if (text.startsWith('$HOME/')) {
|
|
40
|
+
return path.join(homeDir, text.slice('$HOME/'.length));
|
|
41
|
+
}
|
|
42
|
+
return text;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function normalizeVolume(volume, homeDir = os.homedir()) {
|
|
46
|
+
const text = String(volume || '').trim();
|
|
47
|
+
if (!text.startsWith('~') && !text.startsWith('$HOME')) {
|
|
48
|
+
return text;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const separatorIndex = text.indexOf(':');
|
|
52
|
+
if (separatorIndex === -1) {
|
|
53
|
+
return expandHomeAliasPath(text, homeDir);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const hostPath = text.slice(0, separatorIndex);
|
|
57
|
+
const rest = text.slice(separatorIndex);
|
|
58
|
+
return `${expandHomeAliasPath(hostPath, homeDir)}${rest}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = {
|
|
62
|
+
parseEnvEntry,
|
|
63
|
+
expandHomeAliasPath,
|
|
64
|
+
normalizeVolume
|
|
65
|
+
};
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('crypto');
|
|
4
|
+
|
|
5
|
+
function toArray(value) {
|
|
6
|
+
return Array.isArray(value) ? value : (value ? [value] : []);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function resolveRuntimeConfig(options = {}) {
|
|
10
|
+
const params = options;
|
|
11
|
+
const cliOptions = params.cliOptions || {};
|
|
12
|
+
const globalConfig = params.globalConfig || {};
|
|
13
|
+
const runConfig = params.runConfig || {};
|
|
14
|
+
const globalFirstConfig = params.globalFirstConfig || {};
|
|
15
|
+
const runFirstConfig = params.runFirstConfig || {};
|
|
16
|
+
const defaults = params.defaults || {};
|
|
17
|
+
const envVars = params.envVars || process.env;
|
|
18
|
+
const pickConfigValue = params.pickConfigValue;
|
|
19
|
+
const resolveContainerNameTemplate = params.resolveContainerNameTemplate;
|
|
20
|
+
const normalizeCommandSuffix = params.normalizeCommandSuffix;
|
|
21
|
+
const normalizeJsonEnvMap = params.normalizeJsonEnvMap;
|
|
22
|
+
const normalizeCliEnvMap = params.normalizeCliEnvMap;
|
|
23
|
+
const mergeArrayConfig = params.mergeArrayConfig;
|
|
24
|
+
const normalizeVolume = params.normalizeVolume;
|
|
25
|
+
const parseServerListen = params.parseServerListen;
|
|
26
|
+
const argv = Array.isArray(params.argv) ? params.argv : [];
|
|
27
|
+
const isServerMode = params.isServerMode === true;
|
|
28
|
+
const isServerStopMode = params.isServerStopMode === true;
|
|
29
|
+
|
|
30
|
+
let hostPath = pickConfigValue(cliOptions.hostPath, runConfig.hostPath, globalConfig.hostPath, defaults.hostPath) || defaults.hostPath;
|
|
31
|
+
let containerName = defaults.containerName;
|
|
32
|
+
const mergedContainerName = pickConfigValue(cliOptions.contName, runConfig.containerName, globalConfig.containerName);
|
|
33
|
+
if (mergedContainerName) {
|
|
34
|
+
containerName = mergedContainerName;
|
|
35
|
+
}
|
|
36
|
+
containerName = resolveContainerNameTemplate(containerName);
|
|
37
|
+
|
|
38
|
+
let containerPath = defaults.containerPath;
|
|
39
|
+
const mergedContainerPath = pickConfigValue(cliOptions.contPath, runConfig.containerPath, globalConfig.containerPath);
|
|
40
|
+
if (mergedContainerPath) {
|
|
41
|
+
containerPath = mergedContainerPath;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const imageName = pickConfigValue(cliOptions.imageName, runConfig.imageName, globalConfig.imageName, defaults.imageName) || defaults.imageName;
|
|
45
|
+
const imageVersion = pickConfigValue(cliOptions.imageVer, runConfig.imageVersion, globalConfig.imageVersion, defaults.imageVersion) || defaults.imageVersion;
|
|
46
|
+
|
|
47
|
+
let execPrefix = '';
|
|
48
|
+
const mergedShellPrefix = pickConfigValue(cliOptions.shellPrefix, runConfig.shellPrefix, globalConfig.shellPrefix);
|
|
49
|
+
if (mergedShellPrefix) {
|
|
50
|
+
execPrefix = `${mergedShellPrefix} `;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let execShell = '';
|
|
54
|
+
const mergedShell = pickConfigValue(cliOptions.shell, runConfig.shell, globalConfig.shell);
|
|
55
|
+
if (mergedShell) {
|
|
56
|
+
execShell = mergedShell;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let execSuffix = '';
|
|
60
|
+
const mergedShellSuffix = pickConfigValue(cliOptions.shellSuffix, runConfig.shellSuffix, globalConfig.shellSuffix);
|
|
61
|
+
if (mergedShellSuffix) {
|
|
62
|
+
execSuffix = normalizeCommandSuffix(mergedShellSuffix);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
let firstExecPrefix = '';
|
|
66
|
+
const mergedFirstShellPrefix = pickConfigValue(cliOptions.firstShellPrefix, runFirstConfig.shellPrefix, globalFirstConfig.shellPrefix);
|
|
67
|
+
if (mergedFirstShellPrefix) {
|
|
68
|
+
firstExecPrefix = `${mergedFirstShellPrefix} `;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let firstExecShell = '';
|
|
72
|
+
const mergedFirstShell = pickConfigValue(cliOptions.firstShell, runFirstConfig.shell, globalFirstConfig.shell);
|
|
73
|
+
if (mergedFirstShell) {
|
|
74
|
+
firstExecShell = mergedFirstShell;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let firstExecSuffix = '';
|
|
78
|
+
const mergedFirstShellSuffix = pickConfigValue(cliOptions.firstShellSuffix, runFirstConfig.shellSuffix, globalFirstConfig.shellSuffix);
|
|
79
|
+
if (mergedFirstShellSuffix) {
|
|
80
|
+
firstExecSuffix = normalizeCommandSuffix(mergedFirstShellSuffix);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const envFile = [
|
|
84
|
+
...toArray(globalConfig.envFile),
|
|
85
|
+
...toArray(runConfig.envFile),
|
|
86
|
+
...(cliOptions.envFile || [])
|
|
87
|
+
].filter(Boolean);
|
|
88
|
+
|
|
89
|
+
const env = {
|
|
90
|
+
...normalizeJsonEnvMap(globalConfig.env, '全局配置'),
|
|
91
|
+
...normalizeJsonEnvMap(runConfig.env, '运行配置'),
|
|
92
|
+
...normalizeCliEnvMap(cliOptions.env)
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const firstEnvFile = [
|
|
96
|
+
...toArray(globalFirstConfig.envFile),
|
|
97
|
+
...toArray(runFirstConfig.envFile),
|
|
98
|
+
...(cliOptions.firstEnvFile || [])
|
|
99
|
+
].filter(Boolean);
|
|
100
|
+
|
|
101
|
+
const firstEnv = {
|
|
102
|
+
...normalizeJsonEnvMap(globalFirstConfig.env, '全局配置 first'),
|
|
103
|
+
...normalizeJsonEnvMap(runFirstConfig.env, '运行配置 first'),
|
|
104
|
+
...normalizeCliEnvMap(cliOptions.firstEnv)
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const volumes = mergeArrayConfig(globalConfig.volumes, runConfig.volumes, cliOptions.volume)
|
|
108
|
+
.map(volume => normalizeVolume(volume));
|
|
109
|
+
const ports = mergeArrayConfig(globalConfig.ports, runConfig.ports, cliOptions.port);
|
|
110
|
+
const imageBuildArgs = mergeArrayConfig(globalConfig.imageBuildArgs, runConfig.imageBuildArgs, cliOptions.imageBuildArg);
|
|
111
|
+
|
|
112
|
+
const yolo = pickConfigValue(cliOptions.yolo, runConfig.yolo, globalConfig.yolo) || '';
|
|
113
|
+
const containerMode = pickConfigValue(cliOptions.contMode, runConfig.containerMode, globalConfig.containerMode) || '';
|
|
114
|
+
const quiet = pickConfigValue(cliOptions.quiet, runConfig.quiet, globalConfig.quiet) || [];
|
|
115
|
+
|
|
116
|
+
if (cliOptions.shellFull) {
|
|
117
|
+
execShell = cliOptions.shellFull.join(' ');
|
|
118
|
+
execPrefix = '';
|
|
119
|
+
execSuffix = '';
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!cliOptions.shellFull) {
|
|
123
|
+
const doubleDashIndex = argv.indexOf('--');
|
|
124
|
+
if (doubleDashIndex !== -1 && doubleDashIndex < argv.length - 1) {
|
|
125
|
+
execSuffix = normalizeCommandSuffix(argv.slice(doubleDashIndex + 1).join(' '));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let serverHost = null;
|
|
130
|
+
let serverPort = null;
|
|
131
|
+
if (isServerMode) {
|
|
132
|
+
const listen = parseServerListen(cliOptions.server);
|
|
133
|
+
serverHost = listen.host;
|
|
134
|
+
serverPort = listen.port;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
let serverUser = pickConfigValue(cliOptions.serverUser, runConfig.serverUser, globalConfig.serverUser, envVars.MANYOYO_SERVER_USER) || '';
|
|
138
|
+
let serverPass = pickConfigValue(cliOptions.serverPass, runConfig.serverPass, globalConfig.serverPass, envVars.MANYOYO_SERVER_PASS) || '';
|
|
139
|
+
let serverPassAuto = false;
|
|
140
|
+
if (isServerMode && !isServerStopMode) {
|
|
141
|
+
if (!serverUser) {
|
|
142
|
+
serverUser = 'admin';
|
|
143
|
+
}
|
|
144
|
+
if (!serverPass) {
|
|
145
|
+
serverPass = crypto.randomBytes(12).toString('hex');
|
|
146
|
+
serverPassAuto = true;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (!hostPath) {
|
|
151
|
+
hostPath = defaults.hostPath;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
hostPath,
|
|
156
|
+
containerName,
|
|
157
|
+
containerPath,
|
|
158
|
+
imageName,
|
|
159
|
+
imageVersion,
|
|
160
|
+
envFile,
|
|
161
|
+
env,
|
|
162
|
+
firstEnvFile,
|
|
163
|
+
firstEnv,
|
|
164
|
+
volumes,
|
|
165
|
+
ports,
|
|
166
|
+
imageBuildArgs,
|
|
167
|
+
containerMode,
|
|
168
|
+
yolo,
|
|
169
|
+
quiet,
|
|
170
|
+
server: isServerMode,
|
|
171
|
+
serverHost,
|
|
172
|
+
serverPort,
|
|
173
|
+
serverUser,
|
|
174
|
+
serverPass,
|
|
175
|
+
serverPassAuto,
|
|
176
|
+
exec: {
|
|
177
|
+
prefix: execPrefix,
|
|
178
|
+
shell: execShell,
|
|
179
|
+
suffix: execSuffix
|
|
180
|
+
},
|
|
181
|
+
first: {
|
|
182
|
+
envFile: firstEnvFile,
|
|
183
|
+
env: firstEnv,
|
|
184
|
+
exec: {
|
|
185
|
+
prefix: firstExecPrefix,
|
|
186
|
+
shell: firstExecShell,
|
|
187
|
+
suffix: firstExecSuffix
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
module.exports = {
|
|
194
|
+
resolveRuntimeConfig
|
|
195
|
+
};
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
resolveAgentProgram,
|
|
5
|
+
buildAgentResumeCommand
|
|
6
|
+
} = require('../agent-resume');
|
|
7
|
+
|
|
8
|
+
function normalizeAgentPromptCommandTemplate(value, sourceLabel = 'agentPromptCommand') {
|
|
9
|
+
if (value === undefined || value === null) {
|
|
10
|
+
return '';
|
|
11
|
+
}
|
|
12
|
+
if (typeof value !== 'string') {
|
|
13
|
+
throw new Error(`${sourceLabel} 必须是字符串`);
|
|
14
|
+
}
|
|
15
|
+
const text = value.trim();
|
|
16
|
+
if (!text) {
|
|
17
|
+
return '';
|
|
18
|
+
}
|
|
19
|
+
if (!text.includes('{prompt}')) {
|
|
20
|
+
throw new Error(`${sourceLabel} 必须包含 {prompt} 占位符`);
|
|
21
|
+
}
|
|
22
|
+
if (/^codex\s+exec(?:\s|$)/.test(text) && !text.includes('--skip-git-repo-check')) {
|
|
23
|
+
return text.replace(/^codex\s+exec\b/, 'codex exec --skip-git-repo-check');
|
|
24
|
+
}
|
|
25
|
+
return text;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isAgentPromptCommandEnabled(value) {
|
|
29
|
+
return typeof value === 'string' && value.includes('{prompt}') && Boolean(value.trim());
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function quoteBashSingleValue(value) {
|
|
33
|
+
const text = String(value || '');
|
|
34
|
+
return `'${text.replace(/'/g, `'\"'\"'`)}'`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function renderAgentPromptCommand(template, prompt) {
|
|
38
|
+
const templateText = normalizeAgentPromptCommandTemplate(template, 'agentPromptCommand');
|
|
39
|
+
const safePrompt = quoteBashSingleValue(prompt);
|
|
40
|
+
return templateText.replace(/\{prompt\}/g, safePrompt);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function prependAgentFlags(commandText, matchPattern, flagSpecs) {
|
|
44
|
+
const matched = String(commandText || '').match(matchPattern);
|
|
45
|
+
if (!matched) {
|
|
46
|
+
return String(commandText || '');
|
|
47
|
+
}
|
|
48
|
+
const prefix = matched[1] || '';
|
|
49
|
+
let suffix = matched[matched.length - 1] || '';
|
|
50
|
+
for (let i = flagSpecs.length - 1; i >= 0; i -= 1) {
|
|
51
|
+
const spec = flagSpecs[i];
|
|
52
|
+
if (!spec || !spec.flag || !(spec.pattern instanceof RegExp) || spec.pattern.test(suffix)) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
suffix = ` ${spec.flag}${suffix}`;
|
|
56
|
+
}
|
|
57
|
+
return `${prefix}${suffix}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function buildCodexAgentExecCommand(template, prompt) {
|
|
61
|
+
const templateText = normalizeAgentPromptCommandTemplate(template, 'agentPromptCommand');
|
|
62
|
+
const execMatch = templateText.match(
|
|
63
|
+
/^((?:(?:[A-Za-z_][A-Za-z0-9_]*=)(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*'|[^\s]+)\s+)*)codex\s+exec\b/
|
|
64
|
+
);
|
|
65
|
+
let codexTemplate = templateText;
|
|
66
|
+
if (execMatch) {
|
|
67
|
+
const prefix = execMatch[1] || '';
|
|
68
|
+
const suffix = templateText.slice(execMatch[0].length);
|
|
69
|
+
const hasJson = /(?:^|\s)--json(?:\s|$)/.test(suffix);
|
|
70
|
+
const injectedFlags = hasJson ? '' : ' --json';
|
|
71
|
+
codexTemplate = `${prefix}codex exec${injectedFlags}${suffix}`;
|
|
72
|
+
}
|
|
73
|
+
return codexTemplate === templateText
|
|
74
|
+
? renderAgentPromptCommand(templateText, prompt)
|
|
75
|
+
: renderAgentPromptCommand(codexTemplate, prompt);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function buildClaudeAgentExecCommand(template, prompt) {
|
|
79
|
+
const templateText = normalizeAgentPromptCommandTemplate(template, 'agentPromptCommand');
|
|
80
|
+
const claudeTemplate = prependAgentFlags(
|
|
81
|
+
templateText,
|
|
82
|
+
/^(((?:(?:[A-Za-z_][A-Za-z0-9_]*=)(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*'|[^\s]+)\s+)*)claude\b)(.*)$/,
|
|
83
|
+
[
|
|
84
|
+
{ flag: '--verbose', pattern: /(?:^|\s)--verbose(?:\s|$)/ },
|
|
85
|
+
{ flag: '--output-format stream-json', pattern: /(?:^|\s)--output-format(?:\s|$)/ }
|
|
86
|
+
]
|
|
87
|
+
);
|
|
88
|
+
return claudeTemplate === templateText
|
|
89
|
+
? renderAgentPromptCommand(templateText, prompt)
|
|
90
|
+
: renderAgentPromptCommand(claudeTemplate, prompt);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function buildGeminiAgentExecCommand(template, prompt) {
|
|
94
|
+
const templateText = normalizeAgentPromptCommandTemplate(template, 'agentPromptCommand');
|
|
95
|
+
const geminiTemplate = prependAgentFlags(
|
|
96
|
+
templateText,
|
|
97
|
+
/^(((?:(?:[A-Za-z_][A-Za-z0-9_]*=)(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*'|[^\s]+)\s+)*)gemini\b)(.*)$/,
|
|
98
|
+
[
|
|
99
|
+
{ flag: '--output-format stream-json', pattern: /(?:^|\s)--output-format(?:\s|$)/ }
|
|
100
|
+
]
|
|
101
|
+
);
|
|
102
|
+
return geminiTemplate === templateText
|
|
103
|
+
? renderAgentPromptCommand(templateText, prompt)
|
|
104
|
+
: renderAgentPromptCommand(geminiTemplate, prompt);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function buildOpenCodeAgentExecCommand(template, prompt) {
|
|
108
|
+
const templateText = normalizeAgentPromptCommandTemplate(template, 'agentPromptCommand');
|
|
109
|
+
const opencodeTemplate = prependAgentFlags(
|
|
110
|
+
templateText,
|
|
111
|
+
/^(((?:(?:[A-Za-z_][A-Za-z0-9_]*=)(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*'|[^\s]+)\s+)*)opencode\s+run\b)(.*)$/,
|
|
112
|
+
[
|
|
113
|
+
{ flag: '--format json', pattern: /(?:^|\s)--format(?:\s|$)/ }
|
|
114
|
+
]
|
|
115
|
+
);
|
|
116
|
+
return opencodeTemplate === templateText
|
|
117
|
+
? renderAgentPromptCommand(templateText, prompt)
|
|
118
|
+
: renderAgentPromptCommand(opencodeTemplate, prompt);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function buildWebAgentExecCommand(template, prompt, agentProgram) {
|
|
122
|
+
switch (agentProgram) {
|
|
123
|
+
case 'claude':
|
|
124
|
+
return buildClaudeAgentExecCommand(template, prompt);
|
|
125
|
+
case 'gemini':
|
|
126
|
+
return buildGeminiAgentExecCommand(template, prompt);
|
|
127
|
+
case 'codex':
|
|
128
|
+
return buildCodexAgentExecCommand(template, prompt);
|
|
129
|
+
case 'opencode':
|
|
130
|
+
return buildOpenCodeAgentExecCommand(template, prompt);
|
|
131
|
+
default:
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
return renderAgentPromptCommand(template, prompt);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function getAgentRuntimeMeta(template) {
|
|
138
|
+
const normalizedTemplate = normalizeAgentPromptCommandTemplate(template, 'agentPromptCommand');
|
|
139
|
+
const agentProgram = resolveAgentProgram(normalizedTemplate);
|
|
140
|
+
const resumeCommand = buildAgentResumeCommand(agentProgram);
|
|
141
|
+
return {
|
|
142
|
+
agentProgram: agentProgram || '',
|
|
143
|
+
resumeCommand: resumeCommand || '',
|
|
144
|
+
resumeSupported: Boolean(resumeCommand)
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
module.exports = {
|
|
149
|
+
normalizeAgentPromptCommandTemplate,
|
|
150
|
+
isAgentPromptCommandEnabled,
|
|
151
|
+
buildWebAgentExecCommand,
|
|
152
|
+
getAgentRuntimeMeta
|
|
153
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function createApiRouteHelpers(deps) {
|
|
4
|
+
const {
|
|
5
|
+
req,
|
|
6
|
+
res,
|
|
7
|
+
ctx,
|
|
8
|
+
state,
|
|
9
|
+
sendJson,
|
|
10
|
+
readJsonBody,
|
|
11
|
+
getValidSessionRef,
|
|
12
|
+
prepareWebAgentExecution
|
|
13
|
+
} = deps;
|
|
14
|
+
|
|
15
|
+
const withSessionRef = handler => async match => {
|
|
16
|
+
const sessionRef = getValidSessionRef(ctx, res, match[1]);
|
|
17
|
+
if (!sessionRef) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
await handler(sessionRef, match);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const withJsonBody = handler => async (...args) => {
|
|
24
|
+
const payload = await readJsonBody(req);
|
|
25
|
+
await handler(payload, ...args);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const withSessionJsonBody = (handler, fallbackError = '') => withSessionRef(async (sessionRef, match) => {
|
|
29
|
+
let payload = null;
|
|
30
|
+
if (fallbackError) {
|
|
31
|
+
try {
|
|
32
|
+
payload = await readJsonBody(req);
|
|
33
|
+
} catch (e) {
|
|
34
|
+
sendJson(res, 400, { error: e.message || fallbackError });
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
payload = await readJsonBody(req);
|
|
39
|
+
}
|
|
40
|
+
await handler(sessionRef, payload, match);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const getRequiredBodyText = (payload, key, emptyMessage) => {
|
|
44
|
+
const value = String(payload && payload[key] || '').trim();
|
|
45
|
+
if (!value) {
|
|
46
|
+
sendJson(res, 400, { error: emptyMessage });
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
return value;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const prepareAgentRequest = async (sessionRef, prompt) => {
|
|
53
|
+
try {
|
|
54
|
+
return await prepareWebAgentExecution(ctx, state, sessionRef, prompt);
|
|
55
|
+
} catch (e) {
|
|
56
|
+
sendJson(res, 400, { error: e && e.message ? e.message : 'Agent 执行准备失败' });
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
return {
|
|
62
|
+
withSessionRef,
|
|
63
|
+
withJsonBody,
|
|
64
|
+
withSessionJsonBody,
|
|
65
|
+
getRequiredBodyText,
|
|
66
|
+
prepareAgentRequest
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function runMatchedRoute(routes, method, pathname) {
|
|
71
|
+
for (const route of routes) {
|
|
72
|
+
if (route.method !== method) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
const matched = route.match(pathname);
|
|
76
|
+
if (!matched) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
await route.handler(matched);
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = {
|
|
86
|
+
createApiRouteHelpers,
|
|
87
|
+
runMatchedRoute
|
|
88
|
+
};
|