codexmate 0.0.56 → 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/cli.js +680 -129
- 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 +55 -129
- package/web-ui/modules/app.computed.main-tabs.mjs +304 -7
- package/web-ui/modules/app.methods.agents.mjs +3 -0
- 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.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 +415 -68
- package/web-ui/modules/config-template-confirm-pref.mjs +2 -3
- package/web-ui/modules/i18n/locales/en.mjs +146 -26
- package/web-ui/modules/i18n/locales/ja.mjs +143 -23
- package/web-ui/modules/i18n/locales/vi.mjs +145 -25
- package/web-ui/modules/i18n/locales/zh-tw.mjs +146 -26
- package/web-ui/modules/i18n/locales/zh.mjs +148 -28
- 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 +37 -14
- package/web-ui/partials/index/panel-orchestration.html +489 -282
- package/web-ui/res/web-ui-render.precompiled.js +1049 -610
- package/web-ui/styles/layout-shell.css +157 -1
- package/web-ui/styles/navigation-panels.css +11 -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
|
}
|
|
@@ -13015,6 +13099,8 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser
|
|
|
13015
13099
|
detached: true,
|
|
13016
13100
|
taskId,
|
|
13017
13101
|
runId,
|
|
13102
|
+
threadId: plan.threadId || '',
|
|
13103
|
+
cwd: plan.cwd || '',
|
|
13018
13104
|
warnings: validation.warnings || []
|
|
13019
13105
|
};
|
|
13020
13106
|
} else {
|
|
@@ -13859,7 +13945,11 @@ function printTaskHelp() {
|
|
|
13859
13945
|
console.log(' --allow-write 允许写入工作区');
|
|
13860
13946
|
console.log(' --dry-run 仅计划/预演,不执行写入');
|
|
13861
13947
|
console.log(' --plan-only 仅输出计划,不执行');
|
|
13862
|
-
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(兼容别名)');
|
|
13863
13953
|
console.log(' --concurrency <N> 并发度');
|
|
13864
13954
|
console.log(' --auto-fix-rounds <N> 自动修复回合数');
|
|
13865
13955
|
console.log(' --limit <N> runs/queue list 数量');
|
|
@@ -13882,7 +13972,7 @@ function parseTaskCliOptions(args = []) {
|
|
|
13882
13972
|
allowWrite: false,
|
|
13883
13973
|
dryRun: false,
|
|
13884
13974
|
planOnly: false,
|
|
13885
|
-
engine: '
|
|
13975
|
+
engine: 'openai-chat',
|
|
13886
13976
|
concurrency: 2,
|
|
13887
13977
|
autoFixRounds: 1,
|
|
13888
13978
|
limit: 20,
|
|
@@ -14029,6 +14119,38 @@ function parseTaskCliOptions(args = []) {
|
|
|
14029
14119
|
options.explicit.autoFixRounds = true;
|
|
14030
14120
|
continue;
|
|
14031
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
|
+
}
|
|
14032
14154
|
if (arg === '--limit') {
|
|
14033
14155
|
const value = parseInt(args[i + 1], 10);
|
|
14034
14156
|
if (Number.isFinite(value)) options.limit = value;
|
|
@@ -14086,9 +14208,11 @@ function buildTaskCliPayload(options = {}, rest = []) {
|
|
|
14086
14208
|
if (explicit.followUps && Array.isArray(options.followUps)) payload.followUps = options.followUps.slice();
|
|
14087
14209
|
if (explicit.allowWrite) payload.allowWrite = options.allowWrite === true;
|
|
14088
14210
|
if (explicit.dryRun) payload.dryRun = options.dryRun === true;
|
|
14089
|
-
if (explicit.engine) payload.engine = options.engine || '
|
|
14211
|
+
if (explicit.engine) payload.engine = options.engine || 'openai-chat';
|
|
14090
14212
|
if (explicit.concurrency) payload.concurrency = options.concurrency;
|
|
14091
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;
|
|
14092
14216
|
if (explicit.taskId && options.taskId) payload.taskId = options.taskId;
|
|
14093
14217
|
if (explicit.runId && options.runId) payload.runId = options.runId;
|
|
14094
14218
|
if (!payload.target && Array.isArray(rest) && rest.length > 0) {
|
|
@@ -14102,7 +14226,7 @@ function buildTaskCliPayload(options = {}, rest = []) {
|
|
|
14102
14226
|
|
|
14103
14227
|
function printTaskPlanSummary(plan, warnings = []) {
|
|
14104
14228
|
console.log(`\n任务计划: ${plan.title || '(untitled)'}`);
|
|
14105
|
-
console.log(` engine: ${plan.engine || '
|
|
14229
|
+
console.log(` engine: ${plan.engine || 'openai-chat'}`);
|
|
14106
14230
|
console.log(` allowWrite: ${plan.allowWrite === true ? 'yes' : 'no'}`);
|
|
14107
14231
|
console.log(` dryRun: ${plan.dryRun === true ? 'yes' : 'no'}`);
|
|
14108
14232
|
console.log(` concurrency: ${plan.concurrency || 1}`);
|
|
@@ -15556,6 +15680,10 @@ function createTaskRunId() {
|
|
|
15556
15680
|
return `tr-${Date.now()}-${crypto.randomBytes(3).toString('hex')}`;
|
|
15557
15681
|
}
|
|
15558
15682
|
|
|
15683
|
+
function createTaskThreadId() {
|
|
15684
|
+
return `tt-${Date.now()}-${crypto.randomBytes(3).toString('hex')}`;
|
|
15685
|
+
}
|
|
15686
|
+
|
|
15559
15687
|
function validateTaskRunId(value) {
|
|
15560
15688
|
const runId = typeof value === 'string' ? value.trim() : '';
|
|
15561
15689
|
if (!runId) {
|
|
@@ -15567,9 +15695,19 @@ function validateTaskRunId(value) {
|
|
|
15567
15695
|
return { ok: true, error: '', runId };
|
|
15568
15696
|
}
|
|
15569
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
|
+
|
|
15570
15706
|
function normalizeTaskEngine(value) {
|
|
15571
|
-
const normalized = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
15572
|
-
|
|
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';
|
|
15573
15711
|
}
|
|
15574
15712
|
|
|
15575
15713
|
function normalizeTaskFollowUps(input = []) {
|
|
@@ -15610,6 +15748,7 @@ function normalizeTaskPlanRequest(params = {}) {
|
|
|
15610
15748
|
: (typeof source.followUp === 'string' && source.followUp.trim() ? [source.followUp.trim()] : []);
|
|
15611
15749
|
return {
|
|
15612
15750
|
id: typeof source.id === 'string' ? source.id.trim() : '',
|
|
15751
|
+
threadId: normalizeTaskThreadId(source.threadId || source.conversationId || source.sessionId),
|
|
15613
15752
|
title: typeof source.title === 'string' ? source.title.trim() : '',
|
|
15614
15753
|
target: typeof source.target === 'string' ? source.target.trim() : '',
|
|
15615
15754
|
notes: typeof source.notes === 'string' ? source.notes.trim() : '',
|
|
@@ -15619,6 +15758,7 @@ function normalizeTaskPlanRequest(params = {}) {
|
|
|
15619
15758
|
dryRun: source.dryRun === true,
|
|
15620
15759
|
concurrency: Number.isFinite(source.concurrency) ? source.concurrency : parseInt(source.concurrency, 10),
|
|
15621
15760
|
autoFixRounds: Number.isFinite(source.autoFixRounds) ? source.autoFixRounds : parseInt(source.autoFixRounds, 10),
|
|
15761
|
+
previewOnly: source.previewOnly === true,
|
|
15622
15762
|
workflowIds: rawWorkflowIds,
|
|
15623
15763
|
followUps: normalizeTaskFollowUps(rawFollowUps)
|
|
15624
15764
|
};
|
|
@@ -15627,12 +15767,18 @@ function normalizeTaskPlanRequest(params = {}) {
|
|
|
15627
15767
|
function coerceTaskPlanPayload(params = {}) {
|
|
15628
15768
|
if (params && params.plan && typeof params.plan === 'object' && !Array.isArray(params.plan)) {
|
|
15629
15769
|
const plan = cloneJson(params.plan, {});
|
|
15630
|
-
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'];
|
|
15631
15771
|
for (const key of overrideKeys) {
|
|
15632
15772
|
if (Object.prototype.hasOwnProperty.call(params, key) && params[key] !== undefined) {
|
|
15633
|
-
|
|
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
|
+
}
|
|
15634
15778
|
}
|
|
15635
15779
|
}
|
|
15780
|
+
plan.cwd = typeof plan.cwd === 'string' && plan.cwd.trim() ? plan.cwd.trim() : process.cwd();
|
|
15781
|
+
plan.threadId = normalizeTaskThreadId(plan.threadId) || createTaskThreadId();
|
|
15636
15782
|
plan.engine = normalizeTaskEngine(plan.engine);
|
|
15637
15783
|
plan.workflowIds = normalizeTaskFollowUps(plan.workflowIds || []).map((id) => normalizeWorkflowId(id)).filter(Boolean);
|
|
15638
15784
|
plan.followUps = normalizeTaskFollowUps(plan.followUps || []);
|
|
@@ -15647,6 +15793,7 @@ function coerceTaskPlanPayload(params = {}) {
|
|
|
15647
15793
|
});
|
|
15648
15794
|
return {
|
|
15649
15795
|
...plan,
|
|
15796
|
+
threadId: request.threadId || createTaskThreadId(),
|
|
15650
15797
|
engine: normalizeTaskEngine(request.engine || plan.engine)
|
|
15651
15798
|
};
|
|
15652
15799
|
}
|
|
@@ -15669,8 +15816,10 @@ function normalizeTaskQueueItem(raw = {}) {
|
|
|
15669
15816
|
const taskId = typeof raw.taskId === 'string' ? raw.taskId.trim() : '';
|
|
15670
15817
|
return {
|
|
15671
15818
|
taskId: taskId || createTaskId(),
|
|
15819
|
+
threadId: normalizeTaskThreadId(raw.threadId || plan.threadId) || createTaskThreadId(),
|
|
15672
15820
|
title: typeof raw.title === 'string' ? raw.title.trim() : (typeof plan.title === 'string' ? plan.title.trim() : ''),
|
|
15673
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() : ''),
|
|
15674
15823
|
status: typeof raw.status === 'string' ? raw.status.trim().toLowerCase() : 'queued',
|
|
15675
15824
|
createdAt: toIsoTime(raw.createdAt || Date.now(), ''),
|
|
15676
15825
|
updatedAt: toIsoTime(raw.updatedAt || raw.createdAt || Date.now(), ''),
|
|
@@ -15871,8 +16020,10 @@ function collectTaskRunSummary(detail = {}) {
|
|
|
15871
16020
|
return {
|
|
15872
16021
|
runId: detail.runId || '',
|
|
15873
16022
|
taskId: detail.taskId || '',
|
|
16023
|
+
threadId: detail.threadId || '',
|
|
15874
16024
|
title: detail.title || '',
|
|
15875
16025
|
target: detail.target || '',
|
|
16026
|
+
cwd: detail.cwd || '',
|
|
15876
16027
|
engine: detail.engine || '',
|
|
15877
16028
|
allowWrite: detail.allowWrite === true,
|
|
15878
16029
|
dryRun: detail.dryRun === true,
|
|
@@ -15996,6 +16147,7 @@ function buildTaskOverviewPayload(options = {}) {
|
|
|
15996
16147
|
}
|
|
15997
16148
|
return {
|
|
15998
16149
|
workflows: workflowCatalog.workflows,
|
|
16150
|
+
openAiChatStatus: buildTaskOpenAiChatStatus(),
|
|
15999
16151
|
warnings,
|
|
16000
16152
|
queue,
|
|
16001
16153
|
runs,
|
|
@@ -16058,20 +16210,434 @@ function readCodexLastMessageFile(filePath) {
|
|
|
16058
16210
|
}
|
|
16059
16211
|
}
|
|
16060
16212
|
|
|
16061
|
-
|
|
16062
|
-
const
|
|
16063
|
-
|
|
16064
|
-
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) {
|
|
16065
16608
|
return {
|
|
16066
16609
|
success: false,
|
|
16067
|
-
error:
|
|
16068
|
-
summary:
|
|
16610
|
+
error: requestConfig.error,
|
|
16611
|
+
summary: requestConfig.error,
|
|
16069
16612
|
output: null,
|
|
16070
|
-
logs: [{ at: toIsoTime(Date.now()), level: 'error', message:
|
|
16613
|
+
logs: [{ at: toIsoTime(Date.now()), level: 'error', message: requestConfig.error }]
|
|
16614
|
+
};
|
|
16615
|
+
}
|
|
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 }]
|
|
16071
16638
|
};
|
|
16072
16639
|
}
|
|
16073
|
-
const allowWrite = context.allowWrite === true && node.write
|
|
16074
|
-
const cwd = typeof context.cwd === 'string' && context.cwd.trim() ? context.cwd.trim() : process.cwd();
|
|
16640
|
+
const allowWrite = context.allowWrite === true && node.write !== false && context.dryRun !== true;
|
|
16075
16641
|
const dependencyResults = Array.isArray(context.dependencyResults) ? context.dependencyResults : [];
|
|
16076
16642
|
const dependencyLines = dependencyResults
|
|
16077
16643
|
.map((item) => {
|
|
@@ -16091,124 +16657,95 @@ async function runCodexExecTaskNode(node, context = {}) {
|
|
|
16091
16657
|
promptParts.push('请在保持目标不变的前提下修复上一轮失败并继续完成当前节点。');
|
|
16092
16658
|
}
|
|
16093
16659
|
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);
|
|
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
|
|
16154
16696
|
};
|
|
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
|
-
});
|
|
16697
|
+
const result = await postOpenAiChatCompletion(requestConfig, body, {
|
|
16698
|
+
registerAbort: context.registerAbort
|
|
16182
16699
|
});
|
|
16183
|
-
const
|
|
16184
|
-
|
|
16185
|
-
|
|
16186
|
-
|
|
16187
|
-
|
|
16188
|
-
|
|
16189
|
-
|
|
16190
|
-
|
|
16191
|
-
|
|
16192
|
-
|
|
16193
|
-
|
|
16194
|
-
|
|
16195
|
-
|
|
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' };
|
|
16196
16722
|
return {
|
|
16197
16723
|
success,
|
|
16198
16724
|
error: errorMessage,
|
|
16199
16725
|
summary,
|
|
16200
16726
|
output: {
|
|
16201
|
-
|
|
16202
|
-
|
|
16203
|
-
|
|
16204
|
-
|
|
16205
|
-
|
|
16206
|
-
|
|
16207
|
-
|
|
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
|
|
16208
16739
|
},
|
|
16209
16740
|
logs: [
|
|
16210
|
-
|
|
16211
|
-
...
|
|
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 }] : [])
|
|
16212
16749
|
]
|
|
16213
16750
|
};
|
|
16214
16751
|
}
|
|
@@ -16246,7 +16783,7 @@ async function executeTaskNodeAdapter(node, context = {}) {
|
|
|
16246
16783
|
: []
|
|
16247
16784
|
};
|
|
16248
16785
|
}
|
|
16249
|
-
return
|
|
16786
|
+
return runOpenAiChatTaskNode(node, context);
|
|
16250
16787
|
}
|
|
16251
16788
|
|
|
16252
16789
|
async function runTaskPlanInternal(plan, options = {}) {
|
|
@@ -16260,13 +16797,18 @@ async function runTaskPlanInternal(plan, options = {}) {
|
|
|
16260
16797
|
}
|
|
16261
16798
|
const taskId = typeof options.taskId === 'string' && options.taskId.trim() ? options.taskId.trim() : (plan.id || createTaskId());
|
|
16262
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();
|
|
16263
16803
|
const controller = new AbortController();
|
|
16264
16804
|
const baseDetail = {
|
|
16265
16805
|
runId,
|
|
16266
16806
|
taskId,
|
|
16807
|
+
threadId,
|
|
16267
16808
|
workerPid: process.pid,
|
|
16268
16809
|
title: plan.title || '',
|
|
16269
16810
|
target: plan.target || '',
|
|
16811
|
+
cwd,
|
|
16270
16812
|
engine: normalizeTaskEngine(plan.engine),
|
|
16271
16813
|
allowWrite: plan.allowWrite === true,
|
|
16272
16814
|
dryRun: plan.dryRun === true,
|
|
@@ -16302,6 +16844,8 @@ async function runTaskPlanInternal(plan, options = {}) {
|
|
|
16302
16844
|
const queued = upsertTaskQueueItem({
|
|
16303
16845
|
...options.queueItem,
|
|
16304
16846
|
taskId,
|
|
16847
|
+
threadId,
|
|
16848
|
+
cwd,
|
|
16305
16849
|
status: 'running',
|
|
16306
16850
|
runStatus: 'running',
|
|
16307
16851
|
lastRunId: runId,
|
|
@@ -16320,9 +16864,10 @@ async function runTaskPlanInternal(plan, options = {}) {
|
|
|
16320
16864
|
plan,
|
|
16321
16865
|
taskId,
|
|
16322
16866
|
runId,
|
|
16867
|
+
threadId,
|
|
16323
16868
|
allowWrite: plan.allowWrite === true,
|
|
16324
16869
|
dryRun: plan.dryRun === true,
|
|
16325
|
-
cwd
|
|
16870
|
+
cwd
|
|
16326
16871
|
}),
|
|
16327
16872
|
onUpdate: async (snapshot) => {
|
|
16328
16873
|
const nextDetail = {
|
|
@@ -16336,6 +16881,8 @@ async function runTaskPlanInternal(plan, options = {}) {
|
|
|
16336
16881
|
const queued = upsertTaskQueueItem({
|
|
16337
16882
|
...options.queueItem,
|
|
16338
16883
|
taskId,
|
|
16884
|
+
threadId,
|
|
16885
|
+
cwd,
|
|
16339
16886
|
status: snapshot.status === 'success'
|
|
16340
16887
|
? 'completed'
|
|
16341
16888
|
: (snapshot.status === 'failed' ? 'failed' : (snapshot.status === 'cancelled' ? 'cancelled' : 'running')),
|
|
@@ -16365,6 +16912,8 @@ async function runTaskPlanInternal(plan, options = {}) {
|
|
|
16365
16912
|
const queued = upsertTaskQueueItem({
|
|
16366
16913
|
...options.queueItem,
|
|
16367
16914
|
taskId,
|
|
16915
|
+
threadId,
|
|
16916
|
+
cwd,
|
|
16368
16917
|
status: run.status === 'success'
|
|
16369
16918
|
? 'completed'
|
|
16370
16919
|
: (run.status === 'cancelled' ? 'cancelled' : 'failed'),
|
|
@@ -16395,8 +16944,10 @@ function addTaskToQueue(params = {}) {
|
|
|
16395
16944
|
const taskId = typeof params.taskId === 'string' && params.taskId.trim() ? params.taskId.trim() : createTaskId();
|
|
16396
16945
|
const item = upsertTaskQueueItem({
|
|
16397
16946
|
taskId,
|
|
16947
|
+
threadId: plan.threadId || createTaskThreadId(),
|
|
16398
16948
|
title: plan.title,
|
|
16399
16949
|
target: plan.target,
|
|
16950
|
+
cwd: plan.cwd || process.cwd(),
|
|
16400
16951
|
status: 'queued',
|
|
16401
16952
|
createdAt: toIsoTime(Date.now()),
|
|
16402
16953
|
updatedAt: toIsoTime(Date.now()),
|