codexmate 0.0.56 → 0.1.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/README.md +2 -2
- package/README.vi.md +1 -0
- package/README.zh.md +2 -2
- package/cli/openai-bridge-retry.js +62 -0
- package/cli/openai-bridge-runtime.js +1819 -0
- package/cli/openai-bridge.js +137 -2048
- package/cli.js +749 -133
- package/lib/task-orchestrator.js +90 -21
- package/lib/task-workspace-chat.js +292 -0
- package/package.json +2 -2
- package/web-ui/app.js +67 -131
- package/web-ui/modules/app.computed.main-tabs.mjs +304 -7
- package/web-ui/modules/app.methods.agents.mjs +192 -1
- package/web-ui/modules/app.methods.claude-config.mjs +65 -25
- package/web-ui/modules/app.methods.navigation.mjs +67 -83
- package/web-ui/modules/app.methods.openclaw-editing.mjs +3 -6
- package/web-ui/modules/app.methods.providers.mjs +40 -10
- package/web-ui/modules/app.methods.session-actions.mjs +1 -12
- package/web-ui/modules/app.methods.session-browser.mjs +23 -54
- package/web-ui/modules/app.methods.session-trash.mjs +0 -1
- package/web-ui/modules/app.methods.startup-claude.mjs +16 -31
- package/web-ui/modules/app.methods.task-orchestration.mjs +347 -8
- package/web-ui/modules/app.methods.tool-config-permissions.mjs +0 -3
- package/web-ui/modules/app.methods.web-ui-preferences.mjs +442 -68
- package/web-ui/modules/config-template-confirm-pref.mjs +2 -3
- package/web-ui/modules/i18n/locales/en.mjs +181 -30
- package/web-ui/modules/i18n/locales/ja.mjs +178 -27
- package/web-ui/modules/i18n/locales/vi.mjs +180 -29
- package/web-ui/modules/i18n/locales/zh-tw.mjs +181 -30
- package/web-ui/modules/i18n/locales/zh.mjs +183 -32
- package/web-ui/modules/i18n.mjs +5 -12
- package/web-ui/modules/sessions-filters-url.mjs +1 -2
- package/web-ui/partials/index/layout-header.html +44 -38
- package/web-ui/partials/index/modals-basic.html +26 -0
- package/web-ui/partials/index/panel-orchestration.html +489 -282
- package/web-ui/partials/index/panel-prompts.html +66 -3
- package/web-ui/res/web-ui-render.precompiled.js +1181 -604
- package/web-ui/styles/layout-shell.css +157 -1
- package/web-ui/styles/modals-core.css +173 -0
- package/web-ui/styles/navigation-panels.css +11 -0
- package/web-ui/styles/responsive.css +32 -0
- package/web-ui/styles/task-orchestration.css +2161 -4
package/cli.js
CHANGED
|
@@ -73,8 +73,15 @@ const {
|
|
|
73
73
|
truncateText: truncateTaskText,
|
|
74
74
|
buildTaskPlan,
|
|
75
75
|
validateTaskPlan,
|
|
76
|
-
executeTaskPlan
|
|
76
|
+
executeTaskPlan,
|
|
77
|
+
computePlanWaves
|
|
77
78
|
} = require('./lib/task-orchestrator');
|
|
79
|
+
const {
|
|
80
|
+
buildWorkspaceChatContext,
|
|
81
|
+
loadWorkspaceChatThread,
|
|
82
|
+
appendWorkspaceChatThread,
|
|
83
|
+
applyWorkspaceFileOperations
|
|
84
|
+
} = require('./lib/task-workspace-chat');
|
|
78
85
|
const {
|
|
79
86
|
readAutomationConfig,
|
|
80
87
|
matchAutomationRule,
|
|
@@ -258,6 +265,9 @@ const TASK_RUNS_FILE = path.join(CONFIG_DIR, 'codexmate-task-runs.jsonl');
|
|
|
258
265
|
const TASK_RUN_DETAILS_DIR = path.join(CONFIG_DIR, 'codexmate-task-runs');
|
|
259
266
|
const TASK_QUEUE_WORKER_FILE = path.join(CONFIG_DIR, 'codexmate-task-queue-worker.json');
|
|
260
267
|
const TASK_ARTIFACTS_DIR = path.join(CONFIG_DIR, 'codexmate-task-artifacts');
|
|
268
|
+
const TASK_WORKSPACE_CHAT_THREADS_DIR = path.join(CONFIG_DIR, 'codexmate-task-chat-threads');
|
|
269
|
+
const TASK_OPENAI_CHAT_TIMEOUT_MS = 180000;
|
|
270
|
+
const TASK_OPENAI_CHAT_MAX_RESPONSE_BYTES = 4 * 1024 * 1024;
|
|
261
271
|
const AUTOMATION_CONFIG_FILE = path.join(CONFIG_DIR, 'codexmate-automation.json');
|
|
262
272
|
const DEFAULT_CLAUDE_MODEL = 'glm-4.7';
|
|
263
273
|
const DEFAULT_MODEL_CONTEXT_WINDOW = 190000;
|
|
@@ -990,6 +1000,65 @@ function normalizePromptsSubTabPreference(value) {
|
|
|
990
1000
|
return normalized === 'claude-project' ? 'claude-project' : 'codex';
|
|
991
1001
|
}
|
|
992
1002
|
|
|
1003
|
+
function normalizeSidebarCollapsedPreference(value) {
|
|
1004
|
+
return normalizeBooleanPreference(value, false);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
function normalizeSessionFilterSourcePreference(value) {
|
|
1008
|
+
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
1009
|
+
if (normalized === 'all' || normalized === 'claude' || normalized === 'gemini' || normalized === 'codebuddy') return normalized;
|
|
1010
|
+
return 'codex';
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
function normalizeSessionRoleFilterPreference(value) {
|
|
1014
|
+
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
1015
|
+
if (normalized === 'user' || normalized === 'assistant' || normalized === 'system' || normalized === 'tool') return normalized;
|
|
1016
|
+
return 'all';
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
function normalizeSessionTimePresetPreference(value) {
|
|
1020
|
+
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
1021
|
+
if (normalized === '24h' || normalized === '7d' || normalized === '30d') return normalized;
|
|
1022
|
+
return 'all';
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
function normalizeSessionSortModePreference(value) {
|
|
1026
|
+
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
1027
|
+
return normalized === 'hot' ? 'hot' : 'time';
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
function normalizeSessionFiltersPreference(value = {}) {
|
|
1031
|
+
const source = isPlainObject(value) ? value : {};
|
|
1032
|
+
return {
|
|
1033
|
+
source: normalizeSessionFilterSourcePreference(source.source),
|
|
1034
|
+
pathFilter: typeof source.pathFilter === 'string' ? source.pathFilter : '',
|
|
1035
|
+
query: typeof source.query === 'string' ? source.query : '',
|
|
1036
|
+
roleFilter: normalizeSessionRoleFilterPreference(source.roleFilter),
|
|
1037
|
+
timePreset: normalizeSessionTimePresetPreference(source.timePreset),
|
|
1038
|
+
sortMode: normalizeSessionSortModePreference(source.sortMode)
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
function normalizeSessionPinnedMapPreference(value = {}) {
|
|
1043
|
+
const source = isPlainObject(value) ? value : {};
|
|
1044
|
+
const next = {};
|
|
1045
|
+
for (const [key, item] of Object.entries(source)) {
|
|
1046
|
+
if (!key) continue;
|
|
1047
|
+
const numeric = Number(item);
|
|
1048
|
+
if (!Number.isFinite(numeric) || numeric <= 0) continue;
|
|
1049
|
+
next[key] = Math.floor(numeric);
|
|
1050
|
+
}
|
|
1051
|
+
return next;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
function normalizePlainObjectPreference(value = {}) {
|
|
1055
|
+
return isPlainObject(value) ? value : {};
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
function normalizeArrayPreference(value = []) {
|
|
1059
|
+
return Array.isArray(value) ? value : [];
|
|
1060
|
+
}
|
|
1061
|
+
|
|
993
1062
|
function normalizeWebUiPreferences(value = {}) {
|
|
994
1063
|
const source = isPlainObject(value) ? value : {};
|
|
995
1064
|
const navigation = isPlainObject(source.navigation) ? source.navigation : {};
|
|
@@ -1002,6 +1071,18 @@ function normalizeWebUiPreferences(value = {}) {
|
|
|
1002
1071
|
sessionsUsageTimeRange: normalizeUsageTimeRangePreference(source.sessionsUsageTimeRange),
|
|
1003
1072
|
promptsSubTab: normalizePromptsSubTabPreference(source.promptsSubTab),
|
|
1004
1073
|
projectClaudeMdPath: typeof source.projectClaudeMdPath === 'string' ? source.projectClaudeMdPath : '',
|
|
1074
|
+
sidebarCollapsed: normalizeSidebarCollapsedPreference(source.sidebarCollapsed),
|
|
1075
|
+
starPrompted: normalizeBooleanPreference(source.starPrompted, false),
|
|
1076
|
+
taskOrchestrationTabEnabled: normalizeBooleanPreference(source.taskOrchestrationTabEnabled, true),
|
|
1077
|
+
sessionLoadNativeDialog: normalizeBooleanPreference(source.sessionLoadNativeDialog, false),
|
|
1078
|
+
language: typeof source.language === 'string' ? source.language : '',
|
|
1079
|
+
sessionFilters: normalizeSessionFiltersPreference(source.sessionFilters),
|
|
1080
|
+
sessionPinnedMap: normalizeSessionPinnedMapPreference(source.sessionPinnedMap),
|
|
1081
|
+
claudeConfigs: normalizePlainObjectPreference(source.claudeConfigs),
|
|
1082
|
+
currentClaudeConfig: typeof source.currentClaudeConfig === 'string' ? source.currentClaudeConfig : '',
|
|
1083
|
+
openclawConfigs: normalizePlainObjectPreference(source.openclawConfigs),
|
|
1084
|
+
toolConfigPermissions: normalizeToolConfigPermissions(source.toolConfigPermissions || TOOL_CONFIG_PERMISSION_DEFAULTS),
|
|
1085
|
+
deletedClaudeSettingsImports: normalizeArrayPreference(source.deletedClaudeSettingsImports),
|
|
1005
1086
|
navigation: {
|
|
1006
1087
|
mainTab: normalizeMainTabPreference(navigation.mainTab),
|
|
1007
1088
|
configMode: normalizeConfigModePreference(navigation.configMode),
|
|
@@ -1030,6 +1111,9 @@ function setWebUiPreferences(params = {}) {
|
|
|
1030
1111
|
}
|
|
1031
1112
|
});
|
|
1032
1113
|
preferences.webUi = next;
|
|
1114
|
+
if (isPlainObject(incoming.toolConfigPermissions)) {
|
|
1115
|
+
preferences.toolConfigPermissions = normalizeToolConfigPermissions(incoming.toolConfigPermissions);
|
|
1116
|
+
}
|
|
1033
1117
|
writeCodexmatePreferences(preferences);
|
|
1034
1118
|
return { success: true, preferences: next };
|
|
1035
1119
|
}
|
|
@@ -2477,6 +2561,28 @@ function buildClaudeSettingsDiff(params = {}) {
|
|
|
2477
2561
|
};
|
|
2478
2562
|
}
|
|
2479
2563
|
|
|
2564
|
+
|
|
2565
|
+
function normalizeOpenaiBridgeMaxRetries(value, fallback = 2) {
|
|
2566
|
+
const raw = Number(value);
|
|
2567
|
+
const fallbackRaw = Number(fallback);
|
|
2568
|
+
const base = Number.isFinite(raw) ? raw : (Number.isFinite(fallbackRaw) ? fallbackRaw : 2);
|
|
2569
|
+
return Math.min(10, Math.max(2, Math.floor(base)));
|
|
2570
|
+
}
|
|
2571
|
+
|
|
2572
|
+
function resolveProviderOpenaiBridgeMaxRetries(provider) {
|
|
2573
|
+
if (!provider || typeof provider !== 'object') return 2;
|
|
2574
|
+
if (provider.codexmate_bridge_max_retries !== undefined) {
|
|
2575
|
+
return normalizeOpenaiBridgeMaxRetries(provider.codexmate_bridge_max_retries);
|
|
2576
|
+
}
|
|
2577
|
+
if (provider.openai_bridge_max_retries !== undefined) {
|
|
2578
|
+
return normalizeOpenaiBridgeMaxRetries(provider.openai_bridge_max_retries);
|
|
2579
|
+
}
|
|
2580
|
+
if (provider.max_retries !== undefined) {
|
|
2581
|
+
return normalizeOpenaiBridgeMaxRetries(provider.max_retries);
|
|
2582
|
+
}
|
|
2583
|
+
return 2;
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2480
2586
|
function addProviderToConfig(params = {}) {
|
|
2481
2587
|
const name = typeof params.name === 'string' ? params.name.trim() : '';
|
|
2482
2588
|
const url = typeof params.url === 'string' ? params.url.trim() : '';
|
|
@@ -2491,6 +2597,8 @@ function addProviderToConfig(params = {}) {
|
|
|
2491
2597
|
? params.model.trim()
|
|
2492
2598
|
: fallbackModel;
|
|
2493
2599
|
const useTransform = !!params.useTransform;
|
|
2600
|
+
const hasOpenaiBridgeMaxRetries = params.openaiBridgeMaxRetries !== undefined || params.maxRetries !== undefined;
|
|
2601
|
+
const openaiBridgeMaxRetries = normalizeOpenaiBridgeMaxRetries(params.openaiBridgeMaxRetries ?? params.maxRetries);
|
|
2494
2602
|
const allowManaged = !!params.allowManaged;
|
|
2495
2603
|
const normalizedUrl = normalizeBaseUrl(url);
|
|
2496
2604
|
|
|
@@ -2550,7 +2658,7 @@ function addProviderToConfig(params = {}) {
|
|
|
2550
2658
|
const requiresOpenaiAuth = useTransform;
|
|
2551
2659
|
|
|
2552
2660
|
if (useTransform) {
|
|
2553
|
-
const saveRes = upsertOpenaiBridgeProvider(OPENAI_BRIDGE_SETTINGS_FILE, name, normalizedUrl, key);
|
|
2661
|
+
const saveRes = upsertOpenaiBridgeProvider(OPENAI_BRIDGE_SETTINGS_FILE, name, normalizedUrl, key, undefined, { maxRetries: openaiBridgeMaxRetries });
|
|
2554
2662
|
if (saveRes && saveRes.error) {
|
|
2555
2663
|
return { error: String(saveRes.error) };
|
|
2556
2664
|
}
|
|
@@ -2562,6 +2670,7 @@ function addProviderToConfig(params = {}) {
|
|
|
2562
2670
|
).toString().replace(/\/+$/g, '');
|
|
2563
2671
|
authKeyForConfig = 'codexmate';
|
|
2564
2672
|
extraLines.push(`codexmate_bridge = "openai"`);
|
|
2673
|
+
extraLines.push(`codexmate_bridge_max_retries = ${openaiBridgeMaxRetries}`);
|
|
2565
2674
|
}
|
|
2566
2675
|
|
|
2567
2676
|
const safeUrl = escapeTomlBasicString(baseUrlForConfig);
|
|
@@ -2605,6 +2714,8 @@ function updateProviderInConfig(params = {}) {
|
|
|
2605
2714
|
? String(params.key).trim()
|
|
2606
2715
|
: undefined;
|
|
2607
2716
|
const useTransform = !!params.useTransform;
|
|
2717
|
+
const hasOpenaiBridgeMaxRetries = params.openaiBridgeMaxRetries !== undefined || params.maxRetries !== undefined;
|
|
2718
|
+
const openaiBridgeMaxRetries = normalizeOpenaiBridgeMaxRetries(params.openaiBridgeMaxRetries ?? params.maxRetries);
|
|
2608
2719
|
const allowManaged = !!params.allowManaged;
|
|
2609
2720
|
|
|
2610
2721
|
if (!name) return { error: '名称不能为空' };
|
|
@@ -2619,7 +2730,7 @@ function updateProviderInConfig(params = {}) {
|
|
|
2619
2730
|
}
|
|
2620
2731
|
|
|
2621
2732
|
try {
|
|
2622
|
-
cmdUpdate(name, url || undefined, key, true, { allowManaged, useTransform });
|
|
2733
|
+
cmdUpdate(name, url || undefined, key, true, { allowManaged, useTransform, ...(hasOpenaiBridgeMaxRetries ? { openaiBridgeMaxRetries } : {}) });
|
|
2623
2734
|
return { success: true };
|
|
2624
2735
|
} catch (e) {
|
|
2625
2736
|
return { error: e.message || '更新失败' };
|
|
@@ -9762,6 +9873,8 @@ function cmdDelete(name, silent = false) {
|
|
|
9762
9873
|
function cmdUpdate(name, baseUrl, apiKey, silent = false, options = {}) {
|
|
9763
9874
|
const allowManaged = !!(options && options.allowManaged);
|
|
9764
9875
|
const forceUseTransform = !!(options && options.useTransform);
|
|
9876
|
+
const hasOpenaiBridgeMaxRetries = options && Object.prototype.hasOwnProperty.call(options, 'openaiBridgeMaxRetries');
|
|
9877
|
+
const openaiBridgeMaxRetries = normalizeOpenaiBridgeMaxRetries(options && options.openaiBridgeMaxRetries);
|
|
9765
9878
|
const normalizedBaseUrl = baseUrl === undefined ? undefined : normalizeBaseUrl(baseUrl);
|
|
9766
9879
|
if (!name) {
|
|
9767
9880
|
if (!silent) console.error('错误: 提供商名称必填');
|
|
@@ -9921,6 +10034,32 @@ function cmdUpdate(name, baseUrl, apiKey, silent = false, options = {}) {
|
|
|
9921
10034
|
return next;
|
|
9922
10035
|
};
|
|
9923
10036
|
|
|
10037
|
+
const replaceTomlNumberField = (block, fieldName, rawValue) => {
|
|
10038
|
+
const numberValue = String(Math.floor(Number(rawValue)));
|
|
10039
|
+
const escapedFieldName = escapeRegex(fieldName);
|
|
10040
|
+
const multilineRanges = collectTomlMultilineStringRanges(block);
|
|
10041
|
+
const withCommentRegex = new RegExp(`^(\\s*${escapedFieldName}\\s*=\\s*)([-+]?\\d+(?:\\.\\d+)?)(\\s+#.*)?$`, 'mg');
|
|
10042
|
+
let replaced = false;
|
|
10043
|
+
let next = block.replace(withCommentRegex, (full, prefix, _value, suffix = '', offset) => {
|
|
10044
|
+
if (replaced || isIndexInRanges(offset, multilineRanges)) {
|
|
10045
|
+
return full;
|
|
10046
|
+
}
|
|
10047
|
+
replaced = true;
|
|
10048
|
+
return `${prefix}${numberValue}${suffix}`;
|
|
10049
|
+
});
|
|
10050
|
+
if (!replaced) {
|
|
10051
|
+
const keyIndentMatch = block.match(/^(\s*)[A-Za-z0-9_.-]+\s*=/m);
|
|
10052
|
+
const indent = keyIndentMatch ? keyIndentMatch[1] : '';
|
|
10053
|
+
const lineEnding = block.includes('\r\n') ? '\r\n' : '\n';
|
|
10054
|
+
const tailMatch = block.match(/(\s*)$/);
|
|
10055
|
+
const tail = tailMatch ? tailMatch[1] : '';
|
|
10056
|
+
const body = block.slice(0, block.length - tail.length);
|
|
10057
|
+
const separator = body.endsWith('\n') || body.endsWith('\r') ? '' : lineEnding;
|
|
10058
|
+
next = `${body}${separator}${indent}${fieldName} = ${numberValue}${tail}`;
|
|
10059
|
+
}
|
|
10060
|
+
return next;
|
|
10061
|
+
};
|
|
10062
|
+
|
|
9924
10063
|
const replaceTomlBooleanField = (block, fieldName, rawValue) => {
|
|
9925
10064
|
const boolValue = rawValue ? 'true' : 'false';
|
|
9926
10065
|
const escapedFieldName = escapeRegex(fieldName);
|
|
@@ -9977,7 +10116,9 @@ function cmdUpdate(name, baseUrl, apiKey, silent = false, options = {}) {
|
|
|
9977
10116
|
: existingApiKey;
|
|
9978
10117
|
|
|
9979
10118
|
if (upstreamBaseUrl) {
|
|
9980
|
-
const saveRes = upsertOpenaiBridgeProvider(OPENAI_BRIDGE_SETTINGS_FILE, name, upstreamBaseUrl, upstreamApiKey
|
|
10119
|
+
const saveRes = upsertOpenaiBridgeProvider(OPENAI_BRIDGE_SETTINGS_FILE, name, upstreamBaseUrl, upstreamApiKey, undefined, {
|
|
10120
|
+
maxRetries: hasOpenaiBridgeMaxRetries ? openaiBridgeMaxRetries : resolveProviderOpenaiBridgeMaxRetries(providerConfig)
|
|
10121
|
+
});
|
|
9981
10122
|
if (saveRes && saveRes.error) {
|
|
9982
10123
|
throw new Error(String(saveRes.error));
|
|
9983
10124
|
}
|
|
@@ -9993,6 +10134,9 @@ function cmdUpdate(name, baseUrl, apiKey, silent = false, options = {}) {
|
|
|
9993
10134
|
updatedBlock = replaceTomlBooleanField(updatedBlock, 'requires_openai_auth', true);
|
|
9994
10135
|
updatedBlock = replaceTomlStringField(updatedBlock, 'preferred_auth_method', 'codexmate');
|
|
9995
10136
|
updatedBlock = replaceTomlStringField(updatedBlock, 'codexmate_bridge', 'openai');
|
|
10137
|
+
if (hasOpenaiBridgeMaxRetries) {
|
|
10138
|
+
updatedBlock = replaceTomlNumberField(updatedBlock, 'codexmate_bridge_max_retries', openaiBridgeMaxRetries);
|
|
10139
|
+
}
|
|
9996
10140
|
} else {
|
|
9997
10141
|
if (normalizedBaseUrl) {
|
|
9998
10142
|
updatedBlock = replaceTomlStringField(updatedBlock, 'base_url', normalizedBaseUrl);
|
|
@@ -12735,7 +12879,10 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
12735
12879
|
break;
|
|
12736
12880
|
}
|
|
12737
12881
|
// 不返回 apiKey(敏感信息),仅返回用户填过的上游 URL
|
|
12738
|
-
|
|
12882
|
+
const config = readConfig();
|
|
12883
|
+
const provider = config.model_providers && config.model_providers[name];
|
|
12884
|
+
const providerMaxRetries = resolveProviderOpenaiBridgeMaxRetries(provider);
|
|
12885
|
+
result = { baseUrl: upstream.baseUrl, hasApiKey: !!(upstream.apiKey), maxRetries: providerMaxRetries };
|
|
12739
12886
|
break;
|
|
12740
12887
|
}
|
|
12741
12888
|
case 'list-sessions':
|
|
@@ -13015,6 +13162,8 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
13015
13162
|
detached: true,
|
|
13016
13163
|
taskId,
|
|
13017
13164
|
runId,
|
|
13165
|
+
threadId: plan.threadId || '',
|
|
13166
|
+
cwd: plan.cwd || '',
|
|
13018
13167
|
warnings: validation.warnings || []
|
|
13019
13168
|
};
|
|
13020
13169
|
} else {
|
|
@@ -13859,7 +14008,11 @@ function printTaskHelp() {
|
|
|
13859
14008
|
console.log(' --allow-write 允许写入工作区');
|
|
13860
14009
|
console.log(' --dry-run 仅计划/预演,不执行写入');
|
|
13861
14010
|
console.log(' --plan-only 仅输出计划,不执行');
|
|
13862
|
-
console.log(' --engine <
|
|
14011
|
+
console.log(' --engine <openai-chat|workflow> 选择编排引擎');
|
|
14012
|
+
console.log(' --cwd <路径> 指定任务工作区路径');
|
|
14013
|
+
console.log(' --thread-id <ID> 指定任务线程 ID');
|
|
14014
|
+
console.log(' --conversation-id <ID> 指定任务线程 ID(兼容别名)');
|
|
14015
|
+
console.log(' --session-id <ID> 指定任务线程 ID(兼容别名)');
|
|
13863
14016
|
console.log(' --concurrency <N> 并发度');
|
|
13864
14017
|
console.log(' --auto-fix-rounds <N> 自动修复回合数');
|
|
13865
14018
|
console.log(' --limit <N> runs/queue list 数量');
|
|
@@ -13882,7 +14035,7 @@ function parseTaskCliOptions(args = []) {
|
|
|
13882
14035
|
allowWrite: false,
|
|
13883
14036
|
dryRun: false,
|
|
13884
14037
|
planOnly: false,
|
|
13885
|
-
engine: '
|
|
14038
|
+
engine: 'openai-chat',
|
|
13886
14039
|
concurrency: 2,
|
|
13887
14040
|
autoFixRounds: 1,
|
|
13888
14041
|
limit: 20,
|
|
@@ -14029,6 +14182,38 @@ function parseTaskCliOptions(args = []) {
|
|
|
14029
14182
|
options.explicit.autoFixRounds = true;
|
|
14030
14183
|
continue;
|
|
14031
14184
|
}
|
|
14185
|
+
if (arg === '--cwd') {
|
|
14186
|
+
options.cwd = String(args[i + 1] || '').trim();
|
|
14187
|
+
options.explicit.cwd = true;
|
|
14188
|
+
i += 1;
|
|
14189
|
+
continue;
|
|
14190
|
+
}
|
|
14191
|
+
if (arg.startsWith('--cwd=')) {
|
|
14192
|
+
options.cwd = arg.slice('--cwd='.length).trim();
|
|
14193
|
+
options.explicit.cwd = true;
|
|
14194
|
+
continue;
|
|
14195
|
+
}
|
|
14196
|
+
if (arg === '--thread-id' || arg === '--conversation-id' || arg === '--session-id') {
|
|
14197
|
+
options.threadId = String(args[i + 1] || '').trim();
|
|
14198
|
+
options.explicit.threadId = true;
|
|
14199
|
+
i += 1;
|
|
14200
|
+
continue;
|
|
14201
|
+
}
|
|
14202
|
+
if (arg.startsWith('--thread-id=')) {
|
|
14203
|
+
options.threadId = arg.slice('--thread-id='.length).trim();
|
|
14204
|
+
options.explicit.threadId = true;
|
|
14205
|
+
continue;
|
|
14206
|
+
}
|
|
14207
|
+
if (arg.startsWith('--conversation-id=')) {
|
|
14208
|
+
options.threadId = arg.slice('--conversation-id='.length).trim();
|
|
14209
|
+
options.explicit.threadId = true;
|
|
14210
|
+
continue;
|
|
14211
|
+
}
|
|
14212
|
+
if (arg.startsWith('--session-id=')) {
|
|
14213
|
+
options.threadId = arg.slice('--session-id='.length).trim();
|
|
14214
|
+
options.explicit.threadId = true;
|
|
14215
|
+
continue;
|
|
14216
|
+
}
|
|
14032
14217
|
if (arg === '--limit') {
|
|
14033
14218
|
const value = parseInt(args[i + 1], 10);
|
|
14034
14219
|
if (Number.isFinite(value)) options.limit = value;
|
|
@@ -14086,9 +14271,11 @@ function buildTaskCliPayload(options = {}, rest = []) {
|
|
|
14086
14271
|
if (explicit.followUps && Array.isArray(options.followUps)) payload.followUps = options.followUps.slice();
|
|
14087
14272
|
if (explicit.allowWrite) payload.allowWrite = options.allowWrite === true;
|
|
14088
14273
|
if (explicit.dryRun) payload.dryRun = options.dryRun === true;
|
|
14089
|
-
if (explicit.engine) payload.engine = options.engine || '
|
|
14274
|
+
if (explicit.engine) payload.engine = options.engine || 'openai-chat';
|
|
14090
14275
|
if (explicit.concurrency) payload.concurrency = options.concurrency;
|
|
14091
14276
|
if (explicit.autoFixRounds) payload.autoFixRounds = options.autoFixRounds;
|
|
14277
|
+
if (explicit.cwd && options.cwd) payload.cwd = options.cwd;
|
|
14278
|
+
if (explicit.threadId && options.threadId) payload.threadId = options.threadId;
|
|
14092
14279
|
if (explicit.taskId && options.taskId) payload.taskId = options.taskId;
|
|
14093
14280
|
if (explicit.runId && options.runId) payload.runId = options.runId;
|
|
14094
14281
|
if (!payload.target && Array.isArray(rest) && rest.length > 0) {
|
|
@@ -14102,7 +14289,7 @@ function buildTaskCliPayload(options = {}, rest = []) {
|
|
|
14102
14289
|
|
|
14103
14290
|
function printTaskPlanSummary(plan, warnings = []) {
|
|
14104
14291
|
console.log(`\n任务计划: ${plan.title || '(untitled)'}`);
|
|
14105
|
-
console.log(` engine: ${plan.engine || '
|
|
14292
|
+
console.log(` engine: ${plan.engine || 'openai-chat'}`);
|
|
14106
14293
|
console.log(` allowWrite: ${plan.allowWrite === true ? 'yes' : 'no'}`);
|
|
14107
14294
|
console.log(` dryRun: ${plan.dryRun === true ? 'yes' : 'no'}`);
|
|
14108
14295
|
console.log(` concurrency: ${plan.concurrency || 1}`);
|
|
@@ -14935,11 +15122,13 @@ function buildMcpProviderListPayload() {
|
|
|
14935
15122
|
upstreamUrl = upstream.baseUrl.trim();
|
|
14936
15123
|
}
|
|
14937
15124
|
}
|
|
15125
|
+
const openaiBridgeMaxRetries = resolveProviderOpenaiBridgeMaxRetries(p);
|
|
14938
15126
|
return {
|
|
14939
15127
|
name,
|
|
14940
15128
|
url: p.base_url || '',
|
|
14941
15129
|
upstreamUrl,
|
|
14942
15130
|
codexmate_bridge: bridge,
|
|
15131
|
+
openaiBridgeMaxRetries,
|
|
14943
15132
|
key: maskKey(p.preferred_auth_method || ''),
|
|
14944
15133
|
hasKey: !!(p.preferred_auth_method && p.preferred_auth_method.trim()),
|
|
14945
15134
|
models: Array.isArray(p.models)
|
|
@@ -15556,6 +15745,10 @@ function createTaskRunId() {
|
|
|
15556
15745
|
return `tr-${Date.now()}-${crypto.randomBytes(3).toString('hex')}`;
|
|
15557
15746
|
}
|
|
15558
15747
|
|
|
15748
|
+
function createTaskThreadId() {
|
|
15749
|
+
return `tt-${Date.now()}-${crypto.randomBytes(3).toString('hex')}`;
|
|
15750
|
+
}
|
|
15751
|
+
|
|
15559
15752
|
function validateTaskRunId(value) {
|
|
15560
15753
|
const runId = typeof value === 'string' ? value.trim() : '';
|
|
15561
15754
|
if (!runId) {
|
|
@@ -15567,9 +15760,19 @@ function validateTaskRunId(value) {
|
|
|
15567
15760
|
return { ok: true, error: '', runId };
|
|
15568
15761
|
}
|
|
15569
15762
|
|
|
15763
|
+
function normalizeTaskThreadId(value) {
|
|
15764
|
+
const threadId = typeof value === 'string' ? value.trim() : '';
|
|
15765
|
+
if (!threadId) {
|
|
15766
|
+
return '';
|
|
15767
|
+
}
|
|
15768
|
+
return /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(threadId) ? threadId : '';
|
|
15769
|
+
}
|
|
15770
|
+
|
|
15570
15771
|
function normalizeTaskEngine(value) {
|
|
15571
|
-
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
15572
|
-
|
|
15772
|
+
const normalized = typeof value === 'string' ? value.trim().toLowerCase().replace(/[\s_/]+/g, '-') : '';
|
|
15773
|
+
if (normalized === 'workflow') return 'workflow';
|
|
15774
|
+
// Legacy task plans used `codex`; new task execution uses the configured OpenAI Chat-compatible provider.
|
|
15775
|
+
return 'openai-chat';
|
|
15573
15776
|
}
|
|
15574
15777
|
|
|
15575
15778
|
function normalizeTaskFollowUps(input = []) {
|
|
@@ -15610,6 +15813,7 @@ function normalizeTaskPlanRequest(params = {}) {
|
|
|
15610
15813
|
: (typeof source.followUp === 'string' && source.followUp.trim() ? [source.followUp.trim()] : []);
|
|
15611
15814
|
return {
|
|
15612
15815
|
id: typeof source.id === 'string' ? source.id.trim() : '',
|
|
15816
|
+
threadId: normalizeTaskThreadId(source.threadId || source.conversationId || source.sessionId),
|
|
15613
15817
|
title: typeof source.title === 'string' ? source.title.trim() : '',
|
|
15614
15818
|
target: typeof source.target === 'string' ? source.target.trim() : '',
|
|
15615
15819
|
notes: typeof source.notes === 'string' ? source.notes.trim() : '',
|
|
@@ -15619,6 +15823,7 @@ function normalizeTaskPlanRequest(params = {}) {
|
|
|
15619
15823
|
dryRun: source.dryRun === true,
|
|
15620
15824
|
concurrency: Number.isFinite(source.concurrency) ? source.concurrency : parseInt(source.concurrency, 10),
|
|
15621
15825
|
autoFixRounds: Number.isFinite(source.autoFixRounds) ? source.autoFixRounds : parseInt(source.autoFixRounds, 10),
|
|
15826
|
+
previewOnly: source.previewOnly === true,
|
|
15622
15827
|
workflowIds: rawWorkflowIds,
|
|
15623
15828
|
followUps: normalizeTaskFollowUps(rawFollowUps)
|
|
15624
15829
|
};
|
|
@@ -15627,12 +15832,18 @@ function normalizeTaskPlanRequest(params = {}) {
|
|
|
15627
15832
|
function coerceTaskPlanPayload(params = {}) {
|
|
15628
15833
|
if (params && params.plan && typeof params.plan === 'object' && !Array.isArray(params.plan)) {
|
|
15629
15834
|
const plan = cloneJson(params.plan, {});
|
|
15630
|
-
const overrideKeys = ['id', 'title', 'target', 'notes', 'cwd', 'engine', 'allowWrite', 'dryRun', 'concurrency', 'autoFixRounds', 'workflowIds', 'followUps'];
|
|
15835
|
+
const overrideKeys = ['id', 'threadId', 'conversationId', 'sessionId', 'title', 'target', 'notes', 'cwd', 'engine', 'allowWrite', 'dryRun', 'concurrency', 'autoFixRounds', 'previewOnly', 'workflowIds', 'followUps'];
|
|
15631
15836
|
for (const key of overrideKeys) {
|
|
15632
15837
|
if (Object.prototype.hasOwnProperty.call(params, key) && params[key] !== undefined) {
|
|
15633
|
-
|
|
15838
|
+
if (key === 'conversationId' || key === 'sessionId') {
|
|
15839
|
+
plan.threadId = cloneJson(params[key], params[key]);
|
|
15840
|
+
} else {
|
|
15841
|
+
plan[key] = cloneJson(params[key], params[key]);
|
|
15842
|
+
}
|
|
15634
15843
|
}
|
|
15635
15844
|
}
|
|
15845
|
+
plan.cwd = typeof plan.cwd === 'string' && plan.cwd.trim() ? plan.cwd.trim() : process.cwd();
|
|
15846
|
+
plan.threadId = normalizeTaskThreadId(plan.threadId) || createTaskThreadId();
|
|
15636
15847
|
plan.engine = normalizeTaskEngine(plan.engine);
|
|
15637
15848
|
plan.workflowIds = normalizeTaskFollowUps(plan.workflowIds || []).map((id) => normalizeWorkflowId(id)).filter(Boolean);
|
|
15638
15849
|
plan.followUps = normalizeTaskFollowUps(plan.followUps || []);
|
|
@@ -15647,6 +15858,7 @@ function coerceTaskPlanPayload(params = {}) {
|
|
|
15647
15858
|
});
|
|
15648
15859
|
return {
|
|
15649
15860
|
...plan,
|
|
15861
|
+
threadId: request.threadId || createTaskThreadId(),
|
|
15650
15862
|
engine: normalizeTaskEngine(request.engine || plan.engine)
|
|
15651
15863
|
};
|
|
15652
15864
|
}
|
|
@@ -15669,8 +15881,10 @@ function normalizeTaskQueueItem(raw = {}) {
|
|
|
15669
15881
|
const taskId = typeof raw.taskId === 'string' ? raw.taskId.trim() : '';
|
|
15670
15882
|
return {
|
|
15671
15883
|
taskId: taskId || createTaskId(),
|
|
15884
|
+
threadId: normalizeTaskThreadId(raw.threadId || plan.threadId) || createTaskThreadId(),
|
|
15672
15885
|
title: typeof raw.title === 'string' ? raw.title.trim() : (typeof plan.title === 'string' ? plan.title.trim() : ''),
|
|
15673
15886
|
target: typeof raw.target === 'string' ? raw.target.trim() : (typeof plan.target === 'string' ? plan.target.trim() : ''),
|
|
15887
|
+
cwd: typeof raw.cwd === 'string' && raw.cwd.trim() ? raw.cwd.trim() : (typeof plan.cwd === 'string' ? plan.cwd.trim() : ''),
|
|
15674
15888
|
status: typeof raw.status === 'string' ? raw.status.trim().toLowerCase() : 'queued',
|
|
15675
15889
|
createdAt: toIsoTime(raw.createdAt || Date.now(), ''),
|
|
15676
15890
|
updatedAt: toIsoTime(raw.updatedAt || raw.createdAt || Date.now(), ''),
|
|
@@ -15871,8 +16085,10 @@ function collectTaskRunSummary(detail = {}) {
|
|
|
15871
16085
|
return {
|
|
15872
16086
|
runId: detail.runId || '',
|
|
15873
16087
|
taskId: detail.taskId || '',
|
|
16088
|
+
threadId: detail.threadId || '',
|
|
15874
16089
|
title: detail.title || '',
|
|
15875
16090
|
target: detail.target || '',
|
|
16091
|
+
cwd: detail.cwd || '',
|
|
15876
16092
|
engine: detail.engine || '',
|
|
15877
16093
|
allowWrite: detail.allowWrite === true,
|
|
15878
16094
|
dryRun: detail.dryRun === true,
|
|
@@ -15996,6 +16212,7 @@ function buildTaskOverviewPayload(options = {}) {
|
|
|
15996
16212
|
}
|
|
15997
16213
|
return {
|
|
15998
16214
|
workflows: workflowCatalog.workflows,
|
|
16215
|
+
openAiChatStatus: buildTaskOpenAiChatStatus(),
|
|
15999
16216
|
warnings,
|
|
16000
16217
|
queue,
|
|
16001
16218
|
runs,
|
|
@@ -16058,20 +16275,434 @@ function readCodexLastMessageFile(filePath) {
|
|
|
16058
16275
|
}
|
|
16059
16276
|
}
|
|
16060
16277
|
|
|
16061
|
-
|
|
16062
|
-
const
|
|
16063
|
-
|
|
16064
|
-
if (
|
|
16278
|
+
function buildOpenAiChatEndpointUrl(baseUrl) {
|
|
16279
|
+
const trimmed = normalizeBaseUrl(baseUrl);
|
|
16280
|
+
if (!trimmed) return '';
|
|
16281
|
+
if (/\/v1\/chat\/completions$/i.test(trimmed) || /\/chat\/completions$/i.test(trimmed)) {
|
|
16282
|
+
return trimmed;
|
|
16283
|
+
}
|
|
16284
|
+
return joinApiUrl(trimmed, 'chat/completions');
|
|
16285
|
+
}
|
|
16286
|
+
|
|
16287
|
+
function redactTaskEndpointUrl(endpointUrl = '') {
|
|
16288
|
+
const raw = String(endpointUrl || '').trim();
|
|
16289
|
+
if (!raw) return '';
|
|
16290
|
+
try {
|
|
16291
|
+
const parsed = new URL(raw);
|
|
16292
|
+
if (parsed.username) parsed.username = '***';
|
|
16293
|
+
if (parsed.password) parsed.password = '***';
|
|
16294
|
+
const secretKeyPattern = /(?:^|[_-])(?:key|api[-_]?key|token|access[-_]?token|refresh[-_]?token|secret|password|passwd|pwd|credential|credentials|signature|sig)(?:$|[_-])/i;
|
|
16295
|
+
for (const key of [...parsed.searchParams.keys()]) {
|
|
16296
|
+
if (secretKeyPattern.test(key)) {
|
|
16297
|
+
parsed.searchParams.set(key, '***');
|
|
16298
|
+
}
|
|
16299
|
+
}
|
|
16300
|
+
return parsed.toString();
|
|
16301
|
+
} catch (_) {
|
|
16302
|
+
return raw
|
|
16303
|
+
.replace(/\/\/([^/@:]+):([^/@]+)@/g, '//***:***@')
|
|
16304
|
+
.replace(/([?&][^=&]*(?:key|token|secret|password|passwd|pwd|credential|signature|sig)[^=]*=)[^&]*/ig, '$1***');
|
|
16305
|
+
}
|
|
16306
|
+
}
|
|
16307
|
+
|
|
16308
|
+
function pickTaskProviderModel(providerName, provider, config) {
|
|
16309
|
+
const currentModels = readCurrentModels();
|
|
16310
|
+
const savedModel = currentModels && typeof currentModels[providerName] === 'string'
|
|
16311
|
+
? currentModels[providerName].trim()
|
|
16312
|
+
: '';
|
|
16313
|
+
const activeProvider = typeof config.model_provider === 'string' ? config.model_provider.trim() : '';
|
|
16314
|
+
const activeModel = typeof config.model === 'string' ? config.model.trim() : '';
|
|
16315
|
+
const providerModels = Array.isArray(provider && provider.models) ? provider.models : [];
|
|
16316
|
+
const firstProviderModel = providerModels
|
|
16317
|
+
.map((item) => {
|
|
16318
|
+
if (typeof item === 'string') return item.trim();
|
|
16319
|
+
if (item && typeof item === 'object') return String(item.id || item.name || item.model || '').trim();
|
|
16320
|
+
return '';
|
|
16321
|
+
})
|
|
16322
|
+
.find(Boolean) || '';
|
|
16323
|
+
return savedModel || (activeProvider === providerName ? activeModel : '') || firstProviderModel;
|
|
16324
|
+
}
|
|
16325
|
+
|
|
16326
|
+
function pickTaskProviderTemperature(provider) {
|
|
16327
|
+
const raw = provider && Object.prototype.hasOwnProperty.call(provider, 'temperature')
|
|
16328
|
+
? provider.temperature
|
|
16329
|
+
: undefined;
|
|
16330
|
+
if (raw === undefined || raw === null || String(raw).trim() === '') {
|
|
16331
|
+
return 0.2;
|
|
16332
|
+
}
|
|
16333
|
+
const value = Number(raw);
|
|
16334
|
+
if (!Number.isFinite(value) || value < 0 || value > 2) {
|
|
16335
|
+
return 0.2;
|
|
16336
|
+
}
|
|
16337
|
+
return value;
|
|
16338
|
+
}
|
|
16339
|
+
|
|
16340
|
+
function pickTaskOpenAiChatProviderName(config) {
|
|
16341
|
+
const taskProvider = typeof config.task_openai_chat_provider === 'string'
|
|
16342
|
+
? config.task_openai_chat_provider.trim()
|
|
16343
|
+
: '';
|
|
16344
|
+
if (taskProvider) {
|
|
16345
|
+
return taskProvider;
|
|
16346
|
+
}
|
|
16347
|
+
return typeof config.model_provider === 'string' ? config.model_provider.trim() : '';
|
|
16348
|
+
}
|
|
16349
|
+
|
|
16350
|
+
function resolveTaskOpenAiChatConfig() {
|
|
16351
|
+
const configResult = readConfigOrVirtualDefault();
|
|
16352
|
+
const config = configResult && configResult.config && typeof configResult.config === 'object' ? configResult.config : {};
|
|
16353
|
+
const providerName = pickTaskOpenAiChatProviderName(config);
|
|
16354
|
+
if (!providerName) {
|
|
16355
|
+
return { error: '未设置当前 OpenAI Chat 提供商' };
|
|
16356
|
+
}
|
|
16357
|
+
const providers = config.model_providers && typeof config.model_providers === 'object' ? config.model_providers : {};
|
|
16358
|
+
const provider = providers[providerName];
|
|
16359
|
+
if (!provider || typeof provider !== 'object') {
|
|
16360
|
+
return { error: `OpenAI Chat 提供商不存在: ${providerName}` };
|
|
16361
|
+
}
|
|
16362
|
+
|
|
16363
|
+
const bridgeType = typeof provider.codexmate_bridge === 'string' ? provider.codexmate_bridge.trim() : '';
|
|
16364
|
+
const isOpenaiBridgeProvider = bridgeType === 'openai'
|
|
16365
|
+
|| (typeof provider.base_url === 'string' && provider.base_url.includes('/bridge/openai/'));
|
|
16366
|
+
let baseUrl = typeof provider.base_url === 'string' ? provider.base_url.trim() : '';
|
|
16367
|
+
let apiKey = typeof provider.preferred_auth_method === 'string' ? provider.preferred_auth_method.trim() : '';
|
|
16368
|
+
let extraHeaders = {};
|
|
16369
|
+
if (isOpenaiBridgeProvider) {
|
|
16370
|
+
const upstream = resolveOpenaiBridgeUpstream(OPENAI_BRIDGE_SETTINGS_FILE, providerName);
|
|
16371
|
+
if (upstream && !upstream.error) {
|
|
16372
|
+
baseUrl = upstream.baseUrl || baseUrl;
|
|
16373
|
+
apiKey = upstream.apiKey || apiKey;
|
|
16374
|
+
extraHeaders = upstream.headers && typeof upstream.headers === 'object' && !Array.isArray(upstream.headers)
|
|
16375
|
+
? upstream.headers
|
|
16376
|
+
: {};
|
|
16377
|
+
}
|
|
16378
|
+
}
|
|
16379
|
+
|
|
16380
|
+
const model = pickTaskProviderModel(providerName, provider, config);
|
|
16381
|
+
if (!baseUrl) {
|
|
16382
|
+
return { error: `OpenAI Chat 提供商 ${providerName} 缺少 base_url` };
|
|
16383
|
+
}
|
|
16384
|
+
if (!isValidHttpUrl(baseUrl)) {
|
|
16385
|
+
return { error: `OpenAI Chat 提供商 ${providerName} 的 base_url 无效` };
|
|
16386
|
+
}
|
|
16387
|
+
if (!model) {
|
|
16388
|
+
return { error: `OpenAI Chat 提供商 ${providerName} 未设置模型` };
|
|
16389
|
+
}
|
|
16390
|
+
return {
|
|
16391
|
+
providerName,
|
|
16392
|
+
baseUrl,
|
|
16393
|
+
endpointUrl: buildOpenAiChatEndpointUrl(baseUrl),
|
|
16394
|
+
apiKey,
|
|
16395
|
+
extraHeaders,
|
|
16396
|
+
model,
|
|
16397
|
+
temperature: pickTaskProviderTemperature(provider)
|
|
16398
|
+
};
|
|
16399
|
+
}
|
|
16400
|
+
|
|
16401
|
+
function buildTaskOpenAiChatStatus() {
|
|
16402
|
+
const requestConfig = resolveTaskOpenAiChatConfig();
|
|
16403
|
+
if (requestConfig && requestConfig.error) {
|
|
16404
|
+
return {
|
|
16405
|
+
ok: false,
|
|
16406
|
+
ready: false,
|
|
16407
|
+
error: requestConfig.error,
|
|
16408
|
+
providerName: '',
|
|
16409
|
+
model: '',
|
|
16410
|
+
endpoint: '',
|
|
16411
|
+
hasApiKey: false,
|
|
16412
|
+
hasExtraHeaders: false
|
|
16413
|
+
};
|
|
16414
|
+
}
|
|
16415
|
+
const hasApiKey = !!(requestConfig && typeof requestConfig.apiKey === 'string' && requestConfig.apiKey.trim());
|
|
16416
|
+
const hasExtraHeaders = !!(requestConfig
|
|
16417
|
+
&& requestConfig.extraHeaders
|
|
16418
|
+
&& typeof requestConfig.extraHeaders === 'object'
|
|
16419
|
+
&& !Array.isArray(requestConfig.extraHeaders)
|
|
16420
|
+
&& Object.keys(requestConfig.extraHeaders).length > 0);
|
|
16421
|
+
const hasAuthMaterial = hasApiKey || hasExtraHeaders;
|
|
16422
|
+
return {
|
|
16423
|
+
ok: true,
|
|
16424
|
+
ready: hasAuthMaterial,
|
|
16425
|
+
error: hasAuthMaterial ? '' : `OpenAI Chat 提供商 ${requestConfig.providerName || ''} 缺少 API key 或额外 headers`,
|
|
16426
|
+
providerName: requestConfig.providerName || '',
|
|
16427
|
+
model: requestConfig.model || '',
|
|
16428
|
+
endpoint: redactTaskEndpointUrl(requestConfig.endpointUrl || ''),
|
|
16429
|
+
hasApiKey,
|
|
16430
|
+
hasExtraHeaders
|
|
16431
|
+
};
|
|
16432
|
+
}
|
|
16433
|
+
|
|
16434
|
+
function postOpenAiChatCompletion(requestConfig, body, options = {}) {
|
|
16435
|
+
const endpointUrl = requestConfig && requestConfig.endpointUrl ? requestConfig.endpointUrl : '';
|
|
16436
|
+
return new Promise((resolve) => {
|
|
16437
|
+
let parsed;
|
|
16438
|
+
try {
|
|
16439
|
+
parsed = new URL(endpointUrl);
|
|
16440
|
+
} catch (error) {
|
|
16441
|
+
resolve({ ok: false, error: 'OpenAI Chat endpoint URL 无效', status: 0, payload: null, body: '' });
|
|
16442
|
+
return;
|
|
16443
|
+
}
|
|
16444
|
+
const transport = parsed.protocol === 'https:' ? https : http;
|
|
16445
|
+
const agent = parsed.protocol === 'https:' ? HTTPS_KEEP_ALIVE_AGENT : HTTP_KEEP_ALIVE_AGENT;
|
|
16446
|
+
const payloadText = JSON.stringify(body || {});
|
|
16447
|
+
const headers = {
|
|
16448
|
+
'Content-Type': 'application/json',
|
|
16449
|
+
'Accept': 'application/json',
|
|
16450
|
+
'User-Agent': 'codexmate-task-orchestration',
|
|
16451
|
+
'Content-Length': Buffer.byteLength(payloadText, 'utf-8'),
|
|
16452
|
+
...(requestConfig.extraHeaders && typeof requestConfig.extraHeaders === 'object' ? requestConfig.extraHeaders : {})
|
|
16453
|
+
};
|
|
16454
|
+
if (requestConfig.apiKey) {
|
|
16455
|
+
headers.Authorization = `Bearer ${requestConfig.apiKey}`;
|
|
16456
|
+
}
|
|
16457
|
+
let settled = false;
|
|
16458
|
+
let req = null;
|
|
16459
|
+
const finish = (result) => {
|
|
16460
|
+
if (settled) return;
|
|
16461
|
+
settled = true;
|
|
16462
|
+
resolve(result);
|
|
16463
|
+
};
|
|
16464
|
+
req = transport.request(parsed, { method: 'POST', headers, agent }, (res) => {
|
|
16465
|
+
const status = res.statusCode || 0;
|
|
16466
|
+
let raw = '';
|
|
16467
|
+
let receivedBytes = 0;
|
|
16468
|
+
res.on('data', (chunk) => {
|
|
16469
|
+
if (settled) return;
|
|
16470
|
+
receivedBytes += chunk.length || 0;
|
|
16471
|
+
if (receivedBytes > TASK_OPENAI_CHAT_MAX_RESPONSE_BYTES) {
|
|
16472
|
+
finish({ ok: false, status, error: 'OpenAI Chat response too large', payload: null, body: raw });
|
|
16473
|
+
res.destroy();
|
|
16474
|
+
return;
|
|
16475
|
+
}
|
|
16476
|
+
raw += chunk;
|
|
16477
|
+
});
|
|
16478
|
+
res.on('error', (error) => {
|
|
16479
|
+
finish({
|
|
16480
|
+
ok: false,
|
|
16481
|
+
status,
|
|
16482
|
+
error: error && error.message ? error.message : 'OpenAI Chat request failed',
|
|
16483
|
+
payload: null,
|
|
16484
|
+
body: raw
|
|
16485
|
+
});
|
|
16486
|
+
});
|
|
16487
|
+
res.on('end', () => {
|
|
16488
|
+
if (settled) return;
|
|
16489
|
+
let parsedPayload = null;
|
|
16490
|
+
try {
|
|
16491
|
+
parsedPayload = raw ? JSON.parse(raw) : null;
|
|
16492
|
+
} catch (_) {}
|
|
16493
|
+
if (status < 200 || status >= 300) {
|
|
16494
|
+
const message = parsedPayload && parsedPayload.error
|
|
16495
|
+
? (typeof parsedPayload.error === 'string' ? parsedPayload.error : (parsedPayload.error.message || JSON.stringify(parsedPayload.error)))
|
|
16496
|
+
: (raw || `OpenAI Chat request failed: ${status}`);
|
|
16497
|
+
finish({ ok: false, status, error: truncateTaskText(message, 1200), payload: parsedPayload, body: raw });
|
|
16498
|
+
return;
|
|
16499
|
+
}
|
|
16500
|
+
finish({ ok: true, status, error: '', payload: parsedPayload, body: raw });
|
|
16501
|
+
});
|
|
16502
|
+
});
|
|
16503
|
+
if (typeof options.registerAbort === 'function') {
|
|
16504
|
+
options.registerAbort(() => {
|
|
16505
|
+
try {
|
|
16506
|
+
req.destroy(new Error('cancelled'));
|
|
16507
|
+
} catch (_) {}
|
|
16508
|
+
});
|
|
16509
|
+
}
|
|
16510
|
+
req.setTimeout(TASK_OPENAI_CHAT_TIMEOUT_MS, () => {
|
|
16511
|
+
req.destroy(new Error('OpenAI Chat request timeout'));
|
|
16512
|
+
});
|
|
16513
|
+
req.on('error', (error) => {
|
|
16514
|
+
finish({ ok: false, status: 0, error: error && error.message ? error.message : String(error || 'OpenAI Chat request failed'), payload: null, body: '' });
|
|
16515
|
+
});
|
|
16516
|
+
req.write(payloadText);
|
|
16517
|
+
req.end();
|
|
16518
|
+
});
|
|
16519
|
+
}
|
|
16520
|
+
|
|
16521
|
+
const TASK_OPENAI_MATERIALIZED_ARTIFACT_MAX_BYTES = 512 * 1024;
|
|
16522
|
+
const TASK_OPENAI_MATERIALIZED_ARTIFACT_EXTENSIONS = new Set(['.html', '.htm', '.css', '.js', '.mjs', '.json', '.md', '.txt', '.svg']);
|
|
16523
|
+
|
|
16524
|
+
function normalizeTaskMaterializedArtifactPath(rawPath, cwd) {
|
|
16525
|
+
const baseDir = path.resolve(typeof cwd === 'string' && cwd.trim() ? cwd.trim() : process.cwd());
|
|
16526
|
+
const text = typeof rawPath === 'string' ? rawPath.trim() : '';
|
|
16527
|
+
if (!text || text.length > 200) {
|
|
16528
|
+
return { error: 'artifact path is empty or too long' };
|
|
16529
|
+
}
|
|
16530
|
+
if (path.isAbsolute(text) || text.includes('\\')) {
|
|
16531
|
+
return { error: `artifact path must be a safe relative path: ${text}` };
|
|
16532
|
+
}
|
|
16533
|
+
const normalized = path.normalize(text);
|
|
16534
|
+
if (!normalized || normalized === '.' || normalized.startsWith('..') || normalized.split(path.sep).includes('..')) {
|
|
16535
|
+
return { error: `artifact path escapes cwd: ${text}` };
|
|
16536
|
+
}
|
|
16537
|
+
const ext = path.extname(normalized).toLowerCase();
|
|
16538
|
+
if (!TASK_OPENAI_MATERIALIZED_ARTIFACT_EXTENSIONS.has(ext)) {
|
|
16539
|
+
return { error: `artifact extension is not allowed: ${ext || '(none)'}` };
|
|
16540
|
+
}
|
|
16541
|
+
const targetPath = path.resolve(baseDir, normalized);
|
|
16542
|
+
const relative = path.relative(baseDir, targetPath);
|
|
16543
|
+
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
16544
|
+
return { error: `artifact path escapes cwd: ${text}` };
|
|
16545
|
+
}
|
|
16546
|
+
return { path: targetPath, relativePath: normalized, baseDir };
|
|
16547
|
+
}
|
|
16548
|
+
|
|
16549
|
+
function validateTaskMaterializedArtifactParents(targetPath, baseDir) {
|
|
16550
|
+
const resolvedBase = fs.realpathSync.native(baseDir);
|
|
16551
|
+
let current = path.dirname(targetPath);
|
|
16552
|
+
const pending = [];
|
|
16553
|
+
while (true) {
|
|
16554
|
+
if (current === resolvedBase) {
|
|
16555
|
+
return { ok: true };
|
|
16556
|
+
}
|
|
16557
|
+
if (current === path.dirname(current)) {
|
|
16558
|
+
return { ok: false, error: 'artifact parent escapes cwd' };
|
|
16559
|
+
}
|
|
16560
|
+
if (fs.existsSync(current)) {
|
|
16561
|
+
const stats = fs.lstatSync(current);
|
|
16562
|
+
if (stats.isSymbolicLink()) {
|
|
16563
|
+
return { ok: false, error: `artifact parent is a symlink: ${path.relative(baseDir, current) || current}` };
|
|
16564
|
+
}
|
|
16565
|
+
const resolvedCurrent = fs.realpathSync.native(current);
|
|
16566
|
+
const relative = path.relative(resolvedBase, resolvedCurrent);
|
|
16567
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
16568
|
+
return { ok: false, error: 'artifact parent resolves outside cwd' };
|
|
16569
|
+
}
|
|
16570
|
+
for (const child of pending) {
|
|
16571
|
+
const childRelative = path.relative(resolvedBase, child);
|
|
16572
|
+
if (childRelative.startsWith('..') || path.isAbsolute(childRelative)) {
|
|
16573
|
+
return { ok: false, error: 'artifact parent escapes cwd' };
|
|
16574
|
+
}
|
|
16575
|
+
}
|
|
16576
|
+
return { ok: true };
|
|
16577
|
+
}
|
|
16578
|
+
pending.push(current);
|
|
16579
|
+
current = path.dirname(current);
|
|
16580
|
+
}
|
|
16581
|
+
}
|
|
16582
|
+
|
|
16583
|
+
function inferTaskMaterializedArtifactPath(info, prefix, hints) {
|
|
16584
|
+
const sources = [info, prefix, hints].map((item) => String(item || '')).filter(Boolean);
|
|
16585
|
+
const explicitPatterns = [
|
|
16586
|
+
/(?:file|filename|path)\s*[:=]\s*[`"']?([A-Za-z0-9._/-]+\.[A-Za-z0-9]+)[`"']?/i,
|
|
16587
|
+
/(?:文件|路径|保存为|写入到?|输出到?)\s*[::]?\s*[`"']?([A-Za-z0-9._/-]+\.[A-Za-z0-9]+)[`"']?/i,
|
|
16588
|
+
/[`"']([A-Za-z0-9._/-]+\.[A-Za-z0-9]+)[`"']/i
|
|
16589
|
+
];
|
|
16590
|
+
for (const source of sources) {
|
|
16591
|
+
for (const pattern of explicitPatterns) {
|
|
16592
|
+
const match = source.match(pattern);
|
|
16593
|
+
if (match && match[1]) {
|
|
16594
|
+
return match[1];
|
|
16595
|
+
}
|
|
16596
|
+
}
|
|
16597
|
+
}
|
|
16598
|
+
const normalizedInfo = String(info || '').trim().toLowerCase();
|
|
16599
|
+
const hintText = sources.join('\n').toLowerCase();
|
|
16600
|
+
if ((normalizedInfo === 'html' || normalizedInfo.startsWith('html ')) && hintText.includes('index.html')) {
|
|
16601
|
+
return 'index.html';
|
|
16602
|
+
}
|
|
16603
|
+
return '';
|
|
16604
|
+
}
|
|
16605
|
+
|
|
16606
|
+
function materializeOpenAiChatTaskArtifacts(text, options = {}) {
|
|
16607
|
+
const content = typeof text === 'string' ? text : '';
|
|
16608
|
+
if (!content.trim()) {
|
|
16609
|
+
return { files: [], warnings: [] };
|
|
16610
|
+
}
|
|
16611
|
+
const cwd = typeof options.cwd === 'string' && options.cwd.trim() ? options.cwd.trim() : process.cwd();
|
|
16612
|
+
const hints = [options.target, options.notes, options.prompt]
|
|
16613
|
+
.map((item) => String(item || '').trim())
|
|
16614
|
+
.filter(Boolean)
|
|
16615
|
+
.join('\n');
|
|
16616
|
+
const files = [];
|
|
16617
|
+
const warnings = [];
|
|
16618
|
+
const seen = new Set();
|
|
16619
|
+
const fencePattern = /```([^\n`]*)\n([\s\S]*?)```/g;
|
|
16620
|
+
let match = null;
|
|
16621
|
+
while ((match = fencePattern.exec(content)) !== null) {
|
|
16622
|
+
const info = String(match[1] || '').trim();
|
|
16623
|
+
const body = String(match[2] || '');
|
|
16624
|
+
const prefix = content.slice(Math.max(0, match.index - 240), match.index);
|
|
16625
|
+
const inferredPath = inferTaskMaterializedArtifactPath(info, prefix, hints);
|
|
16626
|
+
if (!inferredPath) {
|
|
16627
|
+
continue;
|
|
16628
|
+
}
|
|
16629
|
+
if (Buffer.byteLength(body, 'utf-8') > TASK_OPENAI_MATERIALIZED_ARTIFACT_MAX_BYTES) {
|
|
16630
|
+
warnings.push(`artifact too large and skipped: ${inferredPath}`);
|
|
16631
|
+
continue;
|
|
16632
|
+
}
|
|
16633
|
+
const normalized = normalizeTaskMaterializedArtifactPath(inferredPath, cwd);
|
|
16634
|
+
if (normalized.error) {
|
|
16635
|
+
warnings.push(normalized.error);
|
|
16636
|
+
continue;
|
|
16637
|
+
}
|
|
16638
|
+
if (seen.has(normalized.path)) {
|
|
16639
|
+
warnings.push(`duplicate artifact skipped: ${normalized.relativePath}`);
|
|
16640
|
+
continue;
|
|
16641
|
+
}
|
|
16642
|
+
seen.add(normalized.path);
|
|
16643
|
+
try {
|
|
16644
|
+
const fileContent = body.trimStart();
|
|
16645
|
+
const parentValidation = validateTaskMaterializedArtifactParents(normalized.path, normalized.baseDir);
|
|
16646
|
+
if (!parentValidation.ok) {
|
|
16647
|
+
warnings.push(parentValidation.error || `artifact parent is unsafe: ${normalized.relativePath}`);
|
|
16648
|
+
continue;
|
|
16649
|
+
}
|
|
16650
|
+
ensureDir(path.dirname(normalized.path));
|
|
16651
|
+
try {
|
|
16652
|
+
if (fs.lstatSync(normalized.path).isSymbolicLink()) {
|
|
16653
|
+
warnings.push(`artifact target is a symlink: ${normalized.relativePath}`);
|
|
16654
|
+
continue;
|
|
16655
|
+
}
|
|
16656
|
+
} catch (error) {
|
|
16657
|
+
if (!error || error.code !== 'ENOENT') {
|
|
16658
|
+
throw error;
|
|
16659
|
+
}
|
|
16660
|
+
}
|
|
16661
|
+
fs.writeFileSync(normalized.path, fileContent, { encoding: 'utf-8', mode: 0o600 });
|
|
16662
|
+
files.push({ path: normalized.path, relativePath: normalized.relativePath, bytes: Buffer.byteLength(fileContent, 'utf-8') });
|
|
16663
|
+
} catch (error) {
|
|
16664
|
+
warnings.push(`failed to write artifact ${normalized.relativePath}: ${error && error.message ? error.message : String(error)}`);
|
|
16665
|
+
}
|
|
16666
|
+
}
|
|
16667
|
+
return { files, warnings };
|
|
16668
|
+
}
|
|
16669
|
+
|
|
16670
|
+
async function runOpenAiChatTaskNode(node, context = {}) {
|
|
16671
|
+
const requestConfig = resolveTaskOpenAiChatConfig();
|
|
16672
|
+
if (requestConfig.error) {
|
|
16065
16673
|
return {
|
|
16066
16674
|
success: false,
|
|
16067
|
-
error:
|
|
16068
|
-
summary:
|
|
16675
|
+
error: requestConfig.error,
|
|
16676
|
+
summary: requestConfig.error,
|
|
16069
16677
|
output: null,
|
|
16070
|
-
logs: [{ at: toIsoTime(Date.now()), level: 'error', message:
|
|
16678
|
+
logs: [{ at: toIsoTime(Date.now()), level: 'error', message: requestConfig.error }]
|
|
16071
16679
|
};
|
|
16072
16680
|
}
|
|
16073
|
-
const
|
|
16074
|
-
const
|
|
16681
|
+
const hasApiKey = typeof requestConfig.apiKey === 'string' && requestConfig.apiKey.trim();
|
|
16682
|
+
const hasExtraHeaders = requestConfig.extraHeaders
|
|
16683
|
+
&& typeof requestConfig.extraHeaders === 'object'
|
|
16684
|
+
&& !Array.isArray(requestConfig.extraHeaders)
|
|
16685
|
+
&& Object.keys(requestConfig.extraHeaders).length > 0;
|
|
16686
|
+
if (!hasApiKey && !hasExtraHeaders) {
|
|
16687
|
+
const error = `OpenAI Chat 提供商 ${requestConfig.providerName || ''} 缺少 API key 或额外 headers`;
|
|
16688
|
+
return {
|
|
16689
|
+
success: false,
|
|
16690
|
+
error,
|
|
16691
|
+
summary: error,
|
|
16692
|
+
output: {
|
|
16693
|
+
provider: requestConfig.providerName || '',
|
|
16694
|
+
model: requestConfig.model || '',
|
|
16695
|
+
endpoint: redactTaskEndpointUrl(requestConfig.endpointUrl || ''),
|
|
16696
|
+
status: 0,
|
|
16697
|
+
text: '',
|
|
16698
|
+
response: null,
|
|
16699
|
+
durationMs: 0,
|
|
16700
|
+
materializedFiles: []
|
|
16701
|
+
},
|
|
16702
|
+
logs: [{ at: toIsoTime(Date.now()), level: 'error', message: error }]
|
|
16703
|
+
};
|
|
16704
|
+
}
|
|
16705
|
+
const allowWrite = context.allowWrite === true && node.write !== false && context.dryRun !== true;
|
|
16075
16706
|
const dependencyResults = Array.isArray(context.dependencyResults) ? context.dependencyResults : [];
|
|
16076
16707
|
const dependencyLines = dependencyResults
|
|
16077
16708
|
.map((item) => {
|
|
@@ -16091,124 +16722,95 @@ async function runCodexExecTaskNode(node, context = {}) {
|
|
|
16091
16722
|
promptParts.push('请在保持目标不变的前提下修复上一轮失败并继续完成当前节点。');
|
|
16092
16723
|
}
|
|
16093
16724
|
const finalPrompt = promptParts.filter(Boolean).join('\n\n');
|
|
16094
|
-
const
|
|
16095
|
-
|
|
16096
|
-
const
|
|
16097
|
-
const
|
|
16098
|
-
const
|
|
16099
|
-
|
|
16100
|
-
|
|
16101
|
-
|
|
16102
|
-
'
|
|
16103
|
-
|
|
16104
|
-
'
|
|
16105
|
-
'
|
|
16106
|
-
|
|
16107
|
-
|
|
16108
|
-
|
|
16109
|
-
|
|
16110
|
-
|
|
16111
|
-
|
|
16112
|
-
|
|
16113
|
-
|
|
16114
|
-
|
|
16115
|
-
|
|
16116
|
-
|
|
16117
|
-
|
|
16118
|
-
|
|
16119
|
-
|
|
16120
|
-
|
|
16121
|
-
|
|
16122
|
-
|
|
16123
|
-
|
|
16124
|
-
|
|
16125
|
-
|
|
16126
|
-
}
|
|
16127
|
-
|
|
16128
|
-
|
|
16129
|
-
|
|
16130
|
-
} catch (_) { }
|
|
16131
|
-
};
|
|
16132
|
-
const captureLines = (bucket, text, stream) => {
|
|
16133
|
-
const currentPartial = stream === 'stderr' ? stderrPartial : stdoutPartial;
|
|
16134
|
-
const merged = `${currentPartial}${String(text || '')}`;
|
|
16135
|
-
const pieces = merged.split(/\r?\n/g);
|
|
16136
|
-
const nextPartial = pieces.pop() || '';
|
|
16137
|
-
if (stream === 'stderr') {
|
|
16138
|
-
stderrPartial = nextPartial;
|
|
16139
|
-
} else {
|
|
16140
|
-
stdoutPartial = nextPartial;
|
|
16141
|
-
}
|
|
16142
|
-
for (const line of pieces) {
|
|
16143
|
-
processCapturedLine(bucket, line);
|
|
16144
|
-
}
|
|
16145
|
-
};
|
|
16146
|
-
const flushCapturedPartial = (bucket, stream) => {
|
|
16147
|
-
const partial = stream === 'stderr' ? stderrPartial : stdoutPartial;
|
|
16148
|
-
if (stream === 'stderr') {
|
|
16149
|
-
stderrPartial = '';
|
|
16150
|
-
} else {
|
|
16151
|
-
stdoutPartial = '';
|
|
16152
|
-
}
|
|
16153
|
-
processCapturedLine(bucket, partial);
|
|
16725
|
+
const rawThreadId = String(context.threadId || (context.plan && context.plan.threadId) || '').trim();
|
|
16726
|
+
const threadId = /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(rawThreadId) ? rawThreadId : '';
|
|
16727
|
+
const threadStoreDir = typeof TASK_WORKSPACE_CHAT_THREADS_DIR === 'string' ? TASK_WORKSPACE_CHAT_THREADS_DIR : '';
|
|
16728
|
+
const canLoadWorkspaceThread = typeof loadWorkspaceChatThread === 'function' && !!threadStoreDir;
|
|
16729
|
+
const thread = threadId && canLoadWorkspaceThread ? loadWorkspaceChatThread(threadStoreDir, threadId) : { messages: [] };
|
|
16730
|
+
const historyMessages = Array.isArray(thread.messages) ? thread.messages.slice(-12) : [];
|
|
16731
|
+
const workspaceContext = typeof buildWorkspaceChatContext === 'function'
|
|
16732
|
+
? buildWorkspaceChatContext(context.cwd || process.cwd(), finalPrompt, historyMessages)
|
|
16733
|
+
: '';
|
|
16734
|
+
const systemPrompt = [
|
|
16735
|
+
'你是 codexmate 任务编排中的 OpenAI Chat-compatible 执行节点。',
|
|
16736
|
+
'基于用户给出的目标、前置节点摘要、同一 thread 历史和当前工作区快照,输出可执行、可验证、事实谨慎的结果。',
|
|
16737
|
+
allowWrite
|
|
16738
|
+
? '当前任务允许写入;如果需要创建或更新文件,优先使用 fenced code block:```codexmate-file action="write" path="相对路径"。代码块内容会写入任务工作目录内对应文本文件。删除文件请单独写一行 CODEXMATE_DELETE_FILE: 相对路径。只允许安全相对路径。'
|
|
16739
|
+
: '当前任务是只读/验证节点,不要声称修改了文件。',
|
|
16740
|
+
'查询/读取文件时,先依据用户要求和工作区快照回答;如果快照不足,说明缺口,不要编造。',
|
|
16741
|
+
'回答使用中文,避免空泛总结。'
|
|
16742
|
+
].join('\n');
|
|
16743
|
+
const currentUserMessage = [
|
|
16744
|
+
finalPrompt,
|
|
16745
|
+
workspaceContext ? '\n--- codexmate workspace snapshot ---' : '',
|
|
16746
|
+
workspaceContext
|
|
16747
|
+
].filter(Boolean).join('\n');
|
|
16748
|
+
const startedAt = Date.now();
|
|
16749
|
+
const body = {
|
|
16750
|
+
model: requestConfig.model,
|
|
16751
|
+
messages: [
|
|
16752
|
+
{ role: 'system', content: systemPrompt },
|
|
16753
|
+
...historyMessages.map((message) => ({
|
|
16754
|
+
role: message.role === 'assistant' ? 'assistant' : 'user',
|
|
16755
|
+
content: String(message.content || '')
|
|
16756
|
+
})),
|
|
16757
|
+
{ role: 'user', content: currentUserMessage }
|
|
16758
|
+
],
|
|
16759
|
+
temperature: Number.isFinite(requestConfig.temperature) ? requestConfig.temperature : 0.2,
|
|
16760
|
+
stream: false
|
|
16154
16761
|
};
|
|
16155
|
-
const
|
|
16156
|
-
|
|
16157
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
16158
|
-
windowsHide: true,
|
|
16159
|
-
shell: process.platform === 'win32'
|
|
16160
|
-
});
|
|
16161
|
-
if (typeof context.registerAbort === 'function') {
|
|
16162
|
-
context.registerAbort(() => {
|
|
16163
|
-
try {
|
|
16164
|
-
child.kill('SIGTERM');
|
|
16165
|
-
} catch (_) { }
|
|
16166
|
-
});
|
|
16167
|
-
}
|
|
16168
|
-
child.stdout.on('data', (chunk) => {
|
|
16169
|
-
captureLines(stdoutLines, chunk, 'stdout');
|
|
16170
|
-
});
|
|
16171
|
-
child.stderr.on('data', (chunk) => {
|
|
16172
|
-
captureLines(stderrLines, chunk, 'stderr');
|
|
16173
|
-
});
|
|
16174
|
-
child.on('error', (error) => {
|
|
16175
|
-
resolve({ code: 1, signal: '', error: error && error.message ? error.message : String(error || 'spawn failed') });
|
|
16176
|
-
});
|
|
16177
|
-
child.on('close', (code, signal) => {
|
|
16178
|
-
flushCapturedPartial(stdoutLines, 'stdout');
|
|
16179
|
-
flushCapturedPartial(stderrLines, 'stderr');
|
|
16180
|
-
resolve({ code: typeof code === 'number' ? code : 1, signal: signal || '', error: '' });
|
|
16181
|
-
});
|
|
16762
|
+
const result = await postOpenAiChatCompletion(requestConfig, body, {
|
|
16763
|
+
registerAbort: context.registerAbort
|
|
16182
16764
|
});
|
|
16183
|
-
const
|
|
16184
|
-
|
|
16185
|
-
|
|
16186
|
-
|
|
16187
|
-
|
|
16188
|
-
|
|
16189
|
-
|
|
16190
|
-
|
|
16191
|
-
|
|
16192
|
-
|
|
16193
|
-
|
|
16194
|
-
|
|
16195
|
-
|
|
16765
|
+
const text = result.ok ? extractModelResponseText(result.payload) : '';
|
|
16766
|
+
const success = result.ok && !!text;
|
|
16767
|
+
const errorMessage = success ? '' : (result.error || 'OpenAI Chat response did not contain text');
|
|
16768
|
+
const summary = truncateTaskText(text || errorMessage, 400);
|
|
16769
|
+
const safeEndpoint = redactTaskEndpointUrl(requestConfig.endpointUrl);
|
|
16770
|
+
const materialized = success && allowWrite
|
|
16771
|
+
? materializeOpenAiChatTaskArtifacts(text, {
|
|
16772
|
+
cwd: context.cwd || process.cwd(),
|
|
16773
|
+
target: context.plan && context.plan.target ? context.plan.target : '',
|
|
16774
|
+
notes: context.plan && context.plan.notes ? context.plan.notes : '',
|
|
16775
|
+
prompt: node.prompt || ''
|
|
16776
|
+
})
|
|
16777
|
+
: { files: [], warnings: [] };
|
|
16778
|
+
const workspaceOperations = success && typeof applyWorkspaceFileOperations === 'function'
|
|
16779
|
+
? applyWorkspaceFileOperations(text, context.cwd || process.cwd(), { allowWrite })
|
|
16780
|
+
: { files: [], warnings: [] };
|
|
16781
|
+
const threadAppend = threadId && typeof appendWorkspaceChatThread === 'function' && !!threadStoreDir
|
|
16782
|
+
? appendWorkspaceChatThread(threadStoreDir, threadId, [
|
|
16783
|
+
{ role: 'user', content: finalPrompt },
|
|
16784
|
+
{ role: 'assistant', content: text || errorMessage }
|
|
16785
|
+
], { cwd: context.cwd || process.cwd() })
|
|
16786
|
+
: { ok: false, error: 'thread id is empty' };
|
|
16196
16787
|
return {
|
|
16197
16788
|
success,
|
|
16198
16789
|
error: errorMessage,
|
|
16199
16790
|
summary,
|
|
16200
16791
|
output: {
|
|
16201
|
-
|
|
16202
|
-
|
|
16203
|
-
|
|
16204
|
-
|
|
16205
|
-
|
|
16206
|
-
|
|
16207
|
-
|
|
16792
|
+
provider: requestConfig.providerName,
|
|
16793
|
+
model: requestConfig.model,
|
|
16794
|
+
endpoint: safeEndpoint,
|
|
16795
|
+
status: result.status || 0,
|
|
16796
|
+
text,
|
|
16797
|
+
response: result.payload || null,
|
|
16798
|
+
durationMs: Date.now() - startedAt,
|
|
16799
|
+
messageCount: body.messages.length,
|
|
16800
|
+
threadId,
|
|
16801
|
+
threadStore: threadAppend && threadAppend.file ? threadAppend.file : '',
|
|
16802
|
+
materializedFiles: materialized.files,
|
|
16803
|
+
workspaceFiles: workspaceOperations.files
|
|
16208
16804
|
},
|
|
16209
16805
|
logs: [
|
|
16210
|
-
|
|
16211
|
-
...
|
|
16806
|
+
{ at: toIsoTime(Date.now()), level: 'info', message: `OpenAI Chat request provider=${requestConfig.providerName} model=${requestConfig.model} status=${result.status || 0}` },
|
|
16807
|
+
...(threadId ? [{ at: toIsoTime(Date.now()), level: 'info', message: `workspace chat thread=${threadId} messages=${body.messages.length}` }] : []),
|
|
16808
|
+
...(success ? [{ at: toIsoTime(Date.now()), level: 'info', message: truncateTaskText(text, 1200) }] : [{ at: toIsoTime(Date.now()), level: 'error', message: errorMessage }]),
|
|
16809
|
+
...materialized.files.map((file) => ({ at: toIsoTime(Date.now()), level: 'info', message: `materialized artifact ${file.relativePath} (${file.bytes} bytes)` })),
|
|
16810
|
+
...workspaceOperations.files.map((file) => ({ at: toIsoTime(Date.now()), level: 'info', message: `workspace ${file.operation} ${file.relativePath} (${file.bytes} bytes)` })),
|
|
16811
|
+
...materialized.warnings.map((warning) => ({ at: toIsoTime(Date.now()), level: 'warn', message: warning })),
|
|
16812
|
+
...workspaceOperations.warnings.map((warning) => ({ at: toIsoTime(Date.now()), level: 'warn', message: warning })),
|
|
16813
|
+
...(threadAppend && threadAppend.error ? [{ at: toIsoTime(Date.now()), level: 'warn', message: threadAppend.error }] : [])
|
|
16212
16814
|
]
|
|
16213
16815
|
};
|
|
16214
16816
|
}
|
|
@@ -16246,7 +16848,7 @@ async function executeTaskNodeAdapter(node, context = {}) {
|
|
|
16246
16848
|
: []
|
|
16247
16849
|
};
|
|
16248
16850
|
}
|
|
16249
|
-
return
|
|
16851
|
+
return runOpenAiChatTaskNode(node, context);
|
|
16250
16852
|
}
|
|
16251
16853
|
|
|
16252
16854
|
async function runTaskPlanInternal(plan, options = {}) {
|
|
@@ -16260,13 +16862,18 @@ async function runTaskPlanInternal(plan, options = {}) {
|
|
|
16260
16862
|
}
|
|
16261
16863
|
const taskId = typeof options.taskId === 'string' && options.taskId.trim() ? options.taskId.trim() : (plan.id || createTaskId());
|
|
16262
16864
|
const runId = typeof options.runId === 'string' && options.runId.trim() ? options.runId.trim() : createTaskRunId();
|
|
16865
|
+
const threadId = normalizeTaskThreadId(options.threadId || plan.threadId) || createTaskThreadId();
|
|
16866
|
+
plan.threadId = threadId;
|
|
16867
|
+
const cwd = typeof plan.cwd === 'string' && plan.cwd.trim() ? plan.cwd.trim() : process.cwd();
|
|
16263
16868
|
const controller = new AbortController();
|
|
16264
16869
|
const baseDetail = {
|
|
16265
16870
|
runId,
|
|
16266
16871
|
taskId,
|
|
16872
|
+
threadId,
|
|
16267
16873
|
workerPid: process.pid,
|
|
16268
16874
|
title: plan.title || '',
|
|
16269
16875
|
target: plan.target || '',
|
|
16876
|
+
cwd,
|
|
16270
16877
|
engine: normalizeTaskEngine(plan.engine),
|
|
16271
16878
|
allowWrite: plan.allowWrite === true,
|
|
16272
16879
|
dryRun: plan.dryRun === true,
|
|
@@ -16302,6 +16909,8 @@ async function runTaskPlanInternal(plan, options = {}) {
|
|
|
16302
16909
|
const queued = upsertTaskQueueItem({
|
|
16303
16910
|
...options.queueItem,
|
|
16304
16911
|
taskId,
|
|
16912
|
+
threadId,
|
|
16913
|
+
cwd,
|
|
16305
16914
|
status: 'running',
|
|
16306
16915
|
runStatus: 'running',
|
|
16307
16916
|
lastRunId: runId,
|
|
@@ -16320,9 +16929,10 @@ async function runTaskPlanInternal(plan, options = {}) {
|
|
|
16320
16929
|
plan,
|
|
16321
16930
|
taskId,
|
|
16322
16931
|
runId,
|
|
16932
|
+
threadId,
|
|
16323
16933
|
allowWrite: plan.allowWrite === true,
|
|
16324
16934
|
dryRun: plan.dryRun === true,
|
|
16325
|
-
cwd
|
|
16935
|
+
cwd
|
|
16326
16936
|
}),
|
|
16327
16937
|
onUpdate: async (snapshot) => {
|
|
16328
16938
|
const nextDetail = {
|
|
@@ -16336,6 +16946,8 @@ async function runTaskPlanInternal(plan, options = {}) {
|
|
|
16336
16946
|
const queued = upsertTaskQueueItem({
|
|
16337
16947
|
...options.queueItem,
|
|
16338
16948
|
taskId,
|
|
16949
|
+
threadId,
|
|
16950
|
+
cwd,
|
|
16339
16951
|
status: snapshot.status === 'success'
|
|
16340
16952
|
? 'completed'
|
|
16341
16953
|
: (snapshot.status === 'failed' ? 'failed' : (snapshot.status === 'cancelled' ? 'cancelled' : 'running')),
|
|
@@ -16365,6 +16977,8 @@ async function runTaskPlanInternal(plan, options = {}) {
|
|
|
16365
16977
|
const queued = upsertTaskQueueItem({
|
|
16366
16978
|
...options.queueItem,
|
|
16367
16979
|
taskId,
|
|
16980
|
+
threadId,
|
|
16981
|
+
cwd,
|
|
16368
16982
|
status: run.status === 'success'
|
|
16369
16983
|
? 'completed'
|
|
16370
16984
|
: (run.status === 'cancelled' ? 'cancelled' : 'failed'),
|
|
@@ -16395,8 +17009,10 @@ function addTaskToQueue(params = {}) {
|
|
|
16395
17009
|
const taskId = typeof params.taskId === 'string' && params.taskId.trim() ? params.taskId.trim() : createTaskId();
|
|
16396
17010
|
const item = upsertTaskQueueItem({
|
|
16397
17011
|
taskId,
|
|
17012
|
+
threadId: plan.threadId || createTaskThreadId(),
|
|
16398
17013
|
title: plan.title,
|
|
16399
17014
|
target: plan.target,
|
|
17015
|
+
cwd: plan.cwd || process.cwd(),
|
|
16400
17016
|
status: 'queued',
|
|
16401
17017
|
createdAt: toIsoTime(Date.now()),
|
|
16402
17018
|
updatedAt: toIsoTime(Date.now()),
|