Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f4b2f57
fix(orchestration): redact private task details
awsl233777 Jun 30, 2026
0a57a0e
fix(orchestration): keep workspace selector private
awsl233777 Jun 30, 2026
94c89bd
fix(orchestration): localize Japanese privacy labels
awsl233777 Jun 30, 2026
a076274
fix(orchestration): add private workspace split action
awsl233777 Jun 30, 2026
9425163
fix(orchestration): mask advanced errors and inputs
awsl233777 Jun 30, 2026
eb8696f
fix(orchestration): hide plan node identifiers
awsl233777 Jun 30, 2026
e253c0c
fix(orchestration): hide task toast details
awsl233777 Jun 30, 2026
115a8ee
feat(orchestration): add Kilo webview host shim
awsl233777 Jun 30, 2026
02440cd
refactor(orchestration): align tab with Kilo app shell
awsl233777 Jun 30, 2026
ef76a11
refactor(orchestration): replace tab with Kilo webview UI
awsl233777 Jun 30, 2026
c617770
refactor(orchestration): remove legacy tab settings surface
awsl233777 Jun 30, 2026
41ba76d
fix(orchestration): remove inert title button semantics
awsl233777 Jun 30, 2026
2edac28
fix(orchestration): align tab with chat plugin surface
awsl233777 Jun 30, 2026
950b68b
fix(orchestration): neutralize task chat surface copy
awsl233777 Jun 30, 2026
6f435db
test(orchestration): cover chat composer locale keys
awsl233777 Jun 30, 2026
3602196
fix(orchestration): harden kilo webview bridge privacy
awsl233777 Jun 30, 2026
64d442c
fix(orchestration): keep task composer visible
awsl233777 Jul 1, 2026
354ec45
style(orchestration): separate custom task sizing properties
awsl233777 Jul 1, 2026
e1ed927
style(orchestration): align task surface with reference app
awsl233777 Jul 1, 2026
0ca4836
fix(orchestration): align task UI with chat shell
awsl233777 Jul 1, 2026
c7d935f
fix(providers): keep endpoint validation unchanged
awsl233777 Jul 1, 2026
6e465ba
fix(orchestration): keep changes scoped to task tab
awsl233777 Jul 1, 2026
fb0d484
fix(orchestration): add OpenAI chat provider fallback
awsl233777 Jul 2, 2026
c42afdf
fix(orchestration): surface provider setup in task tab
awsl233777 Jul 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
198 changes: 137 additions & 61 deletions cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -16208,17 +16208,22 @@ function pickTaskOpenAiChatProviderName(config) {
return typeof config.model_provider === 'string' ? config.model_provider.trim() : '';
}

function resolveTaskOpenAiChatConfig() {
const configResult = readConfigOrVirtualDefault();
const config = configResult && configResult.config && typeof configResult.config === 'object' ? configResult.config : {};
const providerName = pickTaskOpenAiChatProviderName(config);
if (!providerName) {
return { error: '未设置当前 OpenAI Chat 提供商' };
function taskOpenAiChatProviderHasAuth(config) {
const hasApiKey = typeof config.apiKey === 'string' && config.apiKey.trim();
const hasExtraHeaders = config.extraHeaders
&& typeof config.extraHeaders === 'object'
&& !Array.isArray(config.extraHeaders)
&& Object.keys(config.extraHeaders).length > 0;
return !!(hasApiKey || hasExtraHeaders);
}

function resolveTaskOpenAiChatProviderConfig(providerName, provider, config) {
const name = typeof providerName === 'string' ? providerName.trim() : '';
if (!name) {
return { error: 'OpenAI Chat 提供商名称为空' };
}
const providers = config.model_providers && typeof config.model_providers === 'object' ? config.model_providers : {};
const provider = providers[providerName];
if (!provider || typeof provider !== 'object') {
return { error: `OpenAI Chat 提供商不存在: ${providerName}` };
return { error: `OpenAI Chat 提供商不存在: ${name}` };
}

const bridgeType = typeof provider.codexmate_bridge === 'string' ? provider.codexmate_bridge.trim() : '';
Expand All @@ -16228,7 +16233,7 @@ function resolveTaskOpenAiChatConfig() {
let apiKey = typeof provider.preferred_auth_method === 'string' ? provider.preferred_auth_method.trim() : '';
let extraHeaders = {};
if (isOpenaiBridgeProvider) {
const upstream = resolveOpenaiBridgeUpstream(OPENAI_BRIDGE_SETTINGS_FILE, providerName);
const upstream = resolveOpenaiBridgeUpstream(OPENAI_BRIDGE_SETTINGS_FILE, name);
if (upstream && !upstream.error) {
baseUrl = upstream.baseUrl || baseUrl;
apiKey = upstream.apiKey || apiKey;
Expand All @@ -16238,18 +16243,18 @@ function resolveTaskOpenAiChatConfig() {
}
}

const model = pickTaskProviderModel(providerName, provider, config);
const model = pickTaskProviderModel(name, provider, config);
if (!baseUrl) {
return { error: `OpenAI Chat 提供商 ${providerName} 缺少 base_url` };
return { error: `OpenAI Chat 提供商 ${name} 缺少 base_url` };
}
if (!isValidHttpUrl(baseUrl)) {
return { error: `OpenAI Chat 提供商 ${providerName} 的 base_url 无效` };
return { error: `OpenAI Chat 提供商 ${name} 的 base_url 无效` };
}
if (!model) {
return { error: `OpenAI Chat 提供商 ${providerName} 未设置模型` };
return { error: `OpenAI Chat 提供商 ${name} 未设置模型` };
}
return {
providerName,
providerName: name,
baseUrl,
endpointUrl: buildOpenAiChatEndpointUrl(baseUrl),
apiKey,
Expand All @@ -16259,6 +16264,52 @@ function resolveTaskOpenAiChatConfig() {
};
}

function resolveTaskOpenAiChatConfig() {
const configResult = readConfigOrVirtualDefault();
const config = configResult && configResult.config && typeof configResult.config === 'object' ? configResult.config : {};
const providerName = pickTaskOpenAiChatProviderName(config);
if (!providerName) {
return { error: '未设置当前 OpenAI Chat 提供商' };
}
const providers = config.model_providers && typeof config.model_providers === 'object' ? config.model_providers : {};
return resolveTaskOpenAiChatProviderConfig(providerName, providers[providerName], config);
}

function listTaskOpenAiChatConfigs() {
const configResult = readConfigOrVirtualDefault();
const config = configResult && configResult.config && typeof configResult.config === 'object' ? configResult.config : {};
const providers = config.model_providers && typeof config.model_providers === 'object' ? config.model_providers : {};
const primaryName = pickTaskOpenAiChatProviderName(config);
const orderedNames = [];
if (primaryName) orderedNames.push(primaryName);
Object.keys(providers).forEach((name) => {
if (!orderedNames.includes(name)) orderedNames.push(name);
});
const configs = [];
const errors = [];
for (const name of orderedNames) {
const provider = providers[name];
const isPrimary = name === primaryName;
const wireApi = typeof provider?.wire_api === 'string' ? provider.wire_api.trim() : '';
const bridgeType = typeof provider?.codexmate_bridge === 'string' ? provider.codexmate_bridge.trim() : '';
const baseUrl = typeof provider?.base_url === 'string' ? provider.base_url.trim() : '';
const looksChatCompatible = isPrimary
|| wireApi === 'chat_completions'
|| bridgeType === 'openai'
|| baseUrl.includes('/bridge/openai/');
if (!looksChatCompatible) {
continue;
}
const resolved = resolveTaskOpenAiChatProviderConfig(name, provider, config);
if (resolved && resolved.error) {
if (isPrimary) errors.push(resolved.error);
continue;
}
resolved.primary = isPrimary;
configs.push(resolved);
}
return { configs, errors, primaryName };
}
function buildTaskOpenAiChatStatus() {
const requestConfig = resolveTaskOpenAiChatConfig();
if (requestConfig && requestConfig.error) {
Expand Down Expand Up @@ -16529,37 +16580,29 @@ function materializeOpenAiChatTaskArtifacts(text, options = {}) {
}

async function runOpenAiChatTaskNode(node, context = {}) {
const requestConfig = resolveTaskOpenAiChatConfig();
if (requestConfig.error) {
return {
success: false,
error: requestConfig.error,
summary: requestConfig.error,
output: null,
logs: [{ at: toIsoTime(Date.now()), level: 'error', message: requestConfig.error }]
};
}
const hasApiKey = typeof requestConfig.apiKey === 'string' && requestConfig.apiKey.trim();
const hasExtraHeaders = requestConfig.extraHeaders
&& typeof requestConfig.extraHeaders === 'object'
&& !Array.isArray(requestConfig.extraHeaders)
&& Object.keys(requestConfig.extraHeaders).length > 0;
if (!hasApiKey && !hasExtraHeaders) {
const error = `OpenAI Chat 提供商 ${requestConfig.providerName || ''} 缺少 API key 或额外 headers`;
const candidatesResult = listTaskOpenAiChatConfigs();
const allCandidates = Array.isArray(candidatesResult.configs) ? candidatesResult.configs : [];
const requestCandidates = allCandidates.filter(taskOpenAiChatProviderHasAuth);
if (requestCandidates.length === 0) {
const firstConfig = allCandidates[0] || {};
const error = allCandidates.length > 0
? `OpenAI Chat 提供商 ${firstConfig.providerName || candidatesResult.primaryName || ''} 缺少 API key 或额外 headers`
: ((candidatesResult.errors && candidatesResult.errors[0]) || '未找到可用 OpenAI Chat 提供商');
return {
success: false,
error,
summary: error,
output: {
provider: requestConfig.providerName || '',
model: requestConfig.model || '',
endpoint: redactTaskEndpointUrl(requestConfig.endpointUrl || ''),
output: allCandidates.length > 0 ? {
provider: firstConfig.providerName || '',
model: firstConfig.model || '',
endpoint: redactTaskEndpointUrl(firstConfig.endpointUrl || ''),
status: 0,
text: '',
response: null,
durationMs: 0,
materializedFiles: []
},
materializedFiles: [],
attemptedProviders: allCandidates.map((item) => item.providerName).filter(Boolean)
} : null,
logs: [{ at: toIsoTime(Date.now()), level: 'error', message: error }]
};
}
Expand Down Expand Up @@ -16607,25 +16650,57 @@ async function runOpenAiChatTaskNode(node, context = {}) {
workspaceContext
].filter(Boolean).join('\n');
const startedAt = Date.now();
const body = {
model: requestConfig.model,
messages: [
{ role: 'system', content: systemPrompt },
...historyMessages.map((message) => ({
role: message.role === 'assistant' ? 'assistant' : 'user',
content: String(message.content || '')
})),
{ role: 'user', content: currentUserMessage }
],
temperature: Number.isFinite(requestConfig.temperature) ? requestConfig.temperature : 0.2,
stream: false
};
const result = await postOpenAiChatCompletion(requestConfig, body, {
registerAbort: context.registerAbort
});
const text = result.ok ? extractModelResponseText(result.payload) : '';
const success = result.ok && !!text;
const errorMessage = success ? '' : (result.error || 'OpenAI Chat response did not contain text');
const messages = [
{ role: 'system', content: systemPrompt },
...historyMessages.map((message) => ({
role: message.role === 'assistant' ? 'assistant' : 'user',
content: String(message.content || '')
})),
{ role: 'user', content: currentUserMessage }
];
const attemptLogs = [];
const attemptedProviders = [];
let requestConfig = requestCandidates[0];
let result = { ok: false, status: 0, error: 'OpenAI Chat request was not attempted', payload: null, body: '' };
let text = '';
let success = false;
let errorMessage = '';
for (let index = 0; index < requestCandidates.length; index += 1) {
const candidate = requestCandidates[index];
attemptedProviders.push(candidate.providerName || '');
const body = {
model: candidate.model,
messages,
temperature: Number.isFinite(candidate.temperature) ? candidate.temperature : 0.2,
stream: false
};
const attemptResult = await postOpenAiChatCompletion(candidate, body, {
registerAbort: context.registerAbort
});
const attemptText = attemptResult.ok ? extractModelResponseText(attemptResult.payload) : '';
const attemptSuccess = attemptResult.ok && !!attemptText;
const attemptError = attemptSuccess ? '' : (attemptResult.error || 'OpenAI Chat response did not contain text');
attemptLogs.push({
at: toIsoTime(Date.now()),
level: attemptSuccess ? 'info' : 'warn',
message: `OpenAI Chat request provider=${candidate.providerName} model=${candidate.model} status=${attemptResult.status || 0}${attemptError ? ` error=${truncateTaskText(attemptError, 180)}` : ''}`
});
requestConfig = candidate;
result = attemptResult;
text = attemptText;
success = attemptSuccess;
errorMessage = attemptError;
if (attemptSuccess) {
break;
}
if (index < requestCandidates.length - 1) {
attemptLogs.push({
at: toIsoTime(Date.now()),
level: 'warn',
message: `OpenAI Chat provider fallback: ${candidate.providerName} -> ${requestCandidates[index + 1].providerName}`
});
}
}
const summary = truncateTaskText(text || errorMessage, 400);
const safeEndpoint = redactTaskEndpointUrl(requestConfig.endpointUrl);
const materialized = success && allowWrite
Expand Down Expand Up @@ -16657,15 +16732,16 @@ async function runOpenAiChatTaskNode(node, context = {}) {
text,
response: result.payload || null,
durationMs: Date.now() - startedAt,
messageCount: body.messages.length,
messageCount: messages.length,
threadId,
threadStore: threadAppend && threadAppend.file ? threadAppend.file : '',
materializedFiles: materialized.files,
workspaceFiles: workspaceOperations.files
workspaceFiles: workspaceOperations.files,
attemptedProviders: attemptedProviders.filter(Boolean)
},
logs: [
{ at: toIsoTime(Date.now()), level: 'info', message: `OpenAI Chat request provider=${requestConfig.providerName} model=${requestConfig.model} status=${result.status || 0}` },
...(threadId ? [{ at: toIsoTime(Date.now()), level: 'info', message: `workspace chat thread=${threadId} messages=${body.messages.length}` }] : []),
...attemptLogs,
...(threadId ? [{ at: toIsoTime(Date.now()), level: 'info', message: `workspace chat thread=${threadId} messages=${messages.length}` }] : []),
...(success ? [{ at: toIsoTime(Date.now()), level: 'info', message: truncateTaskText(text, 1200) }] : [{ at: toIsoTime(Date.now()), level: 'error', message: errorMessage }]),
...materialized.files.map((file) => ({ at: toIsoTime(Date.now()), level: 'info', message: `materialized artifact ${file.relativePath} (${file.bytes} bytes)` })),
...workspaceOperations.files.map((file) => ({ at: toIsoTime(Date.now()), level: 'info', message: `workspace ${file.operation} ${file.relativePath} (${file.bytes} bytes)` })),
Expand Down
Loading
Loading