codexmate 0.0.55 → 0.0.57
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 -0
- package/README.vi.md +2 -0
- package/README.zh.md +2 -0
- package/cli/local-bridge.js +221 -22
- package/cli.js +797 -134
- 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 +57 -129
- package/web-ui/modules/app.computed.main-tabs.mjs +304 -7
- package/web-ui/modules/app.computed.session.mjs +210 -0
- package/web-ui/modules/app.methods.agents.mjs +3 -0
- package/web-ui/modules/app.methods.claude-config.mjs +178 -22
- package/web-ui/modules/app.methods.codex-config.mjs +294 -65
- package/web-ui/modules/app.methods.navigation.mjs +71 -84
- package/web-ui/modules/app.methods.openclaw-editing.mjs +3 -6
- package/web-ui/modules/app.methods.providers.mjs +15 -1
- package/web-ui/modules/app.methods.runtime.mjs +7 -2
- package/web-ui/modules/app.methods.session-actions.mjs +25 -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 +35 -17
- 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 +415 -68
- package/web-ui/modules/config-template-confirm-pref.mjs +2 -3
- package/web-ui/modules/i18n/locales/en.mjs +187 -28
- package/web-ui/modules/i18n/locales/ja.mjs +184 -25
- package/web-ui/modules/i18n/locales/vi.mjs +186 -33
- package/web-ui/modules/i18n/locales/zh-tw.mjs +187 -28
- package/web-ui/modules/i18n/locales/zh.mjs +189 -30
- package/web-ui/modules/i18n.mjs +5 -12
- package/web-ui/modules/provider-default-names.mjs +25 -0
- package/web-ui/modules/sessions-filters-url.mjs +1 -2
- package/web-ui/partials/index/layout-header.html +37 -14
- package/web-ui/partials/index/modal-health-check.html +69 -5
- package/web-ui/partials/index/panel-config-codex.html +2 -2
- package/web-ui/partials/index/panel-orchestration.html +489 -282
- package/web-ui/partials/index/panel-sessions.html +97 -17
- package/web-ui/res/web-ui-render.precompiled.js +1423 -732
- package/web-ui/session-helpers.mjs +4 -1
- package/web-ui/styles/layout-shell.css +157 -1
- package/web-ui/styles/navigation-panels.css +11 -0
- package/web-ui/styles/responsive.css +98 -0
- package/web-ui/styles/sessions-preview.css +212 -2
- package/web-ui/styles/sessions-toolbar-trash.css +61 -18
- package/web-ui/styles/skills-list.css +122 -0
- package/web-ui/styles/task-orchestration.css +2161 -4
- package/web-ui/styles/titles-cards.css +52 -0
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,
|
|
@@ -231,6 +238,15 @@ const PROVIDER_CACHE_FILE_GROUPS = Object.freeze({
|
|
|
231
238
|
'opencode-provider-current-models.json'
|
|
232
239
|
]
|
|
233
240
|
});
|
|
241
|
+
const PROVIDER_CACHE_PROVIDER_FILES = Object.freeze([
|
|
242
|
+
'codex-providers.json',
|
|
243
|
+
'claude-providers.json',
|
|
244
|
+
'opencode-providers.json'
|
|
245
|
+
]);
|
|
246
|
+
const PROVIDER_CACHE_CURRENT_MODEL_FILES = Object.freeze([
|
|
247
|
+
'codex-provider-current-models.json',
|
|
248
|
+
'opencode-provider-current-models.json'
|
|
249
|
+
]);
|
|
234
250
|
const PROVIDER_CACHE_MAX_FILE_BYTES = 256 * 1024;
|
|
235
251
|
const CODEXMATE_PREFERENCES_FILE = path.join(CODEXMATE_DIR, 'preferences.json');
|
|
236
252
|
const CODEXMATE_OPENCODE_DIR = path.join(CODEXMATE_DIR, 'opencode');
|
|
@@ -249,6 +265,9 @@ const TASK_RUNS_FILE = path.join(CONFIG_DIR, 'codexmate-task-runs.jsonl');
|
|
|
249
265
|
const TASK_RUN_DETAILS_DIR = path.join(CONFIG_DIR, 'codexmate-task-runs');
|
|
250
266
|
const TASK_QUEUE_WORKER_FILE = path.join(CONFIG_DIR, 'codexmate-task-queue-worker.json');
|
|
251
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;
|
|
252
271
|
const AUTOMATION_CONFIG_FILE = path.join(CONFIG_DIR, 'codexmate-automation.json');
|
|
253
272
|
const DEFAULT_CLAUDE_MODEL = 'glm-4.7';
|
|
254
273
|
const DEFAULT_MODEL_CONTEXT_WINDOW = 190000;
|
|
@@ -981,6 +1000,65 @@ function normalizePromptsSubTabPreference(value) {
|
|
|
981
1000
|
return normalized === 'claude-project' ? 'claude-project' : 'codex';
|
|
982
1001
|
}
|
|
983
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
|
+
|
|
984
1062
|
function normalizeWebUiPreferences(value = {}) {
|
|
985
1063
|
const source = isPlainObject(value) ? value : {};
|
|
986
1064
|
const navigation = isPlainObject(source.navigation) ? source.navigation : {};
|
|
@@ -993,6 +1071,18 @@ function normalizeWebUiPreferences(value = {}) {
|
|
|
993
1071
|
sessionsUsageTimeRange: normalizeUsageTimeRangePreference(source.sessionsUsageTimeRange),
|
|
994
1072
|
promptsSubTab: normalizePromptsSubTabPreference(source.promptsSubTab),
|
|
995
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),
|
|
996
1086
|
navigation: {
|
|
997
1087
|
mainTab: normalizeMainTabPreference(navigation.mainTab),
|
|
998
1088
|
configMode: normalizeConfigModePreference(navigation.configMode),
|
|
@@ -1021,6 +1111,9 @@ function setWebUiPreferences(params = {}) {
|
|
|
1021
1111
|
}
|
|
1022
1112
|
});
|
|
1023
1113
|
preferences.webUi = next;
|
|
1114
|
+
if (isPlainObject(incoming.toolConfigPermissions)) {
|
|
1115
|
+
preferences.toolConfigPermissions = normalizeToolConfigPermissions(incoming.toolConfigPermissions);
|
|
1116
|
+
}
|
|
1024
1117
|
writeCodexmatePreferences(preferences);
|
|
1025
1118
|
return { success: true, preferences: next };
|
|
1026
1119
|
}
|
|
@@ -1083,7 +1176,8 @@ function getApiToolConfigWriteTarget(action) {
|
|
|
1083
1176
|
'restore-claude-dir',
|
|
1084
1177
|
'claude-local-bridge-toggle',
|
|
1085
1178
|
'claude-local-bridge-set-excluded',
|
|
1086
|
-
'claude-local-bridge-sync-providers'
|
|
1179
|
+
'claude-local-bridge-sync-providers',
|
|
1180
|
+
'delete-provider-cache-record'
|
|
1087
1181
|
]);
|
|
1088
1182
|
const opencodeWriteActions = new Set([
|
|
1089
1183
|
'apply-opencode-config',
|
|
@@ -2877,6 +2971,99 @@ function normalizeProviderCacheProviderMap(rawProviders) {
|
|
|
2877
2971
|
return providers;
|
|
2878
2972
|
}
|
|
2879
2973
|
|
|
2974
|
+
function removeProviderFromProviderCacheContainer(rawProviders, providerName) {
|
|
2975
|
+
const targetName = typeof providerName === 'string' ? providerName.trim() : '';
|
|
2976
|
+
if (!targetName) return { value: rawProviders, changed: false };
|
|
2977
|
+
|
|
2978
|
+
const matchesProviderName = (name) => String(name || '').trim() === targetName;
|
|
2979
|
+
if (Array.isArray(rawProviders)) {
|
|
2980
|
+
const filtered = rawProviders.filter((item) => {
|
|
2981
|
+
if (!isPlainObject(item)) return true;
|
|
2982
|
+
const itemName = pickProviderCacheString(item, ['name', 'id', 'provider']);
|
|
2983
|
+
return !matchesProviderName(itemName);
|
|
2984
|
+
});
|
|
2985
|
+
return { value: filtered, changed: filtered.length !== rawProviders.length };
|
|
2986
|
+
}
|
|
2987
|
+
if (!isPlainObject(rawProviders)) return { value: rawProviders, changed: false };
|
|
2988
|
+
|
|
2989
|
+
const next = { ...rawProviders };
|
|
2990
|
+
let changed = false;
|
|
2991
|
+
for (const [name, entry] of Object.entries(rawProviders)) {
|
|
2992
|
+
const entryName = isPlainObject(entry)
|
|
2993
|
+
? pickProviderCacheString(entry, ['name', 'id', 'provider'])
|
|
2994
|
+
: '';
|
|
2995
|
+
if (matchesProviderName(name) || matchesProviderName(entryName)) {
|
|
2996
|
+
delete next[name];
|
|
2997
|
+
changed = true;
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
return { value: next, changed };
|
|
3001
|
+
}
|
|
3002
|
+
|
|
3003
|
+
function resolveProviderCacheDeleteGroups(groups) {
|
|
3004
|
+
const requested = Array.isArray(groups) ? groups : (groups ? [groups] : []);
|
|
3005
|
+
const normalized = requested
|
|
3006
|
+
.map((item) => String(item || '').trim().toLowerCase())
|
|
3007
|
+
.filter((item) => Object.prototype.hasOwnProperty.call(PROVIDER_CACHE_FILE_GROUPS, item));
|
|
3008
|
+
return normalized.length ? Array.from(new Set(normalized)) : Object.keys(PROVIDER_CACHE_FILE_GROUPS);
|
|
3009
|
+
}
|
|
3010
|
+
|
|
3011
|
+
function removeProviderFromProviderCacheRecords(providerName, options = {}) {
|
|
3012
|
+
const targetName = typeof providerName === 'string' ? providerName.trim() : '';
|
|
3013
|
+
const summary = { removed: false, providerFiles: [], currentModelFiles: [] };
|
|
3014
|
+
if (!targetName) return summary;
|
|
3015
|
+
|
|
3016
|
+
const groups = resolveProviderCacheDeleteGroups(options.groups || options.group);
|
|
3017
|
+
const providerFiles = groups
|
|
3018
|
+
.flatMap((group) => PROVIDER_CACHE_FILE_GROUPS[group] || [])
|
|
3019
|
+
.filter((fileName) => PROVIDER_CACHE_PROVIDER_FILES.includes(fileName));
|
|
3020
|
+
const currentModelFiles = groups
|
|
3021
|
+
.flatMap((group) => PROVIDER_CACHE_FILE_GROUPS[group] || [])
|
|
3022
|
+
.filter((fileName) => PROVIDER_CACHE_CURRENT_MODEL_FILES.includes(fileName));
|
|
3023
|
+
|
|
3024
|
+
for (const fileName of Array.from(new Set(providerFiles))) {
|
|
3025
|
+
const existing = readProviderCacheJsonObject(fileName);
|
|
3026
|
+
if (!isPlainObject(existing) || !Object.prototype.hasOwnProperty.call(existing, 'providers')) continue;
|
|
3027
|
+
const removed = removeProviderFromProviderCacheContainer(existing.providers, targetName);
|
|
3028
|
+
if (!removed.changed) continue;
|
|
3029
|
+
writeProviderCacheJsonObject(fileName, {
|
|
3030
|
+
...existing,
|
|
3031
|
+
generatedAt: new Date().toISOString(),
|
|
3032
|
+
providers: removed.value
|
|
3033
|
+
});
|
|
3034
|
+
summary.removed = true;
|
|
3035
|
+
summary.providerFiles.push(fileName);
|
|
3036
|
+
}
|
|
3037
|
+
|
|
3038
|
+
for (const fileName of Array.from(new Set(currentModelFiles))) {
|
|
3039
|
+
const existing = readProviderCacheJsonObject(fileName);
|
|
3040
|
+
if (!isPlainObject(existing) || !Object.prototype.hasOwnProperty.call(existing, targetName)) continue;
|
|
3041
|
+
const next = { ...existing };
|
|
3042
|
+
delete next[targetName];
|
|
3043
|
+
writeProviderCacheJsonObject(fileName, next);
|
|
3044
|
+
summary.removed = true;
|
|
3045
|
+
summary.currentModelFiles.push(fileName);
|
|
3046
|
+
}
|
|
3047
|
+
return summary;
|
|
3048
|
+
}
|
|
3049
|
+
|
|
3050
|
+
function deleteProviderCacheRecord(params = {}) {
|
|
3051
|
+
const name = typeof params.name === 'string' ? params.name.trim() : '';
|
|
3052
|
+
if (!name) return { error: '名称不能为空' };
|
|
3053
|
+
const group = typeof params.group === 'string' ? params.group.trim().toLowerCase() : '';
|
|
3054
|
+
const groups = resolveProviderCacheDeleteGroups(group || params.groups);
|
|
3055
|
+
const summary = removeProviderFromProviderCacheRecords(name, { groups });
|
|
3056
|
+
return {
|
|
3057
|
+
success: true,
|
|
3058
|
+
name,
|
|
3059
|
+
groups,
|
|
3060
|
+
removed: summary.removed,
|
|
3061
|
+
providerFiles: summary.providerFiles,
|
|
3062
|
+
currentModelFiles: summary.currentModelFiles,
|
|
3063
|
+
records: readProviderCacheRecords()
|
|
3064
|
+
};
|
|
3065
|
+
}
|
|
3066
|
+
|
|
2880
3067
|
function readClaudeProviderCacheProvider(name) {
|
|
2881
3068
|
const targetName = typeof name === 'string' ? name.trim() : '';
|
|
2882
3069
|
if (!targetName) return null;
|
|
@@ -2953,10 +3140,11 @@ function buildProviderCacheSyncProviders() {
|
|
|
2953
3140
|
function mergeProviderCacheFile(fileName, nextProviders, buildEntry) {
|
|
2954
3141
|
const existing = readProviderCacheJsonObject(fileName);
|
|
2955
3142
|
const existingProviders = normalizeProviderCacheProviderMap(existing.providers);
|
|
2956
|
-
const providers = {
|
|
3143
|
+
const providers = {};
|
|
2957
3144
|
for (const provider of nextProviders) {
|
|
2958
3145
|
const previous = isPlainObject(providers[provider.name]) ? providers[provider.name] : {};
|
|
2959
|
-
|
|
3146
|
+
const cachedPrevious = isPlainObject(existingProviders[provider.name]) ? existingProviders[provider.name] : previous;
|
|
3147
|
+
providers[provider.name] = { ...cachedPrevious, ...buildEntry(provider) };
|
|
2960
3148
|
}
|
|
2961
3149
|
const next = {
|
|
2962
3150
|
...existing,
|
|
@@ -2970,9 +3158,13 @@ function mergeProviderCacheFile(fileName, nextProviders, buildEntry) {
|
|
|
2970
3158
|
|
|
2971
3159
|
function mergeProviderCacheCurrentModelsFile(fileName, nextProviders) {
|
|
2972
3160
|
const existing = readProviderCacheJsonObject(fileName);
|
|
2973
|
-
const next = {
|
|
3161
|
+
const next = {};
|
|
2974
3162
|
for (const provider of nextProviders) {
|
|
2975
|
-
if (provider.model)
|
|
3163
|
+
if (provider.model) {
|
|
3164
|
+
next[provider.name] = provider.model;
|
|
3165
|
+
} else if (typeof existing[provider.name] === 'string' && existing[provider.name].trim()) {
|
|
3166
|
+
next[provider.name] = existing[provider.name];
|
|
3167
|
+
}
|
|
2976
3168
|
}
|
|
2977
3169
|
const displayPath = writeProviderCacheJsonObject(fileName, next);
|
|
2978
3170
|
return { path: displayPath, modelCount: Object.keys(next).length };
|
|
@@ -3213,6 +3405,7 @@ function performProviderDeletion(name, options = {}) {
|
|
|
3213
3405
|
|
|
3214
3406
|
writeCurrentModels(currentModels);
|
|
3215
3407
|
writeConfig(updatedContent.trimEnd() + lineEnding);
|
|
3408
|
+
removeProviderFromProviderCacheRecords(name);
|
|
3216
3409
|
|
|
3217
3410
|
return result;
|
|
3218
3411
|
}
|
|
@@ -12387,6 +12580,9 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
12387
12580
|
case 'sync-provider-cache-records':
|
|
12388
12581
|
result = syncProviderCacheRecords();
|
|
12389
12582
|
break;
|
|
12583
|
+
case 'delete-provider-cache-record':
|
|
12584
|
+
result = deleteProviderCacheRecord(params || {});
|
|
12585
|
+
break;
|
|
12390
12586
|
case 'delete-provider':
|
|
12391
12587
|
result = deleteProviderFromConfig(params || {});
|
|
12392
12588
|
break;
|
|
@@ -12903,6 +13099,8 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
12903
13099
|
detached: true,
|
|
12904
13100
|
taskId,
|
|
12905
13101
|
runId,
|
|
13102
|
+
threadId: plan.threadId || '',
|
|
13103
|
+
cwd: plan.cwd || '',
|
|
12906
13104
|
warnings: validation.warnings || []
|
|
12907
13105
|
};
|
|
12908
13106
|
} else {
|
|
@@ -13747,7 +13945,11 @@ function printTaskHelp() {
|
|
|
13747
13945
|
console.log(' --allow-write 允许写入工作区');
|
|
13748
13946
|
console.log(' --dry-run 仅计划/预演,不执行写入');
|
|
13749
13947
|
console.log(' --plan-only 仅输出计划,不执行');
|
|
13750
|
-
console.log(' --engine <
|
|
13948
|
+
console.log(' --engine <openai-chat|workflow> 选择编排引擎');
|
|
13949
|
+
console.log(' --cwd <路径> 指定任务工作区路径');
|
|
13950
|
+
console.log(' --thread-id <ID> 指定任务线程 ID');
|
|
13951
|
+
console.log(' --conversation-id <ID> 指定任务线程 ID(兼容别名)');
|
|
13952
|
+
console.log(' --session-id <ID> 指定任务线程 ID(兼容别名)');
|
|
13751
13953
|
console.log(' --concurrency <N> 并发度');
|
|
13752
13954
|
console.log(' --auto-fix-rounds <N> 自动修复回合数');
|
|
13753
13955
|
console.log(' --limit <N> runs/queue list 数量');
|
|
@@ -13770,7 +13972,7 @@ function parseTaskCliOptions(args = []) {
|
|
|
13770
13972
|
allowWrite: false,
|
|
13771
13973
|
dryRun: false,
|
|
13772
13974
|
planOnly: false,
|
|
13773
|
-
engine: '
|
|
13975
|
+
engine: 'openai-chat',
|
|
13774
13976
|
concurrency: 2,
|
|
13775
13977
|
autoFixRounds: 1,
|
|
13776
13978
|
limit: 20,
|
|
@@ -13917,6 +14119,38 @@ function parseTaskCliOptions(args = []) {
|
|
|
13917
14119
|
options.explicit.autoFixRounds = true;
|
|
13918
14120
|
continue;
|
|
13919
14121
|
}
|
|
14122
|
+
if (arg === '--cwd') {
|
|
14123
|
+
options.cwd = String(args[i + 1] || '').trim();
|
|
14124
|
+
options.explicit.cwd = true;
|
|
14125
|
+
i += 1;
|
|
14126
|
+
continue;
|
|
14127
|
+
}
|
|
14128
|
+
if (arg.startsWith('--cwd=')) {
|
|
14129
|
+
options.cwd = arg.slice('--cwd='.length).trim();
|
|
14130
|
+
options.explicit.cwd = true;
|
|
14131
|
+
continue;
|
|
14132
|
+
}
|
|
14133
|
+
if (arg === '--thread-id' || arg === '--conversation-id' || arg === '--session-id') {
|
|
14134
|
+
options.threadId = String(args[i + 1] || '').trim();
|
|
14135
|
+
options.explicit.threadId = true;
|
|
14136
|
+
i += 1;
|
|
14137
|
+
continue;
|
|
14138
|
+
}
|
|
14139
|
+
if (arg.startsWith('--thread-id=')) {
|
|
14140
|
+
options.threadId = arg.slice('--thread-id='.length).trim();
|
|
14141
|
+
options.explicit.threadId = true;
|
|
14142
|
+
continue;
|
|
14143
|
+
}
|
|
14144
|
+
if (arg.startsWith('--conversation-id=')) {
|
|
14145
|
+
options.threadId = arg.slice('--conversation-id='.length).trim();
|
|
14146
|
+
options.explicit.threadId = true;
|
|
14147
|
+
continue;
|
|
14148
|
+
}
|
|
14149
|
+
if (arg.startsWith('--session-id=')) {
|
|
14150
|
+
options.threadId = arg.slice('--session-id='.length).trim();
|
|
14151
|
+
options.explicit.threadId = true;
|
|
14152
|
+
continue;
|
|
14153
|
+
}
|
|
13920
14154
|
if (arg === '--limit') {
|
|
13921
14155
|
const value = parseInt(args[i + 1], 10);
|
|
13922
14156
|
if (Number.isFinite(value)) options.limit = value;
|
|
@@ -13974,9 +14208,11 @@ function buildTaskCliPayload(options = {}, rest = []) {
|
|
|
13974
14208
|
if (explicit.followUps && Array.isArray(options.followUps)) payload.followUps = options.followUps.slice();
|
|
13975
14209
|
if (explicit.allowWrite) payload.allowWrite = options.allowWrite === true;
|
|
13976
14210
|
if (explicit.dryRun) payload.dryRun = options.dryRun === true;
|
|
13977
|
-
if (explicit.engine) payload.engine = options.engine || '
|
|
14211
|
+
if (explicit.engine) payload.engine = options.engine || 'openai-chat';
|
|
13978
14212
|
if (explicit.concurrency) payload.concurrency = options.concurrency;
|
|
13979
14213
|
if (explicit.autoFixRounds) payload.autoFixRounds = options.autoFixRounds;
|
|
14214
|
+
if (explicit.cwd && options.cwd) payload.cwd = options.cwd;
|
|
14215
|
+
if (explicit.threadId && options.threadId) payload.threadId = options.threadId;
|
|
13980
14216
|
if (explicit.taskId && options.taskId) payload.taskId = options.taskId;
|
|
13981
14217
|
if (explicit.runId && options.runId) payload.runId = options.runId;
|
|
13982
14218
|
if (!payload.target && Array.isArray(rest) && rest.length > 0) {
|
|
@@ -13990,7 +14226,7 @@ function buildTaskCliPayload(options = {}, rest = []) {
|
|
|
13990
14226
|
|
|
13991
14227
|
function printTaskPlanSummary(plan, warnings = []) {
|
|
13992
14228
|
console.log(`\n任务计划: ${plan.title || '(untitled)'}`);
|
|
13993
|
-
console.log(` engine: ${plan.engine || '
|
|
14229
|
+
console.log(` engine: ${plan.engine || 'openai-chat'}`);
|
|
13994
14230
|
console.log(` allowWrite: ${plan.allowWrite === true ? 'yes' : 'no'}`);
|
|
13995
14231
|
console.log(` dryRun: ${plan.dryRun === true ? 'yes' : 'no'}`);
|
|
13996
14232
|
console.log(` concurrency: ${plan.concurrency || 1}`);
|
|
@@ -15444,6 +15680,10 @@ function createTaskRunId() {
|
|
|
15444
15680
|
return `tr-${Date.now()}-${crypto.randomBytes(3).toString('hex')}`;
|
|
15445
15681
|
}
|
|
15446
15682
|
|
|
15683
|
+
function createTaskThreadId() {
|
|
15684
|
+
return `tt-${Date.now()}-${crypto.randomBytes(3).toString('hex')}`;
|
|
15685
|
+
}
|
|
15686
|
+
|
|
15447
15687
|
function validateTaskRunId(value) {
|
|
15448
15688
|
const runId = typeof value === 'string' ? value.trim() : '';
|
|
15449
15689
|
if (!runId) {
|
|
@@ -15455,9 +15695,19 @@ function validateTaskRunId(value) {
|
|
|
15455
15695
|
return { ok: true, error: '', runId };
|
|
15456
15696
|
}
|
|
15457
15697
|
|
|
15698
|
+
function normalizeTaskThreadId(value) {
|
|
15699
|
+
const threadId = typeof value === 'string' ? value.trim() : '';
|
|
15700
|
+
if (!threadId) {
|
|
15701
|
+
return '';
|
|
15702
|
+
}
|
|
15703
|
+
return /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(threadId) ? threadId : '';
|
|
15704
|
+
}
|
|
15705
|
+
|
|
15458
15706
|
function normalizeTaskEngine(value) {
|
|
15459
|
-
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
15460
|
-
|
|
15707
|
+
const normalized = typeof value === 'string' ? value.trim().toLowerCase().replace(/[\s_/]+/g, '-') : '';
|
|
15708
|
+
if (normalized === 'workflow') return 'workflow';
|
|
15709
|
+
// Legacy task plans used `codex`; new task execution uses the configured OpenAI Chat-compatible provider.
|
|
15710
|
+
return 'openai-chat';
|
|
15461
15711
|
}
|
|
15462
15712
|
|
|
15463
15713
|
function normalizeTaskFollowUps(input = []) {
|
|
@@ -15498,6 +15748,7 @@ function normalizeTaskPlanRequest(params = {}) {
|
|
|
15498
15748
|
: (typeof source.followUp === 'string' && source.followUp.trim() ? [source.followUp.trim()] : []);
|
|
15499
15749
|
return {
|
|
15500
15750
|
id: typeof source.id === 'string' ? source.id.trim() : '',
|
|
15751
|
+
threadId: normalizeTaskThreadId(source.threadId || source.conversationId || source.sessionId),
|
|
15501
15752
|
title: typeof source.title === 'string' ? source.title.trim() : '',
|
|
15502
15753
|
target: typeof source.target === 'string' ? source.target.trim() : '',
|
|
15503
15754
|
notes: typeof source.notes === 'string' ? source.notes.trim() : '',
|
|
@@ -15507,6 +15758,7 @@ function normalizeTaskPlanRequest(params = {}) {
|
|
|
15507
15758
|
dryRun: source.dryRun === true,
|
|
15508
15759
|
concurrency: Number.isFinite(source.concurrency) ? source.concurrency : parseInt(source.concurrency, 10),
|
|
15509
15760
|
autoFixRounds: Number.isFinite(source.autoFixRounds) ? source.autoFixRounds : parseInt(source.autoFixRounds, 10),
|
|
15761
|
+
previewOnly: source.previewOnly === true,
|
|
15510
15762
|
workflowIds: rawWorkflowIds,
|
|
15511
15763
|
followUps: normalizeTaskFollowUps(rawFollowUps)
|
|
15512
15764
|
};
|
|
@@ -15515,12 +15767,18 @@ function normalizeTaskPlanRequest(params = {}) {
|
|
|
15515
15767
|
function coerceTaskPlanPayload(params = {}) {
|
|
15516
15768
|
if (params && params.plan && typeof params.plan === 'object' && !Array.isArray(params.plan)) {
|
|
15517
15769
|
const plan = cloneJson(params.plan, {});
|
|
15518
|
-
const overrideKeys = ['id', 'title', 'target', 'notes', 'cwd', 'engine', 'allowWrite', 'dryRun', 'concurrency', 'autoFixRounds', 'workflowIds', 'followUps'];
|
|
15770
|
+
const overrideKeys = ['id', 'threadId', 'conversationId', 'sessionId', 'title', 'target', 'notes', 'cwd', 'engine', 'allowWrite', 'dryRun', 'concurrency', 'autoFixRounds', 'previewOnly', 'workflowIds', 'followUps'];
|
|
15519
15771
|
for (const key of overrideKeys) {
|
|
15520
15772
|
if (Object.prototype.hasOwnProperty.call(params, key) && params[key] !== undefined) {
|
|
15521
|
-
|
|
15773
|
+
if (key === 'conversationId' || key === 'sessionId') {
|
|
15774
|
+
plan.threadId = cloneJson(params[key], params[key]);
|
|
15775
|
+
} else {
|
|
15776
|
+
plan[key] = cloneJson(params[key], params[key]);
|
|
15777
|
+
}
|
|
15522
15778
|
}
|
|
15523
15779
|
}
|
|
15780
|
+
plan.cwd = typeof plan.cwd === 'string' && plan.cwd.trim() ? plan.cwd.trim() : process.cwd();
|
|
15781
|
+
plan.threadId = normalizeTaskThreadId(plan.threadId) || createTaskThreadId();
|
|
15524
15782
|
plan.engine = normalizeTaskEngine(plan.engine);
|
|
15525
15783
|
plan.workflowIds = normalizeTaskFollowUps(plan.workflowIds || []).map((id) => normalizeWorkflowId(id)).filter(Boolean);
|
|
15526
15784
|
plan.followUps = normalizeTaskFollowUps(plan.followUps || []);
|
|
@@ -15535,6 +15793,7 @@ function coerceTaskPlanPayload(params = {}) {
|
|
|
15535
15793
|
});
|
|
15536
15794
|
return {
|
|
15537
15795
|
...plan,
|
|
15796
|
+
threadId: request.threadId || createTaskThreadId(),
|
|
15538
15797
|
engine: normalizeTaskEngine(request.engine || plan.engine)
|
|
15539
15798
|
};
|
|
15540
15799
|
}
|
|
@@ -15557,8 +15816,10 @@ function normalizeTaskQueueItem(raw = {}) {
|
|
|
15557
15816
|
const taskId = typeof raw.taskId === 'string' ? raw.taskId.trim() : '';
|
|
15558
15817
|
return {
|
|
15559
15818
|
taskId: taskId || createTaskId(),
|
|
15819
|
+
threadId: normalizeTaskThreadId(raw.threadId || plan.threadId) || createTaskThreadId(),
|
|
15560
15820
|
title: typeof raw.title === 'string' ? raw.title.trim() : (typeof plan.title === 'string' ? plan.title.trim() : ''),
|
|
15561
15821
|
target: typeof raw.target === 'string' ? raw.target.trim() : (typeof plan.target === 'string' ? plan.target.trim() : ''),
|
|
15822
|
+
cwd: typeof raw.cwd === 'string' && raw.cwd.trim() ? raw.cwd.trim() : (typeof plan.cwd === 'string' ? plan.cwd.trim() : ''),
|
|
15562
15823
|
status: typeof raw.status === 'string' ? raw.status.trim().toLowerCase() : 'queued',
|
|
15563
15824
|
createdAt: toIsoTime(raw.createdAt || Date.now(), ''),
|
|
15564
15825
|
updatedAt: toIsoTime(raw.updatedAt || raw.createdAt || Date.now(), ''),
|
|
@@ -15759,8 +16020,10 @@ function collectTaskRunSummary(detail = {}) {
|
|
|
15759
16020
|
return {
|
|
15760
16021
|
runId: detail.runId || '',
|
|
15761
16022
|
taskId: detail.taskId || '',
|
|
16023
|
+
threadId: detail.threadId || '',
|
|
15762
16024
|
title: detail.title || '',
|
|
15763
16025
|
target: detail.target || '',
|
|
16026
|
+
cwd: detail.cwd || '',
|
|
15764
16027
|
engine: detail.engine || '',
|
|
15765
16028
|
allowWrite: detail.allowWrite === true,
|
|
15766
16029
|
dryRun: detail.dryRun === true,
|
|
@@ -15884,6 +16147,7 @@ function buildTaskOverviewPayload(options = {}) {
|
|
|
15884
16147
|
}
|
|
15885
16148
|
return {
|
|
15886
16149
|
workflows: workflowCatalog.workflows,
|
|
16150
|
+
openAiChatStatus: buildTaskOpenAiChatStatus(),
|
|
15887
16151
|
warnings,
|
|
15888
16152
|
queue,
|
|
15889
16153
|
runs,
|
|
@@ -15946,20 +16210,434 @@ function readCodexLastMessageFile(filePath) {
|
|
|
15946
16210
|
}
|
|
15947
16211
|
}
|
|
15948
16212
|
|
|
15949
|
-
|
|
15950
|
-
const
|
|
15951
|
-
|
|
15952
|
-
if (
|
|
16213
|
+
function buildOpenAiChatEndpointUrl(baseUrl) {
|
|
16214
|
+
const trimmed = normalizeBaseUrl(baseUrl);
|
|
16215
|
+
if (!trimmed) return '';
|
|
16216
|
+
if (/\/v1\/chat\/completions$/i.test(trimmed) || /\/chat\/completions$/i.test(trimmed)) {
|
|
16217
|
+
return trimmed;
|
|
16218
|
+
}
|
|
16219
|
+
return joinApiUrl(trimmed, 'chat/completions');
|
|
16220
|
+
}
|
|
16221
|
+
|
|
16222
|
+
function redactTaskEndpointUrl(endpointUrl = '') {
|
|
16223
|
+
const raw = String(endpointUrl || '').trim();
|
|
16224
|
+
if (!raw) return '';
|
|
16225
|
+
try {
|
|
16226
|
+
const parsed = new URL(raw);
|
|
16227
|
+
if (parsed.username) parsed.username = '***';
|
|
16228
|
+
if (parsed.password) parsed.password = '***';
|
|
16229
|
+
const secretKeyPattern = /(?:^|[_-])(?:key|api[-_]?key|token|access[-_]?token|refresh[-_]?token|secret|password|passwd|pwd|credential|credentials|signature|sig)(?:$|[_-])/i;
|
|
16230
|
+
for (const key of [...parsed.searchParams.keys()]) {
|
|
16231
|
+
if (secretKeyPattern.test(key)) {
|
|
16232
|
+
parsed.searchParams.set(key, '***');
|
|
16233
|
+
}
|
|
16234
|
+
}
|
|
16235
|
+
return parsed.toString();
|
|
16236
|
+
} catch (_) {
|
|
16237
|
+
return raw
|
|
16238
|
+
.replace(/\/\/([^/@:]+):([^/@]+)@/g, '//***:***@')
|
|
16239
|
+
.replace(/([?&][^=&]*(?:key|token|secret|password|passwd|pwd|credential|signature|sig)[^=]*=)[^&]*/ig, '$1***');
|
|
16240
|
+
}
|
|
16241
|
+
}
|
|
16242
|
+
|
|
16243
|
+
function pickTaskProviderModel(providerName, provider, config) {
|
|
16244
|
+
const currentModels = readCurrentModels();
|
|
16245
|
+
const savedModel = currentModels && typeof currentModels[providerName] === 'string'
|
|
16246
|
+
? currentModels[providerName].trim()
|
|
16247
|
+
: '';
|
|
16248
|
+
const activeProvider = typeof config.model_provider === 'string' ? config.model_provider.trim() : '';
|
|
16249
|
+
const activeModel = typeof config.model === 'string' ? config.model.trim() : '';
|
|
16250
|
+
const providerModels = Array.isArray(provider && provider.models) ? provider.models : [];
|
|
16251
|
+
const firstProviderModel = providerModels
|
|
16252
|
+
.map((item) => {
|
|
16253
|
+
if (typeof item === 'string') return item.trim();
|
|
16254
|
+
if (item && typeof item === 'object') return String(item.id || item.name || item.model || '').trim();
|
|
16255
|
+
return '';
|
|
16256
|
+
})
|
|
16257
|
+
.find(Boolean) || '';
|
|
16258
|
+
return savedModel || (activeProvider === providerName ? activeModel : '') || firstProviderModel;
|
|
16259
|
+
}
|
|
16260
|
+
|
|
16261
|
+
function pickTaskProviderTemperature(provider) {
|
|
16262
|
+
const raw = provider && Object.prototype.hasOwnProperty.call(provider, 'temperature')
|
|
16263
|
+
? provider.temperature
|
|
16264
|
+
: undefined;
|
|
16265
|
+
if (raw === undefined || raw === null || String(raw).trim() === '') {
|
|
16266
|
+
return 0.2;
|
|
16267
|
+
}
|
|
16268
|
+
const value = Number(raw);
|
|
16269
|
+
if (!Number.isFinite(value) || value < 0 || value > 2) {
|
|
16270
|
+
return 0.2;
|
|
16271
|
+
}
|
|
16272
|
+
return value;
|
|
16273
|
+
}
|
|
16274
|
+
|
|
16275
|
+
function pickTaskOpenAiChatProviderName(config) {
|
|
16276
|
+
const taskProvider = typeof config.task_openai_chat_provider === 'string'
|
|
16277
|
+
? config.task_openai_chat_provider.trim()
|
|
16278
|
+
: '';
|
|
16279
|
+
if (taskProvider) {
|
|
16280
|
+
return taskProvider;
|
|
16281
|
+
}
|
|
16282
|
+
return typeof config.model_provider === 'string' ? config.model_provider.trim() : '';
|
|
16283
|
+
}
|
|
16284
|
+
|
|
16285
|
+
function resolveTaskOpenAiChatConfig() {
|
|
16286
|
+
const configResult = readConfigOrVirtualDefault();
|
|
16287
|
+
const config = configResult && configResult.config && typeof configResult.config === 'object' ? configResult.config : {};
|
|
16288
|
+
const providerName = pickTaskOpenAiChatProviderName(config);
|
|
16289
|
+
if (!providerName) {
|
|
16290
|
+
return { error: '未设置当前 OpenAI Chat 提供商' };
|
|
16291
|
+
}
|
|
16292
|
+
const providers = config.model_providers && typeof config.model_providers === 'object' ? config.model_providers : {};
|
|
16293
|
+
const provider = providers[providerName];
|
|
16294
|
+
if (!provider || typeof provider !== 'object') {
|
|
16295
|
+
return { error: `OpenAI Chat 提供商不存在: ${providerName}` };
|
|
16296
|
+
}
|
|
16297
|
+
|
|
16298
|
+
const bridgeType = typeof provider.codexmate_bridge === 'string' ? provider.codexmate_bridge.trim() : '';
|
|
16299
|
+
const isOpenaiBridgeProvider = bridgeType === 'openai'
|
|
16300
|
+
|| (typeof provider.base_url === 'string' && provider.base_url.includes('/bridge/openai/'));
|
|
16301
|
+
let baseUrl = typeof provider.base_url === 'string' ? provider.base_url.trim() : '';
|
|
16302
|
+
let apiKey = typeof provider.preferred_auth_method === 'string' ? provider.preferred_auth_method.trim() : '';
|
|
16303
|
+
let extraHeaders = {};
|
|
16304
|
+
if (isOpenaiBridgeProvider) {
|
|
16305
|
+
const upstream = resolveOpenaiBridgeUpstream(OPENAI_BRIDGE_SETTINGS_FILE, providerName);
|
|
16306
|
+
if (upstream && !upstream.error) {
|
|
16307
|
+
baseUrl = upstream.baseUrl || baseUrl;
|
|
16308
|
+
apiKey = upstream.apiKey || apiKey;
|
|
16309
|
+
extraHeaders = upstream.headers && typeof upstream.headers === 'object' && !Array.isArray(upstream.headers)
|
|
16310
|
+
? upstream.headers
|
|
16311
|
+
: {};
|
|
16312
|
+
}
|
|
16313
|
+
}
|
|
16314
|
+
|
|
16315
|
+
const model = pickTaskProviderModel(providerName, provider, config);
|
|
16316
|
+
if (!baseUrl) {
|
|
16317
|
+
return { error: `OpenAI Chat 提供商 ${providerName} 缺少 base_url` };
|
|
16318
|
+
}
|
|
16319
|
+
if (!isValidHttpUrl(baseUrl)) {
|
|
16320
|
+
return { error: `OpenAI Chat 提供商 ${providerName} 的 base_url 无效` };
|
|
16321
|
+
}
|
|
16322
|
+
if (!model) {
|
|
16323
|
+
return { error: `OpenAI Chat 提供商 ${providerName} 未设置模型` };
|
|
16324
|
+
}
|
|
16325
|
+
return {
|
|
16326
|
+
providerName,
|
|
16327
|
+
baseUrl,
|
|
16328
|
+
endpointUrl: buildOpenAiChatEndpointUrl(baseUrl),
|
|
16329
|
+
apiKey,
|
|
16330
|
+
extraHeaders,
|
|
16331
|
+
model,
|
|
16332
|
+
temperature: pickTaskProviderTemperature(provider)
|
|
16333
|
+
};
|
|
16334
|
+
}
|
|
16335
|
+
|
|
16336
|
+
function buildTaskOpenAiChatStatus() {
|
|
16337
|
+
const requestConfig = resolveTaskOpenAiChatConfig();
|
|
16338
|
+
if (requestConfig && requestConfig.error) {
|
|
16339
|
+
return {
|
|
16340
|
+
ok: false,
|
|
16341
|
+
ready: false,
|
|
16342
|
+
error: requestConfig.error,
|
|
16343
|
+
providerName: '',
|
|
16344
|
+
model: '',
|
|
16345
|
+
endpoint: '',
|
|
16346
|
+
hasApiKey: false,
|
|
16347
|
+
hasExtraHeaders: false
|
|
16348
|
+
};
|
|
16349
|
+
}
|
|
16350
|
+
const hasApiKey = !!(requestConfig && typeof requestConfig.apiKey === 'string' && requestConfig.apiKey.trim());
|
|
16351
|
+
const hasExtraHeaders = !!(requestConfig
|
|
16352
|
+
&& requestConfig.extraHeaders
|
|
16353
|
+
&& typeof requestConfig.extraHeaders === 'object'
|
|
16354
|
+
&& !Array.isArray(requestConfig.extraHeaders)
|
|
16355
|
+
&& Object.keys(requestConfig.extraHeaders).length > 0);
|
|
16356
|
+
const hasAuthMaterial = hasApiKey || hasExtraHeaders;
|
|
16357
|
+
return {
|
|
16358
|
+
ok: true,
|
|
16359
|
+
ready: hasAuthMaterial,
|
|
16360
|
+
error: hasAuthMaterial ? '' : `OpenAI Chat 提供商 ${requestConfig.providerName || ''} 缺少 API key 或额外 headers`,
|
|
16361
|
+
providerName: requestConfig.providerName || '',
|
|
16362
|
+
model: requestConfig.model || '',
|
|
16363
|
+
endpoint: redactTaskEndpointUrl(requestConfig.endpointUrl || ''),
|
|
16364
|
+
hasApiKey,
|
|
16365
|
+
hasExtraHeaders
|
|
16366
|
+
};
|
|
16367
|
+
}
|
|
16368
|
+
|
|
16369
|
+
function postOpenAiChatCompletion(requestConfig, body, options = {}) {
|
|
16370
|
+
const endpointUrl = requestConfig && requestConfig.endpointUrl ? requestConfig.endpointUrl : '';
|
|
16371
|
+
return new Promise((resolve) => {
|
|
16372
|
+
let parsed;
|
|
16373
|
+
try {
|
|
16374
|
+
parsed = new URL(endpointUrl);
|
|
16375
|
+
} catch (error) {
|
|
16376
|
+
resolve({ ok: false, error: 'OpenAI Chat endpoint URL 无效', status: 0, payload: null, body: '' });
|
|
16377
|
+
return;
|
|
16378
|
+
}
|
|
16379
|
+
const transport = parsed.protocol === 'https:' ? https : http;
|
|
16380
|
+
const agent = parsed.protocol === 'https:' ? HTTPS_KEEP_ALIVE_AGENT : HTTP_KEEP_ALIVE_AGENT;
|
|
16381
|
+
const payloadText = JSON.stringify(body || {});
|
|
16382
|
+
const headers = {
|
|
16383
|
+
'Content-Type': 'application/json',
|
|
16384
|
+
'Accept': 'application/json',
|
|
16385
|
+
'User-Agent': 'codexmate-task-orchestration',
|
|
16386
|
+
'Content-Length': Buffer.byteLength(payloadText, 'utf-8'),
|
|
16387
|
+
...(requestConfig.extraHeaders && typeof requestConfig.extraHeaders === 'object' ? requestConfig.extraHeaders : {})
|
|
16388
|
+
};
|
|
16389
|
+
if (requestConfig.apiKey) {
|
|
16390
|
+
headers.Authorization = `Bearer ${requestConfig.apiKey}`;
|
|
16391
|
+
}
|
|
16392
|
+
let settled = false;
|
|
16393
|
+
let req = null;
|
|
16394
|
+
const finish = (result) => {
|
|
16395
|
+
if (settled) return;
|
|
16396
|
+
settled = true;
|
|
16397
|
+
resolve(result);
|
|
16398
|
+
};
|
|
16399
|
+
req = transport.request(parsed, { method: 'POST', headers, agent }, (res) => {
|
|
16400
|
+
const status = res.statusCode || 0;
|
|
16401
|
+
let raw = '';
|
|
16402
|
+
let receivedBytes = 0;
|
|
16403
|
+
res.on('data', (chunk) => {
|
|
16404
|
+
if (settled) return;
|
|
16405
|
+
receivedBytes += chunk.length || 0;
|
|
16406
|
+
if (receivedBytes > TASK_OPENAI_CHAT_MAX_RESPONSE_BYTES) {
|
|
16407
|
+
finish({ ok: false, status, error: 'OpenAI Chat response too large', payload: null, body: raw });
|
|
16408
|
+
res.destroy();
|
|
16409
|
+
return;
|
|
16410
|
+
}
|
|
16411
|
+
raw += chunk;
|
|
16412
|
+
});
|
|
16413
|
+
res.on('error', (error) => {
|
|
16414
|
+
finish({
|
|
16415
|
+
ok: false,
|
|
16416
|
+
status,
|
|
16417
|
+
error: error && error.message ? error.message : 'OpenAI Chat request failed',
|
|
16418
|
+
payload: null,
|
|
16419
|
+
body: raw
|
|
16420
|
+
});
|
|
16421
|
+
});
|
|
16422
|
+
res.on('end', () => {
|
|
16423
|
+
if (settled) return;
|
|
16424
|
+
let parsedPayload = null;
|
|
16425
|
+
try {
|
|
16426
|
+
parsedPayload = raw ? JSON.parse(raw) : null;
|
|
16427
|
+
} catch (_) {}
|
|
16428
|
+
if (status < 200 || status >= 300) {
|
|
16429
|
+
const message = parsedPayload && parsedPayload.error
|
|
16430
|
+
? (typeof parsedPayload.error === 'string' ? parsedPayload.error : (parsedPayload.error.message || JSON.stringify(parsedPayload.error)))
|
|
16431
|
+
: (raw || `OpenAI Chat request failed: ${status}`);
|
|
16432
|
+
finish({ ok: false, status, error: truncateTaskText(message, 1200), payload: parsedPayload, body: raw });
|
|
16433
|
+
return;
|
|
16434
|
+
}
|
|
16435
|
+
finish({ ok: true, status, error: '', payload: parsedPayload, body: raw });
|
|
16436
|
+
});
|
|
16437
|
+
});
|
|
16438
|
+
if (typeof options.registerAbort === 'function') {
|
|
16439
|
+
options.registerAbort(() => {
|
|
16440
|
+
try {
|
|
16441
|
+
req.destroy(new Error('cancelled'));
|
|
16442
|
+
} catch (_) {}
|
|
16443
|
+
});
|
|
16444
|
+
}
|
|
16445
|
+
req.setTimeout(TASK_OPENAI_CHAT_TIMEOUT_MS, () => {
|
|
16446
|
+
req.destroy(new Error('OpenAI Chat request timeout'));
|
|
16447
|
+
});
|
|
16448
|
+
req.on('error', (error) => {
|
|
16449
|
+
finish({ ok: false, status: 0, error: error && error.message ? error.message : String(error || 'OpenAI Chat request failed'), payload: null, body: '' });
|
|
16450
|
+
});
|
|
16451
|
+
req.write(payloadText);
|
|
16452
|
+
req.end();
|
|
16453
|
+
});
|
|
16454
|
+
}
|
|
16455
|
+
|
|
16456
|
+
const TASK_OPENAI_MATERIALIZED_ARTIFACT_MAX_BYTES = 512 * 1024;
|
|
16457
|
+
const TASK_OPENAI_MATERIALIZED_ARTIFACT_EXTENSIONS = new Set(['.html', '.htm', '.css', '.js', '.mjs', '.json', '.md', '.txt', '.svg']);
|
|
16458
|
+
|
|
16459
|
+
function normalizeTaskMaterializedArtifactPath(rawPath, cwd) {
|
|
16460
|
+
const baseDir = path.resolve(typeof cwd === 'string' && cwd.trim() ? cwd.trim() : process.cwd());
|
|
16461
|
+
const text = typeof rawPath === 'string' ? rawPath.trim() : '';
|
|
16462
|
+
if (!text || text.length > 200) {
|
|
16463
|
+
return { error: 'artifact path is empty or too long' };
|
|
16464
|
+
}
|
|
16465
|
+
if (path.isAbsolute(text) || text.includes('\\')) {
|
|
16466
|
+
return { error: `artifact path must be a safe relative path: ${text}` };
|
|
16467
|
+
}
|
|
16468
|
+
const normalized = path.normalize(text);
|
|
16469
|
+
if (!normalized || normalized === '.' || normalized.startsWith('..') || normalized.split(path.sep).includes('..')) {
|
|
16470
|
+
return { error: `artifact path escapes cwd: ${text}` };
|
|
16471
|
+
}
|
|
16472
|
+
const ext = path.extname(normalized).toLowerCase();
|
|
16473
|
+
if (!TASK_OPENAI_MATERIALIZED_ARTIFACT_EXTENSIONS.has(ext)) {
|
|
16474
|
+
return { error: `artifact extension is not allowed: ${ext || '(none)'}` };
|
|
16475
|
+
}
|
|
16476
|
+
const targetPath = path.resolve(baseDir, normalized);
|
|
16477
|
+
const relative = path.relative(baseDir, targetPath);
|
|
16478
|
+
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
16479
|
+
return { error: `artifact path escapes cwd: ${text}` };
|
|
16480
|
+
}
|
|
16481
|
+
return { path: targetPath, relativePath: normalized, baseDir };
|
|
16482
|
+
}
|
|
16483
|
+
|
|
16484
|
+
function validateTaskMaterializedArtifactParents(targetPath, baseDir) {
|
|
16485
|
+
const resolvedBase = fs.realpathSync.native(baseDir);
|
|
16486
|
+
let current = path.dirname(targetPath);
|
|
16487
|
+
const pending = [];
|
|
16488
|
+
while (true) {
|
|
16489
|
+
if (current === resolvedBase) {
|
|
16490
|
+
return { ok: true };
|
|
16491
|
+
}
|
|
16492
|
+
if (current === path.dirname(current)) {
|
|
16493
|
+
return { ok: false, error: 'artifact parent escapes cwd' };
|
|
16494
|
+
}
|
|
16495
|
+
if (fs.existsSync(current)) {
|
|
16496
|
+
const stats = fs.lstatSync(current);
|
|
16497
|
+
if (stats.isSymbolicLink()) {
|
|
16498
|
+
return { ok: false, error: `artifact parent is a symlink: ${path.relative(baseDir, current) || current}` };
|
|
16499
|
+
}
|
|
16500
|
+
const resolvedCurrent = fs.realpathSync.native(current);
|
|
16501
|
+
const relative = path.relative(resolvedBase, resolvedCurrent);
|
|
16502
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
16503
|
+
return { ok: false, error: 'artifact parent resolves outside cwd' };
|
|
16504
|
+
}
|
|
16505
|
+
for (const child of pending) {
|
|
16506
|
+
const childRelative = path.relative(resolvedBase, child);
|
|
16507
|
+
if (childRelative.startsWith('..') || path.isAbsolute(childRelative)) {
|
|
16508
|
+
return { ok: false, error: 'artifact parent escapes cwd' };
|
|
16509
|
+
}
|
|
16510
|
+
}
|
|
16511
|
+
return { ok: true };
|
|
16512
|
+
}
|
|
16513
|
+
pending.push(current);
|
|
16514
|
+
current = path.dirname(current);
|
|
16515
|
+
}
|
|
16516
|
+
}
|
|
16517
|
+
|
|
16518
|
+
function inferTaskMaterializedArtifactPath(info, prefix, hints) {
|
|
16519
|
+
const sources = [info, prefix, hints].map((item) => String(item || '')).filter(Boolean);
|
|
16520
|
+
const explicitPatterns = [
|
|
16521
|
+
/(?:file|filename|path)\s*[:=]\s*[`"']?([A-Za-z0-9._/-]+\.[A-Za-z0-9]+)[`"']?/i,
|
|
16522
|
+
/(?:文件|路径|保存为|写入到?|输出到?)\s*[::]?\s*[`"']?([A-Za-z0-9._/-]+\.[A-Za-z0-9]+)[`"']?/i,
|
|
16523
|
+
/[`"']([A-Za-z0-9._/-]+\.[A-Za-z0-9]+)[`"']/i
|
|
16524
|
+
];
|
|
16525
|
+
for (const source of sources) {
|
|
16526
|
+
for (const pattern of explicitPatterns) {
|
|
16527
|
+
const match = source.match(pattern);
|
|
16528
|
+
if (match && match[1]) {
|
|
16529
|
+
return match[1];
|
|
16530
|
+
}
|
|
16531
|
+
}
|
|
16532
|
+
}
|
|
16533
|
+
const normalizedInfo = String(info || '').trim().toLowerCase();
|
|
16534
|
+
const hintText = sources.join('\n').toLowerCase();
|
|
16535
|
+
if ((normalizedInfo === 'html' || normalizedInfo.startsWith('html ')) && hintText.includes('index.html')) {
|
|
16536
|
+
return 'index.html';
|
|
16537
|
+
}
|
|
16538
|
+
return '';
|
|
16539
|
+
}
|
|
16540
|
+
|
|
16541
|
+
function materializeOpenAiChatTaskArtifacts(text, options = {}) {
|
|
16542
|
+
const content = typeof text === 'string' ? text : '';
|
|
16543
|
+
if (!content.trim()) {
|
|
16544
|
+
return { files: [], warnings: [] };
|
|
16545
|
+
}
|
|
16546
|
+
const cwd = typeof options.cwd === 'string' && options.cwd.trim() ? options.cwd.trim() : process.cwd();
|
|
16547
|
+
const hints = [options.target, options.notes, options.prompt]
|
|
16548
|
+
.map((item) => String(item || '').trim())
|
|
16549
|
+
.filter(Boolean)
|
|
16550
|
+
.join('\n');
|
|
16551
|
+
const files = [];
|
|
16552
|
+
const warnings = [];
|
|
16553
|
+
const seen = new Set();
|
|
16554
|
+
const fencePattern = /```([^\n`]*)\n([\s\S]*?)```/g;
|
|
16555
|
+
let match = null;
|
|
16556
|
+
while ((match = fencePattern.exec(content)) !== null) {
|
|
16557
|
+
const info = String(match[1] || '').trim();
|
|
16558
|
+
const body = String(match[2] || '');
|
|
16559
|
+
const prefix = content.slice(Math.max(0, match.index - 240), match.index);
|
|
16560
|
+
const inferredPath = inferTaskMaterializedArtifactPath(info, prefix, hints);
|
|
16561
|
+
if (!inferredPath) {
|
|
16562
|
+
continue;
|
|
16563
|
+
}
|
|
16564
|
+
if (Buffer.byteLength(body, 'utf-8') > TASK_OPENAI_MATERIALIZED_ARTIFACT_MAX_BYTES) {
|
|
16565
|
+
warnings.push(`artifact too large and skipped: ${inferredPath}`);
|
|
16566
|
+
continue;
|
|
16567
|
+
}
|
|
16568
|
+
const normalized = normalizeTaskMaterializedArtifactPath(inferredPath, cwd);
|
|
16569
|
+
if (normalized.error) {
|
|
16570
|
+
warnings.push(normalized.error);
|
|
16571
|
+
continue;
|
|
16572
|
+
}
|
|
16573
|
+
if (seen.has(normalized.path)) {
|
|
16574
|
+
warnings.push(`duplicate artifact skipped: ${normalized.relativePath}`);
|
|
16575
|
+
continue;
|
|
16576
|
+
}
|
|
16577
|
+
seen.add(normalized.path);
|
|
16578
|
+
try {
|
|
16579
|
+
const fileContent = body.trimStart();
|
|
16580
|
+
const parentValidation = validateTaskMaterializedArtifactParents(normalized.path, normalized.baseDir);
|
|
16581
|
+
if (!parentValidation.ok) {
|
|
16582
|
+
warnings.push(parentValidation.error || `artifact parent is unsafe: ${normalized.relativePath}`);
|
|
16583
|
+
continue;
|
|
16584
|
+
}
|
|
16585
|
+
ensureDir(path.dirname(normalized.path));
|
|
16586
|
+
try {
|
|
16587
|
+
if (fs.lstatSync(normalized.path).isSymbolicLink()) {
|
|
16588
|
+
warnings.push(`artifact target is a symlink: ${normalized.relativePath}`);
|
|
16589
|
+
continue;
|
|
16590
|
+
}
|
|
16591
|
+
} catch (error) {
|
|
16592
|
+
if (!error || error.code !== 'ENOENT') {
|
|
16593
|
+
throw error;
|
|
16594
|
+
}
|
|
16595
|
+
}
|
|
16596
|
+
fs.writeFileSync(normalized.path, fileContent, { encoding: 'utf-8', mode: 0o600 });
|
|
16597
|
+
files.push({ path: normalized.path, relativePath: normalized.relativePath, bytes: Buffer.byteLength(fileContent, 'utf-8') });
|
|
16598
|
+
} catch (error) {
|
|
16599
|
+
warnings.push(`failed to write artifact ${normalized.relativePath}: ${error && error.message ? error.message : String(error)}`);
|
|
16600
|
+
}
|
|
16601
|
+
}
|
|
16602
|
+
return { files, warnings };
|
|
16603
|
+
}
|
|
16604
|
+
|
|
16605
|
+
async function runOpenAiChatTaskNode(node, context = {}) {
|
|
16606
|
+
const requestConfig = resolveTaskOpenAiChatConfig();
|
|
16607
|
+
if (requestConfig.error) {
|
|
15953
16608
|
return {
|
|
15954
16609
|
success: false,
|
|
15955
|
-
error:
|
|
15956
|
-
summary:
|
|
16610
|
+
error: requestConfig.error,
|
|
16611
|
+
summary: requestConfig.error,
|
|
15957
16612
|
output: null,
|
|
15958
|
-
logs: [{ at: toIsoTime(Date.now()), level: 'error', message:
|
|
16613
|
+
logs: [{ at: toIsoTime(Date.now()), level: 'error', message: requestConfig.error }]
|
|
15959
16614
|
};
|
|
15960
16615
|
}
|
|
15961
|
-
const
|
|
15962
|
-
const
|
|
16616
|
+
const hasApiKey = typeof requestConfig.apiKey === 'string' && requestConfig.apiKey.trim();
|
|
16617
|
+
const hasExtraHeaders = requestConfig.extraHeaders
|
|
16618
|
+
&& typeof requestConfig.extraHeaders === 'object'
|
|
16619
|
+
&& !Array.isArray(requestConfig.extraHeaders)
|
|
16620
|
+
&& Object.keys(requestConfig.extraHeaders).length > 0;
|
|
16621
|
+
if (!hasApiKey && !hasExtraHeaders) {
|
|
16622
|
+
const error = `OpenAI Chat 提供商 ${requestConfig.providerName || ''} 缺少 API key 或额外 headers`;
|
|
16623
|
+
return {
|
|
16624
|
+
success: false,
|
|
16625
|
+
error,
|
|
16626
|
+
summary: error,
|
|
16627
|
+
output: {
|
|
16628
|
+
provider: requestConfig.providerName || '',
|
|
16629
|
+
model: requestConfig.model || '',
|
|
16630
|
+
endpoint: redactTaskEndpointUrl(requestConfig.endpointUrl || ''),
|
|
16631
|
+
status: 0,
|
|
16632
|
+
text: '',
|
|
16633
|
+
response: null,
|
|
16634
|
+
durationMs: 0,
|
|
16635
|
+
materializedFiles: []
|
|
16636
|
+
},
|
|
16637
|
+
logs: [{ at: toIsoTime(Date.now()), level: 'error', message: error }]
|
|
16638
|
+
};
|
|
16639
|
+
}
|
|
16640
|
+
const allowWrite = context.allowWrite === true && node.write !== false && context.dryRun !== true;
|
|
15963
16641
|
const dependencyResults = Array.isArray(context.dependencyResults) ? context.dependencyResults : [];
|
|
15964
16642
|
const dependencyLines = dependencyResults
|
|
15965
16643
|
.map((item) => {
|
|
@@ -15979,124 +16657,95 @@ async function runCodexExecTaskNode(node, context = {}) {
|
|
|
15979
16657
|
promptParts.push('请在保持目标不变的前提下修复上一轮失败并继续完成当前节点。');
|
|
15980
16658
|
}
|
|
15981
16659
|
const finalPrompt = promptParts.filter(Boolean).join('\n\n');
|
|
15982
|
-
const
|
|
15983
|
-
|
|
15984
|
-
const
|
|
15985
|
-
const
|
|
15986
|
-
const
|
|
15987
|
-
|
|
15988
|
-
|
|
15989
|
-
|
|
15990
|
-
'
|
|
15991
|
-
|
|
15992
|
-
'
|
|
15993
|
-
'
|
|
15994
|
-
|
|
15995
|
-
|
|
15996
|
-
|
|
15997
|
-
|
|
15998
|
-
|
|
15999
|
-
|
|
16000
|
-
|
|
16001
|
-
|
|
16002
|
-
|
|
16003
|
-
|
|
16004
|
-
|
|
16005
|
-
|
|
16006
|
-
|
|
16007
|
-
|
|
16008
|
-
|
|
16009
|
-
|
|
16010
|
-
|
|
16011
|
-
|
|
16012
|
-
|
|
16013
|
-
|
|
16014
|
-
}
|
|
16015
|
-
|
|
16016
|
-
|
|
16017
|
-
|
|
16018
|
-
} catch (_) { }
|
|
16019
|
-
};
|
|
16020
|
-
const captureLines = (bucket, text, stream) => {
|
|
16021
|
-
const currentPartial = stream === 'stderr' ? stderrPartial : stdoutPartial;
|
|
16022
|
-
const merged = `${currentPartial}${String(text || '')}`;
|
|
16023
|
-
const pieces = merged.split(/\r?\n/g);
|
|
16024
|
-
const nextPartial = pieces.pop() || '';
|
|
16025
|
-
if (stream === 'stderr') {
|
|
16026
|
-
stderrPartial = nextPartial;
|
|
16027
|
-
} else {
|
|
16028
|
-
stdoutPartial = nextPartial;
|
|
16029
|
-
}
|
|
16030
|
-
for (const line of pieces) {
|
|
16031
|
-
processCapturedLine(bucket, line);
|
|
16032
|
-
}
|
|
16033
|
-
};
|
|
16034
|
-
const flushCapturedPartial = (bucket, stream) => {
|
|
16035
|
-
const partial = stream === 'stderr' ? stderrPartial : stdoutPartial;
|
|
16036
|
-
if (stream === 'stderr') {
|
|
16037
|
-
stderrPartial = '';
|
|
16038
|
-
} else {
|
|
16039
|
-
stdoutPartial = '';
|
|
16040
|
-
}
|
|
16041
|
-
processCapturedLine(bucket, partial);
|
|
16660
|
+
const rawThreadId = String(context.threadId || (context.plan && context.plan.threadId) || '').trim();
|
|
16661
|
+
const threadId = /^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(rawThreadId) ? rawThreadId : '';
|
|
16662
|
+
const threadStoreDir = typeof TASK_WORKSPACE_CHAT_THREADS_DIR === 'string' ? TASK_WORKSPACE_CHAT_THREADS_DIR : '';
|
|
16663
|
+
const canLoadWorkspaceThread = typeof loadWorkspaceChatThread === 'function' && !!threadStoreDir;
|
|
16664
|
+
const thread = threadId && canLoadWorkspaceThread ? loadWorkspaceChatThread(threadStoreDir, threadId) : { messages: [] };
|
|
16665
|
+
const historyMessages = Array.isArray(thread.messages) ? thread.messages.slice(-12) : [];
|
|
16666
|
+
const workspaceContext = typeof buildWorkspaceChatContext === 'function'
|
|
16667
|
+
? buildWorkspaceChatContext(context.cwd || process.cwd(), finalPrompt, historyMessages)
|
|
16668
|
+
: '';
|
|
16669
|
+
const systemPrompt = [
|
|
16670
|
+
'你是 codexmate 任务编排中的 OpenAI Chat-compatible 执行节点。',
|
|
16671
|
+
'基于用户给出的目标、前置节点摘要、同一 thread 历史和当前工作区快照,输出可执行、可验证、事实谨慎的结果。',
|
|
16672
|
+
allowWrite
|
|
16673
|
+
? '当前任务允许写入;如果需要创建或更新文件,优先使用 fenced code block:```codexmate-file action="write" path="相对路径"。代码块内容会写入任务工作目录内对应文本文件。删除文件请单独写一行 CODEXMATE_DELETE_FILE: 相对路径。只允许安全相对路径。'
|
|
16674
|
+
: '当前任务是只读/验证节点,不要声称修改了文件。',
|
|
16675
|
+
'查询/读取文件时,先依据用户要求和工作区快照回答;如果快照不足,说明缺口,不要编造。',
|
|
16676
|
+
'回答使用中文,避免空泛总结。'
|
|
16677
|
+
].join('\n');
|
|
16678
|
+
const currentUserMessage = [
|
|
16679
|
+
finalPrompt,
|
|
16680
|
+
workspaceContext ? '\n--- codexmate workspace snapshot ---' : '',
|
|
16681
|
+
workspaceContext
|
|
16682
|
+
].filter(Boolean).join('\n');
|
|
16683
|
+
const startedAt = Date.now();
|
|
16684
|
+
const body = {
|
|
16685
|
+
model: requestConfig.model,
|
|
16686
|
+
messages: [
|
|
16687
|
+
{ role: 'system', content: systemPrompt },
|
|
16688
|
+
...historyMessages.map((message) => ({
|
|
16689
|
+
role: message.role === 'assistant' ? 'assistant' : 'user',
|
|
16690
|
+
content: String(message.content || '')
|
|
16691
|
+
})),
|
|
16692
|
+
{ role: 'user', content: currentUserMessage }
|
|
16693
|
+
],
|
|
16694
|
+
temperature: Number.isFinite(requestConfig.temperature) ? requestConfig.temperature : 0.2,
|
|
16695
|
+
stream: false
|
|
16042
16696
|
};
|
|
16043
|
-
const
|
|
16044
|
-
|
|
16045
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
16046
|
-
windowsHide: true,
|
|
16047
|
-
shell: process.platform === 'win32'
|
|
16048
|
-
});
|
|
16049
|
-
if (typeof context.registerAbort === 'function') {
|
|
16050
|
-
context.registerAbort(() => {
|
|
16051
|
-
try {
|
|
16052
|
-
child.kill('SIGTERM');
|
|
16053
|
-
} catch (_) { }
|
|
16054
|
-
});
|
|
16055
|
-
}
|
|
16056
|
-
child.stdout.on('data', (chunk) => {
|
|
16057
|
-
captureLines(stdoutLines, chunk, 'stdout');
|
|
16058
|
-
});
|
|
16059
|
-
child.stderr.on('data', (chunk) => {
|
|
16060
|
-
captureLines(stderrLines, chunk, 'stderr');
|
|
16061
|
-
});
|
|
16062
|
-
child.on('error', (error) => {
|
|
16063
|
-
resolve({ code: 1, signal: '', error: error && error.message ? error.message : String(error || 'spawn failed') });
|
|
16064
|
-
});
|
|
16065
|
-
child.on('close', (code, signal) => {
|
|
16066
|
-
flushCapturedPartial(stdoutLines, 'stdout');
|
|
16067
|
-
flushCapturedPartial(stderrLines, 'stderr');
|
|
16068
|
-
resolve({ code: typeof code === 'number' ? code : 1, signal: signal || '', error: '' });
|
|
16069
|
-
});
|
|
16697
|
+
const result = await postOpenAiChatCompletion(requestConfig, body, {
|
|
16698
|
+
registerAbort: context.registerAbort
|
|
16070
16699
|
});
|
|
16071
|
-
const
|
|
16072
|
-
|
|
16073
|
-
|
|
16074
|
-
|
|
16075
|
-
|
|
16076
|
-
|
|
16077
|
-
|
|
16078
|
-
|
|
16079
|
-
|
|
16080
|
-
|
|
16081
|
-
|
|
16082
|
-
|
|
16083
|
-
|
|
16700
|
+
const text = result.ok ? extractModelResponseText(result.payload) : '';
|
|
16701
|
+
const success = result.ok && !!text;
|
|
16702
|
+
const errorMessage = success ? '' : (result.error || 'OpenAI Chat response did not contain text');
|
|
16703
|
+
const summary = truncateTaskText(text || errorMessage, 400);
|
|
16704
|
+
const safeEndpoint = redactTaskEndpointUrl(requestConfig.endpointUrl);
|
|
16705
|
+
const materialized = success && allowWrite
|
|
16706
|
+
? materializeOpenAiChatTaskArtifacts(text, {
|
|
16707
|
+
cwd: context.cwd || process.cwd(),
|
|
16708
|
+
target: context.plan && context.plan.target ? context.plan.target : '',
|
|
16709
|
+
notes: context.plan && context.plan.notes ? context.plan.notes : '',
|
|
16710
|
+
prompt: node.prompt || ''
|
|
16711
|
+
})
|
|
16712
|
+
: { files: [], warnings: [] };
|
|
16713
|
+
const workspaceOperations = success && typeof applyWorkspaceFileOperations === 'function'
|
|
16714
|
+
? applyWorkspaceFileOperations(text, context.cwd || process.cwd(), { allowWrite })
|
|
16715
|
+
: { files: [], warnings: [] };
|
|
16716
|
+
const threadAppend = threadId && typeof appendWorkspaceChatThread === 'function' && !!threadStoreDir
|
|
16717
|
+
? appendWorkspaceChatThread(threadStoreDir, threadId, [
|
|
16718
|
+
{ role: 'user', content: finalPrompt },
|
|
16719
|
+
{ role: 'assistant', content: text || errorMessage }
|
|
16720
|
+
], { cwd: context.cwd || process.cwd() })
|
|
16721
|
+
: { ok: false, error: 'thread id is empty' };
|
|
16084
16722
|
return {
|
|
16085
16723
|
success,
|
|
16086
16724
|
error: errorMessage,
|
|
16087
16725
|
summary,
|
|
16088
16726
|
output: {
|
|
16089
|
-
|
|
16090
|
-
|
|
16091
|
-
|
|
16092
|
-
|
|
16093
|
-
|
|
16094
|
-
|
|
16095
|
-
|
|
16727
|
+
provider: requestConfig.providerName,
|
|
16728
|
+
model: requestConfig.model,
|
|
16729
|
+
endpoint: safeEndpoint,
|
|
16730
|
+
status: result.status || 0,
|
|
16731
|
+
text,
|
|
16732
|
+
response: result.payload || null,
|
|
16733
|
+
durationMs: Date.now() - startedAt,
|
|
16734
|
+
messageCount: body.messages.length,
|
|
16735
|
+
threadId,
|
|
16736
|
+
threadStore: threadAppend && threadAppend.file ? threadAppend.file : '',
|
|
16737
|
+
materializedFiles: materialized.files,
|
|
16738
|
+
workspaceFiles: workspaceOperations.files
|
|
16096
16739
|
},
|
|
16097
16740
|
logs: [
|
|
16098
|
-
|
|
16099
|
-
...
|
|
16741
|
+
{ at: toIsoTime(Date.now()), level: 'info', message: `OpenAI Chat request provider=${requestConfig.providerName} model=${requestConfig.model} status=${result.status || 0}` },
|
|
16742
|
+
...(threadId ? [{ at: toIsoTime(Date.now()), level: 'info', message: `workspace chat thread=${threadId} messages=${body.messages.length}` }] : []),
|
|
16743
|
+
...(success ? [{ at: toIsoTime(Date.now()), level: 'info', message: truncateTaskText(text, 1200) }] : [{ at: toIsoTime(Date.now()), level: 'error', message: errorMessage }]),
|
|
16744
|
+
...materialized.files.map((file) => ({ at: toIsoTime(Date.now()), level: 'info', message: `materialized artifact ${file.relativePath} (${file.bytes} bytes)` })),
|
|
16745
|
+
...workspaceOperations.files.map((file) => ({ at: toIsoTime(Date.now()), level: 'info', message: `workspace ${file.operation} ${file.relativePath} (${file.bytes} bytes)` })),
|
|
16746
|
+
...materialized.warnings.map((warning) => ({ at: toIsoTime(Date.now()), level: 'warn', message: warning })),
|
|
16747
|
+
...workspaceOperations.warnings.map((warning) => ({ at: toIsoTime(Date.now()), level: 'warn', message: warning })),
|
|
16748
|
+
...(threadAppend && threadAppend.error ? [{ at: toIsoTime(Date.now()), level: 'warn', message: threadAppend.error }] : [])
|
|
16100
16749
|
]
|
|
16101
16750
|
};
|
|
16102
16751
|
}
|
|
@@ -16134,7 +16783,7 @@ async function executeTaskNodeAdapter(node, context = {}) {
|
|
|
16134
16783
|
: []
|
|
16135
16784
|
};
|
|
16136
16785
|
}
|
|
16137
|
-
return
|
|
16786
|
+
return runOpenAiChatTaskNode(node, context);
|
|
16138
16787
|
}
|
|
16139
16788
|
|
|
16140
16789
|
async function runTaskPlanInternal(plan, options = {}) {
|
|
@@ -16148,13 +16797,18 @@ async function runTaskPlanInternal(plan, options = {}) {
|
|
|
16148
16797
|
}
|
|
16149
16798
|
const taskId = typeof options.taskId === 'string' && options.taskId.trim() ? options.taskId.trim() : (plan.id || createTaskId());
|
|
16150
16799
|
const runId = typeof options.runId === 'string' && options.runId.trim() ? options.runId.trim() : createTaskRunId();
|
|
16800
|
+
const threadId = normalizeTaskThreadId(options.threadId || plan.threadId) || createTaskThreadId();
|
|
16801
|
+
plan.threadId = threadId;
|
|
16802
|
+
const cwd = typeof plan.cwd === 'string' && plan.cwd.trim() ? plan.cwd.trim() : process.cwd();
|
|
16151
16803
|
const controller = new AbortController();
|
|
16152
16804
|
const baseDetail = {
|
|
16153
16805
|
runId,
|
|
16154
16806
|
taskId,
|
|
16807
|
+
threadId,
|
|
16155
16808
|
workerPid: process.pid,
|
|
16156
16809
|
title: plan.title || '',
|
|
16157
16810
|
target: plan.target || '',
|
|
16811
|
+
cwd,
|
|
16158
16812
|
engine: normalizeTaskEngine(plan.engine),
|
|
16159
16813
|
allowWrite: plan.allowWrite === true,
|
|
16160
16814
|
dryRun: plan.dryRun === true,
|
|
@@ -16190,6 +16844,8 @@ async function runTaskPlanInternal(plan, options = {}) {
|
|
|
16190
16844
|
const queued = upsertTaskQueueItem({
|
|
16191
16845
|
...options.queueItem,
|
|
16192
16846
|
taskId,
|
|
16847
|
+
threadId,
|
|
16848
|
+
cwd,
|
|
16193
16849
|
status: 'running',
|
|
16194
16850
|
runStatus: 'running',
|
|
16195
16851
|
lastRunId: runId,
|
|
@@ -16208,9 +16864,10 @@ async function runTaskPlanInternal(plan, options = {}) {
|
|
|
16208
16864
|
plan,
|
|
16209
16865
|
taskId,
|
|
16210
16866
|
runId,
|
|
16867
|
+
threadId,
|
|
16211
16868
|
allowWrite: plan.allowWrite === true,
|
|
16212
16869
|
dryRun: plan.dryRun === true,
|
|
16213
|
-
cwd
|
|
16870
|
+
cwd
|
|
16214
16871
|
}),
|
|
16215
16872
|
onUpdate: async (snapshot) => {
|
|
16216
16873
|
const nextDetail = {
|
|
@@ -16224,6 +16881,8 @@ async function runTaskPlanInternal(plan, options = {}) {
|
|
|
16224
16881
|
const queued = upsertTaskQueueItem({
|
|
16225
16882
|
...options.queueItem,
|
|
16226
16883
|
taskId,
|
|
16884
|
+
threadId,
|
|
16885
|
+
cwd,
|
|
16227
16886
|
status: snapshot.status === 'success'
|
|
16228
16887
|
? 'completed'
|
|
16229
16888
|
: (snapshot.status === 'failed' ? 'failed' : (snapshot.status === 'cancelled' ? 'cancelled' : 'running')),
|
|
@@ -16253,6 +16912,8 @@ async function runTaskPlanInternal(plan, options = {}) {
|
|
|
16253
16912
|
const queued = upsertTaskQueueItem({
|
|
16254
16913
|
...options.queueItem,
|
|
16255
16914
|
taskId,
|
|
16915
|
+
threadId,
|
|
16916
|
+
cwd,
|
|
16256
16917
|
status: run.status === 'success'
|
|
16257
16918
|
? 'completed'
|
|
16258
16919
|
: (run.status === 'cancelled' ? 'cancelled' : 'failed'),
|
|
@@ -16283,8 +16944,10 @@ function addTaskToQueue(params = {}) {
|
|
|
16283
16944
|
const taskId = typeof params.taskId === 'string' && params.taskId.trim() ? params.taskId.trim() : createTaskId();
|
|
16284
16945
|
const item = upsertTaskQueueItem({
|
|
16285
16946
|
taskId,
|
|
16947
|
+
threadId: plan.threadId || createTaskThreadId(),
|
|
16286
16948
|
title: plan.title,
|
|
16287
16949
|
target: plan.target,
|
|
16950
|
+
cwd: plan.cwd || process.cwd(),
|
|
16288
16951
|
status: 'queued',
|
|
16289
16952
|
createdAt: toIsoTime(Date.now()),
|
|
16290
16953
|
updatedAt: toIsoTime(Date.now()),
|