From f4b2f5768cb4976a0d50479447f103fe5e9be502 Mon Sep 17 00:00:00 2001 From: awsl233777 Date: Tue, 30 Jun 2026 08:46:28 +0000 Subject: [PATCH 01/25] fix(orchestration): redact private task details --- tests/unit/config-tabs-ui.test.mjs | 11 ++ tests/unit/web-ui-logic.test.mjs | 5 +- web-ui/modules/app.computed.main-tabs.mjs | 30 ++---- .../app.methods.task-orchestration.mjs | 12 +-- web-ui/modules/i18n/locales/en.mjs | 7 ++ web-ui/modules/i18n/locales/ja.mjs | 7 ++ web-ui/modules/i18n/locales/vi.mjs | 7 ++ web-ui/modules/i18n/locales/zh-tw.mjs | 7 ++ web-ui/modules/i18n/locales/zh.mjs | 7 ++ .../partials/index/panel-orchestration.html | 64 +++++------ web-ui/res/web-ui-render.precompiled.js | 100 ++++++------------ 11 files changed, 125 insertions(+), 132 deletions(-) diff --git a/tests/unit/config-tabs-ui.test.mjs b/tests/unit/config-tabs-ui.test.mjs index 96152d4f..287cdc3c 100644 --- a/tests/unit/config-tabs-ui.test.mjs +++ b/tests/unit/config-tabs-ui.test.mjs @@ -156,6 +156,17 @@ test('config template keeps expected config tabs in top and side navigation', () assert.doesNotMatch(orchestrationPanel, /taskOrchestration\.openAiChatStatus\.model \|\| t\('common\.none'\)/); assert.doesNotMatch(orchestrationPanel, /session\.title/); assert.doesNotMatch(orchestrationPanel, /session\.meta \|\| session\.id/); + assert.doesNotMatch(orchestrationPanel, /taskOrchestration\.plan\.threadId\s*\}\}/); + assert.doesNotMatch(orchestrationPanel, /taskOrchestration\.plan\.cwd\s*\}\}/); + assert.doesNotMatch(orchestrationPanel, /taskOrchestrationSelectedRun\.threadId\s*\}\}/); + assert.doesNotMatch(orchestrationPanel, /taskOrchestrationSelectedRun\.cwd\s*\}\}/); + assert.doesNotMatch(orchestrationPanel, /taskOrchestrationSelectedRun\.run\.summary \|\|/); + assert.doesNotMatch(orchestrationPanel, /taskOrchestrationWorkspaceQueue\[0\]\.(title|target|taskId)/); + assert.doesNotMatch(orchestrationPanel, /taskOrchestrationWorkspaceRuns\[0\]\.(summary|runId)/); + assert.doesNotMatch(orchestrationPanel, /\{\{ item\.(title|target|taskId|runId|threadId|cwd|summary|lastSummary)/); + assert.doesNotMatch(orchestrationPanel, /\{\{ node\.(summary|error)/); + assert.doesNotMatch(orchestrationPanel, /\{\{ node\.output\.(provider|model|endpoint)/); + assert.doesNotMatch(orchestrationPanel, /file\.(relativePath|path)/); assert.match(orchestrationPanel, /class="task-chat-context-chip task-chat-context-action" @click="taskOrchestration\.settingsOpen = true">\{\{ t\('orchestration\.advanced\.open'\) \}\}<\/button>/); assert.match(orchestrationPanel, /@change="taskOrchestration\.selectedEngine === 'workflow' \? null : taskOrchestration\.workflowIdsText = ''"/); assert.doesNotMatch(orchestrationPanel, /task-thread-suggestion-card/); diff --git a/tests/unit/web-ui-logic.test.mjs b/tests/unit/web-ui-logic.test.mjs index 34471950..8dc32ea8 100644 --- a/tests/unit/web-ui-logic.test.mjs +++ b/tests/unit/web-ui-logic.test.mjs @@ -1893,7 +1893,7 @@ test('task workspace actions switch projects, start clean sessions, and resume q assert.deepStrictEqual(detailCalls, [{ runId: 'run-history', options: { silent: true, switchToDetail: true } }]); assert.deepStrictEqual(continued, ['run-history']); assert.strictEqual(messages[0].message, '已为当前工作区开始新会话'); - assert.strictEqual(messages[1].message, '已恢复队列任务上下文: task-queued'); + assert.strictEqual(messages[1].message, '已恢复队列上下文'); }); test('taskOrchestrationWorkbenchVisible ignores passive failed run history', () => { @@ -2062,8 +2062,9 @@ test('task orchestration detail helpers surface AI output and open OpenAI Chat c } }; - assert.strictEqual(methods.formatTaskNodeOutputText.call(context, { output: { text: ' actual AI result ' } }), 'actual AI result'); + assert.strictEqual(methods.formatTaskNodeOutputText.call(context, { output: { text: ' actual AI result ' } }), '(output hidden)'); assert.strictEqual(methods.formatTaskNodeOutputText.call(context, { output: {} }), '(no output)'); + assert.strictEqual(methods.formatTaskNodeLogs.call(context, [{ message: 'secret /tmp/workspace thread-1' }]), '(1 log entries hidden)'); methods.openTaskOpenAiChatConfig.call(context); diff --git a/web-ui/modules/app.computed.main-tabs.mjs b/web-ui/modules/app.computed.main-tabs.mjs index 1c51a74d..beb6284e 100644 --- a/web-ui/modules/app.computed.main-tabs.mjs +++ b/web-ui/modules/app.computed.main-tabs.mjs @@ -51,29 +51,19 @@ function formatTaskConversationMeta(items) { return items.filter(Boolean).join(' · '); } -function collectTaskRunWorkspaceFileLines(detail) { +function countTaskRunWorkspaceFiles(detail) { const run = detail && detail.run && typeof detail.run === 'object' ? detail.run : null; const nodes = Array.isArray(detail && detail.nodes) ? detail.nodes : (Array.isArray(run && run.nodes) ? run.nodes : []); - const files = []; + let count = 0; for (const node of nodes) { const output = node && node.output && typeof node.output === 'object' ? node.output : null; const workspaceFiles = Array.isArray(output && output.workspaceFiles) ? output.workspaceFiles : []; const materializedFiles = Array.isArray(output && output.materializedFiles) ? output.materializedFiles : []; - for (const file of workspaceFiles) { - if (!file || typeof file !== 'object') continue; - const op = String(file.operation || '').trim().toUpperCase() || 'FILE'; - const filePath = String(file.relativePath || file.path || '').trim(); - if (filePath) files.push(`${op} ${filePath}`); - } - for (const file of materializedFiles) { - if (!file || typeof file !== 'object') continue; - const filePath = String(file.relativePath || file.path || '').trim(); - if (filePath) files.push(`WRITE ${filePath}`); - } + count += workspaceFiles.length + materializedFiles.length; } - return Array.from(new Set(files)).slice(0, 6); + return count; } function normalizeTaskWorkspacePath(value) { @@ -201,15 +191,15 @@ function createTaskConversationMessages(taskOrchestration, t = null) { const detail = state.selectedRunDetail && typeof state.selectedRunDetail === 'object' ? state.selectedRunDetail : null; const detailRun = detail && detail.run && typeof detail.run === 'object' ? detail.run : null; if (detail) { - const fileLines = collectTaskRunWorkspaceFileLines(detail); + const fileCount = countTaskRunWorkspaceFiles(detail); const resultLines = []; if (detailRun && detailRun.summary) { - resultLines.push(detailRun.summary); + resultLines.push(translateTaskText(t, 'orchestration.privacy.summaryHidden', '摘要已隐藏')); } else { resultLines.push(translateTaskText(t, 'orchestration.chat.assistant.contextFallback', '已选中一个历史任务,继续时会继承它的线程和工作区。')); } - if (fileLines.length > 0) { - resultLines.push(translateTaskText(t, 'orchestration.chat.assistant.filesSummary', `文件操作:${fileLines.join(';')}`, { files: fileLines.join(';') })); + if (fileCount > 0) { + resultLines.push(translateTaskText(t, 'orchestration.privacy.filesChangedCount', '文件操作已隐藏:{count} 项', { count: fileCount })); } messages.push({ id: 'context-run', @@ -219,8 +209,8 @@ function createTaskConversationMessages(taskOrchestration, t = null) { : translateTaskText(t, 'orchestration.chat.assistant.contextLabel', '上一轮上下文'), text: resultLines.join('\n'), meta: formatTaskConversationMeta([ - detail.threadId ? translateTaskText(t, 'orchestration.chat.meta.thread', `线程 ${detail.threadId}`, { value: detail.threadId }) : '', - detail.cwd ? translateTaskText(t, 'orchestration.chat.meta.workspace', `工作区 ${detail.cwd}`, { value: detail.cwd }) : '', + detail.threadId ? translateTaskText(t, 'orchestration.privacy.thread.selected', '已选择线程') : '', + detail.cwd ? translateTaskText(t, 'orchestration.privacy.workspace.selected', '已选择工作区') : '', detailRun && detailRun.status ? detailRun.status : '' ]) }); diff --git a/web-ui/modules/app.methods.task-orchestration.mjs b/web-ui/modules/app.methods.task-orchestration.mjs index 1c8aff5a..4b07525c 100644 --- a/web-ui/modules/app.methods.task-orchestration.mjs +++ b/web-ui/modules/app.methods.task-orchestration.mjs @@ -260,13 +260,13 @@ export function createTaskOrchestrationMethods(options = {}) { if (!Array.isArray(logs) || logs.length === 0) { return '(no logs)'; } - return logs.map((item) => `${item && item.at ? item.at : ''} ${item && item.level ? item.level : ''} ${item && item.message ? item.message : ''}`.trim()).join('\n'); + return `(${logs.length} log entries hidden)`; }, formatTaskNodeOutputText(node) { const output = node && node.output && typeof node.output === 'object' ? node.output : null; const text = output && typeof output.text === 'string' ? output.text.trim() : ''; - return text || '(no output)'; + return text ? '(output hidden)' : '(no output)'; }, openTaskOpenAiChatConfig() { @@ -486,7 +486,7 @@ export function createTaskOrchestrationMethods(options = {}) { await this.loadTaskRunDetail(state.selectedRunId, { silent: true }); } this.syncTaskOrchestrationPolling(); - this.showMessage(`任务已启动: ${res.runId || res.taskId || 'unknown'}`, 'success'); + this.showMessage('任务已启动', 'success'); return res; } catch (error) { const message = error && error.message ? error.message : '任务启动失败'; @@ -518,7 +518,7 @@ export function createTaskOrchestrationMethods(options = {}) { await this.loadTaskOrchestrationOverview({ silent: true, includeDetail: false }); } if (!options.silent) { - this.showMessage(`已加入队列: ${res && res.task ? res.task.taskId : ''}`.trim(), 'success'); + this.showMessage('已加入队列', 'success'); } return res; } catch (error) { @@ -744,7 +744,7 @@ export function createTaskOrchestrationMethods(options = {}) { state.selectedRunError = ''; state.workspaceTab = 'queue'; state.title = String(item.title || state.title || '').trim(); - this.showMessage(taskId ? `已恢复队列任务上下文: ${taskId}` : '已恢复会话上下文', 'success'); + this.showMessage(taskId ? '已恢复队列上下文' : '已恢复上下文', 'success'); scrollTaskOrchestrationThreadToEnd(); this.syncTaskOrchestrationPolling(); }, @@ -771,7 +771,7 @@ export function createTaskOrchestrationMethods(options = {}) { await this.loadTaskRunDetail(state.selectedRunId, { silent: true }); } this.syncTaskOrchestrationPolling(); - this.showMessage(`已开始重试: ${res.runId || normalizedRunId}`, 'success'); + this.showMessage('已开始重试', 'success'); return res; } catch (error) { const message = error && error.message ? error.message : '重试任务失败'; diff --git a/web-ui/modules/i18n/locales/en.mjs b/web-ui/modules/i18n/locales/en.mjs index d1e7a34f..f4cab30c 100644 --- a/web-ui/modules/i18n/locales/en.mjs +++ b/web-ui/modules/i18n/locales/en.mjs @@ -980,6 +980,12 @@ const en = Object.freeze({ 'orchestration.chat.context.sequence.value': 'Sequence: {count} request(s)', 'orchestration.privacy.workspace.selected': 'Workspace selected', 'orchestration.privacy.thread.selected': 'Thread selected', + 'orchestration.privacy.runSelected': 'Run selected', + 'orchestration.privacy.queueSelected': 'Queue item selected', + 'orchestration.privacy.detailsHidden': 'Details hidden for privacy', + 'orchestration.privacy.summaryHidden': 'Summary hidden for privacy', + 'orchestration.privacy.providerHidden': 'Provider details hidden', + 'orchestration.privacy.filesChangedCount': 'File operations hidden: {count} item(s)', 'orchestration.chat.thread.aria': 'Task conversation', 'orchestration.chat.input.send': 'Append request', 'orchestration.chat.input.discuss': 'Preview plan', @@ -1186,6 +1192,7 @@ const en = Object.freeze({ 'orchestration.detail.context.aria': 'Task run context', 'orchestration.detail.context.run': 'Run ID', 'orchestration.detail.node.meta': '{id} · attempts {attempts} · auto-fix {autoFix}', + 'orchestration.detail.node.step': 'Task step', 'orchestration.openai.status.title': 'OpenAI Chat runtime', 'orchestration.openai.status.subtitle': 'Task nodes use the current OpenAI Chat configuration: base URL, API key, and model are checked before execution.', 'orchestration.openai.status.provider': 'Provider', diff --git a/web-ui/modules/i18n/locales/ja.mjs b/web-ui/modules/i18n/locales/ja.mjs index 994a5f6f..0d0bad6a 100644 --- a/web-ui/modules/i18n/locales/ja.mjs +++ b/web-ui/modules/i18n/locales/ja.mjs @@ -969,6 +969,12 @@ const ja = Object.freeze({ 'orchestration.chat.context.sequence.value': '順序: {count} 件の依頼', 'orchestration.privacy.workspace.selected': 'Workspace selected', 'orchestration.privacy.thread.selected': 'Thread selected', + 'orchestration.privacy.runSelected': '実行記録を選択済み', + 'orchestration.privacy.queueSelected': 'キュー項目を選択済み', + 'orchestration.privacy.detailsHidden': '詳細は非表示', + 'orchestration.privacy.summaryHidden': '概要は非表示', + 'orchestration.privacy.providerHidden': 'Provider details hidden', + 'orchestration.privacy.filesChangedCount': 'ファイル操作は非表示: {count} 件', 'orchestration.chat.thread.aria': 'タスク会話', 'orchestration.chat.input.send': '依頼を追加', 'orchestration.chat.input.discuss': '計画プレビュー', @@ -1175,6 +1181,7 @@ const ja = Object.freeze({ 'orchestration.detail.context.aria': 'タスク実行コンテキスト', 'orchestration.detail.context.run': 'Run ID', 'orchestration.detail.node.meta': '{id} · 試行 {attempts} · 自動修正 {autoFix}', + 'orchestration.detail.node.step': 'タスクステップ', 'orchestration.openai.status.title': 'OpenAI Chat 実行設定', 'orchestration.openai.status.subtitle': 'タスクノードは現在の OpenClaw provider を使用し、実行前に base URL、API key、model を確認します。', 'orchestration.openai.status.provider': 'Provider', diff --git a/web-ui/modules/i18n/locales/vi.mjs b/web-ui/modules/i18n/locales/vi.mjs index 9abd016b..10da1b96 100644 --- a/web-ui/modules/i18n/locales/vi.mjs +++ b/web-ui/modules/i18n/locales/vi.mjs @@ -1116,6 +1116,12 @@ const vi = Object.freeze({ 'orchestration.chat.context.sequence.value': 'Thứ tự: {count} yêu cầu', 'orchestration.privacy.workspace.selected': 'Đã chọn workspace', 'orchestration.privacy.thread.selected': 'Đã chọn thread', + 'orchestration.privacy.runSelected': 'Đã chọn bản ghi chạy', + 'orchestration.privacy.queueSelected': 'Đã chọn mục hàng đợi', + 'orchestration.privacy.detailsHidden': 'Đã ẩn chi tiết', + 'orchestration.privacy.summaryHidden': 'Đã ẩn tóm tắt', + 'orchestration.privacy.providerHidden': 'Đã ẩn chi tiết provider', + 'orchestration.privacy.filesChangedCount': 'Đã ẩn thao tác file: {count} mục', 'orchestration.chat.thread.aria': 'Cuộc trò chuyện tác vụ', 'orchestration.chat.input.send': 'Thêm yêu cầu', 'orchestration.chat.input.discuss': 'Xem trước kế hoạch', @@ -1287,6 +1293,7 @@ const vi = Object.freeze({ 'orchestration.detail.context.aria': 'Ngữ cảnh lượt chạy tác vụ', 'orchestration.detail.context.run': 'Run ID', 'orchestration.detail.node.meta': '{id} · thử {attempts} lần · tự sửa {autoFix}', + 'orchestration.detail.node.step': 'Bước tác vụ', 'orchestration.openai.status.title': 'Cấu hình chạy OpenAI Chat', 'orchestration.openai.status.subtitle': 'Node tác vụ dùng provider OpenClaw hiện tại; kiểm tra base URL, API key và model trước khi chạy.', 'orchestration.openai.status.provider': 'Provider', diff --git a/web-ui/modules/i18n/locales/zh-tw.mjs b/web-ui/modules/i18n/locales/zh-tw.mjs index 048b7f4c..887137fb 100644 --- a/web-ui/modules/i18n/locales/zh-tw.mjs +++ b/web-ui/modules/i18n/locales/zh-tw.mjs @@ -979,6 +979,12 @@ const zhTw = Object.freeze({ 'orchestration.chat.context.sequence.value': '順序:{count} 條需求', 'orchestration.privacy.workspace.selected': '已選擇工作區', 'orchestration.privacy.thread.selected': '已選擇執行緒', + 'orchestration.privacy.runSelected': '已選擇執行記錄', + 'orchestration.privacy.queueSelected': '已選擇佇列項目', + 'orchestration.privacy.detailsHidden': '詳情已隱藏', + 'orchestration.privacy.summaryHidden': '摘要已隱藏', + 'orchestration.privacy.providerHidden': 'Provider 詳情已隱藏', + 'orchestration.privacy.filesChangedCount': '檔案操作已隱藏:{count} 項', 'orchestration.chat.thread.aria': '任務對話', 'orchestration.chat.input.send': '追加需求', 'orchestration.chat.input.discuss': '預覽計畫', @@ -1185,6 +1191,7 @@ const zhTw = Object.freeze({ 'orchestration.detail.context.aria': '任務運行上下文', 'orchestration.detail.context.run': 'Run ID', 'orchestration.detail.node.meta': '{id} · attempts {attempts} · auto-fix {autoFix}', + 'orchestration.detail.node.step': '任務步驟', 'orchestration.openai.status.title': 'OpenAI Chat 運行設定', 'orchestration.openai.status.subtitle': '任務節點使用目前 OpenClaw provider;執行前會檢查 base URL、API key 和模型。', 'orchestration.openai.status.provider': 'Provider', diff --git a/web-ui/modules/i18n/locales/zh.mjs b/web-ui/modules/i18n/locales/zh.mjs index 1516c9ac..61eee849 100644 --- a/web-ui/modules/i18n/locales/zh.mjs +++ b/web-ui/modules/i18n/locales/zh.mjs @@ -979,6 +979,12 @@ const zh = Object.freeze({ 'orchestration.chat.context.sequence.value': '顺序:{count} 条需求', 'orchestration.privacy.workspace.selected': '已选择工作区', 'orchestration.privacy.thread.selected': '已选择线程', + 'orchestration.privacy.runSelected': '已选择运行记录', + 'orchestration.privacy.queueSelected': '已选择排队项', + 'orchestration.privacy.detailsHidden': '详情已隐藏', + 'orchestration.privacy.summaryHidden': '摘要已隐藏', + 'orchestration.privacy.providerHidden': 'Provider 详情已隐藏', + 'orchestration.privacy.filesChangedCount': '文件操作已隐藏:{count} 项', 'orchestration.chat.thread.aria': '任务对话', 'orchestration.chat.input.send': '追加需求', 'orchestration.chat.input.discuss': '预览计划', @@ -1185,6 +1191,7 @@ const zh = Object.freeze({ 'orchestration.detail.context.aria': '任务运行上下文', 'orchestration.detail.context.run': 'Run ID', 'orchestration.detail.node.meta': '{id} · attempts {attempts} · auto-fix {autoFix}', + 'orchestration.detail.node.step': '任务步骤', 'orchestration.openai.status.title': 'OpenAI Chat 运行配置', 'orchestration.openai.status.subtitle': '任务节点使用当前 OpenClaw provider;执行前会检查 base URL、API key 和模型。', 'orchestration.openai.status.provider': 'Provider', diff --git a/web-ui/partials/index/panel-orchestration.html b/web-ui/partials/index/panel-orchestration.html index 3172fb1c..7eed7f93 100644 --- a/web-ui/partials/index/panel-orchestration.html +++ b/web-ui/partials/index/panel-orchestration.html @@ -175,11 +175,11 @@
{{ t('orchestration.plan.summary.threadId') }} - {{ taskOrchestration.plan.threadId }} + {{ t('orchestration.privacy.thread.selected') }}
{{ t('orchestration.plan.summary.cwd') }} - {{ taskOrchestration.plan.cwd }} + {{ t('orchestration.privacy.workspace.selected') }}
@@ -192,7 +192,7 @@
-
{{ node.title || node.id }}
+
{{ t('orchestration.detail.node.step') }}
{{ node.id }} · {{ node.kind }} · {{ node.workflowId }}
{{ node.write ? t('orchestration.plan.node.write') : t('orchestration.plan.node.readOnly') }} @@ -256,9 +256,9 @@ {{ taskOrchestrationWorkspaceRuns[0].status }} {{ t('orchestration.workbench.queueCount', { count: taskOrchestrationWorkspaceQueue.length }) }} {{ t('orchestration.workbench.ready') }} - {{ taskOrchestrationSelectedRun.run.summary || taskOrchestrationSelectedRun.run.runId || taskOrchestration.selectedRunId }} - {{ taskOrchestrationWorkspaceRuns[0].summary || taskOrchestrationWorkspaceRuns[0].runId }} - {{ taskOrchestrationWorkspaceQueue[0].title || taskOrchestrationWorkspaceQueue[0].target || taskOrchestrationWorkspaceQueue[0].taskId }} + {{ t('orchestration.privacy.runSelected') }} + {{ t('orchestration.privacy.runSelected') }} + {{ t('orchestration.privacy.queueSelected') }} {{ t('orchestration.workbench.subtitle') }}
@@ -319,14 +319,10 @@ @keydown.enter.self.prevent="item.lastRunId ? (taskOrchestration.workspaceTab = 'detail', selectTaskRun(item.lastRunId)) : null" @keydown.space.self.prevent="item.lastRunId ? (taskOrchestration.workspaceTab = 'detail', selectTaskRun(item.lastRunId)) : null">
-
{{ item.title || item.target || item.taskId }}
-
{{ item.taskId }} · {{ item.updatedAt || item.createdAt }}
-
- {{ t('orchestration.plan.summary.threadId') }}: {{ item.threadId }} - · - {{ t('orchestration.plan.summary.cwd') }}: {{ item.cwd }} -
-
{{ item.lastSummary }}
+
{{ t('orchestration.workspace.sessions.queueTitle') }}
+
{{ item.updatedAt || item.createdAt || t('orchestration.workspace.sessions.detailsHidden') }}
+
{{ t('orchestration.privacy.detailsHidden') }}
+
{{ t('orchestration.privacy.summaryHidden') }}
{{ item.status || item.runStatus }} @@ -349,14 +345,10 @@ :class="['task-runtime-item', { active: taskOrchestration.selectedRunId === item.runId }]" @click="taskOrchestration.workspaceTab = 'detail'; selectTaskRun(item.runId)">
-
{{ item.title || item.taskId || item.runId }}
-
{{ item.runId }} · {{ item.durationMs || 0 }}ms
-
- {{ t('orchestration.plan.summary.threadId') }}: {{ item.threadId }} - · - {{ t('orchestration.plan.summary.cwd') }}: {{ item.cwd }} -
-
{{ item.summary }}
+
{{ t('orchestration.workspace.sessions.runTitle') }}
+
{{ item.durationMs || 0 }}ms
+
{{ t('orchestration.privacy.detailsHidden') }}
+
{{ t('orchestration.privacy.summaryHidden') }}
{{ item.status }} @@ -402,48 +394,48 @@
{{ t('orchestration.detail.summary.summary') }} - {{ taskOrchestrationSelectedRun.run.summary || t('common.none') }} + {{ taskOrchestrationSelectedRun.run.summary ? t('orchestration.privacy.summaryHidden') : t('common.none') }}
{{ t('orchestration.plan.summary.threadId') }} - {{ taskOrchestrationSelectedRun.threadId }} + {{ t('orchestration.privacy.thread.selected') }}
{{ t('orchestration.plan.summary.cwd') }} - {{ taskOrchestrationSelectedRun.cwd }} + {{ t('orchestration.privacy.workspace.selected') }}
-
{{ taskOrchestrationSelectedRun.run.error }}
+
{{ t('orchestration.privacy.detailsHidden') }}
-
{{ node.title || node.id }}
+
{{ t('orchestration.detail.node.step') }}
{{ t('orchestration.detail.node.meta', { id: node.id, attempts: (node.attemptCount || 0), autoFix: (node.autoFixRounds || 0) }) }}
{{ node.status }}
-
{{ node.summary }}
-
{{ t('orchestration.labels.error') }}{{ node.error }}
+
{{ t('orchestration.privacy.summaryHidden') }}
+
{{ t('orchestration.labels.error') }}{{ t('orchestration.privacy.detailsHidden') }}
{{ t('orchestration.labels.dependencies') }}{{ formatTaskNodeDependencies(node) }}
{{ t('orchestration.detail.node.output') }} - {{ node.output.provider || '' }}{{ node.output.model || '' }} + {{ t('orchestration.privacy.providerHidden') }}
- {{ t('orchestration.detail.node.endpoint') }}{{ node.output.endpoint }} + {{ t('orchestration.detail.node.endpoint') }}{{ t('orchestration.openai.status.configured') }} HTTP {{ node.output.status }} {{ node.output.durationMs }}ms
{{ formatTaskNodeOutputText(node) }}
{{ t('orchestration.detail.node.materializedFiles') }}
-
{{ file.relativePath || file.path }} · {{ file.bytes || 0 }} bytes
+
{{ t('orchestration.privacy.filesChangedCount', { count: node.output.materializedFiles.length }) }}
{{ t('orchestration.detail.node.workspaceFiles') }}
-
{{ [String(file.operation || '').trim().toUpperCase(), String(file.relativePath || file.path || '').trim(), Number.isFinite(Number(file.bytes)) ? (Number(file.bytes) + ' bytes') : '', typeof file.existed === 'boolean' ? (file.existed ? 'existed' : 'new') : ''].filter(Boolean).join(' · ') }}
+
{{ t('orchestration.privacy.filesChangedCount', { count: node.output.workspaceFiles.length }) }}
{{ formatTaskNodeLogs(node.logs) }}
@@ -487,12 +479,12 @@
{{ t('orchestration.openai.status.notLoaded') }}
-
{{ taskOrchestration.openAiChatStatus.error }}
+
{{ t('orchestration.privacy.detailsHidden') }}
diff --git a/web-ui/res/web-ui-render.precompiled.js b/web-ui/res/web-ui-render.precompiled.js index c084e1d7..bb554ba6 100644 --- a/web-ui/res/web-ui-render.precompiled.js +++ b/web-ui/res/web-ui-render.precompiled.js @@ -4255,7 +4255,7 @@ return function render(_ctx, _cache) { class: "task-plan-summary-item" }, [ _createElementVNode("span", { class: "task-plan-summary-label" }, _toDisplayString(_ctx.t('orchestration.plan.summary.threadId')), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(_ctx.taskOrchestration.plan.threadId), 1 /* TEXT */) + _createElementVNode("strong", null, _toDisplayString(_ctx.t('orchestration.privacy.thread.selected')), 1 /* TEXT */) ])) : _createCommentVNode("v-if", true), (_ctx.taskOrchestration.plan.cwd) @@ -4264,7 +4264,7 @@ return function render(_ctx, _cache) { class: "task-plan-summary-item" }, [ _createElementVNode("span", { class: "task-plan-summary-label" }, _toDisplayString(_ctx.t('orchestration.plan.summary.cwd')), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(_ctx.taskOrchestration.plan.cwd), 1 /* TEXT */) + _createElementVNode("strong", null, _toDisplayString(_ctx.t('orchestration.privacy.workspace.selected')), 1 /* TEXT */) ])) : _createCommentVNode("v-if", true) ]), @@ -4287,7 +4287,7 @@ return function render(_ctx, _cache) { }, [ _createElementVNode("div", { class: "task-node-head" }, [ _createElementVNode("div", null, [ - _createElementVNode("div", { class: "task-node-title" }, _toDisplayString(node.title || node.id), 1 /* TEXT */), + _createElementVNode("div", { class: "task-node-title" }, _toDisplayString(_ctx.t('orchestration.detail.node.step')), 1 /* TEXT */), _createElementVNode("div", { class: "task-node-meta" }, [ _createTextVNode(_toDisplayString(node.id) + " · " + _toDisplayString(node.kind), 1 /* TEXT */), (node.workflowId) @@ -4390,17 +4390,17 @@ return function render(_ctx, _cache) { ? (_openBlock(), _createElementBlock("span", { key: 4, class: "task-thread-run-summary-copy" - }, _toDisplayString(_ctx.taskOrchestrationSelectedRun.run.summary || _ctx.taskOrchestrationSelectedRun.run.runId || _ctx.taskOrchestration.selectedRunId), 1 /* TEXT */)) + }, _toDisplayString(_ctx.t('orchestration.privacy.runSelected')), 1 /* TEXT */)) : (_ctx.taskOrchestrationWorkspaceRuns.length) ? (_openBlock(), _createElementBlock("span", { key: 5, class: "task-thread-run-summary-copy" - }, _toDisplayString(_ctx.taskOrchestrationWorkspaceRuns[0].summary || _ctx.taskOrchestrationWorkspaceRuns[0].runId), 1 /* TEXT */)) + }, _toDisplayString(_ctx.t('orchestration.privacy.runSelected')), 1 /* TEXT */)) : (_ctx.taskOrchestrationWorkspaceQueue.length) ? (_openBlock(), _createElementBlock("span", { key: 6, class: "task-thread-run-summary-copy" - }, _toDisplayString(_ctx.taskOrchestrationWorkspaceQueue[0].title || _ctx.taskOrchestrationWorkspaceQueue[0].target || _ctx.taskOrchestrationWorkspaceQueue[0].taskId), 1 /* TEXT */)) + }, _toDisplayString(_ctx.t('orchestration.privacy.queueSelected')), 1 /* TEXT */)) : (_openBlock(), _createElementBlock("span", { key: 7, class: "task-thread-run-summary-copy" @@ -4510,29 +4510,19 @@ return function render(_ctx, _cache) { ] }, [ _createElementVNode("div", { class: "task-runtime-item-main" }, [ - _createElementVNode("div", { class: "task-runtime-item-title" }, _toDisplayString(item.title || item.target || item.taskId), 1 /* TEXT */), - _createElementVNode("div", { class: "task-runtime-item-meta" }, _toDisplayString(item.taskId) + " · " + _toDisplayString(item.updatedAt || item.createdAt), 1 /* TEXT */), - (item.threadId || item.cwd) + _createElementVNode("div", { class: "task-runtime-item-title" }, _toDisplayString(_ctx.t('orchestration.workspace.sessions.queueTitle')), 1 /* TEXT */), + _createElementVNode("div", { class: "task-runtime-item-meta" }, _toDisplayString(item.updatedAt || item.createdAt || _ctx.t('orchestration.workspace.sessions.detailsHidden')), 1 /* TEXT */), + (item.threadId || item.cwd || item.taskId) ? (_openBlock(), _createElementBlock("div", { key: 0, class: "task-runtime-item-meta" - }, [ - (item.threadId) - ? (_openBlock(), _createElementBlock("span", { key: 0 }, _toDisplayString(_ctx.t('orchestration.plan.summary.threadId')) + ": " + _toDisplayString(item.threadId), 1 /* TEXT */)) - : _createCommentVNode("v-if", true), - (item.threadId && item.cwd) - ? (_openBlock(), _createElementBlock("span", { key: 1 }, " · ")) - : _createCommentVNode("v-if", true), - (item.cwd) - ? (_openBlock(), _createElementBlock("span", { key: 2 }, _toDisplayString(_ctx.t('orchestration.plan.summary.cwd')) + ": " + _toDisplayString(item.cwd), 1 /* TEXT */)) - : _createCommentVNode("v-if", true) - ])) + }, _toDisplayString(_ctx.t('orchestration.privacy.detailsHidden')), 1 /* TEXT */)) : _createCommentVNode("v-if", true), (item.lastSummary) ? (_openBlock(), _createElementBlock("div", { key: 1, class: "task-runtime-item-summary" - }, _toDisplayString(item.lastSummary), 1 /* TEXT */)) + }, _toDisplayString(_ctx.t('orchestration.privacy.summaryHidden')), 1 /* TEXT */)) : _createCommentVNode("v-if", true) ]), _createElementVNode("div", { class: "task-runtime-item-actions" }, [ @@ -4577,29 +4567,19 @@ return function render(_ctx, _cache) { onClick: $event => {_ctx.taskOrchestration.workspaceTab = 'detail'; _ctx.selectTaskRun(item.runId)} }, [ _createElementVNode("div", { class: "task-runtime-item-main" }, [ - _createElementVNode("div", { class: "task-runtime-item-title" }, _toDisplayString(item.title || item.taskId || item.runId), 1 /* TEXT */), - _createElementVNode("div", { class: "task-runtime-item-meta" }, _toDisplayString(item.runId) + " · " + _toDisplayString(item.durationMs || 0) + "ms", 1 /* TEXT */), - (item.threadId || item.cwd) + _createElementVNode("div", { class: "task-runtime-item-title" }, _toDisplayString(_ctx.t('orchestration.workspace.sessions.runTitle')), 1 /* TEXT */), + _createElementVNode("div", { class: "task-runtime-item-meta" }, _toDisplayString(item.durationMs || 0) + "ms", 1 /* TEXT */), + (item.threadId || item.cwd || item.runId || item.taskId) ? (_openBlock(), _createElementBlock("div", { key: 0, class: "task-runtime-item-meta" - }, [ - (item.threadId) - ? (_openBlock(), _createElementBlock("span", { key: 0 }, _toDisplayString(_ctx.t('orchestration.plan.summary.threadId')) + ": " + _toDisplayString(item.threadId), 1 /* TEXT */)) - : _createCommentVNode("v-if", true), - (item.threadId && item.cwd) - ? (_openBlock(), _createElementBlock("span", { key: 1 }, " · ")) - : _createCommentVNode("v-if", true), - (item.cwd) - ? (_openBlock(), _createElementBlock("span", { key: 2 }, _toDisplayString(_ctx.t('orchestration.plan.summary.cwd')) + ": " + _toDisplayString(item.cwd), 1 /* TEXT */)) - : _createCommentVNode("v-if", true) - ])) + }, _toDisplayString(_ctx.t('orchestration.privacy.detailsHidden')), 1 /* TEXT */)) : _createCommentVNode("v-if", true), (item.summary) ? (_openBlock(), _createElementBlock("div", { key: 1, class: "task-runtime-item-summary" - }, _toDisplayString(item.summary), 1 /* TEXT */)) + }, _toDisplayString(_ctx.t('orchestration.privacy.summaryHidden')), 1 /* TEXT */)) : _createCommentVNode("v-if", true) ]), _createElementVNode("div", { class: "task-runtime-item-actions" }, [ @@ -4673,7 +4653,7 @@ return function render(_ctx, _cache) { ]), _createElementVNode("div", { class: "task-plan-summary-item" }, [ _createElementVNode("span", { class: "task-plan-summary-label" }, _toDisplayString(_ctx.t('orchestration.detail.summary.summary')), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(_ctx.taskOrchestrationSelectedRun.run.summary || _ctx.t('common.none')), 1 /* TEXT */) + _createElementVNode("strong", null, _toDisplayString(_ctx.taskOrchestrationSelectedRun.run.summary ? _ctx.t('orchestration.privacy.summaryHidden') : _ctx.t('common.none')), 1 /* TEXT */) ]), (_ctx.taskOrchestrationSelectedRun.threadId) ? (_openBlock(), _createElementBlock("div", { @@ -4681,7 +4661,7 @@ return function render(_ctx, _cache) { class: "task-plan-summary-item" }, [ _createElementVNode("span", { class: "task-plan-summary-label" }, _toDisplayString(_ctx.t('orchestration.plan.summary.threadId')), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(_ctx.taskOrchestrationSelectedRun.threadId), 1 /* TEXT */) + _createElementVNode("strong", null, _toDisplayString(_ctx.t('orchestration.privacy.thread.selected')), 1 /* TEXT */) ])) : _createCommentVNode("v-if", true), (_ctx.taskOrchestrationSelectedRun.cwd) @@ -4690,7 +4670,7 @@ return function render(_ctx, _cache) { class: "task-plan-summary-item" }, [ _createElementVNode("span", { class: "task-plan-summary-label" }, _toDisplayString(_ctx.t('orchestration.plan.summary.cwd')), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(_ctx.taskOrchestrationSelectedRun.cwd), 1 /* TEXT */) + _createElementVNode("strong", null, _toDisplayString(_ctx.t('orchestration.privacy.workspace.selected')), 1 /* TEXT */) ])) : _createCommentVNode("v-if", true) ]), @@ -4698,7 +4678,7 @@ return function render(_ctx, _cache) { ? (_openBlock(), _createElementBlock("div", { key: 0, class: "task-issue-item" - }, _toDisplayString(_ctx.taskOrchestrationSelectedRun.run.error), 1 /* TEXT */)) + }, _toDisplayString(_ctx.t('orchestration.privacy.detailsHidden')), 1 /* TEXT */)) : _createCommentVNode("v-if", true), _createElementVNode("div", { class: "task-node-list" }, [ (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskOrchestrationSelectedRunNodes, (node) => { @@ -4708,7 +4688,7 @@ return function render(_ctx, _cache) { }, [ _createElementVNode("div", { class: "task-node-head" }, [ _createElementVNode("div", null, [ - _createElementVNode("div", { class: "task-node-title" }, _toDisplayString(node.title || node.id), 1 /* TEXT */), + _createElementVNode("div", { class: "task-node-title" }, _toDisplayString(_ctx.t('orchestration.detail.node.step')), 1 /* TEXT */), _createElementVNode("div", { class: "task-node-meta" }, _toDisplayString(_ctx.t('orchestration.detail.node.meta', { id: node.id, attempts: (node.attemptCount || 0), autoFix: (node.autoFixRounds || 0) })), 1 /* TEXT */) ]), _createElementVNode("span", { @@ -4719,13 +4699,13 @@ return function render(_ctx, _cache) { ? (_openBlock(), _createElementBlock("div", { key: 0, class: "task-runtime-item-summary" - }, _toDisplayString(node.summary), 1 /* TEXT */)) + }, _toDisplayString(_ctx.t('orchestration.privacy.summaryHidden')), 1 /* TEXT */)) : _createCommentVNode("v-if", true), (node.error && node.error !== node.summary) ? (_openBlock(), _createElementBlock("div", { key: 1, class: "task-node-deps" - }, _toDisplayString(_ctx.t('orchestration.labels.error')) + _toDisplayString(node.error), 1 /* TEXT */)) + }, _toDisplayString(_ctx.t('orchestration.labels.error')) + _toDisplayString(_ctx.t('orchestration.privacy.detailsHidden')), 1 /* TEXT */)) : _createCommentVNode("v-if", true), _createElementVNode("div", { class: "task-node-deps" }, _toDisplayString(_ctx.t('orchestration.labels.dependencies')) + _toDisplayString(_ctx.formatTaskNodeDependencies(node)), 1 /* TEXT */), (node.output && typeof node.output === 'object') @@ -4739,20 +4719,12 @@ return function render(_ctx, _cache) { ? (_openBlock(), _createElementBlock("span", { key: 0, class: "task-node-output-meta" - }, [ - _createTextVNode(_toDisplayString(node.output.provider || ''), 1 /* TEXT */), - (node.output.provider && node.output.model) - ? (_openBlock(), _createElementBlock(_Fragment, { key: 0 }, [ - _createTextVNode(" · ") - ], 64 /* STABLE_FRAGMENT */)) - : _createCommentVNode("v-if", true), - _createTextVNode(_toDisplayString(node.output.model || ''), 1 /* TEXT */) - ])) + }, _toDisplayString(_ctx.t('orchestration.privacy.providerHidden')), 1 /* TEXT */)) : _createCommentVNode("v-if", true) ]), _createElementVNode("div", { class: "task-node-output-facts" }, [ (node.output.endpoint) - ? (_openBlock(), _createElementBlock("span", { key: 0 }, _toDisplayString(_ctx.t('orchestration.detail.node.endpoint')) + _toDisplayString(node.output.endpoint), 1 /* TEXT */)) + ? (_openBlock(), _createElementBlock("span", { key: 0 }, _toDisplayString(_ctx.t('orchestration.detail.node.endpoint')) + _toDisplayString(_ctx.t('orchestration.openai.status.configured')), 1 /* TEXT */)) : _createCommentVNode("v-if", true), (node.output.status) ? (_openBlock(), _createElementBlock("span", { key: 1 }, "HTTP " + _toDisplayString(node.output.status), 1 /* TEXT */)) @@ -4768,12 +4740,7 @@ return function render(_ctx, _cache) { class: "task-materialized-files" }, [ _createElementVNode("div", { class: "task-node-deps" }, _toDisplayString(_ctx.t('orchestration.detail.node.materializedFiles')), 1 /* TEXT */), - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(node.output.materializedFiles, (file) => { - return (_openBlock(), _createElementBlock("div", { - key: file.path || file.relativePath, - class: "task-materialized-file" - }, _toDisplayString(file.relativePath || file.path) + " · " + _toDisplayString(file.bytes || 0) + " bytes", 1 /* TEXT */)) - }), 128 /* KEYED_FRAGMENT */)) + _createElementVNode("div", { class: "task-materialized-file" }, _toDisplayString(_ctx.t('orchestration.privacy.filesChangedCount', { count: node.output.materializedFiles.length })), 1 /* TEXT */) ])) : _createCommentVNode("v-if", true), (node.output.workspaceFiles && node.output.workspaceFiles.length) @@ -4782,12 +4749,7 @@ return function render(_ctx, _cache) { class: "task-materialized-files task-workspace-files" }, [ _createElementVNode("div", { class: "task-node-deps" }, _toDisplayString(_ctx.t('orchestration.detail.node.workspaceFiles')), 1 /* TEXT */), - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(node.output.workspaceFiles, (file) => { - return (_openBlock(), _createElementBlock("div", { - key: file.path || file.relativePath, - class: "task-materialized-file" - }, _toDisplayString([String(file.operation || '').trim().toUpperCase(), String(file.relativePath || file.path || '').trim(), Number.isFinite(Number(file.bytes)) ? (Number(file.bytes) + ' bytes') : '', typeof file.existed === 'boolean' ? (file.existed ? 'existed' : 'new') : ''].filter(Boolean).join(' · ')), 1 /* TEXT */)) - }), 128 /* KEYED_FRAGMENT */)) + _createElementVNode("div", { class: "task-materialized-file" }, _toDisplayString(_ctx.t('orchestration.privacy.filesChangedCount', { count: node.output.workspaceFiles.length })), 1 /* TEXT */) ])) : _createCommentVNode("v-if", true) ])) @@ -4869,7 +4831,8 @@ return function render(_ctx, _cache) { _withDirectives(_createElementVNode("input", { "onUpdate:modelValue": $event => ((_ctx.taskOrchestration.workspacePath) = $event), class: "model-input", - type: "text", + type: "password", + autocomplete: "off", placeholder: _ctx.t('orchestration.fields.workspacePath.placeholder') }, null, 8 /* PROPS */, ["onUpdate:modelValue", "placeholder"]), [ [_vModelText, _ctx.taskOrchestration.workspacePath] @@ -4881,7 +4844,8 @@ return function render(_ctx, _cache) { _withDirectives(_createElementVNode("input", { "onUpdate:modelValue": $event => ((_ctx.taskOrchestration.threadId) = $event), class: "model-input", - type: "text", + type: "password", + autocomplete: "off", placeholder: _ctx.t('orchestration.fields.threadId.placeholder') }, null, 8 /* PROPS */, ["onUpdate:modelValue", "placeholder"]), [ [_vModelText, _ctx.taskOrchestration.threadId] @@ -5001,7 +4965,7 @@ return function render(_ctx, _cache) { ? (_openBlock(), _createElementBlock("div", { key: 2, class: "task-issue-item task-provider-status-error" - }, _toDisplayString(_ctx.taskOrchestration.openAiChatStatus.error), 1 /* TEXT */)) + }, _toDisplayString(_ctx.t('orchestration.privacy.detailsHidden')), 1 /* TEXT */)) : _createCommentVNode("v-if", true), _createElementVNode("button", { type: "button", From 0a57a0e5a4b0f253180e20f32ff774e720af2978 Mon Sep 17 00:00:00 2001 From: awsl233777 Date: Tue, 30 Jun 2026 09:37:37 +0000 Subject: [PATCH 02/25] fix(orchestration): keep workspace selector private --- tests/unit/web-ui-logic.test.mjs | 18 ++++++++++++--- web-ui/modules/app.computed.main-tabs.mjs | 22 +++++++++---------- .../app.methods.task-orchestration.mjs | 9 +++++--- web-ui/modules/i18n/locales/en.mjs | 5 +++++ web-ui/modules/i18n/locales/ja.mjs | 5 +++++ web-ui/modules/i18n/locales/vi.mjs | 5 +++++ web-ui/modules/i18n/locales/zh-tw.mjs | 5 +++++ web-ui/modules/i18n/locales/zh.mjs | 5 +++++ 8 files changed, 57 insertions(+), 17 deletions(-) diff --git a/tests/unit/web-ui-logic.test.mjs b/tests/unit/web-ui-logic.test.mjs index 8dc32ea8..9cf23a30 100644 --- a/tests/unit/web-ui-logic.test.mjs +++ b/tests/unit/web-ui-logic.test.mjs @@ -1806,6 +1806,9 @@ test('task orchestration workspace selectors derive projects and sessions from r assert.strictEqual(workspaceItems.find((item) => item.path === '/repo/codexmate').active, true); assert.strictEqual(workspaceItems.find((item) => item.path === '/repo/codexmate').runCount, 1); assert.strictEqual(workspaceItems.find((item) => item.path === '/repo/codexmate').queueCount, 1); + assert.strictEqual(workspaceItems.every((item) => !String(item.label || '').includes('/repo') && !String(item.meta || '').includes('/repo')), true); + assert.strictEqual(workspaceItems.every((item) => !String(item.label || '').includes('codexmate') && !String(item.meta || '').includes('codexmate')), true); + assert.strictEqual(workspaceItems.every((item) => !String(item.label || '').includes('other') && !String(item.meta || '').includes('other')), true); assert.deepStrictEqual(workspaceQueue.map((item) => item.taskId), ['task-codexmate']); assert.deepStrictEqual(workspaceRuns.map((item) => item.runId), ['run-codexmate']); assert.deepStrictEqual(sessions.map((item) => item.id), ['task-codexmate', 'run-codexmate']); @@ -2059,12 +2062,21 @@ test('task orchestration detail helpers surface AI output and open OpenAI Chat c }, openEditModal(provider) { calls.push(['openEditModal', provider.name]); + }, + t(key, values = {}) { + const messages = { + 'orchestration.privacy.outputHidden': '输出已隐藏', + 'orchestration.privacy.logsHiddenCount': `日志已隐藏:${values.count} 条`, + 'orchestration.detail.node.output.empty': '无输出', + 'orchestration.detail.node.logs.empty': '无日志' + }; + return messages[key] || key; } }; - assert.strictEqual(methods.formatTaskNodeOutputText.call(context, { output: { text: ' actual AI result ' } }), '(output hidden)'); - assert.strictEqual(methods.formatTaskNodeOutputText.call(context, { output: {} }), '(no output)'); - assert.strictEqual(methods.formatTaskNodeLogs.call(context, [{ message: 'secret /tmp/workspace thread-1' }]), '(1 log entries hidden)'); + assert.strictEqual(methods.formatTaskNodeOutputText.call(context, { output: { text: ' actual AI result ' } }), '输出已隐藏'); + assert.strictEqual(methods.formatTaskNodeOutputText.call(context, { output: {} }), '无输出'); + assert.strictEqual(methods.formatTaskNodeLogs.call(context, [{ message: 'secret /tmp/workspace thread-1' }]), '日志已隐藏:1 条'); methods.openTaskOpenAiChatConfig.call(context); diff --git a/web-ui/modules/app.computed.main-tabs.mjs b/web-ui/modules/app.computed.main-tabs.mjs index beb6284e..c9a943c4 100644 --- a/web-ui/modules/app.computed.main-tabs.mjs +++ b/web-ui/modules/app.computed.main-tabs.mjs @@ -75,13 +75,6 @@ function readTaskWorkspacePathFromItem(item) { return normalizeTaskWorkspacePath(item.cwd || item.workspacePath || item.projectPath || item.path); } -function formatTaskWorkspaceLabel(workspacePath, fallback = 'Auto workspace') { - const normalized = normalizeTaskWorkspacePath(workspacePath); - if (!normalized) return fallback; - const parts = normalized.split('/').filter(Boolean); - return parts.length ? parts[parts.length - 1] : normalized; -} - function readTaskItemTimestamp(item) { if (!item || typeof item !== 'object') return ''; return String(item.updatedAt || item.completedAt || item.startedAt || item.createdAt || item.enqueuedAt || '').trim(); @@ -94,8 +87,8 @@ function addTaskWorkspaceCandidate(workspaces, workspacePath, source, timestamp const current = workspaces.get(key) || { key, path: normalized, - label: formatTaskWorkspaceLabel(normalized), - meta: normalized, + label: 'Workspace', + meta: '', queueCount: 0, runCount: 0, lastActivity: '' @@ -134,7 +127,7 @@ function createTaskWorkspaceItems(taskOrchestration) { key: '__auto__', path: '', label: 'Auto workspace', - meta: 'Server default cwd', + meta: '', queueCount: queue.length, runCount: runs.length, lastActivity: '' @@ -455,8 +448,15 @@ export function createMainTabsComputed() { taskOrchestrationWorkspaceItems() { const items = createTaskWorkspaceItems(this.taskOrchestration); const selectedPath = this.taskOrchestrationWorkspacePath; - return items.map((item) => ({ + const t = typeof this.t === 'function' ? this.t.bind(this) : null; + return items.map((item, index) => ({ ...item, + label: item.path + ? (t ? t('orchestration.workspace.choice.label', { count: index + 1 }) : `Workspace ${index + 1}`) + : (t ? t('orchestration.chat.context.workspace.auto') : 'Auto workspace'), + meta: item.path + ? (t ? t('orchestration.workspace.choice.meta') : 'Path hidden') + : (t ? t('orchestration.workspace.choice.autoMeta') : 'Server default workspace'), active: normalizeTaskWorkspacePath(item.path) === selectedPath || (!selectedPath && !item.path) })); }, diff --git a/web-ui/modules/app.methods.task-orchestration.mjs b/web-ui/modules/app.methods.task-orchestration.mjs index 4b07525c..d15f790d 100644 --- a/web-ui/modules/app.methods.task-orchestration.mjs +++ b/web-ui/modules/app.methods.task-orchestration.mjs @@ -257,16 +257,19 @@ export function createTaskOrchestrationMethods(options = {}) { }, formatTaskNodeLogs(logs) { + const t = typeof this.t === 'function' ? this.t.bind(this) : null; if (!Array.isArray(logs) || logs.length === 0) { - return '(no logs)'; + return t ? t('orchestration.detail.node.logs.empty') : '(no logs)'; } - return `(${logs.length} log entries hidden)`; + return t ? t('orchestration.privacy.logsHiddenCount', { count: logs.length }) : `(${logs.length} log entries hidden)`; }, formatTaskNodeOutputText(node) { + const t = typeof this.t === 'function' ? this.t.bind(this) : null; const output = node && node.output && typeof node.output === 'object' ? node.output : null; const text = output && typeof output.text === 'string' ? output.text.trim() : ''; - return text ? '(output hidden)' : '(no output)'; + if (!text) return t ? t('orchestration.detail.node.output.empty') : '(no output)'; + return t ? t('orchestration.privacy.outputHidden') : '(output hidden)'; }, openTaskOpenAiChatConfig() { diff --git a/web-ui/modules/i18n/locales/en.mjs b/web-ui/modules/i18n/locales/en.mjs index f4cab30c..c5a5a510 100644 --- a/web-ui/modules/i18n/locales/en.mjs +++ b/web-ui/modules/i18n/locales/en.mjs @@ -986,6 +986,11 @@ const en = Object.freeze({ 'orchestration.privacy.summaryHidden': 'Summary hidden for privacy', 'orchestration.privacy.providerHidden': 'Provider details hidden', 'orchestration.privacy.filesChangedCount': 'File operations hidden: {count} item(s)', + 'orchestration.privacy.outputHidden': 'Output hidden for privacy', + 'orchestration.privacy.logsHiddenCount': 'Log entries hidden: {count}', + 'orchestration.workspace.choice.label': 'Workspace {count}', + 'orchestration.workspace.choice.meta': 'Path hidden', + 'orchestration.workspace.choice.autoMeta': 'Server default workspace', 'orchestration.chat.thread.aria': 'Task conversation', 'orchestration.chat.input.send': 'Append request', 'orchestration.chat.input.discuss': 'Preview plan', diff --git a/web-ui/modules/i18n/locales/ja.mjs b/web-ui/modules/i18n/locales/ja.mjs index 0d0bad6a..a6a50654 100644 --- a/web-ui/modules/i18n/locales/ja.mjs +++ b/web-ui/modules/i18n/locales/ja.mjs @@ -975,6 +975,11 @@ const ja = Object.freeze({ 'orchestration.privacy.summaryHidden': '概要は非表示', 'orchestration.privacy.providerHidden': 'Provider details hidden', 'orchestration.privacy.filesChangedCount': 'ファイル操作は非表示: {count} 件', + 'orchestration.privacy.outputHidden': '出力はプライバシー保護のため非表示です', + 'orchestration.privacy.logsHiddenCount': 'ログは非表示です: {count} 件', + 'orchestration.workspace.choice.label': 'ワークスペース {count}', + 'orchestration.workspace.choice.meta': 'パスは非表示です', + 'orchestration.workspace.choice.autoMeta': 'サーバー既定のワークスペース', 'orchestration.chat.thread.aria': 'タスク会話', 'orchestration.chat.input.send': '依頼を追加', 'orchestration.chat.input.discuss': '計画プレビュー', diff --git a/web-ui/modules/i18n/locales/vi.mjs b/web-ui/modules/i18n/locales/vi.mjs index 10da1b96..dabae05d 100644 --- a/web-ui/modules/i18n/locales/vi.mjs +++ b/web-ui/modules/i18n/locales/vi.mjs @@ -1122,6 +1122,11 @@ const vi = Object.freeze({ 'orchestration.privacy.summaryHidden': 'Đã ẩn tóm tắt', 'orchestration.privacy.providerHidden': 'Đã ẩn chi tiết provider', 'orchestration.privacy.filesChangedCount': 'Đã ẩn thao tác file: {count} mục', + 'orchestration.privacy.outputHidden': 'Đã ẩn output để bảo mật', + 'orchestration.privacy.logsHiddenCount': 'Đã ẩn log: {count}', + 'orchestration.workspace.choice.label': 'Workspace {count}', + 'orchestration.workspace.choice.meta': 'Đã ẩn đường dẫn', + 'orchestration.workspace.choice.autoMeta': 'Workspace mặc định của server', 'orchestration.chat.thread.aria': 'Cuộc trò chuyện tác vụ', 'orchestration.chat.input.send': 'Thêm yêu cầu', 'orchestration.chat.input.discuss': 'Xem trước kế hoạch', diff --git a/web-ui/modules/i18n/locales/zh-tw.mjs b/web-ui/modules/i18n/locales/zh-tw.mjs index 887137fb..7602fb59 100644 --- a/web-ui/modules/i18n/locales/zh-tw.mjs +++ b/web-ui/modules/i18n/locales/zh-tw.mjs @@ -985,6 +985,11 @@ const zhTw = Object.freeze({ 'orchestration.privacy.summaryHidden': '摘要已隱藏', 'orchestration.privacy.providerHidden': 'Provider 詳情已隱藏', 'orchestration.privacy.filesChangedCount': '檔案操作已隱藏:{count} 項', + 'orchestration.privacy.outputHidden': '輸出已隱藏', + 'orchestration.privacy.logsHiddenCount': '日誌已隱藏:{count} 條', + 'orchestration.workspace.choice.label': '工作區 {count}', + 'orchestration.workspace.choice.meta': '路徑已隱藏', + 'orchestration.workspace.choice.autoMeta': '服務預設工作區', 'orchestration.chat.thread.aria': '任務對話', 'orchestration.chat.input.send': '追加需求', 'orchestration.chat.input.discuss': '預覽計畫', diff --git a/web-ui/modules/i18n/locales/zh.mjs b/web-ui/modules/i18n/locales/zh.mjs index 61eee849..9a797801 100644 --- a/web-ui/modules/i18n/locales/zh.mjs +++ b/web-ui/modules/i18n/locales/zh.mjs @@ -985,6 +985,11 @@ const zh = Object.freeze({ 'orchestration.privacy.summaryHidden': '摘要已隐藏', 'orchestration.privacy.providerHidden': 'Provider 详情已隐藏', 'orchestration.privacy.filesChangedCount': '文件操作已隐藏:{count} 项', + 'orchestration.privacy.outputHidden': '输出已隐藏', + 'orchestration.privacy.logsHiddenCount': '日志已隐藏:{count} 条', + 'orchestration.workspace.choice.label': '工作区 {count}', + 'orchestration.workspace.choice.meta': '路径已隐藏', + 'orchestration.workspace.choice.autoMeta': '服务默认工作区', 'orchestration.chat.thread.aria': '任务对话', 'orchestration.chat.input.send': '追加需求', 'orchestration.chat.input.discuss': '预览计划', From 94c89bdbf4e602064cc0e6cb4d8b88fe0d4e9415 Mon Sep 17 00:00:00 2001 From: awsl233777 Date: Tue, 30 Jun 2026 09:44:22 +0000 Subject: [PATCH 03/25] fix(orchestration): localize Japanese privacy labels --- web-ui/modules/i18n/locales/ja.mjs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web-ui/modules/i18n/locales/ja.mjs b/web-ui/modules/i18n/locales/ja.mjs index a6a50654..19aae036 100644 --- a/web-ui/modules/i18n/locales/ja.mjs +++ b/web-ui/modules/i18n/locales/ja.mjs @@ -967,13 +967,13 @@ const ja = Object.freeze({ 'orchestration.chat.context.thread.value': 'Thread {value}', 'orchestration.chat.context.sequence.empty': '順序: 依頼 1 待ち', 'orchestration.chat.context.sequence.value': '順序: {count} 件の依頼', - 'orchestration.privacy.workspace.selected': 'Workspace selected', - 'orchestration.privacy.thread.selected': 'Thread selected', + 'orchestration.privacy.workspace.selected': 'ワークスペースを選択済み', + 'orchestration.privacy.thread.selected': 'スレッドを選択済み', 'orchestration.privacy.runSelected': '実行記録を選択済み', 'orchestration.privacy.queueSelected': 'キュー項目を選択済み', 'orchestration.privacy.detailsHidden': '詳細は非表示', 'orchestration.privacy.summaryHidden': '概要は非表示', - 'orchestration.privacy.providerHidden': 'Provider details hidden', + 'orchestration.privacy.providerHidden': 'プロバイダー詳細は非表示', 'orchestration.privacy.filesChangedCount': 'ファイル操作は非表示: {count} 件', 'orchestration.privacy.outputHidden': '出力はプライバシー保護のため非表示です', 'orchestration.privacy.logsHiddenCount': 'ログは非表示です: {count} 件', From a0762743098dc02a17775fb8dbe1df3f19ae8779 Mon Sep 17 00:00:00 2001 From: awsl233777 Date: Tue, 30 Jun 2026 11:14:30 +0000 Subject: [PATCH 04/25] fix(orchestration): add private workspace split action --- tests/unit/config-tabs-ui.test.mjs | 6 +- web-ui/modules/i18n/locales/en.mjs | 4 + web-ui/modules/i18n/locales/ja.mjs | 4 + web-ui/modules/i18n/locales/vi.mjs | 4 + web-ui/modules/i18n/locales/zh-tw.mjs | 4 + web-ui/modules/i18n/locales/zh.mjs | 4 + .../partials/index/panel-orchestration.html | 32 ++++++- web-ui/res/web-ui-render.precompiled.js | 51 +++++++++- web-ui/styles/task-orchestration.css | 92 +++++++++++++++++++ 9 files changed, 192 insertions(+), 9 deletions(-) diff --git a/tests/unit/config-tabs-ui.test.mjs b/tests/unit/config-tabs-ui.test.mjs index 287cdc3c..e367e6b7 100644 --- a/tests/unit/config-tabs-ui.test.mjs +++ b/tests/unit/config-tabs-ui.test.mjs @@ -122,12 +122,16 @@ test('config template keeps expected config tabs in top and side navigation', () assert.match(orchestrationPanel, /class="[^\"]*task-thread-message-card[^\"]*task-thread-plan-card[^\"]*"/); assert.doesNotMatch(orchestrationPanel, /class="[^\"]*task-thread-message-card[^\"]*task-thread-workbench-card[^\"]*"/); assert.match(orchestrationPanel, /class="task-project-sidebar" :aria-label="t\('orchestration\.workspace\.aria'\)"/); + assert.match(orchestrationPanel, /class="task-workspace-action-row" :aria-label="t\('orchestration\.workspace\.actions\.aria'\)"/); + assert.match(orchestrationPanel, /class="btn-tool btn-primary task-workspace-primary-action"/); + assert.match(orchestrationPanel, /@click="startNewTaskWorkspaceSession\(taskOrchestrationWorkspacePath\)"/); + assert.match(orchestrationPanel, /class="task-workspace-action-menu"/); + assert.match(orchestrationPanel, /role="menuitemradio"/); assert.match(orchestrationPanel, /
/); assert.doesNotMatch(orchestrationPanel, /class="task-project-list" role="listbox"/); assert.match(orchestrationPanel, /v-for="workspace in taskOrchestrationWorkspaceItems"/); assert.match(orchestrationPanel, /:aria-selected="workspace\.active \? 'true' : 'false'"/); assert.match(orchestrationPanel, /@click="selectTaskWorkspace\(workspace\.path\)"/); - assert.match(orchestrationPanel, /@click="startNewTaskWorkspaceSession\(\)"/); assert.match(orchestrationPanel, /v-for="session in taskOrchestrationWorkspaceSessions\.slice\(0, 8\)"/); assert.match(orchestrationPanel, /@click="continueTaskWorkspaceSession\(session\)"/); assert.match(orchestrationPanel, /
+
+ +
+ + + + +
+
-
{{ t('orchestration.workspace.sessions.title') }} diff --git a/web-ui/res/web-ui-render.precompiled.js b/web-ui/res/web-ui-render.precompiled.js index bb554ba6..ae91ad4f 100644 --- a/web-ui/res/web-ui-render.precompiled.js +++ b/web-ui/res/web-ui-render.precompiled.js @@ -4048,6 +4048,52 @@ return function render(_ctx, _cache) { disabled: _ctx.taskOrchestration.loading }, _toDisplayString(_ctx.taskOrchestration.loading ? _ctx.t('common.refreshing') : _ctx.t('common.refresh')), 9 /* TEXT, PROPS */, ["onClick", "disabled"]) ]), + _createElementVNode("div", { + class: "task-workspace-action-row", + "aria-label": _ctx.t('orchestration.workspace.actions.aria') + }, [ + _createElementVNode("button", { + type: "button", + class: "btn-tool btn-primary task-workspace-primary-action", + onClick: $event => (_ctx.startNewTaskWorkspaceSession(_ctx.taskOrchestrationWorkspacePath)), + title: _ctx.taskOrchestrationWorkspacePath ? _ctx.t('orchestration.workspace.pathHidden') : _ctx.t('orchestration.workspace.choice.autoMeta') + }, _toDisplayString(_ctx.t('orchestration.workspace.action.primary')), 9 /* TEXT, PROPS */, ["onClick", "title"]), + _createElementVNode("details", { class: "task-workspace-action-menu" }, [ + _createElementVNode("summary", { + class: "btn-tool btn-primary task-workspace-menu-trigger", + title: _ctx.t('orchestration.workspace.action.more'), + "aria-label": _ctx.t('orchestration.workspace.action.more') + }, [ + _createElementVNode("span", { "aria-hidden": "true" }, "⌄") + ], 8 /* PROPS */, ["title", "aria-label"]), + _createElementVNode("div", { + class: "task-workspace-menu-panel", + role: "menu", + "aria-label": _ctx.t('orchestration.workspace.selector') + }, [ + (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskOrchestrationWorkspaceItems, (workspace) => { + return (_openBlock(), _createElementBlock("button", { + key: 'workspace-menu-' + workspace.key, + type: "button", + class: _normalizeClass(['task-workspace-menu-item', { active: workspace.active }]), + role: "menuitemradio", + "aria-checked": workspace.active ? 'true' : 'false', + title: workspace.path ? _ctx.t('orchestration.workspace.pathHidden') : workspace.meta, + onClick: $event => (_ctx.selectTaskWorkspace(workspace.path)) + }, [ + _createElementVNode("span", { class: "task-workspace-menu-title" }, _toDisplayString(workspace.label), 1 /* TEXT */), + _createElementVNode("span", { class: "task-workspace-menu-meta" }, _toDisplayString(workspace.path ? _ctx.t('orchestration.workspace.pathHidden') : workspace.meta), 1 /* TEXT */), + (workspace.active) + ? (_openBlock(), _createElementBlock("span", { + key: 0, + class: "pill configured" + }, _toDisplayString(_ctx.t('orchestration.workspace.action.selected')), 1 /* TEXT */)) + : _createCommentVNode("v-if", true) + ], 10 /* CLASS, PROPS */, ["aria-checked", "title", "onClick"])) + }), 128 /* KEYED_FRAGMENT */)) + ], 8 /* PROPS */, ["aria-label"]) + ]) + ], 8 /* PROPS */, ["aria-label"]), _createElementVNode("div", { class: "task-project-list", "aria-label": _ctx.t('orchestration.workspace.selector') @@ -4067,11 +4113,6 @@ return function render(_ctx, _cache) { ], 10 /* CLASS, PROPS */, ["aria-selected", "title", "onClick"])) }), 128 /* KEYED_FRAGMENT */)) ], 8 /* PROPS */, ["aria-label"]), - _createElementVNode("button", { - type: "button", - class: "btn-tool btn-primary task-project-new-session", - onClick: $event => (_ctx.startNewTaskWorkspaceSession()) - }, _toDisplayString(_ctx.t('orchestration.workspace.newSession')), 9 /* TEXT, PROPS */, ["onClick"]), _createElementVNode("div", { class: "task-session-inbox" }, [ _createElementVNode("div", { class: "task-session-inbox-head" }, [ _createElementVNode("span", { class: "task-readiness-title" }, _toDisplayString(_ctx.t('orchestration.workspace.sessions.title')), 1 /* TEXT */), diff --git a/web-ui/styles/task-orchestration.css b/web-ui/styles/task-orchestration.css index 7b6c7f7f..4a300b5b 100644 --- a/web-ui/styles/task-orchestration.css +++ b/web-ui/styles/task-orchestration.css @@ -2530,6 +2530,98 @@ padding: 12px; } +#panel-orchestration .task-workspace-action-row { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + align-items: stretch; + gap: 0; + position: relative; +} + +#panel-orchestration .task-workspace-primary-action { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + justify-content: center; + min-width: 0; +} + +#panel-orchestration .task-workspace-action-menu { + position: relative; +} + +#panel-orchestration .task-workspace-action-menu > summary { + list-style: none; +} + +#panel-orchestration .task-workspace-action-menu > summary::-webkit-details-marker { + display: none; +} + +#panel-orchestration .task-workspace-menu-trigger { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-left-color: rgba(255, 255, 255, 0.32); + height: 100%; + min-width: 38px; + padding-inline: 10px; +} + +#panel-orchestration .task-workspace-menu-panel { + position: absolute; + z-index: 20; + top: calc(100% + 8px); + right: 0; + width: min(280px, calc(100vw - 32px)); + display: grid; + gap: 6px; + padding: 8px; + border: 1px solid rgba(148, 163, 184, 0.22); + border-radius: 14px; + background: rgba(255, 255, 255, 0.96); + box-shadow: 0 18px 42px rgba(15, 23, 42, 0.14); +} + +#panel-orchestration .task-workspace-menu-item { + width: 100%; + min-width: 0; + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 2px 8px; + align-items: center; + padding: 8px 9px; + border: 1px solid rgba(148, 163, 184, 0.14); + border-radius: 12px; + background: rgba(248, 250, 252, 0.72); + color: var(--color-text-primary); + text-align: left; + cursor: pointer; +} + +#panel-orchestration .task-workspace-menu-item:hover, +#panel-orchestration .task-workspace-menu-item.active { + border-color: rgba(199, 116, 98, 0.34); + background: rgba(255, 255, 255, 0.94); +} + +#panel-orchestration .task-workspace-menu-title, +#panel-orchestration .task-workspace-menu-meta { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +#panel-orchestration .task-workspace-menu-title { + font-size: 0.82rem; + font-weight: 800; +} + +#panel-orchestration .task-workspace-menu-meta { + grid-column: 1 / -1; + color: var(--color-text-muted); + font-size: 0.72rem; +} + #panel-orchestration .task-project-sidebar-head, #panel-orchestration .task-session-inbox-head { display: flex; From 942516320e179a0a0ff921704feb286e29af0eea Mon Sep 17 00:00:00 2001 From: awsl233777 Date: Tue, 30 Jun 2026 11:27:48 +0000 Subject: [PATCH 05/25] fix(orchestration): mask advanced errors and inputs --- tests/unit/config-tabs-ui.test.mjs | 10 ++++++++++ web-ui/partials/index/panel-orchestration.html | 8 ++++---- web-ui/res/web-ui-render.precompiled.js | 12 ++++++------ web-ui/styles/task-orchestration.css | 10 ++++++++++ 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/tests/unit/config-tabs-ui.test.mjs b/tests/unit/config-tabs-ui.test.mjs index e367e6b7..6d97e6bb 100644 --- a/tests/unit/config-tabs-ui.test.mjs +++ b/tests/unit/config-tabs-ui.test.mjs @@ -15,6 +15,7 @@ test('config template keeps expected config tabs in top and side navigation', () const usagePanel = readProjectFile('web-ui/partials/index/panel-usage.html'); const orchestrationPanel = readProjectFile('web-ui/partials/index/panel-orchestration.html'); const bundledScript = readBundledWebUiScript(); + const precompiledRender = readProjectFile('web-ui/res/web-ui-render.precompiled.js'); const baseTheme = readProjectFile('web-ui/styles/base-theme.css'); const controlsForms = readProjectFile('web-ui/styles/controls-forms.css'); const taskOrchestrationStyles = readProjectFile('web-ui/styles/task-orchestration.css'); @@ -170,6 +171,15 @@ test('config template keeps expected config tabs in top and side navigation', () assert.doesNotMatch(orchestrationPanel, /\{\{ item\.(title|target|taskId|runId|threadId|cwd|summary|lastSummary)/); assert.doesNotMatch(orchestrationPanel, /\{\{ node\.(summary|error)/); assert.doesNotMatch(orchestrationPanel, /\{\{ node\.output\.(provider|model|endpoint)/); + assert.doesNotMatch(orchestrationPanel, /\{\{ taskOrchestration\.(lastError|selectedRunError) \}\}/); + assert.doesNotMatch(orchestrationPanel, /type="password"/); + assert.match(orchestrationPanel, /class="model-input task-sensitive-input" type="text" autocomplete="off"/); + assert.doesNotMatch(taskOrchestrationStyles, /type="password"/); + assert.match(taskOrchestrationStyles, /\.task-sensitive-input\s*\{[\s\S]*-webkit-text-security: disc;/); + assert.doesNotMatch(precompiledRender, /_toDisplayString\(_ctx\.taskOrchestration\.(lastError|selectedRunError)\)/); + assert.match(precompiledRender, /_toDisplayString\(_ctx\.t\('orchestration\.privacy\.detailsHidden'\)\)/); + assert.doesNotMatch(precompiledRender, /type: "password"/); + assert.match(precompiledRender, /class: "model-input task-sensitive-input"/); assert.doesNotMatch(orchestrationPanel, /file\.(relativePath|path)/); assert.match(orchestrationPanel, /class="task-chat-context-chip task-chat-context-action" @click="taskOrchestration\.settingsOpen = true">\{\{ t\('orchestration\.advanced\.open'\) \}\}<\/button>/); assert.match(orchestrationPanel, /@change="taskOrchestration\.selectedEngine === 'workflow' \? null : taskOrchestration\.workflowIdsText = ''"/); diff --git a/web-ui/partials/index/panel-orchestration.html b/web-ui/partials/index/panel-orchestration.html index eefb0b5a..ed56bdf4 100644 --- a/web-ui/partials/index/panel-orchestration.html +++ b/web-ui/partials/index/panel-orchestration.html @@ -168,7 +168,7 @@ v-if="taskOrchestration.plan || taskOrchestration.planIssues.length || taskOrchestration.planWarnings.length || taskOrchestration.lastError" class="selector-section task-plan-card task-thread-message-card task-thread-plan-card">
AI · {{ t('orchestration.plan.title') }}
-
{{ taskOrchestration.lastError }}
+
{{ t('orchestration.privacy.detailsHidden') }}
/plan {{ t('orchestration.chat.assistant.planSummary', { nodes: taskOrchestration.plan.nodes.length, waves: taskOrchestration.plan.waves.length }) }} @@ -399,7 +399,7 @@
-
{{ taskOrchestration.selectedRunError }}
+
{{ t('orchestration.privacy.detailsHidden') }}
{{ t('orchestration.detail.empty.title') }}
{{ t('orchestration.detail.empty.subtitle') }}
@@ -505,12 +505,12 @@
- -
- {{ t('orchestration.advanced.title') }} -
- +
-
-
{{ t('orchestration.openai.status.title') }}
-
{{ t('orchestration.openai.status.subtitle') }}
-
-
- {{ t('orchestration.openai.status.provider') }} - {{ taskOrchestration.openAiChatStatus.providerName ? t('orchestration.openai.status.configured') : t('orchestration.openai.status.notSet') }} -
-
- {{ t('orchestration.openai.status.model') }} - {{ taskOrchestration.openAiChatStatus.model ? t('orchestration.openai.status.configured') : t('orchestration.openai.status.notSet') }} -
-
- {{ t('orchestration.openai.status.endpoint') }} - {{ taskOrchestration.openAiChatStatus.endpoint ? t('orchestration.openai.status.configured') : t('orchestration.openai.status.notSet') }} -
-
- {{ t('orchestration.openai.status.apiKey') }} - {{ taskOrchestration.openAiChatStatus.hasApiKey ? t('orchestration.openai.status.configured') : t('orchestration.openai.status.missing') }} -
-
- {{ t('orchestration.openai.status.headers') }} - {{ taskOrchestration.openAiChatStatus.hasExtraHeaders ? t('orchestration.openai.status.configured') : t('orchestration.openai.status.notSet') }} -
+
+ {{ t('orchestration.agent.trace.runs') }} + {{ taskOrchestrationWorkspaceRuns.length }}
-
{{ t('orchestration.openai.status.notLoaded') }}
-
{{ t('orchestration.privacy.detailsHidden') }}
- -
-
-
-
{{ t('orchestration.quick.checklist.title') }}
-
{{ t('orchestration.quick.checklist.subtitle') }}
+
+ {{ t('orchestration.agent.trace.nodes') }} + {{ taskOrchestrationSelectedRunNodes.length }}
-
-
- {{ item.label }} - {{ item.detail }} -
+
+
-
-
{{ t('orchestration.quick.status.title') }}
- {{ taskOrchestrationDraftReadiness.title }} - {{ taskOrchestrationDraftReadiness.summary }} + + +
+ +
+
+ {{ row.label }} + {{ row.value }} +
- -
-
+ -
-
-
-
{{ t('orchestration.stage.title') }}
-
{{ t('orchestration.stage.subtitle') }}
-
-
-
{{ t('orchestration.stage.pill.target') }}
-
{{ t('orchestration.stage.pill.preview') }}
-
{{ t('orchestration.stage.pill.run') }}
-
-
+
+ {{ t('orchestration.advanced.title') }} +
+ + + + +
+
+ +
+
+
-
diff --git a/web-ui/res/web-ui-render.precompiled.js b/web-ui/res/web-ui-render.precompiled.js index 15842813..bf36a0ba 100644 --- a/web-ui/res/web-ui-render.precompiled.js +++ b/web-ui/res/web-ui-render.precompiled.js @@ -3995,225 +3995,134 @@ return function render(_ctx, _cache) { (_ctx.taskOrchestrationTabEnabled) ? _withDirectives((_openBlock(), _createElementBlock("div", { key: 0, - class: "mode-content", + class: "mode-content task-kilo-mode", id: "panel-orchestration", "data-active": _ctx.mainTab === 'orchestration' ? 'true' : 'false', role: "tabpanel", "aria-labelledby": "tab-orchestration" }, [ - _createElementVNode("section", { class: "selector-section task-hero-card" }, [ - _createElementVNode("div", { class: "task-hero-main" }, [ - _createElementVNode("div", null, [ - _createElementVNode("div", { class: "task-hero-kicker" }, _toDisplayString(_ctx.t('orchestration.hero.kicker')), 1 /* TEXT */), - _createElementVNode("div", { class: "selector-title" }, _toDisplayString(_ctx.t('orchestration.hero.title')), 1 /* TEXT */), - _createElementVNode("div", { class: "skills-panel-note task-hero-copy" }, _toDisplayString(_ctx.t('orchestration.hero.subtitle')), 1 /* TEXT */) - ]), - _createElementVNode("div", { class: "task-hero-actions settings-tab-actions task-header-actions" }, [ + _createElementVNode("section", { + class: "task-kilo-app-shell", + "aria-label": _ctx.t('orchestration.kilo.aria') + }, [ + _createElementVNode("aside", { + class: "task-kilo-activity-bar", + "aria-hidden": "true" + }, [ + _createElementVNode("div", { class: "task-kilo-logo-mark" }, "K"), + _createElementVNode("span", { class: "task-kilo-activity-item active" }, "⌘"), + _createElementVNode("span", { class: "task-kilo-activity-item" }, "▣"), + _createElementVNode("span", { class: "task-kilo-activity-item" }, "◇"), + _createElementVNode("span", { class: "task-kilo-activity-spacer" }), + _createElementVNode("span", { class: "task-kilo-activity-item" }, "⚙") + ]), + _createElementVNode("aside", { + class: "task-kilo-sidebar", + "aria-label": _ctx.t('orchestration.workspace.aria') + }, [ + _createElementVNode("div", { class: "task-kilo-sidebar-head" }, [ + _createElementVNode("div", null, [ + _createElementVNode("div", { class: "task-hero-kicker" }, _toDisplayString(_ctx.t('orchestration.kilo.kicker')), 1 /* TEXT */), + _createElementVNode("div", { class: "task-kilo-sidebar-title" }, _toDisplayString(_ctx.t('orchestration.workspace.title')), 1 /* TEXT */) + ]), _createElementVNode("button", { type: "button", - class: "btn-tool btn-tool-compact", + class: "btn-mini", onClick: $event => (_ctx.loadTaskOrchestrationOverview({ forceRefresh: true, includeDetail: true })), disabled: _ctx.taskOrchestration.loading - }, _toDisplayString(_ctx.taskOrchestration.loading ? _ctx.t('common.refreshing') : _ctx.t('common.refresh')), 9 /* TEXT, PROPS */, ["onClick", "disabled"]), - _createElementVNode("button", { - type: "button", - class: "btn-tool btn-tool-compact", - onClick: $event => (_ctx.resetTaskOrchestrationDraft()), - disabled: _ctx.taskOrchestration.running || _ctx.taskOrchestration.queueAdding || _ctx.taskOrchestration.planning - }, _toDisplayString(_ctx.t('orchestration.draft.reset')), 9 /* TEXT, PROPS */, ["onClick", "disabled"]), - _createElementVNode("button", { - type: "button", - class: "btn-tool btn-tool-compact", - onClick: $event => (_ctx.switchMainTab('dashboard')), - disabled: _ctx.loading || !!_ctx.initError - }, _toDisplayString(_ctx.t('dashboard.doctor.title')), 9 /* TEXT, PROPS */, ["onClick", "disabled"]) - ]) - ]) - ]), - _createElementVNode("div", { class: "task-layout-grid task-layout-grid-primary task-quick-layout" }, [ - _createElementVNode("section", { class: "selector-section task-compose-flow-card task-quick-card" }, [ - _createElementVNode("aside", { - class: "task-project-sidebar", - "aria-label": _ctx.t('orchestration.workspace.aria') - }, [ - _createElementVNode("div", { class: "task-project-sidebar-head" }, [ - _createElementVNode("div", null, [ - _createElementVNode("div", { class: "task-thread-card-label" }, _toDisplayString(_ctx.t('orchestration.workspace.title')), 1 /* TEXT */), - _createElementVNode("div", { class: "skills-panel-note" }, _toDisplayString(_ctx.t('orchestration.workspace.subtitle')), 1 /* TEXT */) - ]), - _createElementVNode("button", { - type: "button", - class: "btn-mini", - onClick: $event => (_ctx.loadTaskOrchestrationOverview({ forceRefresh: true, includeDetail: true })), - disabled: _ctx.taskOrchestration.loading - }, _toDisplayString(_ctx.taskOrchestration.loading ? _ctx.t('common.refreshing') : _ctx.t('common.refresh')), 9 /* TEXT, PROPS */, ["onClick", "disabled"]) - ]), - _createElementVNode("div", { - class: "task-workspace-action-row", - "aria-label": _ctx.t('orchestration.workspace.actions.aria') - }, [ - _createElementVNode("button", { - type: "button", - class: "btn-tool btn-primary task-workspace-primary-action", - onClick: $event => (_ctx.startNewTaskWorkspaceSession(_ctx.taskOrchestrationWorkspacePath)), - title: _ctx.taskOrchestrationWorkspacePath ? _ctx.t('orchestration.workspace.pathHidden') : _ctx.t('orchestration.workspace.choice.autoMeta') - }, _toDisplayString(_ctx.t('orchestration.workspace.action.primary')), 9 /* TEXT, PROPS */, ["onClick", "title"]), - _createElementVNode("details", { class: "task-workspace-action-menu" }, [ - _createElementVNode("summary", { - class: "btn-tool btn-primary task-workspace-menu-trigger", - title: _ctx.t('orchestration.workspace.action.more'), - "aria-label": _ctx.t('orchestration.workspace.action.more') - }, [ - _createElementVNode("span", { "aria-hidden": "true" }, "⌄") - ], 8 /* PROPS */, ["title", "aria-label"]), - _createElementVNode("div", { - class: "task-workspace-menu-panel", - "aria-label": _ctx.t('orchestration.workspace.selector') - }, [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskOrchestrationWorkspaceItems, (workspace) => { - return (_openBlock(), _createElementBlock("button", { - key: 'workspace-menu-' + workspace.key, - type: "button", - class: _normalizeClass(['task-workspace-menu-item', { active: workspace.active }]), - "aria-pressed": workspace.active ? 'true' : 'false', - title: workspace.path ? _ctx.t('orchestration.workspace.pathHidden') : workspace.meta, - onClick: $event => (_ctx.selectTaskWorkspace(workspace.path)) - }, [ - _createElementVNode("span", { class: "task-workspace-menu-title" }, _toDisplayString(workspace.label), 1 /* TEXT */), - _createElementVNode("span", { class: "task-workspace-menu-meta" }, _toDisplayString(workspace.path ? _ctx.t('orchestration.workspace.pathHidden') : workspace.meta), 1 /* TEXT */), - (workspace.active) - ? (_openBlock(), _createElementBlock("span", { - key: 0, - class: "pill configured" - }, _toDisplayString(_ctx.t('orchestration.workspace.action.selected')), 1 /* TEXT */)) - : _createCommentVNode("v-if", true) - ], 10 /* CLASS, PROPS */, ["aria-pressed", "title", "onClick"])) - }), 128 /* KEYED_FRAGMENT */)) - ], 8 /* PROPS */, ["aria-label"]) - ]) - ], 8 /* PROPS */, ["aria-label"]), - _createElementVNode("div", { - class: "task-project-list", - "aria-label": _ctx.t('orchestration.workspace.selector') - }, [ + }, _toDisplayString(_ctx.taskOrchestration.loading ? _ctx.t('common.refreshing') : _ctx.t('common.refresh')), 9 /* TEXT, PROPS */, ["onClick", "disabled"]) + ]), + _createElementVNode("button", { + type: "button", + class: "btn-tool btn-primary task-kilo-new-task", + onClick: $event => (_ctx.startNewTaskWorkspaceSession(_ctx.taskOrchestrationWorkspacePath)), + title: _ctx.taskOrchestrationWorkspacePath ? _ctx.t('orchestration.workspace.pathHidden') : _ctx.t('orchestration.workspace.choice.autoMeta') + }, _toDisplayString(_ctx.t('orchestration.workspace.action.primary')), 9 /* TEXT, PROPS */, ["onClick", "title"]), + _createElementVNode("div", { class: "task-kilo-sidebar-section" }, [ + _createElementVNode("div", { class: "task-kilo-section-label" }, _toDisplayString(_ctx.t('orchestration.workspace.selector')), 1 /* TEXT */), + _createElementVNode("div", { class: "task-kilo-workspace-list" }, [ (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskOrchestrationWorkspaceItems, (workspace) => { return (_openBlock(), _createElementBlock("button", { key: workspace.key, type: "button", - class: _normalizeClass(['task-project-item', { active: workspace.active }]), - "aria-selected": workspace.active ? 'true' : 'false', + class: _normalizeClass(['task-kilo-workspace-item', { active: workspace.active }]), + "aria-pressed": workspace.active ? 'true' : 'false', title: workspace.path ? _ctx.t('orchestration.workspace.pathHidden') : workspace.meta, onClick: $event => (_ctx.selectTaskWorkspace(workspace.path)) }, [ - _createElementVNode("span", { class: "task-project-item-title" }, _toDisplayString(workspace.label), 1 /* TEXT */), - _createElementVNode("span", { class: "task-project-item-meta" }, _toDisplayString(workspace.path ? _ctx.t('orchestration.workspace.pathHidden') : workspace.meta), 1 /* TEXT */), - _createElementVNode("span", { class: "task-project-item-stats" }, _toDisplayString(_ctx.t('orchestration.workspace.counts', { runs: workspace.runCount, queue: workspace.queueCount })), 1 /* TEXT */) - ], 10 /* CLASS, PROPS */, ["aria-selected", "title", "onClick"])) - }), 128 /* KEYED_FRAGMENT */)) - ], 8 /* PROPS */, ["aria-label"]), - _createElementVNode("div", { class: "task-session-inbox" }, [ - _createElementVNode("div", { class: "task-session-inbox-head" }, [ - _createElementVNode("span", { class: "task-readiness-title" }, _toDisplayString(_ctx.t('orchestration.workspace.sessions.title')), 1 /* TEXT */), - _createElementVNode("span", { class: "pill neutral" }, _toDisplayString(_ctx.taskOrchestrationWorkspaceSessions.length), 1 /* TEXT */) - ]), - (!_ctx.taskOrchestrationWorkspaceSessions.length) - ? (_openBlock(), _createElementBlock("div", { - key: 0, - class: "task-empty-state task-session-empty" - }, [ - _createElementVNode("div", { class: "task-empty-title" }, _toDisplayString(_ctx.t('orchestration.workspace.sessions.empty.title')), 1 /* TEXT */), - _createElementVNode("div", { class: "task-empty-copy" }, _toDisplayString(_ctx.t('orchestration.workspace.sessions.empty.subtitle')), 1 /* TEXT */) - ])) - : _createCommentVNode("v-if", true), - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskOrchestrationWorkspaceSessions.slice(0, 8), (session) => { - return (_openBlock(), _createElementBlock("button", { - key: session.id, - type: "button", - class: "task-session-inbox-item", - onClick: $event => (_ctx.continueTaskWorkspaceSession(session)) - }, [ - _createElementVNode("span", { class: "task-session-inbox-main" }, [ - _createElementVNode("span", { class: "task-session-inbox-title" }, _toDisplayString(session.type === 'queue' ? _ctx.t('orchestration.workspace.sessions.queueTitle') : _ctx.t('orchestration.workspace.sessions.runTitle')), 1 /* TEXT */), - _createElementVNode("span", { class: "task-session-inbox-meta" }, _toDisplayString(_ctx.t('orchestration.workspace.sessions.detailsHidden')), 1 /* TEXT */) - ]), - _createElementVNode("span", { - class: _normalizeClass(['pill', _ctx.taskRunStatusTone(session.status)]) - }, _toDisplayString(session.status), 3 /* TEXT, CLASS */) - ], 8 /* PROPS */, ["onClick"])) + _createElementVNode("span", { class: "task-kilo-workspace-title" }, _toDisplayString(workspace.label), 1 /* TEXT */), + _createElementVNode("span", { class: "task-kilo-workspace-meta" }, _toDisplayString(workspace.path ? _ctx.t('orchestration.workspace.pathHidden') : workspace.meta), 1 /* TEXT */), + _createElementVNode("span", { class: "task-kilo-workspace-counts" }, _toDisplayString(_ctx.t('orchestration.workspace.counts', { runs: workspace.runCount, queue: workspace.queueCount })), 1 /* TEXT */) + ], 10 /* CLASS, PROPS */, ["aria-pressed", "title", "onClick"])) }), 128 /* KEYED_FRAGMENT */)) ]) - ], 8 /* PROPS */, ["aria-label"]), - _createElementVNode("div", { class: "task-quick-main" }, [ - _createElementVNode("div", { class: "task-quick-copy" }, [ - _createElementVNode("div", { class: "task-hero-kicker" }, _toDisplayString(_ctx.t('orchestration.quick.kicker')), 1 /* TEXT */), - _createElementVNode("div", { class: "selector-title task-quick-title" }, _toDisplayString(_ctx.t('orchestration.quick.title')), 1 /* TEXT */), - _createElementVNode("div", { class: "skills-panel-note task-hero-copy" }, _toDisplayString(_ctx.t('orchestration.quick.subtitle')), 1 /* TEXT */) + ]), + _createElementVNode("div", { class: "task-kilo-sidebar-section task-kilo-session-section" }, [ + _createElementVNode("div", { class: "task-kilo-session-head" }, [ + _createElementVNode("span", { class: "task-kilo-section-label" }, _toDisplayString(_ctx.t('orchestration.workspace.sessions.title')), 1 /* TEXT */), + _createElementVNode("span", { class: "pill neutral" }, _toDisplayString(_ctx.taskOrchestrationWorkspaceSessions.length), 1 /* TEXT */) ]), - _createElementVNode("section", { - class: "task-agent-cockpit", - "aria-label": _ctx.t('orchestration.agent.aria') - }, [ - _createElementVNode("div", { class: "task-agent-cockpit-head" }, [ - _createElementVNode("div", null, [ - _createElementVNode("div", { class: "task-hero-kicker" }, _toDisplayString(_ctx.t('orchestration.agent.kicker')), 1 /* TEXT */), - _createElementVNode("div", { class: "selector-title task-agent-title" }, _toDisplayString(_ctx.t('orchestration.agent.title')), 1 /* TEXT */), - _createElementVNode("div", { class: "skills-panel-note task-agent-copy" }, _toDisplayString(_ctx.t('orchestration.agent.subtitle')), 1 /* TEXT */) - ]), - _createElementVNode("span", { - class: _normalizeClass(['pill', _ctx.taskOrchestration.running || _ctx.taskOrchestration.planning ? 'warn' : (_ctx.taskOrchestrationSelectedRun && _ctx.taskOrchestrationSelectedRun.run ? _ctx.taskRunStatusTone(_ctx.taskOrchestrationSelectedRun.run.status) : 'neutral')]) - }, _toDisplayString(_ctx.taskOrchestration.running ? _ctx.t('orchestration.agent.state.running') : (_ctx.taskOrchestration.planning ? _ctx.t('orchestration.agent.state.planning') : (_ctx.taskOrchestrationSelectedRun && _ctx.taskOrchestrationSelectedRun.run ? _ctx.taskOrchestrationSelectedRun.run.status : _ctx.t('orchestration.agent.state.ready')))), 3 /* TEXT, CLASS */) - ]), - _createElementVNode("div", { class: "task-agent-surface-grid" }, [ - _createElementVNode("div", { class: "task-agent-surface-card" }, [ - _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.agent.surface.workspace')), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(_ctx.taskOrchestrationWorkspacePath ? _ctx.t('orchestration.privacy.workspace.selected') : _ctx.t('orchestration.chat.context.workspace.auto')), 1 /* TEXT */) - ]), - _createElementVNode("div", { class: "task-agent-surface-card" }, [ - _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.agent.surface.session')), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString((_ctx.taskOrchestration.threadId.trim() || (_ctx.taskOrchestrationSelectedRun && _ctx.taskOrchestrationSelectedRun.threadId)) ? _ctx.t('orchestration.privacy.thread.selected') : _ctx.t('orchestration.chat.context.thread.auto')), 1 /* TEXT */) - ]), - _createElementVNode("div", { class: "task-agent-surface-card" }, [ - _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.agent.surface.mode')), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(_ctx.taskOrchestration.selectedEngine === 'workflow' ? _ctx.t('orchestration.engine.workflow') : _ctx.t('orchestration.engine.openaiChat')) + " · " + _toDisplayString(_ctx.taskOrchestration.runMode === 'dry-run' ? _ctx.t('orchestration.runMode.dryRun') : (_ctx.taskOrchestration.runMode === 'read' ? _ctx.t('orchestration.runMode.readOnly') : _ctx.t('orchestration.runMode.write'))), 1 /* TEXT */) - ]), - _createElementVNode("div", { class: "task-agent-surface-card" }, [ - _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.agent.surface.trace')), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(_ctx.taskOrchestrationWorkspaceRuns.length) + " " + _toDisplayString(_ctx.t('orchestration.agent.surface.runs')) + " · " + _toDisplayString(_ctx.taskOrchestrationWorkspaceQueue.length) + " " + _toDisplayString(_ctx.t('orchestration.agent.surface.queue')), 1 /* TEXT */) - ]) - ]) - ], 8 /* PROPS */, ["aria-label"]), - _createElementVNode("section", { - class: "task-kilo-host-card", - "aria-label": _ctx.t('orchestration.kilo.aria') - }, [ - _createElementVNode("div", { class: "task-kilo-host-header" }, [ - _createElementVNode("div", null, [ - _createElementVNode("div", { class: "task-hero-kicker" }, _toDisplayString(_ctx.t('orchestration.kilo.kicker')), 1 /* TEXT */), - _createElementVNode("div", { class: "selector-title task-kilo-host-title" }, _toDisplayString(_ctx.taskKiloWebviewHostSnapshot.title), 1 /* TEXT */), - _createElementVNode("div", { class: "skills-panel-note task-kilo-host-copy" }, _toDisplayString(_ctx.taskKiloWebviewHostSnapshot.subtitle), 1 /* TEXT */) + (!_ctx.taskOrchestrationWorkspaceSessions.length) + ? (_openBlock(), _createElementBlock("div", { + key: 0, + class: "task-kilo-empty-copy" + }, _toDisplayString(_ctx.t('orchestration.workspace.sessions.empty.subtitle')), 1 /* TEXT */)) + : _createCommentVNode("v-if", true), + (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskOrchestrationWorkspaceSessions.slice(0, 10), (session) => { + return (_openBlock(), _createElementBlock("button", { + key: session.id, + type: "button", + class: "task-kilo-session-item", + onClick: $event => (_ctx.continueTaskWorkspaceSession(session)) + }, [ + _createElementVNode("span", null, [ + _createElementVNode("strong", null, _toDisplayString(session.type === 'queue' ? _ctx.t('orchestration.workspace.sessions.queueTitle') : _ctx.t('orchestration.workspace.sessions.runTitle')), 1 /* TEXT */), + _createElementVNode("small", null, _toDisplayString(_ctx.t('orchestration.workspace.sessions.detailsHidden')), 1 /* TEXT */) ]), _createElementVNode("span", { - class: _normalizeClass(['pill', _ctx.taskKiloWebviewHostSnapshot.statusTone]) - }, _toDisplayString(_ctx.taskKiloWebviewHostSnapshot.status), 3 /* TEXT, CLASS */) - ]), - _createElementVNode("div", { - class: "task-kilo-host-shell", - role: "group", - "aria-label": _ctx.t('orchestration.kilo.shell.aria') - }, [ - _createElementVNode("aside", { - class: "task-kilo-activity-rail", - "aria-hidden": "true" + class: _normalizeClass(['pill', _ctx.taskRunStatusTone(session.status)]) + }, _toDisplayString(session.status), 3 /* TEXT, CLASS */) + ], 8 /* PROPS */, ["onClick"])) + }), 128 /* KEYED_FRAGMENT */)) + ]) + ], 8 /* PROPS */, ["aria-label"]), + _createElementVNode("main", { class: "task-kilo-chat-view" }, [ + _createElementVNode("header", { class: "task-kilo-task-header" }, [ + _createElementVNode("div", null, [ + _createElementVNode("div", { class: "task-hero-kicker" }, _toDisplayString(_ctx.taskKiloWebviewHostSnapshot.status), 1 /* TEXT */), + _createElementVNode("h2", null, _toDisplayString(_ctx.t('orchestration.kilo.surface.title')), 1 /* TEXT */), + _createElementVNode("p", null, _toDisplayString(_ctx.taskKiloWebviewHostSnapshot.subtitle), 1 /* TEXT */) + ]), + _createElementVNode("div", { class: "task-kilo-header-actions" }, [ + _createElementVNode("span", { + class: _normalizeClass(['pill', _ctx.taskKiloWebviewHostSnapshot.statusTone]) + }, _toDisplayString(_ctx.taskKiloWebviewHostSnapshot.status), 3 /* TEXT, CLASS */), + _createElementVNode("button", { + type: "button", + class: "btn-tool btn-tool-compact", + onClick: $event => (_ctx.installTaskKiloWebviewShim()) + }, _toDisplayString(_ctx.t('orchestration.kilo.bridge.install')), 9 /* TEXT, PROPS */, ["onClick"]), + _createElementVNode("button", { + type: "button", + class: "btn-tool btn-tool-compact", + onClick: $event => (_ctx.resetTaskOrchestrationDraft()), + disabled: _ctx.taskOrchestration.running || _ctx.taskOrchestration.queueAdding || _ctx.taskOrchestration.planning + }, _toDisplayString(_ctx.t('orchestration.draft.reset')), 9 /* TEXT, PROPS */, ["onClick", "disabled"]) + ]) + ]), + _createElementVNode("section", { + class: "task-kilo-message-list", + role: "log", + "aria-label": _ctx.t('orchestration.chat.thread.aria') + }, [ + (!_ctx.taskOrchestrationConversationMessages.length && !(_ctx.taskOrchestration.plan || _ctx.taskOrchestration.planIssues.length || _ctx.taskOrchestration.planWarnings.length || _ctx.taskOrchestration.lastError)) + ? (_openBlock(), _createElementBlock("div", { + key: 0, + class: "task-kilo-welcome-card" }, [ - _createElementVNode("span", { class: "active" }, "⌘"), - _createElementVNode("span", null, "◇"), - _createElementVNode("span", null, "▣") - ]), - _createElementVNode("div", { class: "task-kilo-chat-surface" }, [ - _createElementVNode("div", { class: "task-kilo-surface-topbar" }, [ - _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.kilo.surface.title')), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(_ctx.t('orchestration.kilo.surface.host')), 1 /* TEXT */) - ]), + _createElementVNode("div", { class: "task-kilo-welcome-mark" }, "K"), + _createElementVNode("h3", null, _toDisplayString(_ctx.taskKiloWebviewHostSnapshot.title), 1 /* TEXT */), + _createElementVNode("p", null, _toDisplayString(_ctx.t('orchestration.kilo.bridge.copy')), 1 /* TEXT */), _createElementVNode("div", { class: "task-kilo-context-grid" }, [ (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskKiloWebviewHostSnapshot.context, (item) => { return (_openBlock(), _createElementBlock("div", { @@ -4224,909 +4133,259 @@ return function render(_ctx, _cache) { _createElementVNode("strong", null, _toDisplayString(item.value), 1 /* TEXT */) ])) }), 128 /* KEYED_FRAGMENT */)) - ]), - _createElementVNode("div", { - class: "task-kilo-timeline", - "aria-label": _ctx.t('orchestration.kilo.timeline.aria') - }, [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskKiloWebviewHostSnapshot.timelineBars, (bar) => { - return (_openBlock(), _createElementBlock("span", { - key: bar.key, - class: _normalizeClass(['task-kilo-timeline-bar', bar.tone]), - title: bar.label - }, null, 10 /* CLASS, PROPS */, ["title"])) - }), 128 /* KEYED_FRAGMENT */)) - ], 8 /* PROPS */, ["aria-label"]), - _createElementVNode("div", { class: "task-kilo-protocol-grid" }, [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskKiloWebviewHostSnapshot.protocolRows, (row) => { - return (_openBlock(), _createElementBlock("div", { - key: row.key, - class: "task-kilo-protocol-row" - }, [ - _createElementVNode("span", null, _toDisplayString(row.label), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(row.value), 1 /* TEXT */) - ])) - }), 128 /* KEYED_FRAGMENT */)) ]) - ]) - ], 8 /* PROPS */, ["aria-label"]), - _createElementVNode("div", { class: "task-kilo-host-footer" }, [ - _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.kilo.bridge.copy')), 1 /* TEXT */), - _createElementVNode("button", { - type: "button", - class: "btn-tool btn-tool-compact", - onClick: $event => (_ctx.installTaskKiloWebviewShim()) - }, _toDisplayString(_ctx.t('orchestration.kilo.bridge.install')), 9 /* TEXT, PROPS */, ["onClick"]) - ]) - ], 8 /* PROPS */, ["aria-label"]), - _createElementVNode("div", { class: "task-quick-input-card task-chat-panel" }, [ - _createElementVNode("div", { - class: "task-chat-thread", - role: "log", - "aria-label": _ctx.t('orchestration.chat.thread.aria') - }, [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskOrchestrationConversationMessages, (message) => { - return (_openBlock(), _createElementBlock("div", { - key: message.id, - class: _normalizeClass(['task-chat-bubble-row', message.role === 'user' ? 'is-user' : 'is-assistant']) - }, [ - _createElementVNode("div", { class: "task-chat-bubble" }, [ - _createElementVNode("div", { class: "task-chat-bubble-label" }, _toDisplayString(message.label), 1 /* TEXT */), - _createElementVNode("div", { class: "task-chat-bubble-text" }, _toDisplayString(message.text), 1 /* TEXT */), - (message.meta) - ? (_openBlock(), _createElementBlock("div", { - key: 0, - class: "task-chat-bubble-meta" - }, _toDisplayString(message.meta), 1 /* TEXT */)) - : _createCommentVNode("v-if", true) - ]) - ], 2 /* CLASS */)) - }), 128 /* KEYED_FRAGMENT */)), - (_ctx.taskOrchestration.plan || _ctx.taskOrchestration.planIssues.length || _ctx.taskOrchestration.planWarnings.length || _ctx.taskOrchestration.lastError) - ? (_openBlock(), _createElementBlock("div", { - key: 0, - class: "task-chat-bubble-row is-user task-thread-plan-request" - }, [ - _createElementVNode("div", { class: "task-chat-bubble" }, [ - _createElementVNode("div", { class: "task-chat-bubble-label" }, "You · /plan"), - _createElementVNode("div", { class: "task-chat-bubble-text" }, "/plan " + _toDisplayString(_ctx.taskOrchestration.target), 1 /* TEXT */), - (_ctx.taskOrchestrationDraftMetrics.followUpCount) - ? (_openBlock(), _createElementBlock("div", { - key: 0, - class: "task-chat-bubble-meta" - }, _toDisplayString(_ctx.t('orchestration.chat.input.sequenceHint', { count: _ctx.taskOrchestrationDraftMetrics.followUpCount + 1 })), 1 /* TEXT */)) - : _createCommentVNode("v-if", true) - ]) - ])) - : _createCommentVNode("v-if", true), - (_ctx.taskOrchestration.plan || _ctx.taskOrchestration.planIssues.length || _ctx.taskOrchestration.planWarnings.length || _ctx.taskOrchestration.lastError) - ? (_openBlock(), _createElementBlock("section", { - key: 1, - class: "selector-section task-plan-card task-thread-message-card task-thread-plan-card" - }, [ - _createElementVNode("div", { class: "task-thread-card-label" }, "AI · " + _toDisplayString(_ctx.t('orchestration.plan.title')), 1 /* TEXT */), - (_ctx.taskOrchestration.lastError) - ? (_openBlock(), _createElementBlock("div", { - key: 0, - class: "task-issue-item" - }, _toDisplayString(_ctx.t('orchestration.privacy.detailsHidden')), 1 /* TEXT */)) - : _createCommentVNode("v-if", true), - (_ctx.taskOrchestration.plan) - ? (_openBlock(), _createElementBlock("div", { - key: 1, - class: "task-thread-run-summary task-thread-plan-summary" - }, [ - _createElementVNode("span", { class: "pill configured" }, "/plan"), - _createElementVNode("span", { class: "task-thread-run-summary-copy" }, _toDisplayString(_ctx.t('orchestration.chat.assistant.planSummary', { nodes: _ctx.taskOrchestration.plan.nodes.length, waves: _ctx.taskOrchestration.plan.waves.length })), 1 /* TEXT */) - ])) - : _createCommentVNode("v-if", true), - (_ctx.taskOrchestration.planIssues.length) - ? (_openBlock(), _createElementBlock("div", { - key: 2, - class: "task-issues-list" - }, [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskOrchestration.planIssues, (issue) => { - return (_openBlock(), _createElementBlock("div", { - key: issue.code + issue.message, - class: "task-issue-item" - }, _toDisplayString(issue.message), 1 /* TEXT */)) - }), 128 /* KEYED_FRAGMENT */)) - ])) - : _createCommentVNode("v-if", true), - (_ctx.taskOrchestration.planWarnings.length) - ? (_openBlock(), _createElementBlock("div", { - key: 3, - class: "task-warning-list" - }, [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskOrchestration.planWarnings, (warning) => { - return (_openBlock(), _createElementBlock("div", { - key: warning, - class: "task-warning-item" - }, _toDisplayString(warning), 1 /* TEXT */)) - }), 128 /* KEYED_FRAGMENT */)) - ])) - : _createCommentVNode("v-if", true), - (_ctx.taskOrchestration.plan) - ? (_openBlock(), _createElementBlock("details", { - key: 4, - class: "task-thread-run-details task-thread-plan-details" - }, [ - _createElementVNode("summary", null, "Plan details"), - _createElementVNode("div", { class: "task-plan-summary-strip" }, [ - _createElementVNode("div", { class: "task-plan-summary-item" }, [ - _createElementVNode("span", { class: "task-plan-summary-label" }, _toDisplayString(_ctx.t('orchestration.plan.summary.nodes')), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(_ctx.taskOrchestration.plan.nodes.length), 1 /* TEXT */) - ]), - _createElementVNode("div", { class: "task-plan-summary-item" }, [ - _createElementVNode("span", { class: "task-plan-summary-label" }, _toDisplayString(_ctx.t('orchestration.plan.summary.waves')), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(_ctx.taskOrchestration.plan.waves.length), 1 /* TEXT */) - ]), - _createElementVNode("div", { class: "task-plan-summary-item" }, [ - _createElementVNode("span", { class: "task-plan-summary-label" }, _toDisplayString(_ctx.t('orchestration.plan.summary.engine')), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(_ctx.taskOrchestration.plan.engine), 1 /* TEXT */) - ]), - (_ctx.taskOrchestration.plan.threadId) - ? (_openBlock(), _createElementBlock("div", { - key: 0, - class: "task-plan-summary-item" - }, [ - _createElementVNode("span", { class: "task-plan-summary-label" }, _toDisplayString(_ctx.t('orchestration.plan.summary.threadId')), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(_ctx.t('orchestration.privacy.thread.selected')), 1 /* TEXT */) - ])) - : _createCommentVNode("v-if", true), - (_ctx.taskOrchestration.plan.cwd) - ? (_openBlock(), _createElementBlock("div", { - key: 1, - class: "task-plan-summary-item" - }, [ - _createElementVNode("span", { class: "task-plan-summary-label" }, _toDisplayString(_ctx.t('orchestration.plan.summary.cwd')), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(_ctx.t('orchestration.privacy.workspace.selected')), 1 /* TEXT */) - ])) - : _createCommentVNode("v-if", true) - ]), - _createElementVNode("div", { class: "task-wave-list" }, [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskOrchestration.plan.waves, (wave) => { - return (_openBlock(), _createElementBlock("div", { - key: wave.label, - class: "task-wave-card" - }, [ - _createElementVNode("div", { class: "task-wave-title" }, _toDisplayString(wave.label), 1 /* TEXT */), - _createElementVNode("div", { class: "task-wave-nodes" }, _toDisplayString(_ctx.t('orchestration.plan.wave.nodesHidden', { count: (wave.nodeIds || []).length })), 1 /* TEXT */) - ])) - }), 128 /* KEYED_FRAGMENT */)) - ]), - _createElementVNode("div", { class: "task-node-list" }, [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskOrchestration.plan.nodes, (node) => { - return (_openBlock(), _createElementBlock("div", { - key: node.id, - class: "task-node-card" - }, [ - _createElementVNode("div", { class: "task-node-head" }, [ - _createElementVNode("div", null, [ - _createElementVNode("div", { class: "task-node-title" }, _toDisplayString(_ctx.t('orchestration.detail.node.step')), 1 /* TEXT */), - _createElementVNode("div", { class: "task-node-meta" }, _toDisplayString(_ctx.t('orchestration.detail.node.meta', { attempts: 0, autoFix: 0 })) + " · " + _toDisplayString(node.kind), 1 /* TEXT */) - ]), - _createElementVNode("span", { - class: _normalizeClass(['pill', node.write ? 'configured' : 'empty']) - }, _toDisplayString(node.write ? _ctx.t('orchestration.plan.node.write') : _ctx.t('orchestration.plan.node.readOnly')), 3 /* TEXT, CLASS */) - ]), - _createElementVNode("div", { class: "task-node-deps" }, _toDisplayString(_ctx.t('orchestration.labels.dependencies')) + _toDisplayString(_ctx.formatTaskNodeDependencies(node)), 1 /* TEXT */) - ])) - }), 128 /* KEYED_FRAGMENT */)) - ]) - ])) - : _createCommentVNode("v-if", true) - ])) - : _createCommentVNode("v-if", true) - ], 8 /* PROPS */, ["aria-label"]), - _createElementVNode("div", { class: "task-thread-composer" }, [ - _createElementVNode("label", { class: "task-quick-target-field task-chat-composer" }, [ - _createElementVNode("span", { class: "selector-label" }, _toDisplayString(_ctx.t('orchestration.chat.input.label')), 1 /* TEXT */), - _createElementVNode("span", { - class: "task-composer-prompt-glyph", - "aria-hidden": "true" - }, "›"), - _withDirectives(_createElementVNode("textarea", { - "onUpdate:modelValue": $event => ((_ctx.taskOrchestration.chatDraft) = $event), - class: "task-textarea task-textarea-goal task-quick-target", - rows: "3", - placeholder: _ctx.t('orchestration.chat.input.placeholder'), - onKeydown: _withKeys(_withModifiers($event => (_ctx.submitTaskOrchestrationChatMessage()), ["exact","prevent"]), ["enter"]) - }, null, 40 /* PROPS, NEED_HYDRATION */, ["onUpdate:modelValue", "placeholder", "onKeydown"]), [ - [_vModelText, _ctx.taskOrchestration.chatDraft] - ]), - _createElementVNode("span", { class: "task-field-hint" }, _toDisplayString(_ctx.t('orchestration.chat.input.hint')), 1 /* TEXT */) - ]), - _createElementVNode("div", { class: "task-chat-send-row" }, [ - _createElementVNode("div", { class: "task-chat-action-buttons task-chat-primary-action" }, [ - _createElementVNode("button", { - type: "button", - class: "btn-tool btn-primary task-chat-primary-button", - onClick: $event => (_ctx.planAndRunTaskOrchestrationFromChat()), - disabled: _ctx.taskOrchestration.running || _ctx.taskOrchestration.planning || (!_ctx.taskOrchestration.target.trim() && !_ctx.taskOrchestration.chatDraft.trim()) - }, _toDisplayString(_ctx.taskOrchestration.running ? _ctx.t('orchestration.actions.processing') : (_ctx.taskOrchestration.planning ? _ctx.t('orchestration.actions.planning') : _ctx.t('orchestration.chat.input.work'))), 9 /* TEXT, PROPS */, ["onClick", "disabled"]) - ]), - _createElementVNode("span", { class: "task-field-hint" }, _toDisplayString(_ctx.taskOrchestrationDraftMetrics.hasTarget ? _ctx.t('orchestration.chat.input.sequenceHint', { count: _ctx.taskOrchestrationDraftMetrics.followUpCount + 2 }) : _ctx.t('orchestration.chat.input.workHint')), 1 /* TEXT */) - ]), - _createElementVNode("div", { class: "task-chat-execute-caption" }, _toDisplayString(_ctx.t('orchestration.chat.input.workCaption')), 1 /* TEXT */), - _createElementVNode("div", { - class: "task-chat-context-row task-chat-context-row-primary", - role: "group", - "aria-label": _ctx.t('orchestration.chat.context.aria') - }, [ - _createElementVNode("span", { class: "task-chat-context-chip task-chat-context-chip-strong" }, [ - _createElementVNode("small", null, _toDisplayString(_ctx.t('orchestration.plan.summary.cwd')), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString((_ctx.taskOrchestrationWorkspacePath || _ctx.taskOrchestration.workspacePath.trim()) ? _ctx.t('orchestration.privacy.workspace.selected') : _ctx.t('orchestration.chat.context.workspace.auto')), 1 /* TEXT */) - ]), - _createElementVNode("span", { class: "task-chat-context-chip task-chat-context-chip-strong" }, [ - _createElementVNode("small", null, _toDisplayString(_ctx.t('orchestration.plan.summary.threadId')), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString((_ctx.taskOrchestration.threadId.trim() || (_ctx.taskOrchestrationSelectedRun && _ctx.taskOrchestrationSelectedRun.threadId)) ? _ctx.t('orchestration.privacy.thread.selected') : _ctx.t('orchestration.chat.context.thread.auto')), 1 /* TEXT */) - ]), - _createElementVNode("span", { class: "task-chat-context-chip" }, _toDisplayString(_ctx.taskOrchestrationDraftMetrics.hasTarget ? _ctx.t('orchestration.chat.context.sequence.value', { count: _ctx.taskOrchestrationDraftMetrics.requestCount }) : _ctx.t('orchestration.chat.context.sequence.empty')), 1 /* TEXT */), - _createElementVNode("span", { class: "task-chat-context-chip" }, _toDisplayString(_ctx.taskOrchestration.runMode === 'dry-run' ? _ctx.t('orchestration.runMode.dryRun') : (_ctx.taskOrchestration.runMode === 'read' ? _ctx.t('orchestration.runMode.readOnly') : _ctx.t('orchestration.runMode.write') )), 1 /* TEXT */), - _createElementVNode("button", { - type: "button", - class: "task-chat-context-chip task-chat-context-action", - onClick: $event => (_ctx.taskOrchestration.settingsOpen = true) - }, _toDisplayString(_ctx.t('orchestration.advanced.open')), 9 /* TEXT, PROPS */, ["onClick"]) - ], 8 /* PROPS */, ["aria-label"]), - _createElementVNode("div", { class: "task-action-caption" }, _toDisplayString(_ctx.t('orchestration.quick.caption')), 1 /* TEXT */) - ]) - ]) - ]), - _createElementVNode("aside", { class: "task-quick-side-card" }, [ - _createElementVNode("section", { class: "selector-section task-workbench-card task-side-workbench-card" }, [ - _createElementVNode("div", { class: "task-thread-card-label" }, _toDisplayString(_ctx.t('orchestration.workbench.title')), 1 /* TEXT */), - _createElementVNode("div", { class: "task-thread-run-summary" }, [ - (_ctx.taskOrchestrationSelectedRun && _ctx.taskOrchestrationSelectedRun.run) - ? (_openBlock(), _createElementBlock("span", { - key: 0, - class: _normalizeClass(['pill', _ctx.taskRunStatusTone(_ctx.taskOrchestrationSelectedRun.run.status)]) - }, _toDisplayString(_ctx.taskOrchestrationSelectedRun.run.status), 3 /* TEXT, CLASS */)) - : (_ctx.taskOrchestrationWorkspaceRuns.length) - ? (_openBlock(), _createElementBlock("span", { - key: 1, - class: _normalizeClass(['pill', _ctx.taskRunStatusTone(_ctx.taskOrchestrationWorkspaceRuns[0].status)]) - }, _toDisplayString(_ctx.taskOrchestrationWorkspaceRuns[0].status), 3 /* TEXT, CLASS */)) - : (_ctx.taskOrchestrationWorkspaceQueue.length) - ? (_openBlock(), _createElementBlock("span", { - key: 2, - class: "pill neutral" - }, _toDisplayString(_ctx.t('orchestration.workbench.queueCount', { count: _ctx.taskOrchestrationWorkspaceQueue.length })), 1 /* TEXT */)) - : (_openBlock(), _createElementBlock("span", { - key: 3, - class: "pill empty" - }, _toDisplayString(_ctx.t('orchestration.workbench.ready')), 1 /* TEXT */)), - (_ctx.taskOrchestrationSelectedRun && _ctx.taskOrchestrationSelectedRun.run) - ? (_openBlock(), _createElementBlock("span", { - key: 4, - class: "task-thread-run-summary-copy" - }, _toDisplayString(_ctx.t('orchestration.privacy.runSelected')), 1 /* TEXT */)) - : (_ctx.taskOrchestrationWorkspaceRuns.length) - ? (_openBlock(), _createElementBlock("span", { - key: 5, - class: "task-thread-run-summary-copy" - }, _toDisplayString(_ctx.t('orchestration.privacy.runSelected')), 1 /* TEXT */)) - : (_ctx.taskOrchestrationWorkspaceQueue.length) - ? (_openBlock(), _createElementBlock("span", { - key: 6, - class: "task-thread-run-summary-copy" - }, _toDisplayString(_ctx.t('orchestration.privacy.queueSelected')), 1 /* TEXT */)) - : (_openBlock(), _createElementBlock("span", { - key: 7, - class: "task-thread-run-summary-copy" - }, _toDisplayString(_ctx.t('orchestration.workbench.subtitle')), 1 /* TEXT */)) - ]), - _createElementVNode("details", { - class: "task-thread-run-details", - open: "" + ])) + : _createCommentVNode("v-if", true), + (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskOrchestrationConversationMessages, (message) => { + return (_openBlock(), _createElementBlock("div", { + key: message.id, + class: _normalizeClass(['task-kilo-turn', message.role === 'user' ? 'is-user' : 'is-assistant']) }, [ - _createElementVNode("summary", null, _toDisplayString(_ctx.t('orchestration.agent.trace.title')), 1 /* TEXT */), - _createElementVNode("div", { class: "settings-tab-actions task-header-actions task-thread-detail-actions" }, [ - _createElementVNode("button", { - type: "button", - class: "btn-tool btn-tool-compact", - onClick: $event => (_ctx.loadTaskOrchestrationOverview({ forceRefresh: true, includeDetail: true })), - disabled: _ctx.taskOrchestration.loading - }, _toDisplayString(_ctx.taskOrchestration.loading ? _ctx.t('common.refreshing') : _ctx.t('common.refresh')), 9 /* TEXT, PROPS */, ["onClick", "disabled"]), - (_ctx.taskOrchestrationWorkspaceQueue.length) - ? (_openBlock(), _createElementBlock("button", { + _createElementVNode("div", { class: "task-kilo-avatar" }, _toDisplayString(message.role === 'user' ? 'U' : 'K'), 1 /* TEXT */), + _createElementVNode("div", { class: "task-kilo-message-card" }, [ + _createElementVNode("div", { class: "task-kilo-message-label" }, _toDisplayString(message.label), 1 /* TEXT */), + _createElementVNode("div", { class: "task-kilo-message-text" }, _toDisplayString(message.text), 1 /* TEXT */), + (message.meta) + ? (_openBlock(), _createElementBlock("div", { key: 0, - type: "button", - class: "btn-tool btn-tool-compact", - onClick: $event => (_ctx.startTaskQueueRunner()), - disabled: _ctx.taskOrchestration.queueStarting - }, _toDisplayString(_ctx.taskOrchestration.queueStarting ? _ctx.t('orchestration.queue.starting') : _ctx.t('orchestration.queue.start')), 9 /* TEXT, PROPS */, ["onClick", "disabled"])) + class: "task-kilo-message-meta" + }, _toDisplayString(message.meta), 1 /* TEXT */)) : _createCommentVNode("v-if", true) - ]), - _createElementVNode("div", { class: "task-agent-trace-grid" }, [ - _createElementVNode("div", { class: "task-agent-trace-card" }, [ - _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.agent.trace.plan')), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(_ctx.taskOrchestration.plan ? _ctx.taskOrchestration.plan.nodes.length : 0), 1 /* TEXT */) - ]), - _createElementVNode("div", { class: "task-agent-trace-card" }, [ - _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.agent.trace.queue')), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(_ctx.taskOrchestrationWorkspaceQueue.length), 1 /* TEXT */) - ]), - _createElementVNode("div", { class: "task-agent-trace-card" }, [ - _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.agent.trace.runs')), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(_ctx.taskOrchestrationWorkspaceRuns.length), 1 /* TEXT */) - ]), - _createElementVNode("div", { class: "task-agent-trace-card" }, [ - _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.agent.trace.nodes')), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(_ctx.taskOrchestrationSelectedRunNodes.length), 1 /* TEXT */) - ]) - ]), - ((_ctx.taskOrchestrationWorkspaceQueue.length ? 1 : 0) + (_ctx.taskOrchestrationWorkspaceRuns.length ? 1 : 0) + ((_ctx.taskOrchestration.selectedRunId || _ctx.taskOrchestration.selectedRunError) ? 1 : 0) > 1) - ? (_openBlock(), _createElementBlock("div", { - key: 0, - class: "task-workbench-tabs", - role: "group", - "aria-label": _ctx.t('orchestration.workbench.tabs.aria') - }, [ - (_ctx.taskOrchestrationWorkspaceQueue.length) - ? (_openBlock(), _createElementBlock("button", { - key: 0, - type: "button", - class: _normalizeClass(["task-workbench-tab", { active: _ctx.taskOrchestration.workspaceTab === 'queue' }]), - onClick: $event => (_ctx.taskOrchestration.workspaceTab = 'queue') - }, _toDisplayString(_ctx.t('orchestration.workbench.tabs.queue', { count: _ctx.taskOrchestrationWorkspaceQueue.length })), 11 /* TEXT, CLASS, PROPS */, ["onClick"])) - : _createCommentVNode("v-if", true), - (_ctx.taskOrchestrationWorkspaceRuns.length) - ? (_openBlock(), _createElementBlock("button", { - key: 1, - type: "button", - class: _normalizeClass(["task-workbench-tab", { active: _ctx.taskOrchestration.workspaceTab === 'runs' }]), - onClick: $event => (_ctx.taskOrchestration.workspaceTab = 'runs') - }, _toDisplayString(_ctx.t('orchestration.workbench.tabs.runs', { count: _ctx.taskOrchestrationWorkspaceRuns.length })), 11 /* TEXT, CLASS, PROPS */, ["onClick"])) - : _createCommentVNode("v-if", true), - (_ctx.taskOrchestration.selectedRunId || _ctx.taskOrchestration.selectedRunError) - ? (_openBlock(), _createElementBlock("button", { - key: 2, - type: "button", - class: _normalizeClass(["task-workbench-tab", { active: _ctx.taskOrchestration.workspaceTab === 'detail' }]), - onClick: $event => (_ctx.taskOrchestration.workspaceTab = 'detail') - }, _toDisplayString(_ctx.t('orchestration.workbench.tabs.detail')), 11 /* TEXT, CLASS, PROPS */, ["onClick"])) - : _createCommentVNode("v-if", true) - ], 8 /* PROPS */, ["aria-label"])) - : _createCommentVNode("v-if", true), - ((_ctx.taskOrchestrationWorkspaceQueue.length && _ctx.taskOrchestration.workspaceTab === 'queue') || (!_ctx.taskOrchestrationWorkspaceRuns.length && !_ctx.taskOrchestration.selectedRunId && !_ctx.taskOrchestration.selectedRunError)) - ? (_openBlock(), _createElementBlock("div", { - key: 1, - class: "task-workbench-panel" - }, [ - (!_ctx.taskOrchestrationWorkspaceQueue.length) - ? (_openBlock(), _createElementBlock("div", { - key: 0, - class: "task-empty-state" - }, [ - _createElementVNode("div", { class: "task-empty-title" }, _toDisplayString(_ctx.t('orchestration.queue.empty.title')), 1 /* TEXT */), - _createElementVNode("div", { class: "task-empty-copy" }, _toDisplayString(_ctx.t('orchestration.queue.empty.subtitle')), 1 /* TEXT */) - ])) - : (_openBlock(), _createElementBlock("div", { - key: 1, - class: "task-runtime-list" - }, [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskOrchestrationWorkspaceQueue, (item) => { - return (_openBlock(), _createElementBlock("div", { - key: item.taskId, - class: _normalizeClass(['task-runtime-item', { active: item.lastRunId && _ctx.taskOrchestration.selectedRunId === item.lastRunId, clickable: !!item.lastRunId }]), - role: item.lastRunId ? 'button' : null, - tabindex: item.lastRunId ? 0 : -1, - "aria-disabled": item.lastRunId ? null : 'true', - onClick: $event => (item.lastRunId ? (_ctx.taskOrchestration.workspaceTab = 'detail', _ctx.selectTaskRun(item.lastRunId)) : null), - onKeydown: [ - _withKeys(_withModifiers($event => (item.lastRunId ? (_ctx.taskOrchestration.workspaceTab = 'detail', _ctx.selectTaskRun(item.lastRunId)) : null), ["self","prevent"]), ["enter"]), - _withKeys(_withModifiers($event => (item.lastRunId ? (_ctx.taskOrchestration.workspaceTab = 'detail', _ctx.selectTaskRun(item.lastRunId)) : null), ["self","prevent"]), ["space"]) - ] - }, [ - _createElementVNode("div", { class: "task-runtime-item-main" }, [ - _createElementVNode("div", { class: "task-runtime-item-title" }, _toDisplayString(_ctx.t('orchestration.workspace.sessions.queueTitle')), 1 /* TEXT */), - _createElementVNode("div", { class: "task-runtime-item-meta" }, _toDisplayString(item.updatedAt || item.createdAt || _ctx.t('orchestration.workspace.sessions.detailsHidden')), 1 /* TEXT */), - (item.threadId || item.cwd || item.taskId) - ? (_openBlock(), _createElementBlock("div", { - key: 0, - class: "task-runtime-item-meta" - }, _toDisplayString(_ctx.t('orchestration.privacy.detailsHidden')), 1 /* TEXT */)) - : _createCommentVNode("v-if", true), - (item.lastSummary) - ? (_openBlock(), _createElementBlock("div", { - key: 1, - class: "task-runtime-item-summary" - }, _toDisplayString(_ctx.t('orchestration.privacy.summaryHidden')), 1 /* TEXT */)) - : _createCommentVNode("v-if", true) - ]), - _createElementVNode("div", { class: "task-runtime-item-actions" }, [ - _createElementVNode("span", { - class: _normalizeClass(['pill', _ctx.taskRunStatusTone(item.status || item.runStatus)]) - }, _toDisplayString(item.status || item.runStatus), 3 /* TEXT, CLASS */), - (_ctx.isTaskRunActive(item.status || item.runStatus)) - ? (_openBlock(), _createElementBlock("button", { - key: 0, - type: "button", - class: "btn-mini", - onClick: _withModifiers($event => (_ctx.cancelTaskRunFromUi(item.taskId)), ["stop"]) - }, _toDisplayString(_ctx.t('common.cancel')), 9 /* TEXT, PROPS */, ["onClick"])) - : _createCommentVNode("v-if", true) - ]) - ], 42 /* CLASS, PROPS, NEED_HYDRATION */, ["role", "tabindex", "aria-disabled", "onClick", "onKeydown"])) - }), 128 /* KEYED_FRAGMENT */)) - ])) - ])) - : (_ctx.taskOrchestration.workspaceTab === 'runs' || (!_ctx.taskOrchestrationWorkspaceQueue.length && _ctx.taskOrchestrationWorkspaceRuns.length && !_ctx.taskOrchestration.selectedRunId && !_ctx.taskOrchestration.selectedRunError)) - ? (_openBlock(), _createElementBlock("div", { - key: 2, - class: "task-workbench-panel" - }, [ - (!_ctx.taskOrchestrationWorkspaceRuns.length) - ? (_openBlock(), _createElementBlock("div", { - key: 0, - class: "task-empty-state" - }, [ - _createElementVNode("div", { class: "task-empty-title" }, _toDisplayString(_ctx.t('orchestration.runs.empty.title')), 1 /* TEXT */), - _createElementVNode("div", { class: "task-empty-copy" }, _toDisplayString(_ctx.t('orchestration.runs.empty.subtitle')), 1 /* TEXT */) - ])) - : (_openBlock(), _createElementBlock("div", { - key: 1, - class: "task-runtime-list" - }, [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskOrchestrationWorkspaceRuns, (item) => { - return (_openBlock(), _createElementBlock("button", { - key: item.runId, - type: "button", - class: _normalizeClass(['task-runtime-item', { active: _ctx.taskOrchestration.selectedRunId === item.runId }]), - onClick: $event => {_ctx.taskOrchestration.workspaceTab = 'detail'; _ctx.selectTaskRun(item.runId)} - }, [ - _createElementVNode("div", { class: "task-runtime-item-main" }, [ - _createElementVNode("div", { class: "task-runtime-item-title" }, _toDisplayString(_ctx.t('orchestration.workspace.sessions.runTitle')), 1 /* TEXT */), - _createElementVNode("div", { class: "task-runtime-item-meta" }, _toDisplayString(item.durationMs || 0) + "ms", 1 /* TEXT */), - (item.threadId || item.cwd || item.runId || item.taskId) - ? (_openBlock(), _createElementBlock("div", { - key: 0, - class: "task-runtime-item-meta" - }, _toDisplayString(_ctx.t('orchestration.privacy.detailsHidden')), 1 /* TEXT */)) - : _createCommentVNode("v-if", true), - (item.summary) - ? (_openBlock(), _createElementBlock("div", { - key: 1, - class: "task-runtime-item-summary" - }, _toDisplayString(_ctx.t('orchestration.privacy.summaryHidden')), 1 /* TEXT */)) - : _createCommentVNode("v-if", true) - ]), - _createElementVNode("div", { class: "task-runtime-item-actions" }, [ - _createElementVNode("span", { - class: _normalizeClass(['pill', _ctx.taskRunStatusTone(item.status)]) - }, _toDisplayString(item.status), 3 /* TEXT, CLASS */) - ]) - ], 10 /* CLASS, PROPS */, ["onClick"])) - }), 128 /* KEYED_FRAGMENT */)) - ])) - ])) - : (_openBlock(), _createElementBlock("div", { - key: 3, - class: "task-workbench-panel" - }, [ - _createElementVNode("div", { class: "task-detail-toolbar settings-tab-actions" }, [ - _createElementVNode("button", { - type: "button", - class: "btn-tool btn-tool-compact", - onClick: $event => (_ctx.taskOrchestration.selectedRunId ? _ctx.loadTaskRunDetail(_ctx.taskOrchestration.selectedRunId) : null), - disabled: !_ctx.taskOrchestration.selectedRunId || _ctx.taskOrchestration.selectedRunLoading - }, _toDisplayString(_ctx.taskOrchestration.selectedRunLoading ? _ctx.t('common.refreshing') : _ctx.t('orchestration.detail.refresh')), 9 /* TEXT, PROPS */, ["onClick", "disabled"]), - _createElementVNode("button", { - type: "button", - class: "btn-tool btn-tool-compact", - onClick: $event => (_ctx.retryTaskRunFromUi(_ctx.taskOrchestration.selectedRunId)), - disabled: !_ctx.taskOrchestration.selectedRunId || _ctx.taskOrchestration.retrying - }, _toDisplayString(_ctx.taskOrchestration.retrying ? _ctx.t('orchestration.detail.retrying') : _ctx.t('orchestration.detail.retry')), 9 /* TEXT, PROPS */, ["onClick", "disabled"]), - _createElementVNode("button", { - type: "button", - class: "btn-tool btn-tool-compact", - onClick: _ctx.continueTaskThreadFromUi, - disabled: !_ctx.taskOrchestrationSelectedRun - }, _toDisplayString(_ctx.t('orchestration.detail.continueThread')), 9 /* TEXT, PROPS */, ["onClick", "disabled"]), - (_ctx.taskOrchestrationSelectedRun && _ctx.taskOrchestrationSelectedRun.run && _ctx.isTaskRunActive(_ctx.taskOrchestrationSelectedRun.run.status)) - ? (_openBlock(), _createElementBlock("button", { - key: 0, - type: "button", - class: "btn-tool btn-tool-compact", - onClick: $event => (_ctx.cancelTaskRunFromUi(_ctx.taskOrchestration.selectedRunId)) - }, _toDisplayString(_ctx.t('common.cancel')), 9 /* TEXT, PROPS */, ["onClick"])) - : _createCommentVNode("v-if", true) - ]), - (_ctx.taskOrchestration.selectedRunError) - ? (_openBlock(), _createElementBlock("div", { - key: 0, + ]) + ], 2 /* CLASS */)) + }), 128 /* KEYED_FRAGMENT */)), + (_ctx.taskOrchestration.plan || _ctx.taskOrchestration.planIssues.length || _ctx.taskOrchestration.planWarnings.length || _ctx.taskOrchestration.lastError) + ? (_openBlock(), _createElementBlock("div", { + key: 1, + class: "task-kilo-turn is-assistant task-kilo-plan-turn" + }, [ + _createElementVNode("div", { class: "task-kilo-avatar" }, "K"), + _createElementVNode("div", { class: "task-kilo-message-card task-kilo-plan-card" }, [ + _createElementVNode("div", { class: "task-kilo-message-label" }, "AI · " + _toDisplayString(_ctx.t('orchestration.plan.title')), 1 /* TEXT */), + (_ctx.taskOrchestration.lastError) + ? (_openBlock(), _createElementBlock("div", { + key: 0, + class: "task-issue-item" + }, _toDisplayString(_ctx.t('orchestration.privacy.detailsHidden')), 1 /* TEXT */)) + : _createCommentVNode("v-if", true), + (_ctx.taskOrchestration.plan) + ? (_openBlock(), _createElementBlock("div", { + key: 1, + class: "task-kilo-plan-summary" + }, [ + _createElementVNode("span", { class: "pill configured" }, "/plan"), + _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.chat.assistant.planSummary', { nodes: _ctx.taskOrchestration.plan.nodes.length, waves: _ctx.taskOrchestration.plan.waves.length })), 1 /* TEXT */) + ])) + : _createCommentVNode("v-if", true), + (_ctx.taskOrchestration.planIssues.length) + ? (_openBlock(), _createElementBlock("div", { + key: 2, + class: "task-issues-list" + }, [ + (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskOrchestration.planIssues, (_issue, index) => { + return (_openBlock(), _createElementBlock("div", { + key: 'issue-' + index, class: "task-issue-item" }, _toDisplayString(_ctx.t('orchestration.privacy.detailsHidden')), 1 /* TEXT */)) - : _createCommentVNode("v-if", true), - (!_ctx.taskOrchestrationSelectedRun) - ? (_openBlock(), _createElementBlock("div", { - key: 1, - class: "task-empty-state" + }), 128 /* KEYED_FRAGMENT */)) + ])) + : _createCommentVNode("v-if", true), + (_ctx.taskOrchestration.planWarnings.length) + ? (_openBlock(), _createElementBlock("div", { + key: 3, + class: "task-warning-list" + }, [ + (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskOrchestration.planWarnings, (_warning, index) => { + return (_openBlock(), _createElementBlock("div", { + key: 'warning-' + index, + class: "task-warning-item" + }, _toDisplayString(_ctx.t('orchestration.privacy.detailsHidden')), 1 /* TEXT */)) + }), 128 /* KEYED_FRAGMENT */)) + ])) + : _createCommentVNode("v-if", true), + (_ctx.taskOrchestration.plan) + ? (_openBlock(), _createElementBlock("div", { + key: 4, + class: "task-kilo-plan-grid" + }, [ + (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskOrchestration.plan.nodes, (node, index) => { + return (_openBlock(), _createElementBlock("div", { + key: 'plan-node-' + index, + class: "task-kilo-plan-node" }, [ - _createElementVNode("div", { class: "task-empty-title" }, _toDisplayString(_ctx.t('orchestration.detail.empty.title')), 1 /* TEXT */), - _createElementVNode("div", { class: "task-empty-copy" }, _toDisplayString(_ctx.t('orchestration.detail.empty.subtitle')), 1 /* TEXT */) + _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.detail.node.meta', { attempts: 0, autoFix: 0 })) + " · " + _toDisplayString(node.kind), 1 /* TEXT */), + _createElementVNode("strong", null, _toDisplayString(_ctx.t('orchestration.privacy.summaryHidden')), 1 /* TEXT */), + _createElementVNode("small", null, _toDisplayString(node.write ? _ctx.t('orchestration.plan.node.write') : _ctx.t('orchestration.plan.node.readOnly')), 1 /* TEXT */) ])) - : (_openBlock(), _createElementBlock(_Fragment, { key: 2 }, [ - _createElementVNode("div", { class: "task-detail-summary-strip" }, [ - _createElementVNode("div", { class: "task-plan-summary-item" }, [ - _createElementVNode("span", { class: "task-plan-summary-label" }, _toDisplayString(_ctx.t('orchestration.detail.summary.status')), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(_ctx.taskOrchestrationSelectedRun.run.status), 1 /* TEXT */) - ]), - _createElementVNode("div", { class: "task-plan-summary-item" }, [ - _createElementVNode("span", { class: "task-plan-summary-label" }, _toDisplayString(_ctx.t('orchestration.detail.summary.duration')), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(_ctx.taskOrchestrationSelectedRun.run.durationMs || 0) + "ms", 1 /* TEXT */) - ]), - _createElementVNode("div", { class: "task-plan-summary-item" }, [ - _createElementVNode("span", { class: "task-plan-summary-label" }, _toDisplayString(_ctx.t('orchestration.detail.summary.nodes')), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(_ctx.taskOrchestrationSelectedRunNodes.length), 1 /* TEXT */) - ]), - _createElementVNode("div", { class: "task-plan-summary-item" }, [ - _createElementVNode("span", { class: "task-plan-summary-label" }, _toDisplayString(_ctx.t('orchestration.detail.summary.summary')), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(_ctx.taskOrchestrationSelectedRun.run.summary ? _ctx.t('orchestration.privacy.summaryHidden') : _ctx.t('common.none')), 1 /* TEXT */) - ]), - (_ctx.taskOrchestrationSelectedRun.threadId) - ? (_openBlock(), _createElementBlock("div", { - key: 0, - class: "task-plan-summary-item" - }, [ - _createElementVNode("span", { class: "task-plan-summary-label" }, _toDisplayString(_ctx.t('orchestration.plan.summary.threadId')), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(_ctx.t('orchestration.privacy.thread.selected')), 1 /* TEXT */) - ])) - : _createCommentVNode("v-if", true), - (_ctx.taskOrchestrationSelectedRun.cwd) - ? (_openBlock(), _createElementBlock("div", { - key: 1, - class: "task-plan-summary-item" - }, [ - _createElementVNode("span", { class: "task-plan-summary-label" }, _toDisplayString(_ctx.t('orchestration.plan.summary.cwd')), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(_ctx.t('orchestration.privacy.workspace.selected')), 1 /* TEXT */) - ])) - : _createCommentVNode("v-if", true) - ]), - (_ctx.taskOrchestrationSelectedRun.run.error) - ? (_openBlock(), _createElementBlock("div", { - key: 0, - class: "task-issue-item" - }, _toDisplayString(_ctx.t('orchestration.privacy.detailsHidden')), 1 /* TEXT */)) - : _createCommentVNode("v-if", true), - _createElementVNode("div", { class: "task-node-list" }, [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskOrchestrationSelectedRunNodes, (node) => { - return (_openBlock(), _createElementBlock("div", { - key: node.id, - class: "task-node-card task-node-card-detail" - }, [ - _createElementVNode("div", { class: "task-node-head" }, [ - _createElementVNode("div", null, [ - _createElementVNode("div", { class: "task-node-title" }, _toDisplayString(_ctx.t('orchestration.detail.node.step')), 1 /* TEXT */), - _createElementVNode("div", { class: "task-node-meta" }, _toDisplayString(_ctx.t('orchestration.detail.node.meta', { attempts: (node.attemptCount || 0), autoFix: (node.autoFixRounds || 0) })), 1 /* TEXT */) - ]), - _createElementVNode("span", { - class: _normalizeClass(['pill', _ctx.taskRunStatusTone(node.status)]) - }, _toDisplayString(node.status), 3 /* TEXT, CLASS */) - ]), - (node.summary) - ? (_openBlock(), _createElementBlock("div", { - key: 0, - class: "task-runtime-item-summary" - }, _toDisplayString(_ctx.t('orchestration.privacy.summaryHidden')), 1 /* TEXT */)) - : _createCommentVNode("v-if", true), - (node.error && node.error !== node.summary) - ? (_openBlock(), _createElementBlock("div", { - key: 1, - class: "task-node-deps" - }, _toDisplayString(_ctx.t('orchestration.labels.error')) + _toDisplayString(_ctx.t('orchestration.privacy.detailsHidden')), 1 /* TEXT */)) - : _createCommentVNode("v-if", true), - _createElementVNode("div", { class: "task-node-deps" }, _toDisplayString(_ctx.t('orchestration.labels.dependencies')) + _toDisplayString(_ctx.formatTaskNodeDependencies(node)), 1 /* TEXT */), - (node.output && typeof node.output === 'object') - ? (_openBlock(), _createElementBlock("div", { - key: 2, - class: "task-node-output-card" - }, [ - _createElementVNode("div", { class: "task-node-output-head" }, [ - _createElementVNode("strong", null, _toDisplayString(_ctx.t('orchestration.detail.node.output')), 1 /* TEXT */), - (node.output.provider || node.output.model) - ? (_openBlock(), _createElementBlock("span", { - key: 0, - class: "task-node-output-meta" - }, _toDisplayString(_ctx.t('orchestration.privacy.providerHidden')), 1 /* TEXT */)) - : _createCommentVNode("v-if", true) - ]), - _createElementVNode("div", { class: "task-node-output-facts" }, [ - (node.output.endpoint) - ? (_openBlock(), _createElementBlock("span", { key: 0 }, _toDisplayString(_ctx.t('orchestration.detail.node.endpoint')) + _toDisplayString(_ctx.t('orchestration.openai.status.configured')), 1 /* TEXT */)) - : _createCommentVNode("v-if", true), - (node.output.status) - ? (_openBlock(), _createElementBlock("span", { key: 1 }, "HTTP " + _toDisplayString(node.output.status), 1 /* TEXT */)) - : _createCommentVNode("v-if", true), - (node.output.durationMs) - ? (_openBlock(), _createElementBlock("span", { key: 2 }, _toDisplayString(node.output.durationMs) + "ms", 1 /* TEXT */)) - : _createCommentVNode("v-if", true) - ]), - _createElementVNode("pre", { class: "task-log-block task-output-block" }, _toDisplayString(_ctx.formatTaskNodeOutputText(node)), 1 /* TEXT */), - (node.output.materializedFiles && node.output.materializedFiles.length) - ? (_openBlock(), _createElementBlock("div", { - key: 0, - class: "task-materialized-files" - }, [ - _createElementVNode("div", { class: "task-node-deps" }, _toDisplayString(_ctx.t('orchestration.detail.node.materializedFiles')), 1 /* TEXT */), - _createElementVNode("div", { class: "task-materialized-file" }, _toDisplayString(_ctx.t('orchestration.privacy.filesChangedCount', { count: node.output.materializedFiles.length })), 1 /* TEXT */) - ])) - : _createCommentVNode("v-if", true), - (node.output.workspaceFiles && node.output.workspaceFiles.length) - ? (_openBlock(), _createElementBlock("div", { - key: 1, - class: "task-materialized-files task-workspace-files" - }, [ - _createElementVNode("div", { class: "task-node-deps" }, _toDisplayString(_ctx.t('orchestration.detail.node.workspaceFiles')), 1 /* TEXT */), - _createElementVNode("div", { class: "task-materialized-file" }, _toDisplayString(_ctx.t('orchestration.privacy.filesChangedCount', { count: node.output.workspaceFiles.length })), 1 /* TEXT */) - ])) - : _createCommentVNode("v-if", true) - ])) - : _createCommentVNode("v-if", true), - _createElementVNode("pre", { class: "task-log-block" }, _toDisplayString(_ctx.formatTaskNodeLogs(node.logs)), 1 /* TEXT */) - ])) - }), 128 /* KEYED_FRAGMENT */)) - ]) - ], 64 /* STABLE_FRAGMENT */)) - ])) - ]) - ]), - _createElementVNode("details", { - class: "selector-section task-side-settings-card task-advanced-panel task-quick-advanced", - open: _ctx.taskOrchestration.settingsOpen, - onToggle: $event => (_ctx.taskOrchestration.settingsOpen = $event.target.open) - }, [ - _createElementVNode("summary", { class: "task-advanced-summary" }, _toDisplayString(_ctx.t('orchestration.advanced.title')), 1 /* TEXT */), - _createElementVNode("div", { class: "task-action-row-right task-action-row-right-prominent task-codex-queue-action" }, [ - _createElementVNode("button", { - type: "button", - class: "btn-tool", - onClick: $event => (_ctx.queueTaskOrchestrationAndStart()), - disabled: _ctx.taskOrchestration.queueAdding || _ctx.taskOrchestration.queueStarting || _ctx.taskOrchestration.planning || !_ctx.taskOrchestration.target.trim() - }, _toDisplayString((_ctx.taskOrchestration.queueAdding || _ctx.taskOrchestration.queueStarting) ? _ctx.t('orchestration.actions.processing') : _ctx.t('orchestration.actions.queueAndStart')), 9 /* TEXT, PROPS */, ["onClick", "disabled"]) - ]), - _createElementVNode("div", { class: "selector-grid task-composer-grid task-composer-grid-secondary" }, [ - _createElementVNode("label", { class: "selector-field task-field-wide" }, [ - _createElementVNode("span", { class: "selector-label" }, _toDisplayString(_ctx.t('orchestration.fields.title')), 1 /* TEXT */), - _withDirectives(_createElementVNode("input", { - "onUpdate:modelValue": $event => ((_ctx.taskOrchestration.title) = $event), - class: "model-input", - type: "text", - placeholder: _ctx.t('orchestration.fields.title.placeholder') - }, null, 8 /* PROPS */, ["onUpdate:modelValue", "placeholder"]), [ - [_vModelText, _ctx.taskOrchestration.title] - ]) - ]), - _createElementVNode("label", { class: "selector-field" }, [ - _createElementVNode("span", { class: "selector-label" }, _toDisplayString(_ctx.t('orchestration.fields.engine')), 1 /* TEXT */), - _withDirectives(_createElementVNode("select", { - "onUpdate:modelValue": $event => ((_ctx.taskOrchestration.selectedEngine) = $event), - class: "provider-fast-switch-select", - onChange: $event => (_ctx.taskOrchestration.selectedEngine === 'workflow' ? null : _ctx.taskOrchestration.workflowIdsText = '') - }, [ - _createElementVNode("option", { value: "openai-chat" }, _toDisplayString(_ctx.t('orchestration.engine.openaiChat')), 1 /* TEXT */), - _createElementVNode("option", { value: "workflow" }, _toDisplayString(_ctx.t('orchestration.engine.workflow')), 1 /* TEXT */) - ], 40 /* PROPS, NEED_HYDRATION */, ["onUpdate:modelValue", "onChange"]), [ - [_vModelSelect, _ctx.taskOrchestration.selectedEngine] - ]) - ]), - _createElementVNode("label", { class: "selector-field" }, [ - _createElementVNode("span", { class: "selector-label" }, _toDisplayString(_ctx.t('orchestration.fields.runMode')), 1 /* TEXT */), - _withDirectives(_createElementVNode("select", { - "onUpdate:modelValue": $event => ((_ctx.taskOrchestration.runMode) = $event), - class: "provider-fast-switch-select" - }, [ - _createElementVNode("option", { value: "write" }, _toDisplayString(_ctx.t('orchestration.runMode.write')), 1 /* TEXT */), - _createElementVNode("option", { value: "read" }, _toDisplayString(_ctx.t('orchestration.runMode.readOnly')), 1 /* TEXT */), - _createElementVNode("option", { value: "dry-run" }, _toDisplayString(_ctx.t('orchestration.runMode.dryRun')), 1 /* TEXT */) - ], 8 /* PROPS */, ["onUpdate:modelValue"]), [ - [_vModelSelect, _ctx.taskOrchestration.runMode] - ]) - ]), - _createElementVNode("label", { class: "selector-field task-field-wide" }, [ - _createElementVNode("span", { class: "selector-label" }, _toDisplayString(_ctx.t('orchestration.fields.notes')), 1 /* TEXT */), - _withDirectives(_createElementVNode("textarea", { - "onUpdate:modelValue": $event => ((_ctx.taskOrchestration.notes) = $event), - class: "task-textarea", - rows: "3", - placeholder: _ctx.t('orchestration.fields.notes.placeholder') - }, null, 8 /* PROPS */, ["onUpdate:modelValue", "placeholder"]), [ - [_vModelText, _ctx.taskOrchestration.notes] - ]), - _createElementVNode("span", { class: "task-field-hint" }, _toDisplayString(_ctx.t('orchestration.fields.notes.hint')), 1 /* TEXT */) - ]), - _createElementVNode("label", { class: "selector-field task-field-wide" }, [ - _createElementVNode("span", { class: "selector-label" }, _toDisplayString(_ctx.t('orchestration.fields.workspacePath')), 1 /* TEXT */), - _withDirectives(_createElementVNode("input", { - "onUpdate:modelValue": $event => ((_ctx.taskOrchestration.workspacePath) = $event), - class: "model-input task-sensitive-input", - type: "text", - autocomplete: "off", - placeholder: _ctx.t('orchestration.fields.workspacePath.placeholder') - }, null, 8 /* PROPS */, ["onUpdate:modelValue", "placeholder"]), [ - [_vModelText, _ctx.taskOrchestration.workspacePath] - ]), - _createElementVNode("span", { class: "task-field-hint" }, _toDisplayString(_ctx.t('orchestration.fields.workspacePath.hint')), 1 /* TEXT */) - ]), - _createElementVNode("label", { class: "selector-field" }, [ - _createElementVNode("span", { class: "selector-label" }, _toDisplayString(_ctx.t('orchestration.fields.threadId')), 1 /* TEXT */), - _withDirectives(_createElementVNode("input", { - "onUpdate:modelValue": $event => ((_ctx.taskOrchestration.threadId) = $event), - class: "model-input task-sensitive-input", - type: "text", - autocomplete: "off", - placeholder: _ctx.t('orchestration.fields.threadId.placeholder') - }, null, 8 /* PROPS */, ["onUpdate:modelValue", "placeholder"]), [ - [_vModelText, _ctx.taskOrchestration.threadId] - ]), - _createElementVNode("span", { class: "task-field-hint" }, _toDisplayString(_ctx.t('orchestration.fields.threadId.hint')), 1 /* TEXT */) - ]), - _createElementVNode("label", { class: "selector-field task-field-wide" }, [ - _createElementVNode("span", { class: "selector-label" }, _toDisplayString(_ctx.t('orchestration.fields.followUps')), 1 /* TEXT */), - _withDirectives(_createElementVNode("textarea", { - "onUpdate:modelValue": $event => ((_ctx.taskOrchestration.followUpsText) = $event), - class: "task-textarea", - rows: "3", - placeholder: _ctx.t('orchestration.fields.followUps.placeholder') - }, null, 8 /* PROPS */, ["onUpdate:modelValue", "placeholder"]), [ - [_vModelText, _ctx.taskOrchestration.followUpsText] + }), 128 /* KEYED_FRAGMENT */)) + ])) + : _createCommentVNode("v-if", true) ]) - ]), - _createElementVNode("label", { class: "selector-field" }, [ - _createElementVNode("span", { class: "selector-label" }, _toDisplayString(_ctx.t('orchestration.fields.concurrency')), 1 /* TEXT */), - _withDirectives(_createElementVNode("input", { - "onUpdate:modelValue": $event => ((_ctx.taskOrchestration.concurrency) = $event), - class: "model-input", - type: "number", - min: "1", - max: "8" - }, null, 8 /* PROPS */, ["onUpdate:modelValue"]), [ - [_vModelText, _ctx.taskOrchestration.concurrency] - ]), - _createElementVNode("span", { class: "task-field-hint" }, _toDisplayString(_ctx.t('orchestration.fields.concurrency.hint')), 1 /* TEXT */) - ]), - _createElementVNode("label", { class: "selector-field" }, [ - _createElementVNode("span", { class: "selector-label" }, _toDisplayString(_ctx.t('orchestration.fields.autoFixRounds')), 1 /* TEXT */), - _withDirectives(_createElementVNode("input", { - "onUpdate:modelValue": $event => ((_ctx.taskOrchestration.autoFixRounds) = $event), - class: "model-input", - type: "number", - min: "0", - max: "5" - }, null, 8 /* PROPS */, ["onUpdate:modelValue"]), [ - [_vModelText, _ctx.taskOrchestration.autoFixRounds] - ]), - _createElementVNode("span", { class: "task-field-hint" }, _toDisplayString(_ctx.t('orchestration.fields.autoFixRounds.hint')), 1 /* TEXT */) - ]), - (_ctx.taskOrchestration.selectedEngine === 'workflow') - ? (_openBlock(), _createElementBlock("label", { - key: 0, - class: "selector-field task-field-wide" - }, [ - _createElementVNode("span", { class: "selector-label" }, _toDisplayString(_ctx.t('orchestration.fields.workflowIds')), 1 /* TEXT */), - _withDirectives(_createElementVNode("textarea", { - "onUpdate:modelValue": $event => ((_ctx.taskOrchestration.workflowIdsText) = $event), - class: "task-textarea", - rows: "3", - placeholder: _ctx.t('orchestration.fields.workflowIds.placeholder') - }, null, 8 /* PROPS */, ["onUpdate:modelValue", "placeholder"]), [ - [_vModelText, _ctx.taskOrchestration.workflowIdsText] - ]), - _createElementVNode("span", { class: "task-field-hint" }, _toDisplayString(_ctx.t('orchestration.fields.workflowIds.hint', { count: _ctx.taskOrchestration.workflows.length })), 1 /* TEXT */), - (_ctx.taskOrchestration.workflows.length) - ? (_openBlock(), _createElementBlock("div", { - key: 0, - class: "task-workflow-suggestions" - }, [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskOrchestration.workflows, (workflow) => { - return (_openBlock(), _createElementBlock("button", { - key: workflow.id || workflow.name, - type: "button", - class: "task-workflow-chip", - onClick: $event => (_ctx.appendTaskWorkflowId(workflow.id || workflow.name)) - }, [ - _createElementVNode("span", null, _toDisplayString(workflow.name || workflow.id), 1 /* TEXT */), - (workflow.stepCount) - ? (_openBlock(), _createElementBlock("small", { key: 0 }, _toDisplayString(_ctx.t('orchestration.workflow.stepCount', { count: workflow.stepCount })), 1 /* TEXT */)) - : _createCommentVNode("v-if", true) - ], 8 /* PROPS */, ["onClick"])) - }), 128 /* KEYED_FRAGMENT */)) - ])) - : _createCommentVNode("v-if", true) - ])) - : _createCommentVNode("v-if", true) + ])) + : _createCommentVNode("v-if", true) + ], 8 /* PROPS */, ["aria-label"]), + _createElementVNode("footer", { class: "task-kilo-chat-input" }, [ + _createElementVNode("label", { + class: "task-kilo-composer", + "aria-label": _ctx.t('orchestration.chat.input.label') + }, [ + _createElementVNode("span", { class: "task-kilo-composer-glyph" }, "›"), + _withDirectives(_createElementVNode("textarea", { + "onUpdate:modelValue": $event => ((_ctx.taskOrchestration.chatDraft) = $event), + class: "task-textarea task-textarea-goal task-quick-target", + rows: "3", + placeholder: _ctx.t('orchestration.chat.input.placeholder'), + onKeydown: _withKeys(_withModifiers($event => (_ctx.submitTaskOrchestrationChatMessage()), ["exact","prevent"]), ["enter"]) + }, null, 40 /* PROPS, NEED_HYDRATION */, ["onUpdate:modelValue", "placeholder", "onKeydown"]), [ + [_vModelText, _ctx.taskOrchestration.chatDraft] ]) - ], 40 /* PROPS, NEED_HYDRATION */, ["open", "onToggle"]), - _createElementVNode("div", { class: "task-provider-status-card" }, [ - _createElementVNode("div", { class: "task-readiness-title" }, _toDisplayString(_ctx.t('orchestration.openai.status.title')), 1 /* TEXT */), - _createElementVNode("div", { class: "task-readiness-copy" }, _toDisplayString(_ctx.t('orchestration.openai.status.subtitle')), 1 /* TEXT */), - (_ctx.taskOrchestration.openAiChatStatus) - ? (_openBlock(), _createElementBlock("div", { - key: 0, - class: "task-provider-status-grid" - }, [ - _createElementVNode("div", { class: "task-provider-status-row" }, [ - _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.openai.status.provider')), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(_ctx.taskOrchestration.openAiChatStatus.providerName ? _ctx.t('orchestration.openai.status.configured') : _ctx.t('orchestration.openai.status.notSet')), 1 /* TEXT */) - ]), - _createElementVNode("div", { class: "task-provider-status-row" }, [ - _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.openai.status.model')), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(_ctx.taskOrchestration.openAiChatStatus.model ? _ctx.t('orchestration.openai.status.configured') : _ctx.t('orchestration.openai.status.notSet')), 1 /* TEXT */) - ]), - _createElementVNode("div", { class: "task-provider-status-row task-provider-status-row-wide" }, [ - _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.openai.status.endpoint')), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(_ctx.taskOrchestration.openAiChatStatus.endpoint ? _ctx.t('orchestration.openai.status.configured') : _ctx.t('orchestration.openai.status.notSet')), 1 /* TEXT */) - ]), - _createElementVNode("div", { class: "task-provider-status-row" }, [ - _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.openai.status.apiKey')), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(_ctx.taskOrchestration.openAiChatStatus.hasApiKey ? _ctx.t('orchestration.openai.status.configured') : _ctx.t('orchestration.openai.status.missing')), 1 /* TEXT */) - ]), - _createElementVNode("div", { class: "task-provider-status-row" }, [ - _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.openai.status.headers')), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(_ctx.taskOrchestration.openAiChatStatus.hasExtraHeaders ? _ctx.t('orchestration.openai.status.configured') : _ctx.t('orchestration.openai.status.notSet')), 1 /* TEXT */) - ]) - ])) - : (_openBlock(), _createElementBlock("div", { - key: 1, - class: "task-readiness-copy" - }, _toDisplayString(_ctx.t('orchestration.openai.status.notLoaded')), 1 /* TEXT */)), - (_ctx.taskOrchestration.openAiChatStatus && _ctx.taskOrchestration.openAiChatStatus.error) - ? (_openBlock(), _createElementBlock("div", { - key: 2, - class: "task-issue-item task-provider-status-error" - }, _toDisplayString(_ctx.t('orchestration.privacy.detailsHidden')), 1 /* TEXT */)) - : _createCommentVNode("v-if", true), + ], 8 /* PROPS */, ["aria-label"]), + _createElementVNode("div", { class: "task-kilo-input-actions" }, [ + _createElementVNode("span", { class: "task-field-hint" }, _toDisplayString(_ctx.taskOrchestrationDraftMetrics.hasTarget ? _ctx.t('orchestration.chat.input.sequenceHint', { count: _ctx.taskOrchestrationDraftMetrics.followUpCount + 2 }) : _ctx.t('orchestration.chat.input.workHint')), 1 /* TEXT */), _createElementVNode("button", { type: "button", - class: "btn-tool btn-tool-compact task-provider-config-button", - onClick: $event => (_ctx.openTaskOpenAiChatConfig()) - }, _toDisplayString(_ctx.t('orchestration.openai.status.configure')), 9 /* TEXT, PROPS */, ["onClick"]) - ]), - _createElementVNode("div", { class: "task-readiness-head" }, [ - _createElementVNode("div", null, [ - _createElementVNode("div", { class: "task-readiness-title" }, _toDisplayString(_ctx.t('orchestration.quick.checklist.title')), 1 /* TEXT */), - _createElementVNode("div", { class: "task-readiness-copy" }, _toDisplayString(_ctx.t('orchestration.quick.checklist.subtitle')), 1 /* TEXT */) + class: "btn-tool", + onClick: $event => (_ctx.previewTaskPlanFromChat()), + disabled: _ctx.taskOrchestration.planning || _ctx.taskOrchestration.running || (!_ctx.taskOrchestration.target.trim() && !_ctx.taskOrchestration.chatDraft.trim()) + }, _toDisplayString(_ctx.taskOrchestration.planning ? _ctx.t('orchestration.actions.planning') : _ctx.t('orchestration.actions.preview')), 9 /* TEXT, PROPS */, ["onClick", "disabled"]), + _createElementVNode("button", { + type: "button", + class: "btn-tool btn-primary", + onClick: $event => (_ctx.planAndRunTaskOrchestrationFromChat()), + disabled: _ctx.taskOrchestration.running || _ctx.taskOrchestration.planning || (!_ctx.taskOrchestration.target.trim() && !_ctx.taskOrchestration.chatDraft.trim()) + }, _toDisplayString(_ctx.taskOrchestration.running ? _ctx.t('orchestration.actions.processing') : (_ctx.taskOrchestration.planning ? _ctx.t('orchestration.actions.planning') : _ctx.t('orchestration.chat.input.work'))), 9 /* TEXT, PROPS */, ["onClick", "disabled"]) + ]) + ]) + ]), + _createElementVNode("aside", { + class: "task-kilo-inspector", + "aria-label": _ctx.t('orchestration.kilo.shell.aria') + }, [ + _createElementVNode("section", { class: "task-kilo-inspector-card" }, [ + _createElementVNode("div", { class: "task-kilo-section-label" }, _toDisplayString(_ctx.t('orchestration.agent.trace.title')), 1 /* TEXT */), + _createElementVNode("div", { class: "task-kilo-trace-grid" }, [ + _createElementVNode("div", { class: "task-kilo-trace-item" }, [ + _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.agent.trace.plan')), 1 /* TEXT */), + _createElementVNode("strong", null, _toDisplayString(_ctx.taskOrchestration.plan ? _ctx.taskOrchestration.plan.nodes.length : 0), 1 /* TEXT */) + ]), + _createElementVNode("div", { class: "task-kilo-trace-item" }, [ + _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.agent.trace.queue')), 1 /* TEXT */), + _createElementVNode("strong", null, _toDisplayString(_ctx.taskOrchestrationWorkspaceQueue.length), 1 /* TEXT */) + ]), + _createElementVNode("div", { class: "task-kilo-trace-item" }, [ + _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.agent.trace.runs')), 1 /* TEXT */), + _createElementVNode("strong", null, _toDisplayString(_ctx.taskOrchestrationWorkspaceRuns.length), 1 /* TEXT */) + ]), + _createElementVNode("div", { class: "task-kilo-trace-item" }, [ + _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.agent.trace.nodes')), 1 /* TEXT */), + _createElementVNode("strong", null, _toDisplayString(_ctx.taskOrchestrationSelectedRunNodes.length), 1 /* TEXT */) ]) ]), - _createElementVNode("div", { class: "task-readiness-grid task-quick-checklist" }, [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskOrchestrationDraftChecklist, (item) => { + _createElementVNode("div", { + class: "task-kilo-timeline", + "aria-label": _ctx.t('orchestration.kilo.timeline.aria') + }, [ + (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskKiloWebviewHostSnapshot.timelineBars, (bar) => { + return (_openBlock(), _createElementBlock("span", { + key: bar.key, + class: _normalizeClass(['task-kilo-timeline-bar', bar.tone]), + title: bar.label + }, null, 10 /* CLASS, PROPS */, ["title"])) + }), 128 /* KEYED_FRAGMENT */)) + ], 8 /* PROPS */, ["aria-label"]) + ]), + _createElementVNode("section", { class: "task-kilo-inspector-card" }, [ + _createElementVNode("div", { class: "task-kilo-section-label" }, _toDisplayString(_ctx.t('orchestration.kilo.surface.host')), 1 /* TEXT */), + _createElementVNode("div", { class: "task-kilo-protocol-grid" }, [ + (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskKiloWebviewHostSnapshot.protocolRows, (row) => { return (_openBlock(), _createElementBlock("div", { - key: item.key, - class: _normalizeClass(['task-readiness-item', { done: item.done }]) + key: row.key, + class: "task-kilo-protocol-row" }, [ - _createElementVNode("strong", null, _toDisplayString(item.label), 1 /* TEXT */), - _createElementVNode("span", null, _toDisplayString(item.detail), 1 /* TEXT */) - ], 2 /* CLASS */)) + _createElementVNode("span", null, _toDisplayString(row.label), 1 /* TEXT */), + _createElementVNode("strong", null, _toDisplayString(row.value), 1 /* TEXT */) + ])) }), 128 /* KEYED_FRAGMENT */)) - ]), - _createElementVNode("div", { class: "task-quick-status-card" }, [ - _createElementVNode("div", { class: "task-plan-summary-label" }, _toDisplayString(_ctx.t('orchestration.quick.status.title')), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(_ctx.taskOrchestrationDraftReadiness.title), 1 /* TEXT */), - _createElementVNode("span", null, _toDisplayString(_ctx.taskOrchestrationDraftReadiness.summary), 1 /* TEXT */) ]) - ]) - ]) - ]), - (!(_ctx.taskOrchestration.plan || _ctx.taskOrchestration.planIssues.length || _ctx.taskOrchestration.planWarnings.length || _ctx.taskOrchestration.lastError || _ctx.taskOrchestrationWorkspaceQueue.length || _ctx.taskOrchestrationWorkspaceRuns.length || _ctx.taskOrchestration.selectedRunId || _ctx.taskOrchestration.selectedRunError)) - ? (_openBlock(), _createElementBlock("section", { - key: 0, - class: "selector-section task-stage-card" + ]), + _createElementVNode("details", { + class: "task-kilo-inspector-card task-kilo-settings-card", + open: _ctx.taskOrchestration.settingsOpen, + onToggle: $event => (_ctx.taskOrchestration.settingsOpen = $event.target.open) }, [ - _createElementVNode("div", { class: "task-stage-empty" }, [ - _createElementVNode("div", null, [ - _createElementVNode("div", { class: "selector-title" }, _toDisplayString(_ctx.t('orchestration.stage.title')), 1 /* TEXT */), - _createElementVNode("div", { class: "skills-panel-note" }, _toDisplayString(_ctx.t('orchestration.stage.subtitle')), 1 /* TEXT */) + _createElementVNode("summary", null, _toDisplayString(_ctx.t('orchestration.advanced.title')), 1 /* TEXT */), + _createElementVNode("div", { class: "task-kilo-settings-grid" }, [ + _createElementVNode("label", { class: "task-quick-target-field" }, [ + _createElementVNode("span", { class: "selector-label" }, _toDisplayString(_ctx.t('orchestration.advanced.workspacePath.label')), 1 /* TEXT */), + _withDirectives(_createElementVNode("input", { + "onUpdate:modelValue": $event => ((_ctx.taskOrchestration.workspacePath) = $event), + class: "model-input task-sensitive-input", + type: "text", + autocomplete: "off", + placeholder: _ctx.t('orchestration.advanced.workspacePath.placeholder') + }, null, 8 /* PROPS */, ["onUpdate:modelValue", "placeholder"]), [ + [_vModelText, _ctx.taskOrchestration.workspacePath] + ]), + _createElementVNode("span", { class: "task-field-hint" }, _toDisplayString(_ctx.taskOrchestration.workspacePath.trim() ? _ctx.t('orchestration.workspace.pathHidden') : _ctx.t('orchestration.advanced.workspacePath.hint')), 1 /* TEXT */) + ]), + _createElementVNode("label", { class: "task-quick-target-field" }, [ + _createElementVNode("span", { class: "selector-label" }, _toDisplayString(_ctx.t('orchestration.advanced.threadId.label')), 1 /* TEXT */), + _withDirectives(_createElementVNode("input", { + "onUpdate:modelValue": $event => ((_ctx.taskOrchestration.threadId) = $event), + class: "model-input task-sensitive-input", + type: "text", + autocomplete: "off", + placeholder: _ctx.t('orchestration.advanced.threadId.placeholder') + }, null, 8 /* PROPS */, ["onUpdate:modelValue", "placeholder"]), [ + [_vModelText, _ctx.taskOrchestration.threadId] + ]), + _createElementVNode("span", { class: "task-field-hint" }, _toDisplayString(_ctx.taskOrchestration.threadId.trim() ? _ctx.t('orchestration.privacy.thread.selected') : _ctx.t('orchestration.advanced.threadId.hint')), 1 /* TEXT */) + ]), + _createElementVNode("label", { class: "task-quick-target-field" }, [ + _createElementVNode("span", { class: "selector-label" }, _toDisplayString(_ctx.t('orchestration.advanced.engine.label')), 1 /* TEXT */), + _withDirectives(_createElementVNode("select", { + "onUpdate:modelValue": $event => ((_ctx.taskOrchestration.selectedEngine) = $event), + class: "model-input", + onChange: $event => (_ctx.taskOrchestration.selectedEngine === 'workflow' ? null : _ctx.taskOrchestration.workflowIdsText = '') + }, [ + _createElementVNode("option", { value: "openai-chat" }, _toDisplayString(_ctx.t('orchestration.engine.openaiChat')), 1 /* TEXT */), + _createElementVNode("option", { value: "workflow" }, _toDisplayString(_ctx.t('orchestration.engine.workflow')), 1 /* TEXT */) + ], 40 /* PROPS, NEED_HYDRATION */, ["onUpdate:modelValue", "onChange"]), [ + [_vModelSelect, _ctx.taskOrchestration.selectedEngine] + ]) ]), - _createElementVNode("div", { class: "task-stage-strip" }, [ - _createElementVNode("div", { class: "task-stage-pill" }, _toDisplayString(_ctx.t('orchestration.stage.pill.target')), 1 /* TEXT */), - _createElementVNode("div", { class: "task-stage-pill" }, _toDisplayString(_ctx.t('orchestration.stage.pill.preview')), 1 /* TEXT */), - _createElementVNode("div", { class: "task-stage-pill" }, _toDisplayString(_ctx.t('orchestration.stage.pill.run')), 1 /* TEXT */) + _createElementVNode("label", { class: "task-quick-target-field" }, [ + _createElementVNode("span", { class: "selector-label" }, _toDisplayString(_ctx.t('orchestration.advanced.runMode.label')), 1 /* TEXT */), + _withDirectives(_createElementVNode("select", { + "onUpdate:modelValue": $event => ((_ctx.taskOrchestration.runMode) = $event), + class: "model-input" + }, [ + _createElementVNode("option", { value: "write" }, _toDisplayString(_ctx.t('orchestration.runMode.write')), 1 /* TEXT */), + _createElementVNode("option", { value: "read" }, _toDisplayString(_ctx.t('orchestration.runMode.readOnly')), 1 /* TEXT */), + _createElementVNode("option", { value: "dry-run" }, _toDisplayString(_ctx.t('orchestration.runMode.dryRun')), 1 /* TEXT */) + ], 8 /* PROPS */, ["onUpdate:modelValue"]), [ + [_vModelSelect, _ctx.taskOrchestration.runMode] + ]) ]) - ]) - ])) - : _createCommentVNode("v-if", true) + ]), + (_ctx.taskOrchestration.selectedEngine === 'workflow') + ? (_openBlock(), _createElementBlock("div", { + key: 0, + class: "task-workflow-suggestions" + }, [ + (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskOrchestration.workflows.slice(0, 6), (workflow) => { + return (_openBlock(), _createElementBlock("button", { + key: workflow.id || workflow.name, + type: "button", + class: "btn-tool btn-tool-compact", + onClick: $event => (_ctx.appendTaskWorkflowId(workflow.id || workflow.name)) + }, _toDisplayString(workflow.name || workflow.id), 9 /* TEXT, PROPS */, ["onClick"])) + }), 128 /* KEYED_FRAGMENT */)) + ])) + : _createCommentVNode("v-if", true) + ], 40 /* PROPS, NEED_HYDRATION */, ["open", "onToggle"]) + ], 8 /* PROPS */, ["aria-label"]) + ], 8 /* PROPS */, ["aria-label"]) ], 8 /* PROPS */, ["data-active"])), [ [_vShow, _ctx.mainTab === 'orchestration'] ]) diff --git a/web-ui/styles/task-orchestration.css b/web-ui/styles/task-orchestration.css index 2d10e008..3c2004b5 100644 --- a/web-ui/styles/task-orchestration.css +++ b/web-ui/styles/task-orchestration.css @@ -3234,3 +3234,501 @@ min-width: 0; } } + +/* Kilo-aligned orchestration rewrite */ +#panel-orchestration.task-kilo-mode { + --kilo-bg: #0f172a; + --kilo-sidebar: #111827; + --kilo-surface: #f8fafc; + --kilo-card: #ffffff; + --kilo-border: rgba(148, 163, 184, 0.22); + --kilo-muted: #64748b; + min-height: calc(100vh - 92px); + padding: 0; + background: #0b1020; +} + +#panel-orchestration .task-kilo-app-shell { + display: grid; + grid-template-columns: 52px 280px minmax(0, 1fr) 320px; + width: min(100%, 1720px); + min-height: calc(100vh - 92px); + margin: 0 auto; + overflow: hidden; + border: 1px solid rgba(148, 163, 184, 0.16); + border-radius: 22px; + background: var(--kilo-surface); + box-shadow: 0 24px 70px rgba(2, 6, 23, 0.24); +} + +#panel-orchestration .task-kilo-activity-bar { + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + padding: 12px 8px; + background: #111827; + color: rgba(226, 232, 240, 0.78); +} + +#panel-orchestration .task-kilo-logo-mark, +#panel-orchestration .task-kilo-activity-item { + display: grid; + width: 34px; + height: 34px; + place-items: center; + border-radius: 12px; + font-weight: 900; +} + +#panel-orchestration .task-kilo-logo-mark { + background: #6366f1; + color: #fff; + letter-spacing: -0.05em; +} + +#panel-orchestration .task-kilo-activity-item.active { + background: rgba(99, 102, 241, 0.24); + color: #fff; + box-shadow: inset 3px 0 0 #818cf8; +} + +#panel-orchestration .task-kilo-activity-spacer { + flex: 1; +} + +#panel-orchestration .task-kilo-sidebar, +#panel-orchestration .task-kilo-inspector { + min-width: 0; + overflow: auto; + border-right: 1px solid var(--kilo-border); + background: #f1f5f9; +} + +#panel-orchestration .task-kilo-sidebar { + padding: 14px; +} + +#panel-orchestration .task-kilo-inspector { + border-right: 0; + border-left: 1px solid var(--kilo-border); + padding: 14px; +} + +#panel-orchestration .task-kilo-sidebar-head, +#panel-orchestration .task-kilo-session-head, +#panel-orchestration .task-kilo-task-header, +#panel-orchestration .task-kilo-header-actions, +#panel-orchestration .task-kilo-input-actions { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 10px; +} + +#panel-orchestration .task-kilo-sidebar-title { + color: #0f172a; + font-size: 16px; + font-weight: 900; + line-height: 1.2; +} + +#panel-orchestration .task-kilo-new-task { + width: 100%; + margin: 14px 0; + justify-content: center; +} + +#panel-orchestration .task-kilo-sidebar-section, +#panel-orchestration .task-kilo-inspector-card { + margin-top: 12px; + padding: 12px; + border: 1px solid var(--kilo-border); + border-radius: 16px; + background: rgba(255, 255, 255, 0.76); +} + +#panel-orchestration .task-kilo-section-label { + margin-bottom: 8px; + color: var(--kilo-muted); + font-size: 10px; + font-weight: 900; + letter-spacing: 0.07em; + text-transform: uppercase; +} + +#panel-orchestration .task-kilo-workspace-list, +#panel-orchestration .task-kilo-session-section { + display: flex; + flex-direction: column; + gap: 8px; +} + +#panel-orchestration .task-kilo-workspace-item, +#panel-orchestration .task-kilo-session-item { + display: flex; + width: 100%; + min-width: 0; + align-items: center; + justify-content: space-between; + gap: 8px; + padding: 10px; + border: 1px solid transparent; + border-radius: 13px; + background: transparent; + color: #0f172a; + cursor: pointer; + text-align: left; +} + +#panel-orchestration .task-kilo-workspace-item:hover, +#panel-orchestration .task-kilo-session-item:hover, +#panel-orchestration .task-kilo-workspace-item.active { + border-color: rgba(99, 102, 241, 0.24); + background: rgba(99, 102, 241, 0.08); +} + +#panel-orchestration .task-kilo-workspace-title, +#panel-orchestration .task-kilo-session-item strong { + display: block; + overflow: hidden; + font-size: 12px; + font-weight: 900; + text-overflow: ellipsis; + white-space: nowrap; +} + +#panel-orchestration .task-kilo-workspace-meta, +#panel-orchestration .task-kilo-workspace-counts, +#panel-orchestration .task-kilo-session-item small, +#panel-orchestration .task-kilo-empty-copy { + display: block; + margin-top: 3px; + overflow: hidden; + color: var(--kilo-muted); + font-size: 11px; + line-height: 1.35; + text-overflow: ellipsis; + white-space: nowrap; +} + +#panel-orchestration .task-kilo-chat-view { + display: flex; + min-width: 0; + min-height: 0; + flex-direction: column; + background: #f8fafc; +} + +#panel-orchestration .task-kilo-task-header { + flex-shrink: 0; + padding: 18px 22px; + border-bottom: 1px solid var(--kilo-border); + background: rgba(255, 255, 255, 0.86); +} + +#panel-orchestration .task-kilo-task-header h2 { + margin: 2px 0 4px; + color: #0f172a; + font-size: 20px; + font-weight: 950; + letter-spacing: -0.03em; +} + +#panel-orchestration .task-kilo-task-header p { + max-width: 760px; + margin: 0; + color: var(--kilo-muted); + font-size: 12px; + line-height: 1.45; +} + +#panel-orchestration .task-kilo-header-actions { + align-items: center; + flex-wrap: wrap; + justify-content: flex-end; +} + +#panel-orchestration .task-kilo-message-list { + flex: 1; + min-height: 0; + overflow: auto; + padding: 20px clamp(18px, 4vw, 56px); +} + +#panel-orchestration .task-kilo-welcome-card, +#panel-orchestration .task-kilo-message-card { + border: 1px solid var(--kilo-border); + border-radius: 18px; + background: var(--kilo-card); + box-shadow: 0 12px 30px rgba(15, 23, 42, 0.06); +} + +#panel-orchestration .task-kilo-welcome-card { + max-width: 860px; + margin: 42px auto; + padding: 28px; + text-align: center; +} + +#panel-orchestration .task-kilo-welcome-mark, +#panel-orchestration .task-kilo-avatar { + display: grid; + place-items: center; + border-radius: 14px; + background: #6366f1; + color: #fff; + font-weight: 950; +} + +#panel-orchestration .task-kilo-welcome-mark { + width: 48px; + height: 48px; + margin: 0 auto 12px; + font-size: 22px; +} + +#panel-orchestration .task-kilo-welcome-card h3 { + margin: 0 0 8px; + color: #0f172a; + font-size: 22px; + font-weight: 950; +} + +#panel-orchestration .task-kilo-welcome-card p { + max-width: 680px; + margin: 0 auto; + color: var(--kilo-muted); + line-height: 1.5; +} + +#panel-orchestration .task-kilo-turn { + display: grid; + grid-template-columns: 34px minmax(0, 1fr); + gap: 10px; + max-width: 900px; + margin: 0 auto 14px; +} + +#panel-orchestration .task-kilo-turn.is-user { + max-width: 760px; + margin-right: clamp(0px, 5vw, 90px); +} + +#panel-orchestration .task-kilo-avatar { + width: 34px; + height: 34px; + font-size: 12px; +} + +#panel-orchestration .task-kilo-turn.is-user .task-kilo-avatar { + background: #0f172a; +} + +#panel-orchestration .task-kilo-message-card { + min-width: 0; + padding: 12px 14px; +} + +#panel-orchestration .task-kilo-message-label { + color: var(--kilo-muted); + font-size: 10px; + font-weight: 900; + letter-spacing: 0.06em; + text-transform: uppercase; +} + +#panel-orchestration .task-kilo-message-text { + margin-top: 5px; + color: #0f172a; + font-size: 13px; + line-height: 1.55; + white-space: pre-wrap; +} + +#panel-orchestration .task-kilo-message-meta { + margin-top: 6px; + color: var(--kilo-muted); + font-size: 11px; +} + +#panel-orchestration .task-kilo-plan-summary { + display: flex; + align-items: center; + gap: 8px; + margin-top: 10px; + color: #334155; + font-size: 12px; +} + +#panel-orchestration .task-kilo-plan-grid, +#panel-orchestration .task-kilo-context-grid, +#panel-orchestration .task-kilo-trace-grid, +#panel-orchestration .task-kilo-protocol-grid, +#panel-orchestration .task-kilo-settings-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 8px; + margin-top: 10px; +} + +#panel-orchestration .task-kilo-context-grid { + grid-template-columns: repeat(4, minmax(0, 1fr)); +} + +#panel-orchestration .task-kilo-plan-node, +#panel-orchestration .task-kilo-context-item, +#panel-orchestration .task-kilo-trace-item, +#panel-orchestration .task-kilo-protocol-row { + min-width: 0; + padding: 10px; + border: 1px solid rgba(148, 163, 184, 0.18); + border-radius: 13px; + background: rgba(248, 250, 252, 0.82); +} + +#panel-orchestration .task-kilo-plan-node span, +#panel-orchestration .task-kilo-plan-node small, +#panel-orchestration .task-kilo-context-item span, +#panel-orchestration .task-kilo-trace-item span, +#panel-orchestration .task-kilo-protocol-row span { + display: block; + color: var(--kilo-muted); + font-size: 10px; + font-weight: 850; + letter-spacing: 0.05em; + text-transform: uppercase; +} + +#panel-orchestration .task-kilo-plan-node strong, +#panel-orchestration .task-kilo-context-item strong, +#panel-orchestration .task-kilo-trace-item strong, +#panel-orchestration .task-kilo-protocol-row strong { + display: block; + min-width: 0; + margin-top: 4px; + overflow: hidden; + color: #0f172a; + font-size: 12px; + font-weight: 900; + text-overflow: ellipsis; + white-space: nowrap; +} + +#panel-orchestration .task-kilo-trace-item strong { + font-size: 20px; +} + +#panel-orchestration .task-kilo-chat-input { + flex-shrink: 0; + padding: 12px clamp(18px, 4vw, 56px) 18px; + border-top: 1px solid var(--kilo-border); + background: rgba(248, 250, 252, 0.94); +} + +#panel-orchestration .task-kilo-composer { + display: grid; + grid-template-columns: 24px minmax(0, 1fr); + gap: 8px; + padding: 10px 12px; + border: 1px solid rgba(99, 102, 241, 0.22); + border-radius: 18px; + background: #fff; + box-shadow: 0 12px 28px rgba(15, 23, 42, 0.08); +} + +#panel-orchestration .task-kilo-composer-glyph { + color: #6366f1; + font-size: 22px; + font-weight: 900; + line-height: 1.4; +} + +#panel-orchestration .task-kilo-composer textarea { + min-height: 68px; + border: 0; + background: transparent; + box-shadow: none; + resize: vertical; +} + +#panel-orchestration .task-kilo-input-actions { + align-items: center; + margin-top: 10px; +} + +#panel-orchestration .task-kilo-input-actions .task-field-hint { + flex: 1; +} + +#panel-orchestration .task-kilo-timeline { + display: grid; + grid-auto-flow: column; + grid-auto-columns: minmax(14px, 1fr); + gap: 4px; + margin-top: 12px; +} + +#panel-orchestration .task-kilo-timeline-bar { + height: 8px; + border-radius: 999px; + background: rgba(148, 163, 184, 0.48); +} + +#panel-orchestration .task-kilo-timeline-bar.success { background: rgba(34, 197, 94, 0.72); } +#panel-orchestration .task-kilo-timeline-bar.warn { background: rgba(245, 158, 11, 0.78); } +#panel-orchestration .task-kilo-timeline-bar.error { background: rgba(239, 68, 68, 0.78); } + +#panel-orchestration .task-kilo-settings-card summary { + cursor: pointer; + color: #0f172a; + font-size: 13px; + font-weight: 900; +} + +@media (max-width: 1180px) { + #panel-orchestration .task-kilo-app-shell { + grid-template-columns: 48px 240px minmax(0, 1fr); + } + #panel-orchestration .task-kilo-inspector { + display: none; + } +} + +@media (max-width: 820px) { + #panel-orchestration.task-kilo-mode { + background: #f8fafc; + } + #panel-orchestration .task-kilo-app-shell { + display: flex; + min-height: calc(100vh - 72px); + flex-direction: column; + border-radius: 0; + } + #panel-orchestration .task-kilo-activity-bar { + flex-direction: row; + } + #panel-orchestration .task-kilo-activity-spacer { + flex: 1; + } + #panel-orchestration .task-kilo-sidebar { + max-height: 260px; + border-right: 0; + border-bottom: 1px solid var(--kilo-border); + } + #panel-orchestration .task-kilo-task-header, + #panel-orchestration .task-kilo-input-actions { + flex-direction: column; + align-items: stretch; + } + #panel-orchestration .task-kilo-message-list, + #panel-orchestration .task-kilo-chat-input { + padding-right: 12px; + padding-left: 12px; + } + #panel-orchestration .task-kilo-context-grid, + #panel-orchestration .task-kilo-plan-grid, + #panel-orchestration .task-kilo-settings-grid { + grid-template-columns: 1fr; + } +} From ef76a11673923cba2a1be2879deba1bd19ff26de Mon Sep 17 00:00:00 2001 From: awsl233777 Date: Tue, 30 Jun 2026 15:57:33 +0000 Subject: [PATCH 10/25] refactor(orchestration): replace tab with Kilo webview UI --- tests/unit/config-tabs-ui.test.mjs | 121 +- web-ui/modules/i18n/locales/en.mjs | 29 +- web-ui/modules/i18n/locales/ja.mjs | 29 +- web-ui/modules/i18n/locales/vi.mjs | 29 +- web-ui/modules/i18n/locales/zh-tw.mjs | 29 +- web-ui/modules/i18n/locales/zh.mjs | 29 +- web-ui/modules/task-kilo-webview-host.mjs | 6 +- .../partials/index/panel-orchestration.html | 359 +- web-ui/res/web-ui-render.precompiled.js | 625 ++- web-ui/styles/task-orchestration.css | 3918 ++--------------- 10 files changed, 880 insertions(+), 4294 deletions(-) diff --git a/tests/unit/config-tabs-ui.test.mjs b/tests/unit/config-tabs-ui.test.mjs index 473d6814..eee7e520 100644 --- a/tests/unit/config-tabs-ui.test.mjs +++ b/tests/unit/config-tabs-ui.test.mjs @@ -92,44 +92,51 @@ test('config template keeps expected config tabs in top and side navigation', () assert.match(html, /:aria-selected="mainTab === 'orchestration'"/); assert.match(html, /id="panel-orchestration"/); assert.match(html, /v-show="mainTab === 'orchestration'"/); - assert.match(orchestrationPanel, /class="mode-content task-kilo-mode"/); - assert.match(orchestrationPanel, /class="task-kilo-app-shell"/); - assert.match(orchestrationPanel, /class="task-kilo-activity-bar"/); - assert.match(orchestrationPanel, /class="task-kilo-sidebar" :aria-label="t\('orchestration\.workspace\.aria'\)"/); - assert.match(orchestrationPanel, /class="task-kilo-chat-view"/); - assert.match(orchestrationPanel, /class="task-kilo-inspector" :aria-label="t\('orchestration\.kilo\.shell\.aria'\)"/); + assert.match(orchestrationPanel, /class="mode-content kilo-webview-mode"/); + assert.match(orchestrationPanel, /class="container kilo-webview-container"/); + assert.match(orchestrationPanel, /class="chat-view kilo-chat-view"/); + assert.match(orchestrationPanel, /data-component="task-header"/); + assert.match(orchestrationPanel, /data-slot="task-header-title"/); + assert.match(orchestrationPanel, /data-slot="task-header-stats"/); + assert.match(orchestrationPanel, /class="chat-messages-wrapper"/); + assert.match(orchestrationPanel, /class="message-list" role="log" :aria-label="t\('orchestration\.chat\.thread\.aria'\)"/); + assert.match(orchestrationPanel, /class="chat-input"/); + assert.match(orchestrationPanel, /class="prompt-input-container"/); + assert.match(orchestrationPanel, /class="prompt-input-shell"/); assert.match(orchestrationPanel, /taskKiloWebviewHostSnapshot\.title/); assert.match(orchestrationPanel, /taskKiloWebviewHostSnapshot\.subtitle/); - assert.match(orchestrationPanel, /v-for="row in taskKiloWebviewHostSnapshot\.protocolRows"/); + assert.match(orchestrationPanel, /taskKiloWebviewHostSnapshot\.timelineBars/); assert.match(orchestrationPanel, /@click="installTaskKiloWebviewShim\(\)"/); assert.doesNotMatch(orchestrationPanel, /taskKiloWebviewHostSnapshot\.bridge\.allowedMessageTypes/); assert.doesNotMatch(orchestrationPanel, /taskKiloWebviewHostSnapshot\.bridge\.blockedMessageTypes/); + assert.doesNotMatch(orchestrationPanel, /taskKiloWebviewHostSnapshot\.protocolRows/); + assert.doesNotMatch(orchestrationPanel, /task-kilo-sidebar|task-kilo-inspector|task-kilo-app-shell|task-kilo-activity-bar/); + assert.doesNotMatch(orchestrationPanel, /task-workbench|task-quick|task-agent|task-config-strip/); assert.doesNotMatch(orchestrationPanel, /@click="previewTaskPlan\(\)"/); assert.doesNotMatch(orchestrationPanel, /@click="planAndRunTaskOrchestration\(\)"/); assert.doesNotMatch(orchestrationPanel, /orchestration\.actions\.generatePlan/); assert.match(orchestrationPanel, /@click="previewTaskPlanFromChat\(\)"/); assert.match(orchestrationPanel, /@click="planAndRunTaskOrchestrationFromChat\(\)"/); - assert.match(orchestrationPanel, /v-for="workspace in taskOrchestrationWorkspaceItems"/); - assert.match(orchestrationPanel, /:aria-pressed="workspace\.active \? 'true' : 'false'"/); - assert.match(orchestrationPanel, /@click="selectTaskWorkspace\(workspace\.path\)"/); - assert.match(orchestrationPanel, /v-for="session in taskOrchestrationWorkspaceSessions\.slice\(0, 10\)"/); - assert.match(orchestrationPanel, /@click="continueTaskWorkspaceSession\(session\)"/); + assert.doesNotMatch(orchestrationPanel, /v-for="workspace in taskOrchestrationWorkspaceItems"/); + assert.doesNotMatch(orchestrationPanel, /@click="selectTaskWorkspace\(workspace\.path\)"/); + assert.doesNotMatch(orchestrationPanel, /v-for="session in taskOrchestrationWorkspaceSessions/); + assert.doesNotMatch(orchestrationPanel, /@click="continueTaskWorkspaceSession\(session\)"/); assert.match(orchestrationPanel, /v-for="message in taskOrchestrationConversationMessages"/); - assert.match(orchestrationPanel, /message\.role === 'user' \? 'is-user' : 'is-assistant'/); - assert.match(orchestrationPanel, /class="task-kilo-message-list" role="log" :aria-label="t\('orchestration\.chat\.thread\.aria'\)"/); + assert.match(orchestrationPanel, /:data-row="message\.role === 'user' \? 'user' : 'assistant'"/); assert.match(orchestrationPanel, /v-model="taskOrchestration\.chatDraft"/); assert.match(orchestrationPanel, /@keydown\.enter\.exact\.prevent="submitTaskOrchestrationChatMessage\(\)"/); assert.doesNotMatch(orchestrationPanel, /@click="submitTaskOrchestrationChatMessage\(\)"/); assert.match(orchestrationPanel, /t\('orchestration\.chat\.input\.placeholder'\)/); assert.doesNotMatch(orchestrationPanel, /t\('orchestration\.chat\.input\.discuss'\)/); assert.match(orchestrationPanel, /t\('orchestration\.chat\.input\.work'\)/); - assert.match(orchestrationPanel, /class="[^"]*task-kilo-plan-card[^"]*"/); + assert.match(orchestrationPanel, /class="vscode-session-turn-assistant kilo-plan-response"/); assert.match(orchestrationPanel, /AI · \{\{ t\('orchestration\.plan\.title'\) \}\}/); assert.match(orchestrationPanel, /taskOrchestration\.plan \|\| taskOrchestration\.planIssues\.length \|\| taskOrchestration\.planWarnings\.length \|\| taskOrchestration\.lastError/); - assert.match(orchestrationPanel, /v-for="\(_issue, index\) in taskOrchestration\.planIssues"/); - assert.match(orchestrationPanel, /v-for="\(_warning, index\) in taskOrchestration\.planWarnings"/); + assert.doesNotMatch(orchestrationPanel, /v-for="\(_issue, index\) in taskOrchestration\.planIssues"/); + assert.doesNotMatch(orchestrationPanel, /v-for="\(_warning, index\) in taskOrchestration\.planWarnings"/); assert.match(orchestrationPanel, /v-for="\(node, index\) in taskOrchestration\.plan\.nodes"/); assert.match(orchestrationPanel, /t\('orchestration\.privacy\.summaryHidden'\)/); + assert.match(orchestrationPanel, /t\('orchestration\.privacy\.detailsHidden'\)/); assert.doesNotMatch(orchestrationPanel, /\{\{ node\.(summary|error|id|workflowId)/); assert.doesNotMatch(orchestrationPanel, /wave\.nodeIds\.join/); assert.doesNotMatch(orchestrationPanel, /taskOrchestration\.plan\.threadId\s*\}\}/); @@ -143,7 +150,7 @@ test('config template keeps expected config tabs in top and side navigation', () assert.doesNotMatch(orchestrationPanel, /\{\{ node\.output\.(provider|model|endpoint)/); assert.doesNotMatch(orchestrationPanel, /\{\{ taskOrchestration\.(lastError|selectedRunError) \}\}/); assert.doesNotMatch(orchestrationPanel, /type="password"/); - assert.match(orchestrationPanel, /class="model-input task-sensitive-input" type="text" autocomplete="off"/); + assert.match(orchestrationPanel, /class="kilo-input task-sensitive-input" type="text" autocomplete="off"/); assert.doesNotMatch(taskOrchestrationStyles, /type="password"/); assert.match(taskOrchestrationStyles, /\.task-sensitive-input\s*\{[\s\S]*-webkit-text-security: disc;/); assert.doesNotMatch(precompiledRender, /_toDisplayString\(_ctx\.taskOrchestration\.(lastError|selectedRunError)\)/); @@ -151,75 +158,29 @@ test('config template keeps expected config tabs in top and side navigation', () assert.doesNotMatch(precompiledRender, /node\.workflowId/); assert.match(precompiledRender, /_toDisplayString\(_ctx\.t\('orchestration\.privacy\.detailsHidden'\)\)/); assert.doesNotMatch(precompiledRender, /type: "password"/); - assert.match(precompiledRender, /class: "model-input task-sensitive-input"/); + assert.match(precompiledRender, /class: "kilo-input task-sensitive-input"/); assert.match(precompiledRender, /taskKiloWebviewHostSnapshot\.title/); assert.doesNotMatch(orchestrationPanel, /file\.(relativePath|path)/); assert.match(orchestrationPanel, /@change="taskOrchestration\.selectedEngine === 'workflow' \? null : taskOrchestration\.workflowIdsText = ''"/); - assert.match(orchestrationPanel, /class="task-workflow-suggestions"/); + assert.match(orchestrationPanel, /class="kilo-command-suggestions"/); assert.match(orchestrationPanel, /@click="appendTaskWorkflowId\(workflow.id \|\| workflow.name\)"/); - assert.doesNotMatch(orchestrationPanel, /class="task-config-strip"/); - assert.match(orchestrationPanel, /taskOrchestrationWorkspaceQueue/); - assert.match(orchestrationPanel, /taskOrchestrationWorkspaceRuns/); - assert.match(orchestrationPanel, /taskOrchestrationWorkspaceSessions\.length/); - assert.match(orchestrationPanel, /taskOrchestrationSelectedRunNodes/); + assert.match(orchestrationPanel, /taskOrchestrationWorkspaceQueue\.length/); + assert.match(orchestrationPanel, /taskOrchestrationWorkspaceRuns\.length/); + assert.doesNotMatch(orchestrationPanel, /taskOrchestrationWorkspaceSessions\.length/); + assert.match(orchestrationPanel, /taskOrchestrationSelectedRunNodes\.length/); assert.match(orchestrationPanel, /:data-active="mainTab === 'orchestration' \? 'true' : 'false'"/); assert.match(html, /:data-active="mainTab === 'orchestration' \? 'true' : 'false'"/); for (const styles of [taskOrchestrationStyles, bundledStyles]) { - assert.match(styles, /\.task-layout-grid-primary\s*\{[\s\S]*grid-template-columns:\s*minmax\(0, 1fr\);/); - assert.match(styles, /\.task-layout-grid-secondary\s*\{[\s\S]*grid-template-columns:\s*minmax\(0, 1fr\);/); - assert.match(styles, /#panel-orchestration\s*\{[\s\S]*--task-orchestration-main-width:\s*1360px;[\s\S]*--task-orchestration-project-width:\s*260px;[\s\S]*--task-orchestration-records-width:\s*320px;/); - assert.match(styles, /#panel-orchestration \.task-hero-card,[\s\S]*#panel-orchestration \.task-quick-copy,[\s\S]*#panel-orchestration \.task-stage-card\s*\{[\s\S]*display:\s*none;/); - assert.doesNotMatch(styles, /#panel-orchestration \.task-hero-card,[\s\S]*#panel-orchestration \.task-quick-side-card,[\s\S]*#panel-orchestration \.task-quick-advanced,[\s\S]*#panel-orchestration \.task-template-block,[\s\S]*display:\s*none;/); - assert.match(styles, /#panel-orchestration \.task-layout-grid,[\s\S]*#panel-orchestration \.task-layout-grid-primary,[\s\S]*#panel-orchestration \.task-layout-grid-secondary,[\s\S]*#panel-orchestration \.task-quick-card\s*\{[\s\S]*width:\s*min\(100%, var\(--task-orchestration-main-width\)\);/); - assert.match(styles, /#panel-orchestration \.task-quick-card\s*\{[\s\S]*display:\s*grid;[\s\S]*grid-template-columns:\s*var\(--task-orchestration-project-width\) minmax\(0, 1fr\) var\(--task-orchestration-records-width\);/); - assert.match(styles, /#panel-orchestration \.task-project-sidebar,[\s\S]*#panel-orchestration \.task-quick-side-card\s*\{[\s\S]*position:\s*sticky;[\s\S]*max-height:\s*calc\(100vh - 112px\);/); - assert.match(styles, /#panel-orchestration \.task-project-item\.active\s*\{[\s\S]*box-shadow:\s*inset 3px 0 0 rgba\(199, 116, 98, 0\.56\);/); - assert.match(styles, /#panel-orchestration \.task-chat-panel\s*\{[\s\S]*border:\s*0;[\s\S]*background:\s*transparent;[\s\S]*box-shadow:\s*none;/); - assert.match(styles, /#panel-orchestration \.task-chat-thread\s*\{[\s\S]*padding:\s*8px 8px 188px;[\s\S]*scroll-padding-bottom:\s*188px;/); - assert.match(styles, /#panel-orchestration \.task-thread-composer\s*\{[\s\S]*position:\s*fixed;[\s\S]*bottom:\s*16px;[\s\S]*width:\s*min\(872px, calc\(100vw - 48px\)\);/); - assert.match(styles, /#panel-orchestration \.task-thread-message-card\s*\{[\s\S]*width:\s*min\(100%, 660px\);/); - assert.doesNotMatch(styles, /task-panel-toolbar/); - assert.doesNotMatch(styles, /task-panel-config-action/); - assert.doesNotMatch(styles, /task-chat-status-strip/); - assert.doesNotMatch(styles, /task-chat-config-link/); - assert.doesNotMatch(styles, /task-thread-suggestion-card/); - assert.doesNotMatch(styles, /task-thread-readiness-card/); - assert.doesNotMatch(styles, /task-thread-checklist/); - assert.match(styles, /\.task-chat-context-action\s*\{[\s\S]*cursor:\s*pointer;/); - assert.doesNotMatch(styles, /#panel-orchestration \.task-quick-side-card\s*\{\s*display:\s*none;\s*\}/); - assert.match(styles, /#panel-orchestration \.task-side-workbench-card,[\s\S]*#panel-orchestration \.task-side-settings-card\s*\{[\s\S]*max-height:\s*min\(560px, calc\(100vh - 220px\)\);[\s\S]*overflow:\s*auto;/); - assert.match(styles, /#panel-orchestration \.task-side-workbench-card \.task-node-list\s*\{[\s\S]*max-height:\s*320px;[\s\S]*overflow:\s*auto;/); - assert.doesNotMatch(styles, /#panel-orchestration \.task-thread-workbench-card\s*\{/); - assert.match(styles, /#panel-orchestration \.task-thread-plan-card \.task-wave-list\s*\{[\s\S]*display:\s*none;/); - assert.match(styles, /#panel-orchestration \.task-thread-plan-card \.task-node-list\s*\{[\s\S]*display:\s*none;/); - assert.match(styles, /@media \(max-width:\s*760px\)\s*\{[\s\S]*#panel-orchestration\s*\{[\s\S]*--task-orchestration-main-width:\s*100%;/); - assert.match(styles, /@media \(max-width:\s*760px\)\s*\{[\s\S]*#panel-orchestration \.task-quick-card,[\s\S]*#panel-orchestration \.task-quick-side-card\s*\{[\s\S]*display:\s*flex;[\s\S]*flex-direction:\s*column;/); - assert.match(styles, /@media \(max-width:\s*760px\)\s*\{[\s\S]*#panel-orchestration \.task-chat-thread\s*\{[\s\S]*padding-bottom:\s*208px;[\s\S]*scroll-padding-bottom:\s*208px;/); - assert.match(styles, /@media \(max-width:\s*760px\)\s*\{[\s\S]*#panel-orchestration \.task-thread-composer\s*\{[\s\S]*grid-template-columns:\s*minmax\(0, 1fr\) auto;[\s\S]*right:\s*10px;[\s\S]*bottom:\s*calc\(env\(safe-area-inset-bottom, 0px\) \+ 8px\);[\s\S]*left:\s*10px;/); - assert.match(styles, /#panel-orchestration \.task-thread-composer \.task-chat-send-row\s*\{[\s\S]*grid-row:\s*1 \/ span 2;[\s\S]*align-self:\s*center;/); - assert.doesNotMatch(styles, /\.container:has\(#panel-orchestration\[data-active="true"\]\)\s*\{[\s\S]*background:\s*#F6F8FB;/); - assert.doesNotMatch(styles, /body:has\(#panel-orchestration\[data-active="true"\]\)\s*\{[\s\S]*background:\s*#F6F8FB;/); - assert.doesNotMatch(styles, /body:has\(#panel-orchestration\[data-active="true"\]\)::before\s*\{[\s\S]*background:\s*none;/); - assert.match(styles, /\.task-hero-card,[\s\S]*\.task-empty-state\s*\{[\s\S]*border:\s*1px solid/); - assert.match(styles, /\.task-hero-card\s*\{[\s\S]*background:\s*linear-gradient\(180deg, rgba\(199, 116, 98, 0\.10\) 0%, rgba\(199, 116, 98, 0\.03\) 100%\), var\(--color-surface\);/); - assert.match(styles, /\.task-quick-input-card\s*\{[\s\S]*background:\s*#F8FAFC;/); - assert.match(styles, /\.task-template-chip-group,[\s\S]*\.task-workflow-suggestions\s*\{[\s\S]*display:\s*flex;[\s\S]*flex-wrap:\s*wrap;/); - assert.match(styles, /\.task-checklist-inline\s*\{[\s\S]*grid-template-columns:\s*repeat\(2, minmax\(0, 1fr\)\);/); - assert.match(styles, /\.task-chat-thread\s*\{[\s\S]*display:\s*flex;[\s\S]*flex-direction:\s*column;[\s\S]*overflow-y:\s*auto;[\s\S]*scrollbar-width:\s*thin;/); - assert.match(styles, /\.task-chat-thread:hover,[\s\S]*\.task-chat-thread:focus-within\s*\{[\s\S]*scrollbar-color:\s*rgba\(100, 116, 139, 0\.28\) transparent;/); - assert.match(styles, /\.task-chat-thread::-webkit-scrollbar-thumb\s*\{[\s\S]*background:\s*transparent;/); - assert.match(styles, /\.task-chat-bubble-row\.is-user\s*\{[\s\S]*justify-content:\s*flex-end;/); - assert.match(styles, /\.task-chat-bubble-row\.is-assistant\s*\{[\s\S]*justify-content:\s*flex-start;/); - assert.match(styles, /\.task-chat-context-row\s*\{[\s\S]*display:\s*flex;[\s\S]*flex-wrap:\s*wrap;/); - assert.match(styles, /\.task-chat-context-chip\s*\{[\s\S]*border-radius:\s*999px;[\s\S]*text-overflow:\s*ellipsis;/); - assert.match(styles, /\.task-stage-empty\s*\{[\s\S]*display:\s*flex;/); - assert.match(styles, /\.task-workbench-tabs\s*\{[\s\S]*display:\s*flex;/); - assert.match(styles, /\.task-action-row-right\s*\{[\s\S]*display:\s*flex;[\s\S]*flex-wrap:\s*wrap;/); - assert.match(styles, /\.task-runtime-item-actions\s*\{[\s\S]*flex-direction:\s*row;[\s\S]*align-items:\s*center;/); - assert.match(styles, /\.task-provider-status-card\s*\{[\s\S]*background:\s*#FFFFFF;/); - assert.match(styles, /\.task-provider-status-grid\s*\{[\s\S]*grid-template-columns:\s*repeat\(2, minmax\(0, 1fr\)\);/); - assert.match(styles, /\.task-node-output-card\s*\{[\s\S]*background:\s*#F8FAFC;/); - assert.match(styles, /\.task-output-block\s*\{[\s\S]*max-height:\s*260px;/); + assert.match(styles, /#panel-orchestration\.kilo-webview-mode\s*\{/); + assert.match(styles, /#panel-orchestration \.kilo-webview-container\s*\{/); + assert.match(styles, /#panel-orchestration \.kilo-chat-view\s*\{[\s\S]*display:\s*flex;[\s\S]*flex-direction:\s*column;/); + assert.match(styles, /#panel-orchestration \[data-component="task-header"\]/); + assert.match(styles, /#panel-orchestration \.chat-messages-wrapper\s*\{[\s\S]*flex:\s*1;/); + assert.match(styles, /#panel-orchestration \.message-list\s*\{[\s\S]*overflow-y:\s*auto;/); + assert.match(styles, /#panel-orchestration \.chat-input\s*\{/); + assert.match(styles, /#panel-orchestration \.prompt-input-container\s*\{/); + assert.match(styles, /#panel-orchestration \.prompt-input-textarea\s*\{/); + assert.doesNotMatch(styles, /task-quick|task-workbench|task-agent|task-kilo-host|task-layout-grid|task-thread-composer|task-chat-thread/); } for (const styles of [bundledStyles]) { assert.match(styles, /\.health-failed-provider-main input\s*\{[\s\S]*flex:\s*0 0 auto;[\s\S]*width:\s*13px;[\s\S]*height:\s*13px;[\s\S]*accent-color:\s*var\(--color-brand-dark\);/); diff --git a/web-ui/modules/i18n/locales/en.mjs b/web-ui/modules/i18n/locales/en.mjs index d9a3a6dd..13a160d9 100644 --- a/web-ui/modules/i18n/locales/en.mjs +++ b/web-ui/modules/i18n/locales/en.mjs @@ -1018,14 +1018,14 @@ const en = Object.freeze({ 'orchestration.chat.input.workCaption': 'Enter appends a request; /plan previews only; Start work plans and runs.', 'orchestration.chat.input.sequenceHint': 'Sending now becomes request {count}.', 'orchestration.chat.assistant.contextLabel': 'Previous context', - 'orchestration.chat.assistant.contextFallback': 'A previous task is selected. Continuing will inherit its thread and workspace.', + 'orchestration.chat.assistant.contextFallback': 'A previous task is selected. Continuing will reuse that task context.', 'orchestration.chat.assistant.resultLabel': 'Result', 'orchestration.chat.assistant.filesSummary': 'File operations: {files}', - 'orchestration.chat.assistant.readyLabel': 'Codexmate', - 'orchestration.chat.assistant.empty': 'Enter a task objective. Multiple requests run in order with context preserved.', + 'orchestration.chat.assistant.readyLabel': 'Kilo', + 'orchestration.chat.assistant.empty': 'Send the first request. Add another message to continue with the same context.', 'orchestration.chat.assistant.planLabel': 'Plan preview', 'orchestration.chat.assistant.planSummary': 'Plan ready: {nodes} nodes across {waves} waves.', - 'orchestration.chat.assistant.sequenceReady': 'Multiple requests received. Codexmate will finish request 1 first, then continue with the remaining context.', + 'orchestration.chat.assistant.sequenceReady': 'Multiple requests received. The session will finish request 1 first, then continue with the remaining context.', 'orchestration.chat.assistant.singleReady': 'Request received. Add another request or type /plan to preview.', 'orchestration.chat.user.step': 'Request {count}', 'orchestration.chat.meta.thread': 'Thread {value}', @@ -1053,10 +1053,10 @@ const en = Object.freeze({ 'orchestration.kilo.aria': 'Kilo-compatible task host', 'orchestration.kilo.kicker': 'Kilo webview host', 'orchestration.kilo.header.title': 'Kilo-compatible task host', - 'orchestration.kilo.header.subtitle': 'Codexmate exposes a VSCode-style webview shell with a privacy-safe task bridge.', + 'orchestration.kilo.header.subtitle': 'VS Code-style webview shell with a privacy-safe task bridge.', 'orchestration.kilo.shell.aria': 'Kilo-compatible webview shell', 'orchestration.kilo.surface.title': 'Webview surface', - 'orchestration.kilo.surface.host': 'Codexmate host adapter', + 'orchestration.kilo.surface.host': 'Webview host adapter', 'orchestration.kilo.timeline.aria': 'Task activity timeline', 'orchestration.kilo.timeline.draft': 'Draft', 'orchestration.kilo.timeline.plan': 'Plan', @@ -1069,6 +1069,9 @@ const en = Object.freeze({ 'orchestration.kilo.protocol.browserState': 'Browser state', 'orchestration.kilo.protocol.blocked': 'Blocked', 'orchestration.kilo.trace.counts': '{runs} runs · {queue} queued · {nodes} nodes', + 'orchestration.kilo.engine.chat': 'Chat engine', + 'orchestration.kilo.engine.workflow': 'Workflow engine', + 'orchestration.kilo.engine.configured': 'Execution engine configured', 'orchestration.kilo.bridge.copy': 'VSCode APIs are shimmed only for task orchestration; file, terminal, and external-open capabilities stay blocked.', 'orchestration.kilo.bridge.install': 'Reinstall shim', 'orchestration.agent.trace.title': 'Live trace', @@ -1151,7 +1154,7 @@ const en = Object.freeze({ 'orchestration.fields.target': 'Task message', 'orchestration.fields.target.placeholder': 'e.g. Fix current PR review comments, add regression tests, and avoid unrelated modules', 'orchestration.fields.target.hint': 'One chat-style message is enough: outcome, constraints, and acceptance criteria.', - 'orchestration.engine.openaiChat': 'OpenAI Chat', + 'orchestration.engine.openaiChat': 'Chat engine', 'orchestration.engine.workflow': 'Workflow', 'orchestration.runMode.write': 'Write', 'orchestration.runMode.readOnly': 'Read-only', @@ -1242,18 +1245,18 @@ const en = Object.freeze({ 'orchestration.detail.context.run': 'Run ID', 'orchestration.detail.node.meta': 'Attempts {attempts} · auto-fix {autoFix}', 'orchestration.detail.node.step': 'Task step', - 'orchestration.openai.status.title': 'OpenAI Chat runtime', - 'orchestration.openai.status.subtitle': 'Task nodes use the current OpenAI Chat configuration: base URL, API key, and model are checked before execution.', - 'orchestration.openai.status.provider': 'Provider', - 'orchestration.openai.status.model': 'Model', + 'orchestration.openai.status.title': 'Chat runtime', + 'orchestration.openai.status.subtitle': 'Task nodes use the configured execution engine; readiness is checked before execution.', + 'orchestration.openai.status.provider': 'Runtime', + 'orchestration.openai.status.model': 'Profile', 'orchestration.openai.status.endpoint': 'Endpoint', 'orchestration.openai.status.apiKey': 'API key', 'orchestration.openai.status.headers': 'Extra headers', 'orchestration.openai.status.configured': 'Configured', 'orchestration.openai.status.missing': 'Missing', 'orchestration.openai.status.notSet': 'Not set', - 'orchestration.openai.status.notLoaded': 'Refresh to load OpenAI Chat readiness.', - 'orchestration.openai.status.configure': 'Configure OpenAI Chat', + 'orchestration.openai.status.notLoaded': 'Refresh to load chat runtime readiness.', + 'orchestration.openai.status.configure': 'Configure chat runtime', 'orchestration.detail.node.output': 'AI output', 'orchestration.detail.node.endpoint': 'Endpoint: ', 'orchestration.detail.node.materializedFiles': 'Materialized files:', diff --git a/web-ui/modules/i18n/locales/ja.mjs b/web-ui/modules/i18n/locales/ja.mjs index 03362f5b..ed0d87a9 100644 --- a/web-ui/modules/i18n/locales/ja.mjs +++ b/web-ui/modules/i18n/locales/ja.mjs @@ -1007,14 +1007,14 @@ const ja = Object.freeze({ 'orchestration.chat.input.workHint': '実行前に現在の入力を取り込みます。', 'orchestration.chat.input.workCaption': 'Enter で依頼を追加、/plan はプレビューのみ、実行開始で計画して実行します。', 'orchestration.chat.assistant.contextLabel': '前回のコンテキスト', - 'orchestration.chat.assistant.contextFallback': '過去のタスクが選択されています。続行するとスレッドとワークスペースを引き継ぎます。', + 'orchestration.chat.assistant.contextFallback': '以前のタスクが選択されています。続行するとそのタスクのコンテキストを再利用します。', 'orchestration.chat.assistant.resultLabel': '実装結果', 'orchestration.chat.assistant.filesSummary': 'ファイル操作: {files}', - 'orchestration.chat.assistant.readyLabel': 'Codexmate', - 'orchestration.chat.assistant.empty': '最初の依頼を送信してください。2 件目があれば続けて送信すると、Codexmate はコンテキストを保ったまま順番に処理します。', + 'orchestration.chat.assistant.readyLabel': 'Kilo', + 'orchestration.chat.assistant.empty': '最初の依頼を送信してください。続けて送信すると同じコンテキストで処理します。', 'orchestration.chat.assistant.planLabel': '計画プレビュー', 'orchestration.chat.assistant.planSummary': '/plan の計画を作成しました:{nodes} ノード、{waves} ウェーブ。', - 'orchestration.chat.assistant.sequenceReady': '複数の依頼を受け取りました。まず依頼 1 を完了し、そのコンテキストで後続の依頼を続けます。', + 'orchestration.chat.assistant.sequenceReady': '複数の依頼を受信しました。依頼 1 を完了してから、残りのコンテキストで続行します。', 'orchestration.chat.assistant.singleReady': '最初の依頼を受け取りました。依頼 2 を追加するか、このままプレビューして実行できます。', 'orchestration.chat.user.step': '依頼 {count}', 'orchestration.chat.meta.thread': 'スレッド {value}', @@ -1060,10 +1060,10 @@ const ja = Object.freeze({ 'orchestration.kilo.aria': 'Kilo 互換タスクホスト', 'orchestration.kilo.kicker': 'Kilo webview ホスト', 'orchestration.kilo.header.title': 'Kilo 互換タスクホスト', - 'orchestration.kilo.header.subtitle': 'Codexmate は VSCode 風の webview シェルと、プライバシー安全なタスクブリッジを提供します。', + 'orchestration.kilo.header.subtitle': 'VS Code 風の webview シェルに、プライバシー安全なタスクブリッジを接続しています。', 'orchestration.kilo.shell.aria': 'Kilo 互換 webview シェル', 'orchestration.kilo.surface.title': 'Webview surface', - 'orchestration.kilo.surface.host': 'Codexmate ホストアダプター', + 'orchestration.kilo.surface.host': 'Webview ホストアダプター', 'orchestration.kilo.timeline.aria': 'タスク活動タイムライン', 'orchestration.kilo.timeline.draft': '下書き', 'orchestration.kilo.timeline.plan': 'プラン', @@ -1076,6 +1076,9 @@ const ja = Object.freeze({ 'orchestration.kilo.protocol.browserState': 'ブラウザー状態', 'orchestration.kilo.protocol.blocked': 'ブロック済み', 'orchestration.kilo.trace.counts': '{runs} 件の実行 · {queue} 件のキュー · {nodes} 件のノード', + 'orchestration.kilo.engine.chat': 'チャットエンジン', + 'orchestration.kilo.engine.workflow': 'ワークフローエンジン', + 'orchestration.kilo.engine.configured': '実行エンジン設定済み', 'orchestration.kilo.bridge.copy': 'VSCode API はタスク編成用にのみ shim され、ファイル、ターミナル、外部オープン機能はブロックされます。', 'orchestration.kilo.bridge.install': 'shim を再適用', 'orchestration.agent.trace.title': 'Live trace', @@ -1140,7 +1143,7 @@ const ja = Object.freeze({ 'orchestration.fields.target': 'タスクメッセージ', 'orchestration.fields.target.placeholder': '例: 現在の PR レビューコメントを修正し、回帰テストを追加し、関連しないモジュールは避ける', 'orchestration.fields.target.hint': 'チャットのように、結果・制約・受け入れ条件を書けば十分です。', - 'orchestration.engine.openaiChat': 'OpenAI Chat', + 'orchestration.engine.openaiChat': 'チャットエンジン', 'orchestration.engine.workflow': 'Workflow', 'orchestration.runMode.write': '書き込み', 'orchestration.runMode.readOnly': '読み取り専用', @@ -1231,18 +1234,18 @@ const ja = Object.freeze({ 'orchestration.detail.context.run': 'Run ID', 'orchestration.detail.node.meta': '試行 {attempts} · 自動修正 {autoFix}', 'orchestration.detail.node.step': 'タスクステップ', - 'orchestration.openai.status.title': 'OpenAI Chat 実行設定', - 'orchestration.openai.status.subtitle': 'タスクノードは現在の OpenClaw provider を使用し、実行前に base URL、API key、model を確認します。', - 'orchestration.openai.status.provider': 'Provider', - 'orchestration.openai.status.model': 'Model', + 'orchestration.openai.status.title': 'チャット実行設定', + 'orchestration.openai.status.subtitle': 'タスクノードは設定済みの実行エンジンを使用し、実行前に準備状態を確認します。', + 'orchestration.openai.status.provider': 'ランタイム', + 'orchestration.openai.status.model': 'プロファイル', 'orchestration.openai.status.endpoint': 'Endpoint', 'orchestration.openai.status.apiKey': 'API Key', 'orchestration.openai.status.headers': '追加 headers', 'orchestration.openai.status.configured': '設定済み', 'orchestration.openai.status.missing': '未設定', 'orchestration.openai.status.notSet': 'なし', - 'orchestration.openai.status.notLoaded': '更新すると OpenAI Chat の準備状態を読み込みます。', - 'orchestration.openai.status.configure': 'OpenAI Chat を設定', + 'orchestration.openai.status.notLoaded': '更新するとチャット実行設定の準備状態を読み込みます。', + 'orchestration.openai.status.configure': 'チャット実行環境を設定', 'orchestration.detail.node.output': 'AI 出力', 'orchestration.detail.node.endpoint': 'Endpoint: ', 'orchestration.detail.node.materializedFiles': '生成ファイル:', diff --git a/web-ui/modules/i18n/locales/vi.mjs b/web-ui/modules/i18n/locales/vi.mjs index 444915e1..4d5c87f3 100644 --- a/web-ui/modules/i18n/locales/vi.mjs +++ b/web-ui/modules/i18n/locales/vi.mjs @@ -1154,14 +1154,14 @@ const vi = Object.freeze({ 'orchestration.chat.input.workHint': 'Nội dung hiện tại sẽ được đưa vào trước khi chạy.', 'orchestration.chat.input.workCaption': 'Enter thêm yêu cầu; /plan chỉ xem trước; Bắt đầu chạy sẽ lập kế hoạch và chạy.', 'orchestration.chat.assistant.contextLabel': 'Ngữ cảnh trước đó', - 'orchestration.chat.assistant.contextFallback': 'Đã chọn một tác vụ cũ. Khi tiếp tục, Codexmate sẽ kế thừa thread và workspace của tác vụ đó.', + 'orchestration.chat.assistant.contextFallback': 'Đã chọn một tác vụ cũ. Khi tiếp tục, phiên sẽ dùng lại ngữ cảnh tác vụ đó.', 'orchestration.chat.assistant.resultLabel': 'Kết quả', 'orchestration.chat.assistant.filesSummary': 'Thao tác file: {files}', - 'orchestration.chat.assistant.readyLabel': 'Codexmate', - 'orchestration.chat.assistant.empty': 'Gửi yêu cầu đầu tiên. Nếu còn yêu cầu thứ hai, hãy gửi tiếp; Codexmate sẽ giữ ngữ cảnh và xử lý theo thứ tự.', + 'orchestration.chat.assistant.readyLabel': 'Kilo', + 'orchestration.chat.assistant.empty': 'Gửi yêu cầu đầu tiên. Gửi tiếp để tiếp tục với cùng ngữ cảnh.', 'orchestration.chat.assistant.planLabel': 'Xem trước kế hoạch', 'orchestration.chat.assistant.planSummary': 'Đã tạo kế hoạch: {nodes} node trong {waves} lượt.', - 'orchestration.chat.assistant.sequenceReady': 'Đã nhận nhiều yêu cầu. Codexmate sẽ hoàn tất yêu cầu 1 trước, rồi tiếp tục với ngữ cảnh còn lại.', + 'orchestration.chat.assistant.sequenceReady': 'Đã nhận nhiều yêu cầu. Phiên sẽ hoàn tất yêu cầu 1 trước rồi tiếp tục với ngữ cảnh còn lại.', 'orchestration.chat.assistant.singleReady': 'Đã nhận yêu cầu đầu tiên. Có thể thêm yêu cầu 2 hoặc xem trước và chạy ngay.', 'orchestration.chat.user.step': 'Yêu cầu {count}', 'orchestration.chat.meta.thread': 'Thread {value}', @@ -1207,10 +1207,10 @@ const vi = Object.freeze({ 'orchestration.kilo.aria': 'Máy chủ tác vụ tương thích Kilo', 'orchestration.kilo.kicker': 'Máy chủ webview Kilo', 'orchestration.kilo.header.title': 'Máy chủ tác vụ tương thích Kilo', - 'orchestration.kilo.header.subtitle': 'Codexmate cung cấp vỏ webview kiểu VSCode cùng cầu tác vụ an toàn cho quyền riêng tư.', + 'orchestration.kilo.header.subtitle': 'Vỏ webview kiểu VS Code với cầu tác vụ an toàn cho quyền riêng tư.', 'orchestration.kilo.shell.aria': 'Vỏ webview tương thích Kilo', 'orchestration.kilo.surface.title': 'Webview surface', - 'orchestration.kilo.surface.host': 'Bộ chuyển đổi máy chủ Codexmate', + 'orchestration.kilo.surface.host': 'Bộ chuyển đổi máy chủ webview', 'orchestration.kilo.timeline.aria': 'Dòng thời gian hoạt động tác vụ', 'orchestration.kilo.timeline.draft': 'Bản nháp', 'orchestration.kilo.timeline.plan': 'Kế hoạch', @@ -1223,6 +1223,9 @@ const vi = Object.freeze({ 'orchestration.kilo.protocol.browserState': 'Trạng thái trình duyệt', 'orchestration.kilo.protocol.blocked': 'Đã chặn', 'orchestration.kilo.trace.counts': '{runs} lượt chạy · {queue} đang đợi · {nodes} nút', + 'orchestration.kilo.engine.chat': 'Công cụ chat', + 'orchestration.kilo.engine.workflow': 'Công cụ workflow', + 'orchestration.kilo.engine.configured': 'Đã cấu hình công cụ thực thi', 'orchestration.kilo.bridge.copy': 'API VSCode chỉ được shim cho điều phối tác vụ; tệp, terminal và mở ngoài vẫn bị chặn.', 'orchestration.kilo.bridge.install': 'Cài lại shim', 'orchestration.agent.trace.title': 'Live trace', @@ -1252,7 +1255,7 @@ const vi = Object.freeze({ 'orchestration.fields.target': 'Tin nhắn tác vụ', 'orchestration.fields.target.placeholder': 'vd: sửa comment review PR hiện tại, thêm test regression và tránh module không liên quan', 'orchestration.fields.target.hint': 'Một tin nhắn kiểu chat là đủ: kết quả, ràng buộc và tiêu chí chấp nhận.', - 'orchestration.engine.openaiChat': 'OpenAI Chat', + 'orchestration.engine.openaiChat': 'Công cụ chat', 'orchestration.engine.workflow': 'Workflow', 'orchestration.runMode.write': 'Ghi', 'orchestration.runMode.readOnly': 'Chỉ đọc', @@ -1343,18 +1346,18 @@ const vi = Object.freeze({ 'orchestration.detail.context.run': 'Run ID', 'orchestration.detail.node.meta': 'Thử {attempts} lần · tự sửa {autoFix}', 'orchestration.detail.node.step': 'Bước tác vụ', - 'orchestration.openai.status.title': 'Cấu hình chạy OpenAI Chat', - 'orchestration.openai.status.subtitle': 'Node tác vụ dùng provider OpenClaw hiện tại; kiểm tra base URL, API key và model trước khi chạy.', - 'orchestration.openai.status.provider': 'Provider', - 'orchestration.openai.status.model': 'Model', + 'orchestration.openai.status.title': 'Cấu hình chạy chat', + 'orchestration.openai.status.subtitle': 'Node tác vụ dùng công cụ thực thi đã cấu hình; kiểm tra trạng thái sẵn sàng trước khi chạy.', + 'orchestration.openai.status.provider': 'Runtime', + 'orchestration.openai.status.model': 'Hồ sơ', 'orchestration.openai.status.endpoint': 'Endpoint', 'orchestration.openai.status.apiKey': 'API Key', 'orchestration.openai.status.headers': 'Headers thêm', 'orchestration.openai.status.configured': 'Đã cấu hình', 'orchestration.openai.status.missing': 'Thiếu', 'orchestration.openai.status.notSet': 'Chưa đặt', - 'orchestration.openai.status.notLoaded': 'Làm mới để tải trạng thái sẵn sàng OpenAI Chat.', - 'orchestration.openai.status.configure': 'Cấu hình OpenAI Chat', + 'orchestration.openai.status.notLoaded': 'Làm mới để tải trạng thái sẵn sàng runtime chat.', + 'orchestration.openai.status.configure': 'Cấu hình runtime chat', 'orchestration.detail.node.output': 'AI output', 'orchestration.detail.node.endpoint': 'Endpoint: ', 'orchestration.detail.node.materializedFiles': 'File đã tạo:', diff --git a/web-ui/modules/i18n/locales/zh-tw.mjs b/web-ui/modules/i18n/locales/zh-tw.mjs index 710dd6e3..71f069da 100644 --- a/web-ui/modules/i18n/locales/zh-tw.mjs +++ b/web-ui/modules/i18n/locales/zh-tw.mjs @@ -1017,14 +1017,14 @@ const zhTw = Object.freeze({ 'orchestration.chat.input.workCaption': 'Enter 追加需求;/plan 只預覽;開始執行會規劃並執行。', 'orchestration.chat.input.sequenceHint': '現在送出會成為需求 {count}。', 'orchestration.chat.assistant.contextLabel': '上一輪上下文', - 'orchestration.chat.assistant.contextFallback': '已選中一個歷史任務,繼續時會繼承它的執行緒和工作區。', + 'orchestration.chat.assistant.contextFallback': '已選擇歷史任務;繼續時會復用該任務上下文。', 'orchestration.chat.assistant.resultLabel': '實作結果', 'orchestration.chat.assistant.filesSummary': '檔案操作:{files}', - 'orchestration.chat.assistant.readyLabel': 'Codexmate', - 'orchestration.chat.assistant.empty': '輸入任務目標。多條需求會按順序執行並保留上下文。', + 'orchestration.chat.assistant.readyLabel': 'Kilo', + 'orchestration.chat.assistant.empty': '傳送第一個請求。有後續請求時繼續傳送,會沿用同一上下文。', 'orchestration.chat.assistant.planLabel': '計畫預覽', 'orchestration.chat.assistant.planSummary': '計畫已生成:{nodes} 個節點,{waves} 個批次。', - 'orchestration.chat.assistant.sequenceReady': '已收到多條需求;執行時會先完成需求 1,再帶著上下文繼續後續需求。', + 'orchestration.chat.assistant.sequenceReady': '已收到多個請求。會先完成請求 1,再帶著剩餘上下文繼續。', 'orchestration.chat.assistant.singleReady': '已收到需求。可繼續追加,或輸入 /plan 預覽計畫。', 'orchestration.chat.user.step': '需求 {count}', 'orchestration.chat.meta.thread': '執行緒 {value}', @@ -1052,10 +1052,10 @@ const zhTw = Object.freeze({ 'orchestration.kilo.aria': 'Kilo 相容任務宿主', 'orchestration.kilo.kicker': 'Kilo webview 宿主', 'orchestration.kilo.header.title': 'Kilo 相容任務宿主', - 'orchestration.kilo.header.subtitle': 'Codexmate 提供 VSCode 風格 webview 外殼,並接入隱私安全的任務橋。', + 'orchestration.kilo.header.subtitle': 'VS Code 風格 webview 外殼,已接入隱私安全的任務橋。', 'orchestration.kilo.shell.aria': 'Kilo 相容 webview 外殼', 'orchestration.kilo.surface.title': 'Webview surface', - 'orchestration.kilo.surface.host': 'Codexmate 宿主適配器', + 'orchestration.kilo.surface.host': 'Webview 宿主適配器', 'orchestration.kilo.timeline.aria': '任務活動時間線', 'orchestration.kilo.timeline.draft': '草稿', 'orchestration.kilo.timeline.plan': '計畫', @@ -1068,6 +1068,9 @@ const zhTw = Object.freeze({ 'orchestration.kilo.protocol.browserState': '瀏覽器狀態', 'orchestration.kilo.protocol.blocked': '已攔截', 'orchestration.kilo.trace.counts': '{runs} 個執行 · {queue} 個排隊 · {nodes} 個節點', + 'orchestration.kilo.engine.chat': '聊天引擎', + 'orchestration.kilo.engine.workflow': '工作流程引擎', + 'orchestration.kilo.engine.configured': '執行引擎已設定', 'orchestration.kilo.bridge.copy': 'VSCode API 只為任務編排做 shim;檔案、終端機和外部開啟能力保持攔截。', 'orchestration.kilo.bridge.install': '重裝 shim', 'orchestration.agent.trace.title': 'Live trace', @@ -1150,7 +1153,7 @@ const zhTw = Object.freeze({ 'orchestration.fields.target': '任務訊息', 'orchestration.fields.target.placeholder': '例如:修復目前 PR review 評論,補對應迴歸測試,並避免改無關模塊', 'orchestration.fields.target.hint': '像發訊息一樣寫清結果、邊界和驗收標準就夠了。', - 'orchestration.engine.openaiChat': 'OpenAI Chat', + 'orchestration.engine.openaiChat': '聊天引擎', 'orchestration.engine.workflow': 'Workflow', 'orchestration.runMode.write': '寫入', 'orchestration.runMode.readOnly': '只讀', @@ -1241,18 +1244,18 @@ const zhTw = Object.freeze({ 'orchestration.detail.context.run': 'Run ID', 'orchestration.detail.node.meta': '嘗試 {attempts} 次 · 自動修復 {autoFix} 次', 'orchestration.detail.node.step': '任務步驟', - 'orchestration.openai.status.title': 'OpenAI Chat 運行設定', - 'orchestration.openai.status.subtitle': '任務節點使用目前 OpenClaw provider;執行前會檢查 base URL、API key 和模型。', - 'orchestration.openai.status.provider': 'Provider', - 'orchestration.openai.status.model': '模型', + 'orchestration.openai.status.title': '聊天執行設定', + 'orchestration.openai.status.subtitle': '任務節點使用已設定的執行引擎;執行前會檢查就緒狀態。', + 'orchestration.openai.status.provider': '執行環境', + 'orchestration.openai.status.model': '設定檔', 'orchestration.openai.status.endpoint': 'Endpoint', 'orchestration.openai.status.apiKey': 'API Key', 'orchestration.openai.status.headers': '額外 headers', 'orchestration.openai.status.configured': '已設定', 'orchestration.openai.status.missing': '缺失', 'orchestration.openai.status.notSet': '未設定', - 'orchestration.openai.status.notLoaded': '重新整理後載入 OpenAI Chat 就緒狀態。', - 'orchestration.openai.status.configure': '設定 OpenAI Chat', + 'orchestration.openai.status.notLoaded': '重新整理後載入聊天執行設定就緒狀態。', + 'orchestration.openai.status.configure': '設定聊天執行環境', 'orchestration.detail.node.output': 'AI 輸出', 'orchestration.detail.node.endpoint': 'Endpoint:', 'orchestration.detail.node.materializedFiles': '物化檔案:', diff --git a/web-ui/modules/i18n/locales/zh.mjs b/web-ui/modules/i18n/locales/zh.mjs index 537dde1d..83d5c779 100644 --- a/web-ui/modules/i18n/locales/zh.mjs +++ b/web-ui/modules/i18n/locales/zh.mjs @@ -1017,14 +1017,14 @@ const zh = Object.freeze({ 'orchestration.chat.input.workCaption': 'Enter 追加需求;/plan 只预览;开始执行会规划并运行。', 'orchestration.chat.input.sequenceHint': '现在发送会成为需求 {count}。', 'orchestration.chat.assistant.contextLabel': '上一轮上下文', - 'orchestration.chat.assistant.contextFallback': '已选中一个历史任务,继续时会继承它的线程和工作区。', + 'orchestration.chat.assistant.contextFallback': '已选择历史任务;继续时会复用该任务上下文。', 'orchestration.chat.assistant.resultLabel': '实现结果', 'orchestration.chat.assistant.filesSummary': '文件操作:{files}', - 'orchestration.chat.assistant.readyLabel': 'Codexmate', - 'orchestration.chat.assistant.empty': '输入任务目标。多条需求会按顺序执行并保留上下文。', + 'orchestration.chat.assistant.readyLabel': 'Kilo', + 'orchestration.chat.assistant.empty': '发送第一个请求。还有后续请求时继续发送,会沿用同一上下文。', 'orchestration.chat.assistant.planLabel': '计划预览', 'orchestration.chat.assistant.planSummary': '计划已生成:{nodes} 个节点,{waves} 个批次。', - 'orchestration.chat.assistant.sequenceReady': '已收到多条需求;执行时会先完成需求 1,再带着上下文继续后续需求。', + 'orchestration.chat.assistant.sequenceReady': '已收到多个请求。会先完成请求 1,再带着剩余上下文继续。', 'orchestration.chat.assistant.singleReady': '已收到需求。可继续追加,或输入 /plan 预览计划。', 'orchestration.chat.user.step': '需求 {count}', 'orchestration.chat.meta.thread': '线程 {value}', @@ -1052,10 +1052,10 @@ const zh = Object.freeze({ 'orchestration.kilo.aria': 'Kilo 兼容任务宿主', 'orchestration.kilo.kicker': 'Kilo webview 宿主', 'orchestration.kilo.header.title': 'Kilo 兼容任务宿主', - 'orchestration.kilo.header.subtitle': 'Codexmate 提供 VSCode 风格 webview 外壳,并接入隐私安全的任务桥。', + 'orchestration.kilo.header.subtitle': 'VS Code 风格 webview 外壳,已接入隐私安全的任务桥。', 'orchestration.kilo.shell.aria': 'Kilo 兼容 webview 外壳', 'orchestration.kilo.surface.title': 'Webview surface', - 'orchestration.kilo.surface.host': 'Codexmate 宿主适配器', + 'orchestration.kilo.surface.host': 'Webview 宿主适配器', 'orchestration.kilo.timeline.aria': '任务活动时间线', 'orchestration.kilo.timeline.draft': '草稿', 'orchestration.kilo.timeline.plan': '计划', @@ -1068,6 +1068,9 @@ const zh = Object.freeze({ 'orchestration.kilo.protocol.browserState': '浏览器状态', 'orchestration.kilo.protocol.blocked': '已拦截', 'orchestration.kilo.trace.counts': '{runs} 个运行 · {queue} 个排队 · {nodes} 个节点', + 'orchestration.kilo.engine.chat': '聊天引擎', + 'orchestration.kilo.engine.workflow': '工作流引擎', + 'orchestration.kilo.engine.configured': '执行引擎已配置', 'orchestration.kilo.bridge.copy': 'VSCode API 只为任务编排做 shim;文件、终端和外部打开能力保持拦截。', 'orchestration.kilo.bridge.install': '重装 shim', 'orchestration.agent.trace.title': 'Live trace', @@ -1150,7 +1153,7 @@ const zh = Object.freeze({ 'orchestration.fields.target': '任务消息', 'orchestration.fields.target.placeholder': '例如:修复当前 PR review 评论,补对应回归测试,并避免改无关模块', 'orchestration.fields.target.hint': '像发消息一样写清结果、边界和验收标准就够了。', - 'orchestration.engine.openaiChat': 'OpenAI Chat', + 'orchestration.engine.openaiChat': '聊天引擎', 'orchestration.engine.workflow': 'Workflow', 'orchestration.runMode.write': '写入', 'orchestration.runMode.readOnly': '只读', @@ -1241,18 +1244,18 @@ const zh = Object.freeze({ 'orchestration.detail.context.run': 'Run ID', 'orchestration.detail.node.meta': '尝试 {attempts} 次 · 自动修复 {autoFix} 次', 'orchestration.detail.node.step': '任务步骤', - 'orchestration.openai.status.title': 'OpenAI Chat 运行配置', - 'orchestration.openai.status.subtitle': '任务节点使用当前 OpenClaw provider;执行前会检查 base URL、API key 和模型。', - 'orchestration.openai.status.provider': 'Provider', - 'orchestration.openai.status.model': '模型', + 'orchestration.openai.status.title': '聊天运行配置', + 'orchestration.openai.status.subtitle': '任务节点使用已配置的执行引擎;执行前会检查就绪状态。', + 'orchestration.openai.status.provider': '运行时', + 'orchestration.openai.status.model': '配置档', 'orchestration.openai.status.endpoint': 'Endpoint', 'orchestration.openai.status.apiKey': 'API Key', 'orchestration.openai.status.headers': '额外 headers', 'orchestration.openai.status.configured': '已配置', 'orchestration.openai.status.missing': '缺失', 'orchestration.openai.status.notSet': '未设置', - 'orchestration.openai.status.notLoaded': '刷新后加载 OpenAI Chat 就绪状态。', - 'orchestration.openai.status.configure': '配置 OpenAI Chat', + 'orchestration.openai.status.notLoaded': '刷新后加载聊天运行配置就绪状态。', + 'orchestration.openai.status.configure': '配置聊天运行时', 'orchestration.detail.node.output': 'AI 输出', 'orchestration.detail.node.endpoint': 'Endpoint:', 'orchestration.detail.node.materializedFiles': '物化文件:', diff --git a/web-ui/modules/task-kilo-webview-host.mjs b/web-ui/modules/task-kilo-webview-host.mjs index c5393afe..a48b45b9 100644 --- a/web-ui/modules/task-kilo-webview-host.mjs +++ b/web-ui/modules/task-kilo-webview-host.mjs @@ -207,7 +207,7 @@ export function createTaskKiloWebviewHostSnapshot(input = {}) { : (selectedRunStatus || translateTaskKiloText(t, 'orchestration.agent.state.ready', 'Ready'))); return { title: translateTaskKiloText(t, 'orchestration.kilo.header.title', 'Kilo-compatible task host'), - subtitle: translateTaskKiloText(t, 'orchestration.kilo.header.subtitle', 'VSCode webview API is shimmed into Codexmate task orchestration.'), + subtitle: translateTaskKiloText(t, 'orchestration.kilo.header.subtitle', 'VS Code-style webview shell with a privacy-safe task bridge.'), status, statusTone: normalizeStatusTone(taskOrchestration.running ? 'running' : (taskOrchestration.planning ? 'planning' : selectedRunStatus)), flags: { @@ -234,8 +234,8 @@ export function createTaskKiloWebviewHostSnapshot(input = {}) { key: 'mode', label: translateTaskKiloText(t, 'orchestration.agent.surface.mode', 'Mode'), value: taskOrchestration.selectedEngine === 'workflow' - ? translateTaskKiloText(t, 'orchestration.engine.workflow', 'Workflow') - : translateTaskKiloText(t, 'orchestration.engine.openaiChat', 'OpenAI Chat') + ? translateTaskKiloText(t, 'orchestration.kilo.engine.workflow', 'Workflow engine') + : translateTaskKiloText(t, 'orchestration.kilo.engine.chat', 'Chat engine') }, { key: 'trace', diff --git a/web-ui/partials/index/panel-orchestration.html b/web-ui/partials/index/panel-orchestration.html index 9b243493..24e5e6b8 100644 --- a/web-ui/partials/index/panel-orchestration.html +++ b/web-ui/partials/index/panel-orchestration.html @@ -1,256 +1,175 @@
-
- - - - -
-
-
-
{{ taskKiloWebviewHostSnapshot.status }}
-

{{ t('orchestration.kilo.surface.title') }}

-

{{ taskKiloWebviewHostSnapshot.subtitle }}

+ {{ taskKiloWebviewHostSnapshot.subtitle }}
-
+
{{ taskKiloWebviewHostSnapshot.status }} - - -
-
- -
-
-
K
-

{{ taskKiloWebviewHostSnapshot.title }}

-

{{ t('orchestration.kilo.bridge.copy') }}

-
-
- {{ item.label }} - {{ item.value }} -
-
-
- -
-
{{ message.role === 'user' ? 'U' : 'K' }}
-
-
{{ message.label }}
-
{{ message.text }}
-
{{ message.meta }}
-
-
- -
-
K
-
-
AI · {{ t('orchestration.plan.title') }}
-
{{ t('orchestration.privacy.detailsHidden') }}
-
- /plan - {{ t('orchestration.chat.assistant.planSummary', { nodes: taskOrchestration.plan.nodes.length, waves: taskOrchestration.plan.waves.length }) }} -
-
-
- {{ t('orchestration.privacy.detailsHidden') }} -
-
-
-
- {{ t('orchestration.privacy.detailsHidden') }} -
-
-
-
- {{ t('orchestration.detail.node.meta', { attempts: 0, autoFix: 0 }) }} · {{ node.kind }} - {{ t('orchestration.privacy.summaryHidden') }} - {{ node.write ? t('orchestration.plan.node.write') : t('orchestration.plan.node.readOnly') }} -
-
-
-
-
- -
- -
- {{ taskOrchestrationDraftMetrics.hasTarget ? t('orchestration.chat.input.sequenceHint', { count: taskOrchestrationDraftMetrics.followUpCount + 2 }) : t('orchestration.chat.input.workHint') }} - -
-
-
- -
-
- {{ t('orchestration.advanced.title') }} -
-
- - +
+ +
+
+
+
+
+
+ +

{{ t('orchestration.kilo.surface.title') }}

+

{{ t('orchestration.kilo.bridge.copy') }}

+
+ + +
+
+ + {{ item.label }} + {{ item.value }} + +
+
+ +
+
+
{{ message.label }}
+
{{ message.text }}
+
{{ message.meta }}
+
+
+ +
+
+
AI · {{ t('orchestration.plan.title') }}
+
{{ t('orchestration.privacy.detailsHidden') }}
+
+ /plan + {{ t('orchestration.chat.assistant.planSummary', { nodes: taskOrchestration.plan.nodes.length, waves: taskOrchestration.plan.waves.length }) }} +
+
+ {{ t('orchestration.privacy.detailsHidden') }} +
+
+
+
+ {{ node.kind }} + {{ t('orchestration.privacy.summaryHidden') }} +
+ {{ node.write ? t('orchestration.plan.node.write') : t('orchestration.plan.node.readOnly') }} +
+
+
+
+
+
+
+
+
+ +
+
+ {{ (taskOrchestrationWorkspacePath || taskOrchestration.workspacePath.trim()) ? t('orchestration.privacy.workspace.selected') : t('orchestration.chat.context.workspace.auto') }} + {{ (taskOrchestration.threadId.trim() || (taskOrchestrationSelectedRun && taskOrchestrationSelectedRun.threadId)) ? t('orchestration.privacy.thread.selected') : t('orchestration.chat.context.thread.auto') }} + {{ t('orchestration.kilo.engine.configured') }} + {{ taskOrchestration.runMode === 'dry-run' ? t('orchestration.runMode.dryRun') : (taskOrchestration.runMode === 'read' ? t('orchestration.runMode.readOnly') : t('orchestration.runMode.write')) }} +
+
+ +
+ {{ taskOrchestrationDraftMetrics.hasTarget ? t('orchestration.chat.input.sequenceHint', { count: taskOrchestrationDraftMetrics.followUpCount + 2 }) : t('orchestration.chat.input.workHint') }} + + +
+
+
+
+
diff --git a/web-ui/res/web-ui-render.precompiled.js b/web-ui/res/web-ui-render.precompiled.js index bf36a0ba..55b6b874 100644 --- a/web-ui/res/web-ui-render.precompiled.js +++ b/web-ui/res/web-ui-render.precompiled.js @@ -3995,397 +3995,308 @@ return function render(_ctx, _cache) { (_ctx.taskOrchestrationTabEnabled) ? _withDirectives((_openBlock(), _createElementBlock("div", { key: 0, - class: "mode-content task-kilo-mode", + class: "mode-content kilo-webview-mode", id: "panel-orchestration", "data-active": _ctx.mainTab === 'orchestration' ? 'true' : 'false', role: "tabpanel", "aria-labelledby": "tab-orchestration" }, [ - _createElementVNode("section", { - class: "task-kilo-app-shell", - "aria-label": _ctx.t('orchestration.kilo.aria') + _createElementVNode("div", { + class: "container kilo-webview-container", + "data-kilo-webview": "orchestration" }, [ - _createElementVNode("aside", { - class: "task-kilo-activity-bar", - "aria-hidden": "true" - }, [ - _createElementVNode("div", { class: "task-kilo-logo-mark" }, "K"), - _createElementVNode("span", { class: "task-kilo-activity-item active" }, "⌘"), - _createElementVNode("span", { class: "task-kilo-activity-item" }, "▣"), - _createElementVNode("span", { class: "task-kilo-activity-item" }, "◇"), - _createElementVNode("span", { class: "task-kilo-activity-spacer" }), - _createElementVNode("span", { class: "task-kilo-activity-item" }, "⚙") - ]), - _createElementVNode("aside", { - class: "task-kilo-sidebar", - "aria-label": _ctx.t('orchestration.workspace.aria') - }, [ - _createElementVNode("div", { class: "task-kilo-sidebar-head" }, [ - _createElementVNode("div", null, [ - _createElementVNode("div", { class: "task-hero-kicker" }, _toDisplayString(_ctx.t('orchestration.kilo.kicker')), 1 /* TEXT */), - _createElementVNode("div", { class: "task-kilo-sidebar-title" }, _toDisplayString(_ctx.t('orchestration.workspace.title')), 1 /* TEXT */) - ]), - _createElementVNode("button", { - type: "button", - class: "btn-mini", - onClick: $event => (_ctx.loadTaskOrchestrationOverview({ forceRefresh: true, includeDetail: true })), - disabled: _ctx.taskOrchestration.loading - }, _toDisplayString(_ctx.taskOrchestration.loading ? _ctx.t('common.refreshing') : _ctx.t('common.refresh')), 9 /* TEXT, PROPS */, ["onClick", "disabled"]) - ]), - _createElementVNode("button", { - type: "button", - class: "btn-tool btn-primary task-kilo-new-task", - onClick: $event => (_ctx.startNewTaskWorkspaceSession(_ctx.taskOrchestrationWorkspacePath)), - title: _ctx.taskOrchestrationWorkspacePath ? _ctx.t('orchestration.workspace.pathHidden') : _ctx.t('orchestration.workspace.choice.autoMeta') - }, _toDisplayString(_ctx.t('orchestration.workspace.action.primary')), 9 /* TEXT, PROPS */, ["onClick", "title"]), - _createElementVNode("div", { class: "task-kilo-sidebar-section" }, [ - _createElementVNode("div", { class: "task-kilo-section-label" }, _toDisplayString(_ctx.t('orchestration.workspace.selector')), 1 /* TEXT */), - _createElementVNode("div", { class: "task-kilo-workspace-list" }, [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskOrchestrationWorkspaceItems, (workspace) => { - return (_openBlock(), _createElementBlock("button", { - key: workspace.key, - type: "button", - class: _normalizeClass(['task-kilo-workspace-item', { active: workspace.active }]), - "aria-pressed": workspace.active ? 'true' : 'false', - title: workspace.path ? _ctx.t('orchestration.workspace.pathHidden') : workspace.meta, - onClick: $event => (_ctx.selectTaskWorkspace(workspace.path)) - }, [ - _createElementVNode("span", { class: "task-kilo-workspace-title" }, _toDisplayString(workspace.label), 1 /* TEXT */), - _createElementVNode("span", { class: "task-kilo-workspace-meta" }, _toDisplayString(workspace.path ? _ctx.t('orchestration.workspace.pathHidden') : workspace.meta), 1 /* TEXT */), - _createElementVNode("span", { class: "task-kilo-workspace-counts" }, _toDisplayString(_ctx.t('orchestration.workspace.counts', { runs: workspace.runCount, queue: workspace.queueCount })), 1 /* TEXT */) - ], 10 /* CLASS, PROPS */, ["aria-pressed", "title", "onClick"])) - }), 128 /* KEYED_FRAGMENT */)) - ]) - ]), - _createElementVNode("div", { class: "task-kilo-sidebar-section task-kilo-session-section" }, [ - _createElementVNode("div", { class: "task-kilo-session-head" }, [ - _createElementVNode("span", { class: "task-kilo-section-label" }, _toDisplayString(_ctx.t('orchestration.workspace.sessions.title')), 1 /* TEXT */), - _createElementVNode("span", { class: "pill neutral" }, _toDisplayString(_ctx.taskOrchestrationWorkspaceSessions.length), 1 /* TEXT */) - ]), - (!_ctx.taskOrchestrationWorkspaceSessions.length) - ? (_openBlock(), _createElementBlock("div", { - key: 0, - class: "task-kilo-empty-copy" - }, _toDisplayString(_ctx.t('orchestration.workspace.sessions.empty.subtitle')), 1 /* TEXT */)) - : _createCommentVNode("v-if", true), - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskOrchestrationWorkspaceSessions.slice(0, 10), (session) => { - return (_openBlock(), _createElementBlock("button", { - key: session.id, - type: "button", - class: "task-kilo-session-item", - onClick: $event => (_ctx.continueTaskWorkspaceSession(session)) + _createElementVNode("div", { class: "chat-view kilo-chat-view" }, [ + _createElementVNode("div", { + "data-component": "task-header", + class: "kilo-task-header" + }, [ + _createElementVNode("div", { "data-slot": "task-header-title" }, [ + _createElementVNode("span", { + "data-slot": "task-header-title-trigger", + role: "button", + tabindex: "0" }, [ - _createElementVNode("span", null, [ - _createElementVNode("strong", null, _toDisplayString(session.type === 'queue' ? _ctx.t('orchestration.workspace.sessions.queueTitle') : _ctx.t('orchestration.workspace.sessions.runTitle')), 1 /* TEXT */), - _createElementVNode("small", null, _toDisplayString(_ctx.t('orchestration.workspace.sessions.detailsHidden')), 1 /* TEXT */) - ]), - _createElementVNode("span", { - class: _normalizeClass(['pill', _ctx.taskRunStatusTone(session.status)]) - }, _toDisplayString(session.status), 3 /* TEXT, CLASS */) - ], 8 /* PROPS */, ["onClick"])) - }), 128 /* KEYED_FRAGMENT */)) - ]) - ], 8 /* PROPS */, ["aria-label"]), - _createElementVNode("main", { class: "task-kilo-chat-view" }, [ - _createElementVNode("header", { class: "task-kilo-task-header" }, [ - _createElementVNode("div", null, [ - _createElementVNode("div", { class: "task-hero-kicker" }, _toDisplayString(_ctx.taskKiloWebviewHostSnapshot.status), 1 /* TEXT */), - _createElementVNode("h2", null, _toDisplayString(_ctx.t('orchestration.kilo.surface.title')), 1 /* TEXT */), - _createElementVNode("p", null, _toDisplayString(_ctx.taskKiloWebviewHostSnapshot.subtitle), 1 /* TEXT */) + _createElementVNode("span", { "data-slot": "task-header-title-label" }, _toDisplayString(_ctx.taskKiloWebviewHostSnapshot.title), 1 /* TEXT */) + ]), + _createElementVNode("span", { "data-slot": "task-header-subtitle" }, _toDisplayString(_ctx.taskKiloWebviewHostSnapshot.subtitle), 1 /* TEXT */) ]), - _createElementVNode("div", { class: "task-kilo-header-actions" }, [ + _createElementVNode("div", { "data-slot": "task-header-stats" }, [ _createElementVNode("span", { class: _normalizeClass(['pill', _ctx.taskKiloWebviewHostSnapshot.statusTone]) }, _toDisplayString(_ctx.taskKiloWebviewHostSnapshot.status), 3 /* TEXT, CLASS */), + _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.kilo.trace.counts', { runs: _ctx.taskOrchestrationWorkspaceRuns.length, queue: _ctx.taskOrchestrationWorkspaceQueue.length, nodes: _ctx.taskOrchestrationSelectedRunNodes.length || (_ctx.taskOrchestration.plan ? _ctx.taskOrchestration.plan.nodes.length : 0) })), 1 /* TEXT */), _createElementVNode("button", { type: "button", - class: "btn-tool btn-tool-compact", - onClick: $event => (_ctx.installTaskKiloWebviewShim()) - }, _toDisplayString(_ctx.t('orchestration.kilo.bridge.install')), 9 /* TEXT, PROPS */, ["onClick"]), + class: "kilo-icon-button", + onClick: $event => (_ctx.loadTaskOrchestrationOverview({ forceRefresh: true, includeDetail: true })), + disabled: _ctx.taskOrchestration.loading, + "aria-label": _ctx.taskOrchestration.loading ? _ctx.t('common.refreshing') : _ctx.t('common.refresh') + }, " ↻ ", 8 /* PROPS */, ["onClick", "disabled", "aria-label"]), _createElementVNode("button", { type: "button", - class: "btn-tool btn-tool-compact", - onClick: $event => (_ctx.resetTaskOrchestrationDraft()), - disabled: _ctx.taskOrchestration.running || _ctx.taskOrchestration.queueAdding || _ctx.taskOrchestration.planning - }, _toDisplayString(_ctx.t('orchestration.draft.reset')), 9 /* TEXT, PROPS */, ["onClick", "disabled"]) + class: "kilo-icon-button", + onClick: $event => (_ctx.installTaskKiloWebviewShim()), + "aria-label": _ctx.t('orchestration.kilo.bridge.install') + }, " ⌘ ", 8 /* PROPS */, ["onClick", "aria-label"]), + _createElementVNode("button", { + type: "button", + class: "kilo-icon-button", + onClick: $event => (_ctx.taskOrchestration.settingsOpen = !_ctx.taskOrchestration.settingsOpen), + "aria-label": _ctx.t('orchestration.advanced.open') + }, " ⚙ ", 8 /* PROPS */, ["onClick", "aria-label"]) + ]), + _createElementVNode("div", { "data-slot": "task-header-progress" }, [ + _createElementVNode("div", { + class: "kilo-task-timeline", + "aria-label": _ctx.t('orchestration.kilo.timeline.aria') + }, [ + (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskKiloWebviewHostSnapshot.timelineBars, (bar) => { + return (_openBlock(), _createElementBlock("span", { + key: bar.key, + class: _normalizeClass(['kilo-task-timeline-bar', bar.tone]), + title: bar.label + }, null, 10 /* CLASS, PROPS */, ["title"])) + }), 128 /* KEYED_FRAGMENT */)) + ], 8 /* PROPS */, ["aria-label"]) ]) ]), - _createElementVNode("section", { - class: "task-kilo-message-list", - role: "log", - "aria-label": _ctx.t('orchestration.chat.thread.aria') - }, [ - (!_ctx.taskOrchestrationConversationMessages.length && !(_ctx.taskOrchestration.plan || _ctx.taskOrchestration.planIssues.length || _ctx.taskOrchestration.planWarnings.length || _ctx.taskOrchestration.lastError)) - ? (_openBlock(), _createElementBlock("div", { - key: 0, - class: "task-kilo-welcome-card" - }, [ - _createElementVNode("div", { class: "task-kilo-welcome-mark" }, "K"), - _createElementVNode("h3", null, _toDisplayString(_ctx.taskKiloWebviewHostSnapshot.title), 1 /* TEXT */), - _createElementVNode("p", null, _toDisplayString(_ctx.t('orchestration.kilo.bridge.copy')), 1 /* TEXT */), - _createElementVNode("div", { class: "task-kilo-context-grid" }, [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskKiloWebviewHostSnapshot.context, (item) => { - return (_openBlock(), _createElementBlock("div", { - key: item.key, - class: "task-kilo-context-item" - }, [ - _createElementVNode("span", null, _toDisplayString(item.label), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(item.value), 1 /* TEXT */) - ])) - }), 128 /* KEYED_FRAGMENT */)) - ]) - ])) - : _createCommentVNode("v-if", true), - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskOrchestrationConversationMessages, (message) => { - return (_openBlock(), _createElementBlock("div", { - key: message.id, - class: _normalizeClass(['task-kilo-turn', message.role === 'user' ? 'is-user' : 'is-assistant']) + (_ctx.taskOrchestration.settingsOpen) + ? (_openBlock(), _createElementBlock("div", { + key: 0, + class: "kilo-settings-popover", + role: "region", + "aria-label": _ctx.t('orchestration.advanced.title') }, [ - _createElementVNode("div", { class: "task-kilo-avatar" }, _toDisplayString(message.role === 'user' ? 'U' : 'K'), 1 /* TEXT */), - _createElementVNode("div", { class: "task-kilo-message-card" }, [ - _createElementVNode("div", { class: "task-kilo-message-label" }, _toDisplayString(message.label), 1 /* TEXT */), - _createElementVNode("div", { class: "task-kilo-message-text" }, _toDisplayString(message.text), 1 /* TEXT */), - (message.meta) - ? (_openBlock(), _createElementBlock("div", { - key: 0, - class: "task-kilo-message-meta" - }, _toDisplayString(message.meta), 1 /* TEXT */)) - : _createCommentVNode("v-if", true) - ]) - ], 2 /* CLASS */)) - }), 128 /* KEYED_FRAGMENT */)), - (_ctx.taskOrchestration.plan || _ctx.taskOrchestration.planIssues.length || _ctx.taskOrchestration.planWarnings.length || _ctx.taskOrchestration.lastError) - ? (_openBlock(), _createElementBlock("div", { - key: 1, - class: "task-kilo-turn is-assistant task-kilo-plan-turn" + _createElementVNode("div", { class: "kilo-settings-row" }, [ + _createElementVNode("label", null, [ + _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.advanced.workspacePath.label')), 1 /* TEXT */), + _withDirectives(_createElementVNode("input", { + "onUpdate:modelValue": $event => ((_ctx.taskOrchestration.workspacePath) = $event), + class: "kilo-input task-sensitive-input", + type: "text", + autocomplete: "off", + placeholder: _ctx.t('orchestration.advanced.workspacePath.placeholder') + }, null, 8 /* PROPS */, ["onUpdate:modelValue", "placeholder"]), [ + [_vModelText, _ctx.taskOrchestration.workspacePath] + ]) + ]), + _createElementVNode("label", null, [ + _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.advanced.threadId.label')), 1 /* TEXT */), + _withDirectives(_createElementVNode("input", { + "onUpdate:modelValue": $event => ((_ctx.taskOrchestration.threadId) = $event), + class: "kilo-input task-sensitive-input", + type: "text", + autocomplete: "off", + placeholder: _ctx.t('orchestration.advanced.threadId.placeholder') + }, null, 8 /* PROPS */, ["onUpdate:modelValue", "placeholder"]), [ + [_vModelText, _ctx.taskOrchestration.threadId] + ]) + ]), + _createElementVNode("label", null, [ + _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.advanced.engine.label')), 1 /* TEXT */), + _withDirectives(_createElementVNode("select", { + "onUpdate:modelValue": $event => ((_ctx.taskOrchestration.selectedEngine) = $event), + class: "kilo-input", + onChange: $event => (_ctx.taskOrchestration.selectedEngine === 'workflow' ? null : _ctx.taskOrchestration.workflowIdsText = '') + }, [ + _createElementVNode("option", { value: "openai-chat" }, _toDisplayString(_ctx.t('orchestration.kilo.engine.chat')), 1 /* TEXT */), + _createElementVNode("option", { value: "workflow" }, _toDisplayString(_ctx.t('orchestration.kilo.engine.workflow')), 1 /* TEXT */) + ], 40 /* PROPS, NEED_HYDRATION */, ["onUpdate:modelValue", "onChange"]), [ + [_vModelSelect, _ctx.taskOrchestration.selectedEngine] + ]) + ]), + _createElementVNode("label", null, [ + _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.advanced.runMode.label')), 1 /* TEXT */), + _withDirectives(_createElementVNode("select", { + "onUpdate:modelValue": $event => ((_ctx.taskOrchestration.runMode) = $event), + class: "kilo-input" + }, [ + _createElementVNode("option", { value: "write" }, _toDisplayString(_ctx.t('orchestration.runMode.write')), 1 /* TEXT */), + _createElementVNode("option", { value: "read" }, _toDisplayString(_ctx.t('orchestration.runMode.readOnly')), 1 /* TEXT */), + _createElementVNode("option", { value: "dry-run" }, _toDisplayString(_ctx.t('orchestration.runMode.dryRun')), 1 /* TEXT */) + ], 8 /* PROPS */, ["onUpdate:modelValue"]), [ + [_vModelSelect, _ctx.taskOrchestration.runMode] + ]) + ]) + ]), + (_ctx.taskOrchestration.selectedEngine === 'workflow') + ? (_openBlock(), _createElementBlock("div", { + key: 0, + class: "kilo-command-suggestions" + }, [ + (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskOrchestration.workflows.slice(0, 6), (workflow) => { + return (_openBlock(), _createElementBlock("button", { + key: workflow.id || workflow.name, + type: "button", + class: "kilo-secondary-button", + onClick: $event => (_ctx.appendTaskWorkflowId(workflow.id || workflow.name)) + }, _toDisplayString(workflow.name || workflow.id), 9 /* TEXT, PROPS */, ["onClick"])) + }), 128 /* KEYED_FRAGMENT */)) + ])) + : _createCommentVNode("v-if", true) + ], 8 /* PROPS */, ["aria-label"])) + : _createCommentVNode("v-if", true), + _createElementVNode("div", { class: "chat-messages-wrapper" }, [ + _createElementVNode("div", { class: "chat-messages" }, [ + _createElementVNode("div", { class: "message-list-container" }, [ + _createElementVNode("div", { + class: "message-list", + role: "log", + "aria-label": _ctx.t('orchestration.chat.thread.aria') }, [ - _createElementVNode("div", { class: "task-kilo-avatar" }, "K"), - _createElementVNode("div", { class: "task-kilo-message-card task-kilo-plan-card" }, [ - _createElementVNode("div", { class: "task-kilo-message-label" }, "AI · " + _toDisplayString(_ctx.t('orchestration.plan.title')), 1 /* TEXT */), - (_ctx.taskOrchestration.lastError) + _createElementVNode("div", { + class: _normalizeClass(['message-list-content', { 'message-list-content-empty': !_ctx.taskOrchestrationConversationMessages.length && !(_ctx.taskOrchestration.plan || _ctx.taskOrchestration.planIssues.length || _ctx.taskOrchestration.planWarnings.length || _ctx.taskOrchestration.lastError) }]) + }, [ + (!_ctx.taskOrchestrationConversationMessages.length && !(_ctx.taskOrchestration.plan || _ctx.taskOrchestration.planIssues.length || _ctx.taskOrchestration.planWarnings.length || _ctx.taskOrchestration.lastError)) ? (_openBlock(), _createElementBlock("div", { key: 0, - class: "task-issue-item" - }, _toDisplayString(_ctx.t('orchestration.privacy.detailsHidden')), 1 /* TEXT */)) - : _createCommentVNode("v-if", true), - (_ctx.taskOrchestration.plan) - ? (_openBlock(), _createElementBlock("div", { - key: 1, - class: "task-kilo-plan-summary" - }, [ - _createElementVNode("span", { class: "pill configured" }, "/plan"), - _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.chat.assistant.planSummary', { nodes: _ctx.taskOrchestration.plan.nodes.length, waves: _ctx.taskOrchestration.plan.waves.length })), 1 /* TEXT */) - ])) - : _createCommentVNode("v-if", true), - (_ctx.taskOrchestration.planIssues.length) - ? (_openBlock(), _createElementBlock("div", { - key: 2, - class: "task-issues-list" + class: "message-list-empty kilo-welcome-empty" }, [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskOrchestration.planIssues, (_issue, index) => { - return (_openBlock(), _createElementBlock("div", { - key: 'issue-' + index, - class: "task-issue-item" - }, _toDisplayString(_ctx.t('orchestration.privacy.detailsHidden')), 1 /* TEXT */)) - }), 128 /* KEYED_FRAGMENT */)) + _createElementVNode("div", { class: "kilo-logo" }, "K"), + _createElementVNode("h3", null, _toDisplayString(_ctx.t('orchestration.kilo.surface.title')), 1 /* TEXT */), + _createElementVNode("p", null, _toDisplayString(_ctx.t('orchestration.kilo.bridge.copy')), 1 /* TEXT */), + _createElementVNode("div", { class: "kilo-empty-actions" }, [ + _createElementVNode("button", { + type: "button", + class: "kilo-primary-button", + onClick: $event => (_ctx.startNewTaskWorkspaceSession(_ctx.taskOrchestrationWorkspacePath)) + }, _toDisplayString(_ctx.t('orchestration.workspace.action.primary')), 9 /* TEXT, PROPS */, ["onClick"]), + _createElementVNode("button", { + type: "button", + class: "kilo-secondary-button", + onClick: $event => (_ctx.loadTaskOrchestrationOverview({ forceRefresh: true, includeDetail: true })) + }, _toDisplayString(_ctx.t('common.refresh')), 9 /* TEXT, PROPS */, ["onClick"]) + ]), + _createElementVNode("div", { class: "kilo-context-strip" }, [ + (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskKiloWebviewHostSnapshot.context, (item) => { + return (_openBlock(), _createElementBlock("span", { + key: item.key + }, [ + _createElementVNode("small", null, _toDisplayString(item.label), 1 /* TEXT */), + _createElementVNode("strong", null, _toDisplayString(item.value), 1 /* TEXT */) + ])) + }), 128 /* KEYED_FRAGMENT */)) + ]) ])) : _createCommentVNode("v-if", true), - (_ctx.taskOrchestration.planWarnings.length) - ? (_openBlock(), _createElementBlock("div", { - key: 3, - class: "task-warning-list" + (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskOrchestrationConversationMessages, (message) => { + return (_openBlock(), _createElementBlock("div", { + key: message.id, + class: "vscode-session-turn", + "data-row": message.role === 'user' ? 'user' : 'assistant' + }, [ + _createElementVNode("div", { + class: _normalizeClass(message.role === 'user' ? 'vscode-session-turn-user' : 'vscode-session-turn-assistant') }, [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskOrchestration.planWarnings, (_warning, index) => { - return (_openBlock(), _createElementBlock("div", { - key: 'warning-' + index, - class: "task-warning-item" - }, _toDisplayString(_ctx.t('orchestration.privacy.detailsHidden')), 1 /* TEXT */)) - }), 128 /* KEYED_FRAGMENT */)) - ])) - : _createCommentVNode("v-if", true), - (_ctx.taskOrchestration.plan) + _createElementVNode("div", { class: "kilo-message-heading" }, _toDisplayString(message.label), 1 /* TEXT */), + _createElementVNode("div", { class: "kilo-message-body" }, _toDisplayString(message.text), 1 /* TEXT */), + (message.meta) + ? (_openBlock(), _createElementBlock("div", { + key: 0, + class: "kilo-message-meta" + }, _toDisplayString(message.meta), 1 /* TEXT */)) + : _createCommentVNode("v-if", true) + ], 2 /* CLASS */) + ], 8 /* PROPS */, ["data-row"])) + }), 128 /* KEYED_FRAGMENT */)), + (_ctx.taskOrchestration.plan || _ctx.taskOrchestration.planIssues.length || _ctx.taskOrchestration.planWarnings.length || _ctx.taskOrchestration.lastError) ? (_openBlock(), _createElementBlock("div", { - key: 4, - class: "task-kilo-plan-grid" + key: 1, + class: "vscode-session-turn", + "data-row": "assistant" }, [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskOrchestration.plan.nodes, (node, index) => { - return (_openBlock(), _createElementBlock("div", { - key: 'plan-node-' + index, - class: "task-kilo-plan-node" - }, [ - _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.detail.node.meta', { attempts: 0, autoFix: 0 })) + " · " + _toDisplayString(node.kind), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(_ctx.t('orchestration.privacy.summaryHidden')), 1 /* TEXT */), - _createElementVNode("small", null, _toDisplayString(node.write ? _ctx.t('orchestration.plan.node.write') : _ctx.t('orchestration.plan.node.readOnly')), 1 /* TEXT */) - ])) - }), 128 /* KEYED_FRAGMENT */)) + _createElementVNode("div", { class: "vscode-session-turn-assistant kilo-plan-response" }, [ + _createElementVNode("div", { class: "kilo-message-heading" }, "AI · " + _toDisplayString(_ctx.t('orchestration.plan.title')), 1 /* TEXT */), + (_ctx.taskOrchestration.lastError) + ? (_openBlock(), _createElementBlock("div", { + key: 0, + class: "kilo-redacted-block" + }, _toDisplayString(_ctx.t('orchestration.privacy.detailsHidden')), 1 /* TEXT */)) + : _createCommentVNode("v-if", true), + (_ctx.taskOrchestration.plan) + ? (_openBlock(), _createElementBlock("div", { + key: 1, + class: "kilo-plan-summary-line" + }, [ + _createElementVNode("span", null, "/plan"), + _createElementVNode("strong", null, _toDisplayString(_ctx.t('orchestration.chat.assistant.planSummary', { nodes: _ctx.taskOrchestration.plan.nodes.length, waves: _ctx.taskOrchestration.plan.waves.length })), 1 /* TEXT */) + ])) + : _createCommentVNode("v-if", true), + (_ctx.taskOrchestration.planIssues.length || _ctx.taskOrchestration.planWarnings.length) + ? (_openBlock(), _createElementBlock("div", { + key: 2, + class: "kilo-redacted-block" + }, _toDisplayString(_ctx.t('orchestration.privacy.detailsHidden')), 1 /* TEXT */)) + : _createCommentVNode("v-if", true), + (_ctx.taskOrchestration.plan) + ? (_openBlock(), _createElementBlock("div", { + key: 3, + class: "kilo-tool-list" + }, [ + (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskOrchestration.plan.nodes, (node, index) => { + return (_openBlock(), _createElementBlock("div", { + key: 'plan-node-' + index, + class: "kilo-tool-card" + }, [ + _createElementVNode("div", { class: "kilo-tool-title" }, [ + _createElementVNode("span", null, _toDisplayString(node.kind), 1 /* TEXT */), + _createElementVNode("strong", null, _toDisplayString(_ctx.t('orchestration.privacy.summaryHidden')), 1 /* TEXT */) + ]), + _createElementVNode("small", null, _toDisplayString(node.write ? _ctx.t('orchestration.plan.node.write') : _ctx.t('orchestration.plan.node.readOnly')), 1 /* TEXT */) + ])) + }), 128 /* KEYED_FRAGMENT */)) + ])) + : _createCommentVNode("v-if", true) + ]) ])) : _createCommentVNode("v-if", true) - ]) - ])) - : _createCommentVNode("v-if", true) - ], 8 /* PROPS */, ["aria-label"]), - _createElementVNode("footer", { class: "task-kilo-chat-input" }, [ - _createElementVNode("label", { - class: "task-kilo-composer", - "aria-label": _ctx.t('orchestration.chat.input.label') - }, [ - _createElementVNode("span", { class: "task-kilo-composer-glyph" }, "›"), - _withDirectives(_createElementVNode("textarea", { - "onUpdate:modelValue": $event => ((_ctx.taskOrchestration.chatDraft) = $event), - class: "task-textarea task-textarea-goal task-quick-target", - rows: "3", - placeholder: _ctx.t('orchestration.chat.input.placeholder'), - onKeydown: _withKeys(_withModifiers($event => (_ctx.submitTaskOrchestrationChatMessage()), ["exact","prevent"]), ["enter"]) - }, null, 40 /* PROPS, NEED_HYDRATION */, ["onUpdate:modelValue", "placeholder", "onKeydown"]), [ - [_vModelText, _ctx.taskOrchestration.chatDraft] - ]) - ], 8 /* PROPS */, ["aria-label"]), - _createElementVNode("div", { class: "task-kilo-input-actions" }, [ - _createElementVNode("span", { class: "task-field-hint" }, _toDisplayString(_ctx.taskOrchestrationDraftMetrics.hasTarget ? _ctx.t('orchestration.chat.input.sequenceHint', { count: _ctx.taskOrchestrationDraftMetrics.followUpCount + 2 }) : _ctx.t('orchestration.chat.input.workHint')), 1 /* TEXT */), - _createElementVNode("button", { - type: "button", - class: "btn-tool", - onClick: $event => (_ctx.previewTaskPlanFromChat()), - disabled: _ctx.taskOrchestration.planning || _ctx.taskOrchestration.running || (!_ctx.taskOrchestration.target.trim() && !_ctx.taskOrchestration.chatDraft.trim()) - }, _toDisplayString(_ctx.taskOrchestration.planning ? _ctx.t('orchestration.actions.planning') : _ctx.t('orchestration.actions.preview')), 9 /* TEXT, PROPS */, ["onClick", "disabled"]), - _createElementVNode("button", { - type: "button", - class: "btn-tool btn-primary", - onClick: $event => (_ctx.planAndRunTaskOrchestrationFromChat()), - disabled: _ctx.taskOrchestration.running || _ctx.taskOrchestration.planning || (!_ctx.taskOrchestration.target.trim() && !_ctx.taskOrchestration.chatDraft.trim()) - }, _toDisplayString(_ctx.taskOrchestration.running ? _ctx.t('orchestration.actions.processing') : (_ctx.taskOrchestration.planning ? _ctx.t('orchestration.actions.planning') : _ctx.t('orchestration.chat.input.work'))), 9 /* TEXT, PROPS */, ["onClick", "disabled"]) - ]) - ]) - ]), - _createElementVNode("aside", { - class: "task-kilo-inspector", - "aria-label": _ctx.t('orchestration.kilo.shell.aria') - }, [ - _createElementVNode("section", { class: "task-kilo-inspector-card" }, [ - _createElementVNode("div", { class: "task-kilo-section-label" }, _toDisplayString(_ctx.t('orchestration.agent.trace.title')), 1 /* TEXT */), - _createElementVNode("div", { class: "task-kilo-trace-grid" }, [ - _createElementVNode("div", { class: "task-kilo-trace-item" }, [ - _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.agent.trace.plan')), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(_ctx.taskOrchestration.plan ? _ctx.taskOrchestration.plan.nodes.length : 0), 1 /* TEXT */) - ]), - _createElementVNode("div", { class: "task-kilo-trace-item" }, [ - _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.agent.trace.queue')), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(_ctx.taskOrchestrationWorkspaceQueue.length), 1 /* TEXT */) - ]), - _createElementVNode("div", { class: "task-kilo-trace-item" }, [ - _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.agent.trace.runs')), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(_ctx.taskOrchestrationWorkspaceRuns.length), 1 /* TEXT */) - ]), - _createElementVNode("div", { class: "task-kilo-trace-item" }, [ - _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.agent.trace.nodes')), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(_ctx.taskOrchestrationSelectedRunNodes.length), 1 /* TEXT */) + ], 2 /* CLASS */) + ], 8 /* PROPS */, ["aria-label"]) ]) - ]), - _createElementVNode("div", { - class: "task-kilo-timeline", - "aria-label": _ctx.t('orchestration.kilo.timeline.aria') - }, [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskKiloWebviewHostSnapshot.timelineBars, (bar) => { - return (_openBlock(), _createElementBlock("span", { - key: bar.key, - class: _normalizeClass(['task-kilo-timeline-bar', bar.tone]), - title: bar.label - }, null, 10 /* CLASS, PROPS */, ["title"])) - }), 128 /* KEYED_FRAGMENT */)) - ], 8 /* PROPS */, ["aria-label"]) - ]), - _createElementVNode("section", { class: "task-kilo-inspector-card" }, [ - _createElementVNode("div", { class: "task-kilo-section-label" }, _toDisplayString(_ctx.t('orchestration.kilo.surface.host')), 1 /* TEXT */), - _createElementVNode("div", { class: "task-kilo-protocol-grid" }, [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskKiloWebviewHostSnapshot.protocolRows, (row) => { - return (_openBlock(), _createElementBlock("div", { - key: row.key, - class: "task-kilo-protocol-row" - }, [ - _createElementVNode("span", null, _toDisplayString(row.label), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(row.value), 1 /* TEXT */) - ])) - }), 128 /* KEYED_FRAGMENT */)) ]) ]), - _createElementVNode("details", { - class: "task-kilo-inspector-card task-kilo-settings-card", - open: _ctx.taskOrchestration.settingsOpen, - onToggle: $event => (_ctx.taskOrchestration.settingsOpen = $event.target.open) - }, [ - _createElementVNode("summary", null, _toDisplayString(_ctx.t('orchestration.advanced.title')), 1 /* TEXT */), - _createElementVNode("div", { class: "task-kilo-settings-grid" }, [ - _createElementVNode("label", { class: "task-quick-target-field" }, [ - _createElementVNode("span", { class: "selector-label" }, _toDisplayString(_ctx.t('orchestration.advanced.workspacePath.label')), 1 /* TEXT */), - _withDirectives(_createElementVNode("input", { - "onUpdate:modelValue": $event => ((_ctx.taskOrchestration.workspacePath) = $event), - class: "model-input task-sensitive-input", - type: "text", - autocomplete: "off", - placeholder: _ctx.t('orchestration.advanced.workspacePath.placeholder') - }, null, 8 /* PROPS */, ["onUpdate:modelValue", "placeholder"]), [ - [_vModelText, _ctx.taskOrchestration.workspacePath] - ]), - _createElementVNode("span", { class: "task-field-hint" }, _toDisplayString(_ctx.taskOrchestration.workspacePath.trim() ? _ctx.t('orchestration.workspace.pathHidden') : _ctx.t('orchestration.advanced.workspacePath.hint')), 1 /* TEXT */) - ]), - _createElementVNode("label", { class: "task-quick-target-field" }, [ - _createElementVNode("span", { class: "selector-label" }, _toDisplayString(_ctx.t('orchestration.advanced.threadId.label')), 1 /* TEXT */), - _withDirectives(_createElementVNode("input", { - "onUpdate:modelValue": $event => ((_ctx.taskOrchestration.threadId) = $event), - class: "model-input task-sensitive-input", - type: "text", - autocomplete: "off", - placeholder: _ctx.t('orchestration.advanced.threadId.placeholder') - }, null, 8 /* PROPS */, ["onUpdate:modelValue", "placeholder"]), [ - [_vModelText, _ctx.taskOrchestration.threadId] - ]), - _createElementVNode("span", { class: "task-field-hint" }, _toDisplayString(_ctx.taskOrchestration.threadId.trim() ? _ctx.t('orchestration.privacy.thread.selected') : _ctx.t('orchestration.advanced.threadId.hint')), 1 /* TEXT */) - ]), - _createElementVNode("label", { class: "task-quick-target-field" }, [ - _createElementVNode("span", { class: "selector-label" }, _toDisplayString(_ctx.t('orchestration.advanced.engine.label')), 1 /* TEXT */), - _withDirectives(_createElementVNode("select", { - "onUpdate:modelValue": $event => ((_ctx.taskOrchestration.selectedEngine) = $event), - class: "model-input", - onChange: $event => (_ctx.taskOrchestration.selectedEngine === 'workflow' ? null : _ctx.taskOrchestration.workflowIdsText = '') - }, [ - _createElementVNode("option", { value: "openai-chat" }, _toDisplayString(_ctx.t('orchestration.engine.openaiChat')), 1 /* TEXT */), - _createElementVNode("option", { value: "workflow" }, _toDisplayString(_ctx.t('orchestration.engine.workflow')), 1 /* TEXT */) - ], 40 /* PROPS, NEED_HYDRATION */, ["onUpdate:modelValue", "onChange"]), [ - [_vModelSelect, _ctx.taskOrchestration.selectedEngine] - ]) - ]), - _createElementVNode("label", { class: "task-quick-target-field" }, [ - _createElementVNode("span", { class: "selector-label" }, _toDisplayString(_ctx.t('orchestration.advanced.runMode.label')), 1 /* TEXT */), - _withDirectives(_createElementVNode("select", { - "onUpdate:modelValue": $event => ((_ctx.taskOrchestration.runMode) = $event), - class: "model-input" - }, [ - _createElementVNode("option", { value: "write" }, _toDisplayString(_ctx.t('orchestration.runMode.write')), 1 /* TEXT */), - _createElementVNode("option", { value: "read" }, _toDisplayString(_ctx.t('orchestration.runMode.readOnly')), 1 /* TEXT */), - _createElementVNode("option", { value: "dry-run" }, _toDisplayString(_ctx.t('orchestration.runMode.dryRun')), 1 /* TEXT */) - ], 8 /* PROPS */, ["onUpdate:modelValue"]), [ - [_vModelSelect, _ctx.taskOrchestration.runMode] + _createElementVNode("div", { class: "chat-input" }, [ + _createElementVNode("div", { class: "kilo-status-dock" }, [ + _createElementVNode("span", null, _toDisplayString((_ctx.taskOrchestrationWorkspacePath || _ctx.taskOrchestration.workspacePath.trim()) ? _ctx.t('orchestration.privacy.workspace.selected') : _ctx.t('orchestration.chat.context.workspace.auto')), 1 /* TEXT */), + _createElementVNode("span", null, _toDisplayString((_ctx.taskOrchestration.threadId.trim() || (_ctx.taskOrchestrationSelectedRun && _ctx.taskOrchestrationSelectedRun.threadId)) ? _ctx.t('orchestration.privacy.thread.selected') : _ctx.t('orchestration.chat.context.thread.auto')), 1 /* TEXT */), + _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.kilo.engine.configured')), 1 /* TEXT */), + _createElementVNode("span", null, _toDisplayString(_ctx.taskOrchestration.runMode === 'dry-run' ? _ctx.t('orchestration.runMode.dryRun') : (_ctx.taskOrchestration.runMode === 'read' ? _ctx.t('orchestration.runMode.readOnly') : _ctx.t('orchestration.runMode.write'))), 1 /* TEXT */) + ]), + _createElementVNode("div", { class: "prompt-input-container" }, [ + _createElementVNode("label", { + class: "prompt-input-shell", + "aria-label": _ctx.t('orchestration.chat.input.label') + }, [ + _createElementVNode("span", { class: "prompt-input-prefix" }, "›"), + _withDirectives(_createElementVNode("textarea", { + "onUpdate:modelValue": $event => ((_ctx.taskOrchestration.chatDraft) = $event), + class: "prompt-input-textarea", + rows: "3", + placeholder: _ctx.t('orchestration.chat.input.placeholder'), + onKeydown: _withKeys(_withModifiers($event => (_ctx.submitTaskOrchestrationChatMessage()), ["exact","prevent"]), ["enter"]) + }, null, 40 /* PROPS, NEED_HYDRATION */, ["onUpdate:modelValue", "placeholder", "onKeydown"]), [ + [_vModelText, _ctx.taskOrchestration.chatDraft] ]) + ], 8 /* PROPS */, ["aria-label"]), + _createElementVNode("div", { class: "prompt-input-actions" }, [ + _createElementVNode("span", null, _toDisplayString(_ctx.taskOrchestrationDraftMetrics.hasTarget ? _ctx.t('orchestration.chat.input.sequenceHint', { count: _ctx.taskOrchestrationDraftMetrics.followUpCount + 2 }) : _ctx.t('orchestration.chat.input.workHint')), 1 /* TEXT */), + _createElementVNode("button", { + type: "button", + class: "kilo-secondary-button", + onClick: $event => (_ctx.previewTaskPlanFromChat()), + disabled: _ctx.taskOrchestration.planning || _ctx.taskOrchestration.running || (!_ctx.taskOrchestration.target.trim() && !_ctx.taskOrchestration.chatDraft.trim()) + }, _toDisplayString(_ctx.taskOrchestration.planning ? _ctx.t('orchestration.actions.planning') : _ctx.t('orchestration.actions.preview')), 9 /* TEXT, PROPS */, ["onClick", "disabled"]), + _createElementVNode("button", { + type: "button", + class: "kilo-primary-button", + onClick: $event => (_ctx.planAndRunTaskOrchestrationFromChat()), + disabled: _ctx.taskOrchestration.running || _ctx.taskOrchestration.planning || (!_ctx.taskOrchestration.target.trim() && !_ctx.taskOrchestration.chatDraft.trim()) + }, _toDisplayString(_ctx.taskOrchestration.running ? _ctx.t('orchestration.actions.processing') : (_ctx.taskOrchestration.planning ? _ctx.t('orchestration.actions.planning') : _ctx.t('orchestration.chat.input.work'))), 9 /* TEXT, PROPS */, ["onClick", "disabled"]) ]) - ]), - (_ctx.taskOrchestration.selectedEngine === 'workflow') - ? (_openBlock(), _createElementBlock("div", { - key: 0, - class: "task-workflow-suggestions" - }, [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskOrchestration.workflows.slice(0, 6), (workflow) => { - return (_openBlock(), _createElementBlock("button", { - key: workflow.id || workflow.name, - type: "button", - class: "btn-tool btn-tool-compact", - onClick: $event => (_ctx.appendTaskWorkflowId(workflow.id || workflow.name)) - }, _toDisplayString(workflow.name || workflow.id), 9 /* TEXT, PROPS */, ["onClick"])) - }), 128 /* KEYED_FRAGMENT */)) - ])) - : _createCommentVNode("v-if", true) - ], 40 /* PROPS, NEED_HYDRATION */, ["open", "onToggle"]) - ], 8 /* PROPS */, ["aria-label"]) - ], 8 /* PROPS */, ["aria-label"]) + ]) + ]) + ]) + ]) ], 8 /* PROPS */, ["data-active"])), [ [_vShow, _ctx.mainTab === 'orchestration'] ]) diff --git a/web-ui/styles/task-orchestration.css b/web-ui/styles/task-orchestration.css index 3c2004b5..b11996d3 100644 --- a/web-ui/styles/task-orchestration.css +++ b/web-ui/styles/task-orchestration.css @@ -1,3734 +1,514 @@ -.task-layout-grid { - display: grid; - gap: 16px; - margin-bottom: 16px; -} - -.task-layout-grid-primary { - grid-template-columns: minmax(0, 1fr); -} - -.task-layout-grid-secondary { - grid-template-columns: minmax(0, 1fr); -} - -.task-hero-card, -.task-compose-flow-card, -.task-sidebar-card, -.task-plan-card, -.task-workbench-card, -.task-template-block, -.task-readiness-card, -.task-action-help, -.task-empty-state { - min-width: 0; - border: 1px solid var(--color-border-soft); - border-radius: 18px; - background: var(--color-surface); - box-shadow: var(--shadow-subtle); -} - -.task-hero-card { - padding: 18px; - margin-bottom: 16px; - background: linear-gradient(180deg, rgba(199, 116, 98, 0.10) 0%, rgba(199, 116, 98, 0.03) 100%), var(--color-surface); -} - -.task-hero-main { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 16px; -} - -.task-hero-kicker { - font-size: 11px; - font-weight: 700; - letter-spacing: 0.08em; - text-transform: uppercase; - color: var(--color-text-secondary); - margin-bottom: 8px; -} - -.task-hero-copy { - margin-top: 8px; - max-width: 760px; - line-height: 1.65; -} - -.task-hero-actions { - flex: 0 0 auto; - flex-wrap: nowrap; - align-self: flex-start; -} - -.task-hero-actions > .btn-tool + .btn-tool, -.task-hero-actions > .btn-tool-compact + .btn-tool, -.task-hero-actions > .btn-tool + .btn-tool-compact, -.task-hero-actions > .btn-tool-compact + .btn-tool-compact { - margin-top: 0; -} - -.task-section-header .task-header-actions > .btn-tool + .btn-tool, -.task-section-header .task-header-actions > .btn-tool-compact + .btn-tool, -.task-section-header .task-header-actions > .btn-tool + .btn-tool-compact, -.task-section-header .task-header-actions > .btn-tool-compact + .btn-tool-compact { - margin-top: 0; -} - -.task-action-row > .btn-tool + .btn-tool, -.task-action-row > .btn-tool-compact + .btn-tool, -.task-action-row > .btn-tool + .btn-tool-compact, -.task-action-row > .btn-tool-compact + .btn-tool-compact, -.task-action-row-right > .btn-tool + .btn-tool, -.task-action-row-right > .btn-tool-compact + .btn-tool, -.task-action-row-right > .btn-tool + .btn-tool-compact, -.task-action-row-right > .btn-tool-compact + .btn-tool-compact { - margin-top: 0; -} - -.task-detail-toolbar > .btn-tool + .btn-tool, -.task-detail-toolbar > .btn-tool-compact + .btn-tool, -.task-detail-toolbar > .btn-tool + .btn-tool-compact, -.task-detail-toolbar > .btn-tool-compact + .btn-tool-compact { - margin-top: 0; -} - -.task-hero-meta-strip { - display: flex; - flex-wrap: wrap; - gap: 8px; - margin-top: 14px; -} - -.task-hero-meta { - display: inline-flex; - align-items: center; - gap: 6px; - min-height: 34px; - padding: 0 12px; - border-radius: 999px; - background: rgba(255, 255, 255, 0.56); - border: 1px solid rgba(199, 116, 98, 0.14); - color: var(--color-text-secondary); - font-size: 12px; - font-weight: 600; -} - -.task-hero-meta strong { - color: var(--color-text-primary); -} - -.task-compose-flow-card, -.task-sidebar-card, -.task-plan-card, -.task-workbench-card { - padding: 18px; -} - -.task-flow-section + .task-flow-section, -.task-sidebar-group + .task-sidebar-group { - margin-top: 18px; - padding-top: 18px; - border-top: 1px solid var(--color-border-soft); -} - -.task-flow-section-compact { - margin-top: 0; -} - -.task-flow-head { - display: flex; - align-items: flex-start; - gap: 12px; - margin-bottom: 14px; -} - -.task-flow-step { - width: 28px; - height: 28px; - border-radius: 999px; - background: var(--color-brand-light); - color: var(--color-brand-dark); - font-size: 13px; - font-weight: 700; - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 auto; -} - -.task-flow-title, -.task-sidebar-title, -.task-readiness-title, -.task-empty-title, -.task-template-label { - font-size: 14px; - font-weight: 700; - color: var(--color-text-primary); -} - -.task-flow-copy, -.task-readiness-copy, -.task-field-hint, -.task-empty-copy, -.task-action-help-item { - font-size: 12px; - line-height: 1.6; - color: var(--color-text-secondary); -} - -.task-template-block { - padding: 12px 14px; - background: var(--color-surface-alt); -} - -.task-template-panel { - margin-bottom: 14px; - padding: 12px 14px; - border: 1px solid var(--color-border-soft); - border-radius: 16px; - background: var(--color-surface-alt); -} - -.task-template-chip-group, -.task-workflow-suggestions { - display: flex; - flex-wrap: wrap; - gap: 8px; - margin-top: 10px; -} - -.task-template-chip, -.task-workflow-chip, -.task-workbench-tab { - border: 1px solid var(--color-border); - background: var(--color-surface); - color: var(--color-text-primary); - border-radius: 999px; - padding: 7px 12px; - font-size: 12px; - font-weight: 600; - cursor: pointer; - transition: - background var(--transition-fast) var(--ease-smooth), - border-color var(--transition-fast) var(--ease-smooth), - color var(--transition-fast) var(--ease-smooth), - box-shadow var(--transition-fast) var(--ease-smooth); -} - -.task-template-chip:hover, -.task-workflow-chip:hover, -.task-workbench-tab:hover { - background: var(--color-brand-light); - border-color: var(--color-brand); - color: var(--color-brand-dark); -} - -.task-workflow-chip { - display: inline-flex; - align-items: center; - gap: 8px; -} - -.task-workflow-chip small { - color: var(--color-text-secondary); - font-size: 11px; -} - -.task-composer-grid { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 12px; -} - -.task-composer-grid-primary { - grid-template-columns: minmax(0, 1fr); -} - -.task-composer-grid-compact { - grid-template-columns: minmax(180px, 240px) minmax(0, 1fr); - align-items: stretch; -} - -.task-composer-grid-inline { - grid-template-columns: repeat(2, minmax(0, 1fr)); -} - -.task-field-wide { - grid-column: 1 / -1; -} - -.task-goal-field { - padding: 16px; - border-radius: 16px; - background: var(--color-surface-alt); - border: 1px solid var(--color-border-soft); -} - -.task-textarea, -.task-log-block { - width: 100%; - min-height: 92px; - resize: vertical; - padding: 10px 12px; - border-radius: 12px; - border: 1px solid var(--color-border); - background: var(--color-surface); - color: var(--color-text-primary); - font: inherit; - box-sizing: border-box; -} - -.task-sensitive-input { - -webkit-text-security: disc; - font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; -} - -.task-sensitive-input::placeholder { - -webkit-text-security: none; - font-family: inherit; -} - -.task-textarea-goal { - min-height: 132px; - font-size: 14px; - line-height: 1.65; -} - -.task-toggle-card { - padding: 14px; - border-radius: 16px; - border: 1px solid var(--color-border-soft); - background: var(--color-surface-alt); -} - -.task-toggle-stack { - display: flex; - flex-direction: column; - gap: 10px; - margin-top: 8px; -} - -.task-toggle-inline { - display: flex; - flex-wrap: wrap; - gap: 12px; - margin-top: 8px; -} - -.task-toggle-card-inline { - min-height: 100%; -} - -.task-toggle { - display: inline-flex; - align-items: center; - gap: 8px; - color: var(--color-text-secondary); - font-size: 13px; -} - -.task-config-strip { - display: flex; - flex-wrap: wrap; - gap: 10px; -} - -.task-config-strip-vertical { - flex-direction: column; -} - -.task-config-pill { - display: inline-flex; - align-items: center; - min-height: 34px; - padding: 0 12px; - border-radius: 999px; - background: var(--color-surface-alt); - border: 1px solid var(--color-border); - color: var(--color-text-secondary); - font-size: 12px; - font-weight: 600; -} - -.task-readiness-card, -.task-action-help, -.task-empty-state, -.task-stage-card { - padding: 14px; - background: var(--color-surface-alt); -} - -.task-draft-overview, -.task-draft-inline, -.task-advanced-panel { - margin-top: 14px; - padding: 14px; - border: 1px solid var(--color-border-soft); - border-radius: 16px; - background: var(--color-surface-alt); -} - -.task-draft-inline { - display: flex; - flex-direction: column; - gap: 12px; -} - -.task-draft-inline-head { - display: flex; - align-items: center; - gap: 10px; - flex-wrap: wrap; -} - -.task-draft-inline-copy { - min-width: 0; -} - -.task-draft-overview-head { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 12px; -} - -.task-checklist-inline { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 10px; - margin-top: 12px; -} - -.task-check-chip { - display: flex; - flex-direction: column; - gap: 4px; - padding: 12px; - border-radius: 14px; - border: 1px solid var(--color-border); - background: var(--color-surface); -} - -.task-check-chip strong { - font-size: 12px; - color: var(--color-text-primary); -} - -.task-check-chip span { - font-size: 12px; - line-height: 1.5; - color: var(--color-text-secondary); -} - -.task-check-chip.done { - border-color: rgba(81, 154, 91, 0.24); - background: rgba(81, 154, 91, 0.08); -} - -.task-draft-overview .task-config-strip { - margin-top: 12px; -} - -.task-advanced-panel[open] { - box-shadow: var(--shadow-subtle); -} - -.task-advanced-summary { - display: flex; - align-items: center; - justify-content: space-between; - cursor: pointer; - font-size: 13px; - font-weight: 700; - color: var(--color-text-primary); - list-style: none; - min-height: 36px; - padding: 0 10px; - border-radius: 12px; - background: rgba(255, 255, 255, 0.64); - border: 1px solid var(--color-border-soft); - transition: - border-color var(--transition-fast) var(--ease-smooth), - background var(--transition-fast) var(--ease-smooth), - box-shadow var(--transition-fast) var(--ease-smooth); -} - -.task-advanced-summary:hover { - border-color: var(--color-border-strong); - background: rgba(255, 255, 255, 0.86); -} - -.task-advanced-panel[open] > .task-advanced-summary { - border-color: rgba(199, 116, 98, 0.4); - box-shadow: var(--shadow-subtle); -} - -.task-advanced-summary::-webkit-details-marker { - display: none; -} - -.task-advanced-panel > .task-composer-grid, -.task-advanced-panel > .selector-grid { - margin-top: 12px; -} - -.task-readiness-head { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 12px; -} - -.task-readiness-grid { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 10px; - margin-top: 12px; -} - -.task-readiness-item { - display: flex; - flex-direction: column; - gap: 4px; - padding: 12px; - border-radius: 14px; - border: 1px solid var(--color-border); - background: var(--color-surface); -} - -.task-readiness-item strong { - font-size: 12px; - color: var(--color-text-primary); -} - -.task-readiness-item span { - font-size: 12px; - color: var(--color-text-secondary); - line-height: 1.5; -} - -.task-readiness-item.done { - border-color: rgba(81, 154, 91, 0.24); - background: rgba(81, 154, 91, 0.08); -} - -.task-action-row, -.task-plan-summary-strip, -.task-detail-summary-strip { - display: flex; - flex-wrap: wrap; - gap: 10px; -} - -.task-action-row-prominent { - align-items: center; - margin-top: 4px; -} - -.task-action-row > .btn-tool, -.task-action-row-right > .btn-tool { - width: auto; - min-height: 36px; - height: 36px; - display: inline-flex; - align-items: center; - justify-content: center; - box-sizing: border-box; -} - - -.task-action-row-right { - display: flex; - flex-wrap: wrap; - gap: 10px; -} - -.task-action-row-right-prominent > .btn-tool { - min-width: 104px; -} - -.task-action-help-prominent, -.task-action-help-sidebar { - display: flex; - flex-direction: column; - gap: 8px; - margin-top: 12px; -} - -.task-section-header { - align-items: center; - justify-content: space-between; - gap: 12px; -} - -.task-section-header > :first-child { - flex: 1 1 auto; - min-width: 0; -} - -.task-header-actions, -.task-section-header .settings-tab-actions, -.task-detail-toolbar { - display: flex; - flex-wrap: wrap; - gap: 8px; - align-items: center; - justify-content: flex-end; - margin-left: auto; -} - -.task-workbench-tabs { - display: flex; - flex-wrap: wrap; - gap: 8px; - margin-top: 14px; -} - -.task-workbench-tab.active { - background: var(--color-brand-light); - border-color: var(--color-brand); - color: var(--color-brand-dark); - box-shadow: var(--shadow-subtle); -} - -.task-workbench-panel { - margin-top: 14px; -} - -.task-action-caption { - margin-top: 12px; - font-size: 12px; - line-height: 1.6; - color: var(--color-text-secondary); -} - -.task-stage-empty { - display: flex; - align-items: center; - justify-content: space-between; - gap: 16px; -} - -.task-stage-strip { - display: flex; - flex-wrap: wrap; - gap: 8px; - justify-content: flex-end; -} - -.task-stage-pill { - padding: 8px 12px; - border-radius: 999px; - border: 1px solid var(--color-border-soft); - background: var(--color-surface); - font-size: 12px; - font-weight: 600; - color: var(--color-text-secondary); -} - -.task-plan-summary-item { - min-width: 120px; - padding: 12px; - border-radius: 14px; - border: 1px solid var(--color-border); - background: var(--color-surface-alt); - display: flex; - flex-direction: column; - gap: 4px; -} - -.task-plan-summary-label { - font-size: 11px; - color: var(--color-text-muted); - text-transform: uppercase; - letter-spacing: 0.06em; -} - -.task-wave-list, -.task-node-list, -.task-runtime-list, -.task-warning-list, -.task-issues-list { - display: flex; - flex-direction: column; - gap: 10px; - margin-top: 12px; -} - -.task-wave-card, -.task-node-card, -.task-runtime-item, -.task-warning-item, -.task-issue-item { - border: 1px solid var(--color-border); - border-radius: 14px; - background: var(--color-surface-alt); -} - -.task-wave-card, -.task-node-card { - padding: 14px; -} - -.task-warning-item, -.task-issue-item { - padding: 10px 12px; - font-size: 13px; -} - -.task-warning-item { - color: var(--color-text-secondary); - background: rgba(255, 244, 220, 0.7); -} - -.task-issue-item { - color: #8f2d21; - background: rgba(255, 232, 228, 0.75); -} - -.task-wave-title, -.task-node-title, -.task-runtime-item-title { - font-weight: 700; - color: var(--color-text-primary); -} - -.task-wave-nodes, -.task-node-meta, -.task-node-deps, -.task-runtime-item-meta, -.task-runtime-item-summary { - font-size: 12px; - color: var(--color-text-secondary); - line-height: 1.55; -} - -.task-node-head, -.task-runtime-item, -.task-runtime-item-actions { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 10px; -} - -.task-runtime-item { - width: 100%; - padding: 14px; - cursor: default; - text-align: left; -} - -.task-runtime-item.clickable { - cursor: pointer; -} - -.task-runtime-item.active { - border-color: rgba(199, 116, 98, 0.3); - background: var(--color-brand-light); -} - -.task-runtime-item-main { - min-width: 0; - flex: 1; - display: flex; - flex-direction: column; - gap: 4px; -} - -.task-runtime-item-actions { - flex-direction: row; - align-items: center; - justify-content: flex-end; - flex-wrap: nowrap; - flex: 0 0 auto; -} - -.task-runtime-item-actions .btn-mini { - min-width: 72px; - min-height: 30px; - height: 30px; - display: inline-flex; - align-items: center; - justify-content: center; - box-sizing: border-box; -} - -.pill.success { - background: rgba(81, 154, 91, 0.14); - color: #285d31; -} - -.pill.warn { - background: rgba(214, 153, 58, 0.16); - color: #7a5110; -} - -.pill.error { - background: rgba(194, 73, 63, 0.16); - color: #8f2d21; -} - -.pill.neutral { - background: rgba(116, 126, 138, 0.14); - color: #45515e; -} - -.task-log-block { - margin: 10px 0 0; - min-height: 120px; - white-space: pre-wrap; - word-break: break-word; - font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; - font-size: 12px; - line-height: 1.55; -} - -.task-node-output-card { - margin-top: 12px; - padding: 12px; - border-radius: 16px; - border: 1px solid rgba(100, 116, 139, 0.16); - background: #F8FAFC; -} - -.task-node-output-head, -.task-node-output-facts { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - flex-wrap: wrap; -} - -.task-node-output-head strong { - font-size: 13px; - color: var(--color-text-primary); -} - -.task-node-output-meta, -.task-node-output-facts, -.task-materialized-file { - font-size: 12px; - line-height: 1.55; - color: var(--color-text-secondary); -} - -.task-node-output-facts { - justify-content: flex-start; - margin-top: 6px; -} - -.task-output-block { - max-height: 260px; - min-height: 96px; - overflow: auto; - background: #FFFFFF; -} - -.task-materialized-files { - margin-top: 10px; - display: flex; - flex-direction: column; - gap: 4px; -} - -.task-workspace-files { - padding: 10px; - border: 1px solid rgba(199, 116, 98, 0.2); - border-radius: 14px; - background: #FFFDFC; -} - -.task-workspace-files .task-node-deps { - color: var(--color-brand-dark); - font-weight: 800; -} - -@media (max-width: 1200px) { - .task-layout-grid-primary, - .task-layout-grid-secondary, - .task-chat-context-row-primary { - grid-template-columns: 1fr; - } -} - -@media (max-width: 720px) { - .task-composer-grid, - .task-readiness-grid, - .task-checklist-inline, - .task-composer-grid-compact { - grid-template-columns: 1fr; - } - - .task-hero-main, - .task-flow-head, - .task-draft-overview-head, - .task-stage-empty, - .task-readiness-head, - .task-section-header, - .task-node-head, - .task-runtime-item, - .task-runtime-item-actions { - flex-direction: column; - align-items: stretch; - } - - .task-header-actions, - .task-section-header .settings-tab-actions, - .task-detail-toolbar, - .task-action-row-right { - margin-left: 0; - width: 100%; - justify-content: flex-start; - } - - .task-hero-actions { - flex-wrap: wrap; - } -} - -.task-quick-layout { - margin-bottom: 18px; -} - -.task-quick-card { - display: grid; - grid-template-columns: minmax(0, 1fr) minmax(260px, 340px); - gap: 18px; - align-items: stretch; -} - -.task-quick-main { - min-width: 0; - display: flex; - flex-direction: column; - gap: 14px; -} - -.task-quick-copy { - max-width: 780px; -} - -.task-quick-title { - font-size: clamp(22px, 2vw, 30px); - line-height: 1.18; -} - -.task-quick-input-card { - padding: 18px; - border-radius: 22px; - border: 1px solid var(--color-border-soft); - background: #F8FAFC; - box-shadow: var(--shadow-subtle); -} - -.task-quick-target-field { - display: block; -} - -.task-chat-composer .selector-label { - font-size: 12px; - letter-spacing: 0.04em; - text-transform: uppercase; -} - -.task-quick-target { - min-height: 88px; - border-radius: 18px; - background: var(--color-surface); - font-size: 15px; -} - - -.task-chat-panel { - display: flex; - flex-direction: column; - gap: 14px; -} - -.task-chat-thread { - display: flex; - flex-direction: column; - gap: 10px; - max-height: 420px; - overflow-x: hidden; - overflow-y: auto; - overscroll-behavior: contain; - scrollbar-width: thin; - scrollbar-color: transparent transparent; - padding: 12px; - border-radius: 20px; - border: 1px solid var(--color-border-soft); - background: rgba(255, 255, 255, 0.74); - transition: scrollbar-color 0.16s ease; -} - -.task-chat-thread:hover, -.task-chat-thread:focus-within { - scrollbar-color: rgba(100, 116, 139, 0.28) transparent; -} - -.task-chat-thread::-webkit-scrollbar { - width: 6px; - height: 6px; -} - -.task-chat-thread::-webkit-scrollbar-track { - background: transparent; -} - -.task-chat-thread::-webkit-scrollbar-thumb { - border-radius: 999px; - background: transparent; -} - -.task-chat-thread:hover::-webkit-scrollbar-thumb, -.task-chat-thread:focus-within::-webkit-scrollbar-thumb { - background: rgba(100, 116, 139, 0.28); -} - -.task-chat-thread::-webkit-scrollbar-thumb:hover { - background: rgba(100, 116, 139, 0.42); -} - -.task-chat-bubble-row { - display: flex; - width: 100%; -} - -.task-chat-bubble-row.is-assistant { - justify-content: flex-start; -} - -.task-chat-bubble-row.is-user { - justify-content: flex-end; -} - -.task-chat-bubble { - max-width: min(76%, 680px); - padding: 11px 13px; - border-radius: 18px; - border: 1px solid var(--color-border-soft); - background: var(--color-surface); - box-shadow: 0 8px 24px rgba(36, 24, 20, 0.06); -} - -.task-chat-bubble-row.is-user .task-chat-bubble { - border-color: rgba(199, 116, 98, 0.28); - background: var(--color-brand-light); - color: var(--color-brand-dark); - border-bottom-right-radius: 6px; -} - -.task-chat-bubble-row.is-assistant .task-chat-bubble { - border-bottom-left-radius: 6px; -} - -.task-chat-bubble-label { - font-size: 11px; - font-weight: 800; - letter-spacing: 0.04em; - text-transform: uppercase; - color: var(--color-text-muted); - margin-bottom: 5px; -} - -.task-chat-bubble-text { - font-size: 14px; - line-height: 1.55; - color: var(--color-text-primary); - white-space: pre-wrap; - overflow-wrap: anywhere; -} - -.task-chat-bubble-row.is-user .task-chat-bubble-text, -.task-chat-bubble-row.is-user .task-chat-bubble-label, -.task-chat-bubble-row.is-user .task-chat-bubble-meta { - color: var(--color-brand-dark); -} - -.task-chat-bubble-meta { - margin-top: 6px; - font-size: 11px; - line-height: 1.45; - color: var(--color-text-secondary); -} - -.task-chat-send-row { - display: flex; - align-items: center; - justify-content: space-between; - gap: 12px; - flex-wrap: wrap; -} - -.task-chat-action-buttons { - display: flex; - flex-wrap: wrap; - gap: 8px; -} - -.task-chat-primary-action { - flex: 1 1 180px; - display: grid; - place-items: center; - align-self: stretch; -} - -.task-chat-primary-button { - min-width: 168px; - min-height: 44px; - justify-content: center; - font-weight: 800; -} - -.task-chat-execute-caption { - margin-top: 8px; - color: var(--color-text-secondary); - font-size: 12px; - line-height: 1.45; -} - -.task-chat-context-row { - display: flex; - flex-wrap: wrap; - gap: 8px; - margin-top: 12px; -} - -.task-chat-context-row-primary { - display: grid; - grid-template-columns: minmax(260px, 1.2fr) minmax(220px, 0.8fr) repeat(3, minmax(96px, auto)); - align-items: stretch; - padding: 10px; - border: 1px solid rgba(199, 116, 98, 0.18); - border-radius: 18px; - background: #FFFFFF; -} - -.task-chat-context-chip { - display: inline-flex; - align-items: center; - min-height: 30px; - max-width: 100%; - padding: 0 10px; - border-radius: 999px; - border: 1px solid rgba(199, 116, 98, 0.18); - background: rgba(255, 255, 255, 0.68); - color: var(--color-text-secondary); - font-size: 12px; - font-weight: 600; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.task-chat-context-chip-strong { - display: flex; - align-items: flex-start; - flex-direction: column; - justify-content: center; - gap: 2px; - min-height: 42px; - border-radius: 14px; - background: #FFFDFC; - color: var(--color-text-primary); - white-space: normal; -} - -.task-chat-context-chip-strong small { - color: var(--color-text-muted); - font-size: 10px; - font-weight: 800; - letter-spacing: 0.06em; - text-transform: uppercase; -} - -.task-chat-context-chip-strong strong { - max-width: 100%; - overflow-wrap: anywhere; - word-break: break-word; - font-size: 12px; - line-height: 1.35; -} - -.task-chat-context-action { - cursor: pointer; - font: inherit; -} - -.task-chat-context-action:hover, -.task-chat-context-action:focus-visible { - border-color: rgba(199, 116, 98, 0.38); - color: var(--color-brand-dark); - outline: none; -} - -.task-quick-readiness { - margin-top: 14px; - background: rgba(255, 255, 255, 0.58); -} - -.task-quick-template-block { - padding: 14px; -} - -.task-quick-template-grid { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 10px; - margin-top: 10px; -} - -.task-quick-template-card { - min-width: 0; - padding: 14px; - border-radius: 16px; - border: 1px solid var(--color-border-soft); - background: var(--color-surface); - color: var(--color-text-primary); - text-align: left; - cursor: pointer; - transition: - transform var(--transition-fast) var(--ease-smooth), - border-color var(--transition-fast) var(--ease-smooth), - box-shadow var(--transition-fast) var(--ease-smooth), - background var(--transition-fast) var(--ease-smooth); -} - -.task-quick-template-card:hover { - transform: translateY(-1px); - border-color: rgba(199, 116, 98, 0.34); - background: var(--color-brand-light); - box-shadow: var(--shadow-subtle); -} - -.task-quick-template-card strong, -.task-quick-template-card span { - display: block; -} - -.task-quick-template-card strong { - font-size: 13px; - color: var(--color-text-primary); -} - -.task-quick-template-card span { - margin-top: 6px; - font-size: 12px; - line-height: 1.5; - color: var(--color-text-secondary); -} - -.task-quick-advanced { - margin-top: 0; -} - -.task-quick-side-card { - min-width: 0; - padding: 16px; - border-radius: 20px; - border: 1px solid var(--color-border-soft); - background: var(--color-surface-alt); - box-shadow: var(--shadow-subtle); -} - -.task-provider-status-card { - margin-bottom: 14px; - padding: 14px; - border-radius: 16px; - border: 1px solid rgba(100, 116, 139, 0.16); - background: #FFFFFF; - display: flex; - flex-direction: column; - gap: 10px; -} - -.task-provider-status-grid { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 8px; -} - -.task-provider-status-row { - min-width: 0; - padding: 9px 10px; - border-radius: 12px; - background: #F8FAFC; - border: 1px solid rgba(100, 116, 139, 0.10); -} - -.task-provider-status-row-wide { - grid-column: 1 / -1; -} - -.task-provider-status-row span, -.task-provider-status-row strong { - display: block; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.task-provider-status-row span { - font-size: 11px; - color: var(--color-text-muted); -} - -.task-provider-status-row strong { - margin-top: 3px; - font-size: 12px; - color: var(--color-text-primary); -} - -.task-provider-status-error { - margin: 0; -} - -.task-provider-config-button { - align-self: flex-start; -} - -.task-quick-checklist { - grid-template-columns: 1fr; -} - -.task-quick-status-card { - margin-top: 14px; - padding: 14px; - border-radius: 16px; - border: 1px solid rgba(199, 116, 98, 0.18); - background: var(--color-surface); - display: flex; - flex-direction: column; - gap: 6px; -} - -.task-quick-status-card strong { - color: var(--color-text-primary); -} - -.task-quick-status-card span { - font-size: 12px; - line-height: 1.55; - color: var(--color-text-secondary); -} - -@media (max-width: 1200px) { - .task-quick-card { - grid-template-columns: 1fr; - } - - .task-quick-checklist { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } -} - -@media (max-width: 720px) { - .task-quick-template-grid, - .task-quick-checklist { - grid-template-columns: 1fr; - } -} - -/* Codex-style orchestration simplification: keep the chat thread as the main surface. */ -#panel-orchestration { - --task-orchestration-main-width: 1200px; - --task-orchestration-context-width: 260px; -} - -#panel-orchestration .task-hero-card { - max-width: var(--task-orchestration-main-width); - margin: -4px auto 14px; - padding: 16px 18px; - box-shadow: none; -} - -#panel-orchestration .task-hero-main { - align-items: center; -} - -#panel-orchestration .task-hero-copy, -#panel-orchestration .task-hero-meta-strip, -#panel-orchestration .task-quick-copy, -#panel-orchestration .task-template-block, -#panel-orchestration .task-stage-card { - display: none; -} - -#panel-orchestration .task-layout-grid-primary, -#panel-orchestration .task-layout-grid-secondary { - max-width: var(--task-orchestration-main-width); - margin-left: auto; - margin-right: auto; -} - -#panel-orchestration .task-quick-card { - display: grid; - grid-template-columns: minmax(0, 1fr) var(--task-orchestration-context-width); - gap: 14px; - align-items: start; - padding: 0; - border: 0; - background: transparent; - box-shadow: none; -} - -#panel-orchestration .task-quick-main { - display: block; - min-width: 0; -} - -#panel-orchestration { - min-height: calc(100vh - 96px); -} - -#panel-orchestration .task-hero-card { - padding: 10px 14px; - border-radius: 16px; - background: rgba(255, 255, 255, 0.62); - box-shadow: none; -} - -#panel-orchestration .task-hero-main { - align-items: center; - gap: 12px; -} - -#panel-orchestration .task-hero-card .selector-title { - font-size: 1rem; - line-height: 1.25; -} - -#panel-orchestration .task-hero-card .task-hero-copy, -#panel-orchestration .task-hero-card .task-hero-kicker { - display: none; -} - -#panel-orchestration .task-quick-card { - display: grid; - grid-template-columns: minmax(0, 1fr) var(--task-orchestration-context-width); - gap: 14px; - align-items: start; - padding: 0; - border: 0; - background: transparent; - box-shadow: none; -} - -#panel-orchestration .task-quick-main { - display: flex; - flex-direction: column; - gap: 10px; - min-width: 0; -} - -#panel-orchestration .task-chat-panel { - display: flex; - flex-direction: column; - gap: 12px; - height: min(780px, calc(100vh - 170px)); - min-height: 620px; - margin: 0; - padding: 12px; - overflow: hidden; - border: 1px solid rgba(199, 116, 98, 0.16); - border-radius: 22px; - background: - linear-gradient(180deg, rgba(255, 255, 255, 0.82), rgba(255, 252, 248, 0.7)), - rgba(255, 255, 255, 0.6); - box-shadow: 0 18px 52px rgba(86, 58, 39, 0.08); -} - -#panel-orchestration .task-chat-thread { - flex: 1 1 auto; - min-height: 0; - max-height: none; - margin: 0; - padding: 10px 8px 16px; - gap: 12px; - overflow-y: auto; - border: 0; - background: transparent; -} - -#panel-orchestration .task-thread-message-card { - position: relative; - align-self: flex-start; - width: min(100%, 760px); - margin: 2px 0 4px 44px; - padding: 14px; - border: 1px solid rgba(199, 116, 98, 0.18); - border-radius: 20px; - background: rgba(255, 255, 255, 0.84); - box-shadow: 0 12px 30px rgba(86, 58, 39, 0.09); -} - -#panel-orchestration .task-thread-message-card::before { - content: 'AI'; - position: absolute; - left: -40px; - top: 12px; - display: grid; - width: 30px; - height: 30px; - place-items: center; - border-radius: 999px; - background: var(--color-brand-light); - color: var(--color-brand-dark); - font-size: 11px; - font-weight: 800; - letter-spacing: 0.04em; -} - -#panel-orchestration .task-thread-message-card .selector-header { - margin-bottom: 10px; -} - -#panel-orchestration .task-thread-message-card .selector-title { - font-size: 0.98rem; -} - -#panel-orchestration .task-thread-message-card .skills-panel-note { - font-size: 0.82rem; -} - -#panel-orchestration .task-thread-composer { - flex: 0 0 auto; - position: sticky; - bottom: 0; - z-index: 2; - width: min(100%, 800px); - align-self: center; - margin-top: 0; - padding: 10px; - border: 1px solid rgba(199, 116, 98, 0.18); - border-radius: 18px 18px 20px 20px; - background: rgba(255, 255, 255, 0.96); - box-shadow: 0 -10px 34px rgba(86, 58, 39, 0.13); -} - -#panel-orchestration .task-thread-composer .selector-label { - font-size: 0.78rem; -} - -#panel-orchestration .task-thread-composer .task-textarea-goal { - min-height: 76px; - resize: vertical; - border: 0; - border-radius: 12px; - background: rgba(255, 248, 241, 0.48); - font-family: var(--font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace); - font-size: 15px; - line-height: 1.55; - box-shadow: none; -} - -#panel-orchestration .task-thread-composer .task-textarea-goal:focus { - box-shadow: none; -} - -#panel-orchestration .task-thread-composer .task-chat-send-row, -#panel-orchestration .task-thread-composer .task-chat-context-row, -#panel-orchestration .task-thread-composer .task-draft-inline, -#panel-orchestration .task-thread-composer .task-action-row-prominent { - margin-top: 8px; -} - -#panel-orchestration .task-thread-composer .task-draft-inline { - padding: 8px 10px; - border-radius: 14px; - background: rgba(255, 248, 241, 0.58); -} - -#panel-orchestration .task-thread-composer .task-action-row-prominent { - padding-top: 0; - border-top: 1px solid rgba(199, 116, 98, 0.12); -} - -#panel-orchestration .task-thread-composer .task-action-row-right { - margin-left: auto; -} - -#panel-orchestration .task-thread-composer .task-field-hint { - font-size: 0.75rem; -} - -#panel-orchestration .task-plan-summary-strip, -#panel-orchestration .task-wave-list, -#panel-orchestration .task-node-list, -#panel-orchestration .task-runtime-list { - gap: 8px; -} - -#panel-orchestration .task-quick-advanced { - margin-top: 8px; - border: 1px solid rgba(148, 163, 184, 0.14); - background: rgba(255, 255, 255, 0.46); - box-shadow: none; -} - -#panel-orchestration .task-codex-queue-action { - justify-content: flex-start; - margin: 0 0 12px; -} - -#panel-orchestration .task-quick-side-card { - position: sticky; - top: 14px; - padding: 12px; - border-radius: 18px; - background: rgba(255, 255, 255, 0.42); - opacity: 0.82; - box-shadow: none; -} - -#panel-orchestration .task-provider-status-card, -#panel-orchestration .task-quick-status-card, -#panel-orchestration .task-quick-side-card .task-readiness-item { - background: rgba(255, 255, 255, 0.48); - border-color: rgba(148, 163, 184, 0.16); - box-shadow: none; -} - -#panel-orchestration .task-provider-status-card, -#panel-orchestration .task-quick-status-card { - margin-bottom: 10px; - padding: 10px; - border-radius: 14px; -} - -#panel-orchestration .task-provider-status-grid { - grid-template-columns: 1fr; - gap: 6px; -} - -#panel-orchestration .task-provider-status-row { - padding: 7px 8px; - border-radius: 10px; - background: rgba(248, 250, 252, 0.62); -} - -#panel-orchestration .task-provider-status-card .task-readiness-copy, -#panel-orchestration .task-readiness-head .task-readiness-copy { - display: none; -} - -#panel-orchestration .task-readiness-grid.task-quick-checklist { - grid-template-columns: 1fr; - gap: 7px; - margin-top: 8px; -} - -#panel-orchestration .task-readiness-item { - padding: 9px 10px; - border-radius: 12px; -} - -#panel-orchestration .task-quick-status-card { - margin-top: 10px; -} - -@media (max-width: 1180px) { - #panel-orchestration .task-quick-card { - grid-template-columns: minmax(0, 1fr); - } - - #panel-orchestration .task-thread-message-card { - width: 100%; - margin-left: 0; - } - - #panel-orchestration .task-thread-message-card::before { - display: none; - } - - #panel-orchestration .task-quick-side-card { - position: static; - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - gap: 10px; - } - - #panel-orchestration .task-provider-status-card, - #panel-orchestration .task-quick-status-card { - margin-bottom: 0; - } -} - -@media (max-width: 760px) { - #panel-orchestration .task-hero-main, - #panel-orchestration .task-action-row-prominent, - #panel-orchestration .task-action-row-prominent .task-action-row-right { - align-items: stretch; - flex-direction: column; - } - - #panel-orchestration .task-action-row-prominent .btn-tool, - #panel-orchestration .task-action-row-prominent .task-action-row-right { - width: 100%; - } - - #panel-orchestration .task-quick-side-card, - #panel-orchestration .task-template-block, - #panel-orchestration .task-quick-copy, - #panel-orchestration .task-stage-card { - display: none; - } - - #panel-orchestration .task-chat-panel { - height: calc(100vh - 126px); - min-height: 620px; - padding: 10px; - border-radius: 18px; - } - - #panel-orchestration .task-chat-thread { - padding: 10px 6px 14px; - } - - #panel-orchestration .task-thread-message-card { - padding: 12px; - border-radius: 18px; - } - - #panel-orchestration .task-thread-composer { - width: 100%; - padding: 8px; - border-radius: 16px; - box-shadow: 0 -8px 26px rgba(86, 58, 39, 0.12); - } - - #panel-orchestration .task-thread-composer .task-textarea-goal { - min-height: 56px; - max-height: 120px; - } - - #panel-orchestration .task-thread-composer .task-chat-context-row, - #panel-orchestration .task-thread-composer .task-draft-inline, - #panel-orchestration .task-thread-composer .task-action-caption, - #panel-orchestration .task-thread-composer .task-field-hint { - display: none; - } - - #panel-orchestration .task-thread-composer .task-chat-send-row, - #panel-orchestration .task-thread-composer .task-action-row-prominent { - margin-top: 6px; - } - - #panel-orchestration .task-thread-composer .btn-tool { - min-height: 34px; - padding: 0 12px; - font-size: 0.78rem; - } -} - -/* Codex-style transcript final pass: the task flow is a chat, not a dashboard. */ -#panel-orchestration { - --task-orchestration-main-width: 920px; - --task-orchestration-context-width: 0px; - - min-height: calc(100vh - 72px); - padding-bottom: 188px; -} - -#panel-orchestration .task-hero-card, -#panel-orchestration .task-quick-copy, -#panel-orchestration .task-stage-card { - display: none; -} - -#panel-orchestration .task-layout-grid, -#panel-orchestration .task-layout-grid-primary, -#panel-orchestration .task-layout-grid-secondary, -#panel-orchestration .task-quick-card { - max-width: var(--task-orchestration-main-width); - width: min(100%, var(--task-orchestration-main-width)); - margin-left: auto; - margin-right: auto; -} - -#panel-orchestration .task-layout-grid { - gap: 0; - margin-bottom: 0; -} - -#panel-orchestration .task-quick-card { - display: block; - padding: 0; - border: 0; - background: transparent; - box-shadow: none; -} - -#panel-orchestration .task-quick-main { - display: block; - min-width: 0; -} - -#panel-orchestration .task-template-block { - display: block; -} - -#panel-orchestration .task-chat-panel { - display: flex; - flex-direction: column; - gap: 8px; - height: auto; - min-height: 0; - margin: 0 auto; - padding: 0; - overflow: visible; - border: 0; - border-radius: 0; - background: transparent; - box-shadow: none; -} - -#panel-orchestration .task-chat-thread { - flex: 0 0 auto; - min-height: 0; - max-height: none; - margin: 0; - padding: 8px 8px 188px; - gap: 7px; - overflow: visible; - border: 0; - background: transparent; - scroll-padding-bottom: 188px; -} - -#panel-orchestration .task-chat-bubble-row { - position: relative; - justify-content: flex-start; - gap: 8px; - margin: 0; - padding: 1px 0 6px 28px; -} - -#panel-orchestration .task-chat-bubble-row::before { - content: '•'; - position: absolute; - top: 4px; - left: 8px; - color: #64748B; - font-family: var(--font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace); - font-size: 16px; - font-weight: 800; - line-height: 1; -} - -#panel-orchestration .task-chat-bubble-row.is-user::before { - content: '›'; - top: 2px; - color: #2563EB; - font-size: 20px; -} - -#panel-orchestration .task-chat-bubble { - max-width: min(100%, 760px); - padding: 0 0 5px; - border: 0; - border-radius: 0; - background: transparent; - box-shadow: none; -} - -#panel-orchestration .task-chat-bubble-row.is-user .task-chat-bubble, -#panel-orchestration .task-chat-bubble-row.is-assistant .task-chat-bubble { - border-radius: 0; - background: transparent; -} - -#panel-orchestration .task-chat-bubble-label { - margin-bottom: 3px; - color: #64748B; - font-family: var(--font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace); - font-size: 10px; - letter-spacing: 0.04em; - text-transform: uppercase; -} - -#panel-orchestration .task-chat-bubble-text { - color: #0F172A; - font-family: var(--font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace); - font-size: 14px; - line-height: 1.5; -} - -#panel-orchestration .task-chat-bubble-meta { - margin-top: 5px; - color: #64748B; - font-family: var(--font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace); -} - -#panel-orchestration .task-thread-plan-request { - margin-top: 2px; -} - -#panel-orchestration .task-thread-plan-request .task-chat-bubble { - max-width: min(100%, 760px); -} - -#panel-orchestration .task-thread-message-card { - position: relative; - align-self: flex-start; - width: min(100%, 660px); - margin: 0 0 2px 34px; - padding: 8px 10px; - border: 1px solid rgba(148, 163, 184, 0.18); - border-left: 3px solid rgba(199, 116, 98, 0.32); - border-radius: 14px 14px 14px 5px; - background: rgba(255, 255, 255, 0.50); - box-shadow: none; -} - -#panel-orchestration .task-thread-message-card::before { - content: 'AI'; - position: absolute; - left: -34px; - top: 8px; - display: grid; - width: 24px; - height: 24px; - place-items: center; - border-radius: 999px; - background: var(--color-brand-light); - color: var(--color-brand-dark); - font-size: 9px; - font-weight: 800; - letter-spacing: 0.04em; -} - -#panel-orchestration .task-thread-card-label { - margin-bottom: 6px; - color: var(--color-text-secondary); - font-size: 10px; - font-weight: 800; - letter-spacing: 0.07em; - text-transform: uppercase; -} - -#panel-orchestration .task-thread-message-card .selector-header { - margin-bottom: 6px; - padding-bottom: 0; - border-bottom: 0; -} - -#panel-orchestration .task-thread-message-card .selector-title { - font-size: 0.9rem; - line-height: 1.2; -} - -#panel-orchestration .task-side-settings-card .task-advanced-summary { - margin: 0 0 8px; - color: var(--color-text-primary); - font-size: 0.86rem; - font-weight: 750; -} - -#panel-orchestration .task-side-settings-card:not([open]) { - padding: 8px 10px; -} - -#panel-orchestration .task-side-settings-card .selector-grid { - margin-top: 8px; -} - -#panel-orchestration .task-side-settings-card .task-codex-queue-action { - justify-content: flex-start; - margin: 8px 0 0; -} - -#panel-orchestration .task-thread-message-card .skills-panel-note { - font-size: 0.76rem; - line-height: 1.45; -} - -#panel-orchestration .task-thread-message-card .task-plan-summary-strip { - display: flex; - flex-wrap: wrap; - gap: 5px; - margin-top: 5px; -} - -#panel-orchestration .task-thread-message-card .task-plan-summary-item { - min-width: 0; - padding: 0; - border: 0; - border-radius: 0; - background: transparent; - box-shadow: none; -} - -#panel-orchestration .task-thread-message-card .task-plan-summary-label { - font-size: 9px; -} - -#panel-orchestration .task-thread-plan-card .task-wave-list, -#panel-orchestration .task-runtime-list { - gap: 5px; - margin-top: 7px; -} - -#panel-orchestration .task-thread-plan-card .task-wave-list { - display: none; -} - -#panel-orchestration .task-thread-plan-card .task-node-list { - display: none; -} - -#panel-orchestration .task-thread-plan-card .task-plan-summary-item:nth-child(n+3) { - display: none; -} - -#panel-orchestration .task-thread-plan-card .task-wave-card, -#panel-orchestration .task-thread-plan-card .task-node-card, -#panel-orchestration .task-thread-message-card .task-runtime-item { - padding: 4px 0; - border: 0; - border-radius: 0; - background: transparent; - box-shadow: none; -} - -#panel-orchestration .task-thread-plan-card .task-wave-nodes, -#panel-orchestration .task-thread-plan-card .task-node-meta, -#panel-orchestration .task-thread-plan-card .task-node-deps { - display: none; -} - -#panel-orchestration .task-thread-plan-card .task-node-head { - align-items: center; -} - -#panel-orchestration .task-thread-plan-card .pill { - padding: 3px 7px; - font-size: 10px; -} - -#panel-orchestration .task-thread-plan-card .task-node-title, -#panel-orchestration .task-thread-plan-card .task-wave-title, -#panel-orchestration .task-thread-message-card .task-runtime-item-title { - font-size: 0.8rem; - font-weight: 650; -} - -#panel-orchestration .task-thread-message-card .task-runtime-item-meta { - font-size: 0.72rem; - line-height: 1.4; -} - -#panel-orchestration .task-side-workbench-card, -#panel-orchestration .task-side-settings-card { - max-height: min(560px, calc(100vh - 220px)); - margin-bottom: 8px; - overflow: auto; -} - -#panel-orchestration .task-side-workbench-card .task-node-list { - max-height: 320px; - overflow: auto; - padding-right: 4px; -} - -#panel-orchestration .task-quick-side-card { - width: min(100%, 660px); - margin: 8px auto 0; - padding: 10px 12px; - border: 1px solid rgba(148, 163, 184, 0.18); - border-left: 3px solid rgba(100, 116, 139, 0.24); - border-radius: 14px; - background: rgba(255, 255, 255, 0.42); - box-shadow: none; -} - -#panel-orchestration .task-quick-side-card .task-provider-status-card { - margin-bottom: 8px; - padding: 0; - border: 0; - background: transparent; -} - -#panel-orchestration .task-quick-side-card .task-readiness-head, -#panel-orchestration .task-quick-side-card .task-readiness-grid, -#panel-orchestration .task-quick-side-card .task-quick-status-card { - display: none; -} - -#panel-orchestration .task-quick-side-card .task-provider-status-grid { - grid-template-columns: repeat(2, minmax(0, 1fr)); -} - -#panel-orchestration .task-thread-composer { - display: grid; - grid-template-columns: minmax(0, 1fr) auto; - grid-template-rows: auto auto; - align-items: stretch; - gap: 6px; - flex: 0 0 auto; - position: fixed; - right: 50%; - bottom: 16px; - z-index: 20; - width: min(872px, calc(100vw - 48px)); - transform: translateX(50%); - margin-top: 0; - padding: 6px 8px; - border: 1px solid rgba(148, 163, 184, 0.20); - border-radius: 14px; - background: rgba(255, 255, 255, 0.92); - box-shadow: 0 14px 36px rgba(15, 23, 42, 0.13); -} - -#panel-orchestration .task-thread-composer .selector-label, -#panel-orchestration .task-thread-composer .task-chat-context-row, -#panel-orchestration .task-thread-composer .task-draft-inline, -#panel-orchestration .task-thread-composer .task-action-caption, -#panel-orchestration .task-thread-composer .task-field-hint { - display: none; -} - -#panel-orchestration .task-thread-composer .task-chat-composer { - grid-column: 1; - grid-row: 1 / span 2; - display: grid; - grid-template-columns: auto minmax(0, 1fr); - align-items: start; - gap: 8px; -} - -#panel-orchestration .task-thread-composer .task-composer-prompt-glyph { - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 18px; - padding-top: 7px; - color: #2563EB; - font-family: var(--font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace); - font-size: 22px; - font-weight: 800; - line-height: 1; -} - -#panel-orchestration .task-thread-composer .task-textarea-goal { - min-height: 48px; - max-height: 92px; - padding: 8px 10px; - resize: vertical; - border: 0; - border-radius: 12px; - background: rgba(248, 250, 252, 0.66); - font-family: var(--font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace); - font-size: 14px; - line-height: 1.35; - box-shadow: none; -} - -#panel-orchestration .task-thread-composer .task-textarea-goal:focus { - box-shadow: none; -} - -#panel-orchestration .task-thread-composer .task-chat-send-row { - grid-column: 2; - grid-row: 1 / span 2; - display: flex; - align-items: center; - justify-content: center; - align-self: center; - margin-top: 0; -} - -#panel-orchestration .task-thread-composer .task-chat-execute-caption { - grid-column: 1 / -1; - grid-row: 3; - margin: -1px 2px 0 28px; - color: #64748B; - font-family: var(--font-family-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace); - font-size: 11px; - line-height: 1.35; -} - -#panel-orchestration .task-thread-composer .task-action-row-prominent { - grid-column: 2; - grid-row: 2; - display: flex; - align-items: center; - justify-content: flex-end; - gap: 7px; - margin-top: 0; - padding-top: 0; - border-top: 0; -} - -#panel-orchestration .task-thread-composer .task-action-row-right { - margin-left: 0; -} - -#panel-orchestration .task-thread-composer .btn-tool { - display: inline-flex; - align-items: center; - justify-content: center; - min-height: 32px; - padding: 5px 10px; - font-size: 0.78rem; -} - -@media (max-width: 760px) { - #panel-orchestration { - --task-orchestration-main-width: 100%; - - min-height: calc(100vh - 52px); - padding-bottom: 208px; - } - - #panel-orchestration .task-layout-grid, - #panel-orchestration .task-layout-grid-primary, - #panel-orchestration .task-layout-grid-secondary, - #panel-orchestration .task-quick-card { - width: 100%; - max-width: 100%; - } - - #panel-orchestration .task-template-block, - #panel-orchestration .task-quick-advanced, - #panel-orchestration .task-quick-side-card { - display: block; - } - - #panel-orchestration .task-chat-panel { - height: auto; - min-height: 0; - } - - #panel-orchestration .task-chat-thread { - padding: 8px 4px 16px; - gap: 7px; - scroll-padding-bottom: 28px; - } - - #panel-orchestration .task-chat-bubble, - #panel-orchestration .task-thread-plan-request .task-chat-bubble { - max-width: 92%; - } - - #panel-orchestration .task-thread-message-card { - width: 100%; - margin-left: 0; - padding: 9px 10px; - border-radius: 15px; - } - - #panel-orchestration .task-thread-message-card::before { - display: none; - } - - #panel-orchestration .task-thread-message-card .task-plan-summary-item { - min-width: calc(50% - 4px); - } - - #panel-orchestration .task-side-workbench-card { - max-height: calc(100vh - 190px); - } - - #panel-orchestration .task-side-workbench-card .task-node-list { - max-height: 260px; - } - - #panel-orchestration .task-chat-thread { - padding-bottom: 208px; - scroll-padding-bottom: 208px; - } - - #panel-orchestration .task-thread-composer { - grid-template-columns: minmax(0, 1fr) auto; - grid-template-rows: auto; - right: 10px; - bottom: calc(env(safe-area-inset-bottom, 0px) + 8px); - left: 10px; - width: auto; - transform: none; - padding: 7px; - border-radius: 14px; - box-shadow: 0 14px 34px rgba(15, 23, 42, 0.16); - } - - #panel-orchestration .task-thread-composer .task-chat-composer { - grid-column: 1; - grid-row: 1; - } - - #panel-orchestration .task-thread-composer .task-chat-send-row { - grid-column: 2; - grid-row: 1; - align-self: center; - } - - #panel-orchestration .task-thread-composer .task-action-row-prominent, - #panel-orchestration .task-thread-composer .task-action-row-prominent .task-action-row-right, - #panel-orchestration .task-thread-composer .task-action-row-prominent .btn-tool { - width: 100%; - } - -#panel-orchestration .task-thread-composer .task-textarea-goal { - min-height: 52px; - max-height: 118px; - } -} - -/* Chat-alignment correction: keep the transcript clean while preserving a - separate side rail for settings, history, and run details. */ -#panel-orchestration { - padding-bottom: 240px; -} - -#panel-orchestration .task-chat-thread { - padding-bottom: 240px; - scroll-padding-bottom: 240px; -} - -#panel-orchestration .task-thread-message-card { - width: min(100%, 620px); - padding: 10px 12px; - border-color: rgba(148, 163, 184, 0.14); - border-left-color: rgba(199, 116, 98, 0.26); - background: rgba(255, 255, 255, 0.62); -} - -#panel-orchestration .task-thread-plan-card, -#panel-orchestration .task-side-workbench-card { - overflow: visible; - max-height: none; -} - -#panel-orchestration .task-thread-message-card .selector-header { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - margin-bottom: 8px; -} - -#panel-orchestration .task-thread-message-card .selector-title { - font-size: 0.86rem; -} - -#panel-orchestration .task-thread-plan-card .skills-panel-note, -#panel-orchestration .task-side-workbench-card .skills-panel-note { - display: none; -} - -#panel-orchestration .task-thread-plan-card .task-plan-summary-strip, -#panel-orchestration .task-side-workbench-card .task-detail-summary-strip { - display: flex; - flex-wrap: wrap; - gap: 6px; - margin: 6px 0 0; -} - -#panel-orchestration .task-thread-plan-card .task-plan-summary-item, -#panel-orchestration .task-side-workbench-card .task-plan-summary-item { - min-width: auto; - padding: 4px 7px; - border: 1px solid rgba(148, 163, 184, 0.12); - border-radius: 999px; - background: rgba(248, 250, 252, 0.60); -} - -#panel-orchestration .task-thread-plan-card .task-plan-summary-label, -#panel-orchestration .task-side-workbench-card .task-plan-summary-label { - display: inline; - margin-right: 4px; - color: var(--color-text-muted); - font-size: 9px; -} - -#panel-orchestration .task-thread-plan-card .task-plan-summary-item strong, -#panel-orchestration .task-side-workbench-card .task-plan-summary-item strong { - display: inline; - color: var(--color-text-primary); - font-size: 11px; - font-weight: 700; -} - -#panel-orchestration .task-side-workbench-card .task-header-actions, -#panel-orchestration .task-side-workbench-card .task-detail-toolbar, -#panel-orchestration .task-side-workbench-card .task-workbench-tabs { - margin: 0; - padding: 0; - border: 0; - background: transparent; - box-shadow: none; -} - -#panel-orchestration .task-side-workbench-card .task-header-actions .btn-tool, -#panel-orchestration .task-side-workbench-card .task-detail-toolbar .btn-tool, -#panel-orchestration .task-side-workbench-card .task-workbench-tab { - min-height: 26px; - padding: 3px 7px; - border-radius: 999px; - font-size: 0.7rem; -} - -#panel-orchestration .task-side-workbench-card .task-workbench-panel { - margin-top: 8px; - padding: 0; - border: 0; - background: transparent; - box-shadow: none; -} - -#panel-orchestration .task-thread-run-summary { - display: flex; - align-items: center; - gap: 8px; - margin-top: 4px; - color: var(--color-text-secondary); - font-size: 0.82rem; - line-height: 1.45; -} - -#panel-orchestration .task-thread-run-summary-copy { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -#panel-orchestration .task-thread-run-details { - margin-top: 8px; - color: var(--color-text-secondary); -} - -#panel-orchestration .task-thread-run-details > summary { - width: fit-content; - cursor: pointer; - border-radius: 999px; - padding: 4px 9px; - background: rgba(248, 250, 252, 0.70); - color: var(--color-text-muted); - font-size: 0.72rem; - font-weight: 700; -} - -#panel-orchestration .task-thread-run-details:not([open]) { - margin-bottom: 0; -} - -#panel-orchestration .task-thread-run-details[open] > summary { - margin-bottom: 8px; -} - -#panel-orchestration .task-thread-detail-actions { - justify-content: flex-start; - margin-bottom: 8px; -} - -#panel-orchestration .task-thread-plan-details .task-plan-summary-strip { - margin-top: 0; -} - -#panel-orchestration .task-side-workbench-card .task-runtime-list, -#panel-orchestration .task-side-workbench-card .task-node-list { - max-height: none; - gap: 6px; - padding-right: 0; - overflow: visible; -} - -#panel-orchestration .task-side-workbench-card .task-node-card, -#panel-orchestration .task-side-workbench-card .task-runtime-item, -#panel-orchestration .task-side-workbench-card .task-node-output-card { - padding: 7px 0; - border-width: 1px 0 0; - border-radius: 0; - background: transparent; - box-shadow: none; -} - -#panel-orchestration .task-side-workbench-card .task-node-output-card, -#panel-orchestration .task-side-workbench-card .task-log-block { - max-height: 120px; - overflow: auto; - border-radius: 10px; - background: rgba(15, 23, 42, 0.04); -} - -#panel-orchestration .task-thread-composer { - bottom: 20px; - background: rgba(255, 255, 255, 0.97); - box-shadow: 0 8px 26px rgba(15, 23, 42, 0.13); -} - -@media (max-width: 760px) { - #panel-orchestration { - padding-bottom: 72px; - } - - #panel-orchestration .task-chat-thread { - padding-bottom: 16px; - scroll-padding-bottom: 130px; - } - - #panel-orchestration .task-thread-message-card { - width: calc(100% - 10px); - margin-left: 0; - } - - #panel-orchestration .task-side-workbench-card .task-node-output-card, - #panel-orchestration .task-side-workbench-card .task-log-block { - max-height: 96px; - } - - #panel-orchestration .task-thread-composer { - bottom: calc(env(safe-area-inset-bottom, 0px) + 10px); - } -} - - -/* Workspace selector + records rail: project context belongs beside the chat, not inside it. */ -#panel-orchestration { - --task-orchestration-main-width: 1360px; - --task-orchestration-project-width: 260px; - --task-orchestration-records-width: 320px; -} - -#panel-orchestration .task-layout-grid, -#panel-orchestration .task-layout-grid-primary, -#panel-orchestration .task-layout-grid-secondary, -#panel-orchestration .task-quick-card { - max-width: var(--task-orchestration-main-width); - width: min(100%, var(--task-orchestration-main-width)); -} - -#panel-orchestration .task-quick-card { - display: grid; - grid-template-columns: var(--task-orchestration-project-width) minmax(0, 1fr) var(--task-orchestration-records-width); - align-items: start; - gap: 16px; -} - -#panel-orchestration .task-project-sidebar, -#panel-orchestration .task-quick-side-card { - position: sticky; - top: 84px; - align-self: start; - max-height: calc(100vh - 112px); - overflow: auto; - scrollbar-width: thin; -} - -#panel-orchestration .task-project-sidebar, -#panel-orchestration .task-quick-side-card > .selector-section, -#panel-orchestration .task-quick-side-card > details, -#panel-orchestration .task-provider-status-card { - border: 1px solid rgba(148, 163, 184, 0.16); - border-radius: 18px; - background: rgba(255, 255, 255, 0.62); - box-shadow: none; -} - -#panel-orchestration .task-project-sidebar { - display: flex; - flex-direction: column; - gap: 12px; - padding: 12px; -} - -#panel-orchestration .task-workspace-action-row { - display: grid; - grid-template-columns: minmax(0, 1fr) auto; - align-items: stretch; - gap: 0; - position: relative; -} - -#panel-orchestration .task-workspace-primary-action { - border-top-right-radius: 0; - border-bottom-right-radius: 0; - justify-content: center; - min-width: 0; -} - -#panel-orchestration .task-workspace-action-menu { - position: relative; -} - -#panel-orchestration .task-workspace-action-menu > summary { - list-style: none; -} - -#panel-orchestration .task-workspace-action-menu > summary::-webkit-details-marker { - display: none; -} - -#panel-orchestration .task-workspace-menu-trigger { - border-top-left-radius: 0; - border-bottom-left-radius: 0; - border-left-color: rgba(255, 255, 255, 0.32); - height: 100%; - min-width: 38px; - padding-inline: 10px; -} - -#panel-orchestration .task-workspace-menu-panel { - position: absolute; - z-index: 20; - top: calc(100% + 8px); - right: 0; - width: min(280px, calc(100vw - 32px)); - display: grid; - gap: 6px; - padding: 8px; - border: 1px solid rgba(148, 163, 184, 0.22); - border-radius: 14px; - background: rgba(255, 255, 255, 0.96); - box-shadow: 0 18px 42px rgba(15, 23, 42, 0.14); -} - -#panel-orchestration .task-workspace-menu-item { - width: 100%; - min-width: 0; - display: grid; - grid-template-columns: minmax(0, 1fr) auto; - gap: 2px 8px; - align-items: center; - padding: 8px 9px; - border: 1px solid rgba(148, 163, 184, 0.14); - border-radius: 12px; - background: rgba(248, 250, 252, 0.72); - color: var(--color-text-primary); - text-align: left; - cursor: pointer; -} - -#panel-orchestration .task-workspace-menu-item:hover, -#panel-orchestration .task-workspace-menu-item.active { - border-color: rgba(199, 116, 98, 0.34); - background: rgba(255, 255, 255, 0.94); -} - -#panel-orchestration .task-workspace-menu-title, -#panel-orchestration .task-workspace-menu-meta { - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -#panel-orchestration .task-workspace-menu-title { - font-size: 0.82rem; - font-weight: 800; -} - -#panel-orchestration .task-workspace-menu-meta { - grid-column: 1 / -1; - color: var(--color-text-muted); - font-size: 0.72rem; -} - -#panel-orchestration .task-project-sidebar-head, -#panel-orchestration .task-session-inbox-head { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 10px; -} - -#panel-orchestration .task-project-sidebar .skills-panel-note { - margin-top: 2px; - font-size: 0.76rem; - line-height: 1.4; -} - -#panel-orchestration .task-project-list, -#panel-orchestration .task-session-inbox { - display: flex; - flex-direction: column; - gap: 7px; -} - -#panel-orchestration .task-project-item, -#panel-orchestration .task-session-inbox-item { - width: 100%; - min-width: 0; - border: 1px solid rgba(148, 163, 184, 0.14); - border-radius: 14px; - background: rgba(248, 250, 252, 0.58); - color: var(--color-text-primary); - text-align: left; - cursor: pointer; - transition: border-color 0.16s ease, background 0.16s ease, transform 0.16s ease; -} - -#panel-orchestration .task-project-item { - display: grid; - gap: 3px; - padding: 9px 10px; -} - -#panel-orchestration .task-project-item:hover, -#panel-orchestration .task-session-inbox-item:hover, -#panel-orchestration .task-project-item.active { - border-color: rgba(199, 116, 98, 0.34); - background: rgba(255, 255, 255, 0.86); -} - -#panel-orchestration .task-project-item.active { - box-shadow: inset 3px 0 0 rgba(199, 116, 98, 0.56); -} - -#panel-orchestration .task-project-item-title, -#panel-orchestration .task-session-inbox-title { - display: block; - min-width: 0; - overflow: hidden; - color: var(--color-text-primary); - font-size: 0.86rem; - font-weight: 800; - text-overflow: ellipsis; - white-space: nowrap; -} - -#panel-orchestration .task-project-item-meta, -#panel-orchestration .task-session-inbox-meta, -#panel-orchestration .task-project-item-stats { - display: block; - min-width: 0; - overflow: hidden; - color: var(--color-text-muted); - font-size: 0.72rem; - line-height: 1.35; - text-overflow: ellipsis; - white-space: nowrap; -} - -#panel-orchestration .task-project-new-session { - width: 100%; - justify-content: center; -} - -#panel-orchestration .task-session-inbox { - padding-top: 2px; -} - -#panel-orchestration .task-session-inbox-item { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - padding: 8px 9px; -} - -#panel-orchestration .task-session-inbox-main { - min-width: 0; -} - -#panel-orchestration .task-session-empty { - padding: 10px; - border-radius: 14px; - background: rgba(248, 250, 252, 0.52); -} - -#panel-orchestration .task-quick-main { - min-width: 0; -} - -#panel-orchestration .task-quick-side-card { - display: flex; - flex-direction: column; - gap: 10px; - width: auto; - margin: 0; +#panel-orchestration.kilo-webview-mode { + min-height: calc(100vh - 92px); padding: 0; - border: 0; - background: transparent; -} - -#panel-orchestration .task-quick-side-card > .selector-section, -#panel-orchestration .task-quick-side-card > details, -#panel-orchestration .task-provider-status-card { - margin: 0; - padding: 12px; -} - -#panel-orchestration .task-side-workbench-card, -#panel-orchestration .task-side-settings-card { - max-height: none; -} - -#panel-orchestration .task-side-workbench-card .task-runtime-list, -#panel-orchestration .task-side-workbench-card .task-node-list { - max-height: 340px; - overflow: auto; -} - -#panel-orchestration .task-thread-composer { - width: min(700px, calc(100vw - 680px)); - min-width: 520px; -} - -@media (max-width: 1180px) { - #panel-orchestration { - --task-orchestration-main-width: 100%; - } - - #panel-orchestration .task-quick-card { - grid-template-columns: minmax(220px, 0.34fr) minmax(0, 0.66fr); - } - - #panel-orchestration .task-quick-side-card { - grid-column: 1 / -1; - position: static; - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - } - - #panel-orchestration .task-project-sidebar { - position: sticky; - } - - #panel-orchestration .task-thread-composer { - width: min(680px, calc(100vw - 360px)); - min-width: 420px; - } -} - -@media (max-width: 760px) { - #panel-orchestration .task-quick-card, - #panel-orchestration .task-quick-side-card { - display: flex; - flex-direction: column; - } - - #panel-orchestration .task-project-sidebar, - #panel-orchestration .task-quick-side-card { - position: static; - max-height: none; - } - - #panel-orchestration .task-thread-composer { - right: auto; - bottom: 10px; - left: auto; - width: 100%; - min-width: 0; - transform: none; - } -} - -/* Web Agent cockpit pass: the orchestration tab is an agent workbench, not a hidden task form. */ -#panel-orchestration { - --task-orchestration-main-width: 1440px; - --task-orchestration-project-width: 270px; - --task-orchestration-records-width: 340px; - - padding-bottom: 40px; -} - -#panel-orchestration .task-hero-card { - display: block; - max-width: var(--task-orchestration-main-width); - margin: -2px auto 14px; - border-color: rgba(59, 130, 246, 0.12); - background: - radial-gradient(circle at 12% 10%, rgba(59, 130, 246, 0.08), transparent 26%), - linear-gradient(135deg, rgba(255, 255, 255, 0.92), rgba(248, 250, 252, 0.76)); -} - -#panel-orchestration .task-hero-card .task-hero-kicker, -#panel-orchestration .task-hero-card .task-hero-copy { - display: block; -} - -#panel-orchestration .task-quick-card { - grid-template-columns: var(--task-orchestration-project-width) minmax(0, 1fr) var(--task-orchestration-records-width); - gap: 18px; -} - -#panel-orchestration .task-project-sidebar, -#panel-orchestration .task-quick-side-card { - top: 92px; - max-height: calc(100vh - 120px); -} - -#panel-orchestration .task-agent-cockpit { - margin-bottom: 12px; - padding: 14px; - border: 1px solid rgba(59, 130, 246, 0.14); - border-radius: 20px; - background: - linear-gradient(135deg, rgba(255, 255, 255, 0.92), rgba(239, 246, 255, 0.54)), - var(--color-surface); - box-shadow: 0 14px 38px rgba(15, 23, 42, 0.07); -} - -#panel-orchestration .task-agent-cockpit-head { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 12px; -} - -#panel-orchestration .task-agent-title { - font-size: clamp(18px, 1.45vw, 24px); - line-height: 1.18; -} - -#panel-orchestration .task-agent-copy { - display: block; - margin-top: 6px; - max-width: 760px; - line-height: 1.5; -} - -#panel-orchestration .task-agent-surface-grid, -#panel-orchestration .task-agent-trace-grid { - display: grid; - grid-template-columns: repeat(4, minmax(0, 1fr)); - gap: 8px; - margin-top: 12px; -} - -#panel-orchestration .task-agent-surface-card, -#panel-orchestration .task-agent-trace-card { - min-width: 0; - padding: 10px; - border: 1px solid rgba(148, 163, 184, 0.16); - border-radius: 14px; - background: rgba(255, 255, 255, 0.72); -} - -#panel-orchestration .task-agent-surface-card span, -#panel-orchestration .task-agent-trace-card span { - display: block; - color: var(--color-text-muted); - font-size: 10px; - font-weight: 800; - letter-spacing: 0.06em; - text-transform: uppercase; -} - -#panel-orchestration .task-agent-surface-card strong, -#panel-orchestration .task-agent-trace-card strong { - display: block; - min-width: 0; - margin-top: 5px; - overflow: hidden; - color: var(--color-text-primary); - font-size: 12px; - font-weight: 800; - line-height: 1.35; - text-overflow: ellipsis; - white-space: nowrap; -} - -#panel-orchestration .task-agent-trace-grid { - grid-template-columns: repeat(2, minmax(0, 1fr)); - margin: 10px 0 2px; -} - -#panel-orchestration .task-agent-trace-card { - padding: 8px 9px; - background: rgba(248, 250, 252, 0.72); -} - -#panel-orchestration .task-agent-trace-card strong { - font-size: 18px; -} - -#panel-orchestration .task-kilo-host-card { - margin-bottom: 12px; - padding: 14px; - border: 1px solid rgba(99, 102, 241, 0.18); - border-radius: 20px; - background: - radial-gradient(circle at top left, rgba(99, 102, 241, 0.10), transparent 32%), - linear-gradient(135deg, rgba(255, 255, 255, 0.94), rgba(248, 250, 252, 0.70)); - box-shadow: 0 16px 42px rgba(15, 23, 42, 0.08); -} - -#panel-orchestration .task-kilo-host-header, -#panel-orchestration .task-kilo-host-footer, -#panel-orchestration .task-kilo-surface-topbar { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 12px; -} - -#panel-orchestration .task-kilo-host-title { - font-size: clamp(17px, 1.35vw, 22px); - line-height: 1.2; -} - -#panel-orchestration .task-kilo-host-copy { - display: block; - margin-top: 6px; - max-width: 780px; - line-height: 1.48; + background: #0f172a; } -#panel-orchestration .task-kilo-host-shell { - display: grid; - grid-template-columns: 44px minmax(0, 1fr); - gap: 10px; - margin-top: 12px; +#panel-orchestration .kilo-webview-container { + height: calc(100vh - 92px); + min-height: 680px; overflow: hidden; border: 1px solid rgba(148, 163, 184, 0.20); - border-radius: 16px; - background: rgba(15, 23, 42, 0.04); + border-radius: 18px; + background: #0f172a; + color: #d4d4d4; } -#panel-orchestration .task-kilo-activity-rail { +#panel-orchestration .kilo-chat-view { display: flex; + height: 100%; + min-height: 0; flex-direction: column; - align-items: center; - gap: 8px; - padding: 10px 6px; - border-right: 1px solid rgba(148, 163, 184, 0.18); - background: rgba(15, 23, 42, 0.88); - color: rgba(226, 232, 240, 0.76); -} - -#panel-orchestration .task-kilo-activity-rail span { - display: grid; - width: 28px; - height: 28px; - place-items: center; - border-radius: 10px; - font-size: 13px; - font-weight: 900; -} - -#panel-orchestration .task-kilo-activity-rail span.active { - background: rgba(99, 102, 241, 0.92); - color: #fff; -} - -#panel-orchestration .task-kilo-chat-surface { - min-width: 0; - padding: 12px; - background: rgba(255, 255, 255, 0.76); -} - -#panel-orchestration .task-kilo-surface-topbar span, -#panel-orchestration .task-kilo-protocol-row span, -#panel-orchestration .task-kilo-context-item span { - color: var(--color-text-muted); - font-size: 10px; - font-weight: 850; - letter-spacing: 0.06em; - text-transform: uppercase; -} - -#panel-orchestration .task-kilo-surface-topbar strong { - color: var(--color-text-primary); - font-size: 12px; + background: #181818; + color: #d4d4d4; + --vscode-descriptionForeground: #9ca3af; + --vscode-button-background: #2563eb; + --vscode-button-foreground: #fff; + --vscode-button-hoverBackground: #1d4ed8; + --vscode-button-secondaryBackground: #2d2d2d; + --vscode-button-secondaryForeground: #d4d4d4; + --vscode-button-secondaryHoverBackground: #3a3a3a; + --kilo-font-size-12: 12px; + --kilo-font-size-13: 13px; + --kilo-font-size-16: 16px; +} + +#panel-orchestration [data-component="task-header"] { + flex-shrink: 0; + border-bottom: 1px solid #2d2d2d; + background: #1f1f1f; } -#panel-orchestration .task-kilo-context-grid, -#panel-orchestration .task-kilo-protocol-grid { +#panel-orchestration .kilo-task-header { display: grid; - grid-template-columns: repeat(4, minmax(0, 1fr)); - gap: 8px; - margin-top: 10px; + grid-template-columns: minmax(0, 1fr) auto; + gap: 8px 16px; + padding: 10px 14px 8px; } -#panel-orchestration .task-kilo-context-item, -#panel-orchestration .task-kilo-protocol-row { +#panel-orchestration [data-slot="task-header-title"] { min-width: 0; - padding: 10px; - border: 1px solid rgba(148, 163, 184, 0.16); - border-radius: 13px; - background: rgba(248, 250, 252, 0.78); } -#panel-orchestration .task-kilo-context-item strong, -#panel-orchestration .task-kilo-protocol-row strong { +#panel-orchestration [data-slot="task-header-title-label"] { display: block; - min-width: 0; - margin-top: 5px; overflow: hidden; - color: var(--color-text-primary); - font-size: 12px; - font-weight: 850; - line-height: 1.35; - text-overflow: ellipsis; - white-space: nowrap; -} - -#panel-orchestration .task-kilo-timeline { - display: grid; - grid-auto-flow: column; - grid-auto-columns: minmax(16px, 1fr); - gap: 4px; - margin-top: 10px; -} - -#panel-orchestration .task-kilo-timeline-bar { - height: 8px; - border-radius: 999px; - background: rgba(148, 163, 184, 0.42); -} - -#panel-orchestration .task-kilo-timeline-bar.success { - background: rgba(34, 197, 94, 0.72); -} - -#panel-orchestration .task-kilo-timeline-bar.warn { - background: rgba(245, 158, 11, 0.76); -} - -#panel-orchestration .task-kilo-timeline-bar.error { - background: rgba(239, 68, 68, 0.78); -} - -#panel-orchestration .task-kilo-host-footer { - align-items: center; - margin-top: 10px; - color: var(--color-text-muted); - font-size: 12px; - line-height: 1.45; -} - -#panel-orchestration .task-chat-panel { - min-height: calc(100vh - 330px); - border-radius: 22px; - background: - linear-gradient(180deg, rgba(255, 255, 255, 0.88), rgba(248, 250, 252, 0.66)), - var(--color-surface); -} - -#panel-orchestration .task-chat-thread { - padding-bottom: 18px; - scroll-padding-bottom: 120px; -} - -#panel-orchestration .task-thread-composer { - grid-template-columns: minmax(0, 1fr) auto; - right: auto; - bottom: 14px; - left: auto; - width: min(100%, 760px); - min-width: 0; - margin-top: 10px; - transform: none; - align-self: center; - border-color: rgba(59, 130, 246, 0.16); - border-radius: 18px; -} - -#panel-orchestration .task-chat-action-buttons { - justify-content: center; -} - -#panel-orchestration .task-chat-action-buttons .btn-tool { - min-width: 168px; -} - -#panel-orchestration .task-chat-action-buttons .btn-primary { - min-width: 168px; -} - -#panel-orchestration .task-chat-primary-button { - border-color: #1D4ED8; - background: #2563EB; - color: #FFFFFF; - box-shadow: 0 10px 22px rgba(37, 99, 235, 0.24); -} - -#panel-orchestration .task-chat-primary-button:hover:not(:disabled), -#panel-orchestration .task-chat-primary-button:focus-visible:not(:disabled) { - border-color: #1E40AF; - background: #1D4ED8; - color: #FFFFFF; -} - -#panel-orchestration .task-side-workbench-card .task-thread-run-details { - margin-top: 10px; -} - -#panel-orchestration .task-side-workbench-card .task-thread-run-details > summary { - background: rgba(239, 246, 255, 0.76); - color: #1D4ED8; -} - -#panel-orchestration .task-side-workbench-card .task-workbench-panel, -#panel-orchestration .task-side-workbench-card .task-runtime-list, -#panel-orchestration .task-side-workbench-card .task-node-list { - max-height: 360px; - overflow: auto; -} - -#panel-orchestration .task-side-settings-card { - border-style: dashed; -} - -@media (max-width: 1180px) { - #panel-orchestration .task-quick-card { - grid-template-columns: minmax(220px, 0.35fr) minmax(0, 0.65fr); - } - - #panel-orchestration .task-agent-surface-grid { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } - - #panel-orchestration .task-thread-composer { - width: min(100%, 720px); - min-width: 0; - } -} - -@media (max-width: 760px) { - #panel-orchestration { - padding-bottom: 72px; - } - - #panel-orchestration .task-hero-card { - margin-bottom: 10px; - padding: 12px; - } - - #panel-orchestration .task-agent-cockpit { - padding: 12px; - border-radius: 18px; - } - - #panel-orchestration .task-agent-cockpit-head { - flex-direction: column; - align-items: stretch; - } - - #panel-orchestration .task-agent-surface-grid, - #panel-orchestration .task-agent-trace-grid { - grid-template-columns: 1fr; - } - - #panel-orchestration .task-chat-thread { - padding-bottom: 16px; - scroll-padding-bottom: 130px; - } - - #panel-orchestration .task-thread-composer { - right: auto; - bottom: 10px; - left: auto; - width: 100%; - min-width: 0; - transform: none; - } - - #panel-orchestration .task-chat-action-buttons { - flex-direction: column; - gap: 6px; - } - - #panel-orchestration .task-chat-action-buttons .btn-tool { - width: 100%; - min-width: 0; - } -} - -/* Kilo-aligned orchestration rewrite */ -#panel-orchestration.task-kilo-mode { - --kilo-bg: #0f172a; - --kilo-sidebar: #111827; - --kilo-surface: #f8fafc; - --kilo-card: #ffffff; - --kilo-border: rgba(148, 163, 184, 0.22); - --kilo-muted: #64748b; - min-height: calc(100vh - 92px); - padding: 0; - background: #0b1020; + color: #f8fafc; + font-size: 13px; + font-weight: 700; + text-overflow: ellipsis; + white-space: nowrap; } -#panel-orchestration .task-kilo-app-shell { - display: grid; - grid-template-columns: 52px 280px minmax(0, 1fr) 320px; - width: min(100%, 1720px); - min-height: calc(100vh - 92px); - margin: 0 auto; +#panel-orchestration [data-slot="task-header-subtitle"] { + display: block; + margin-top: 3px; overflow: hidden; - border: 1px solid rgba(148, 163, 184, 0.16); - border-radius: 22px; - background: var(--kilo-surface); - box-shadow: 0 24px 70px rgba(2, 6, 23, 0.24); + color: #9ca3af; + font-size: 12px; + text-overflow: ellipsis; + white-space: nowrap; } -#panel-orchestration .task-kilo-activity-bar { +#panel-orchestration [data-slot="task-header-stats"] { display: flex; - flex-direction: column; align-items: center; - gap: 10px; - padding: 12px 8px; - background: #111827; - color: rgba(226, 232, 240, 0.78); + justify-content: flex-end; + gap: 8px; + color: #9ca3af; + font-size: 12px; } -#panel-orchestration .task-kilo-logo-mark, -#panel-orchestration .task-kilo-activity-item { - display: grid; - width: 34px; - height: 34px; - place-items: center; - border-radius: 12px; - font-weight: 900; +#panel-orchestration [data-slot="task-header-progress"] { + grid-column: 1 / -1; } -#panel-orchestration .task-kilo-logo-mark { - background: #6366f1; - color: #fff; - letter-spacing: -0.05em; +#panel-orchestration .kilo-task-timeline { + display: grid; + grid-auto-flow: column; + grid-auto-columns: minmax(12px, 1fr); + gap: 3px; } -#panel-orchestration .task-kilo-activity-item.active { - background: rgba(99, 102, 241, 0.24); - color: #fff; - box-shadow: inset 3px 0 0 #818cf8; +#panel-orchestration .kilo-task-timeline-bar { + height: 4px; + border-radius: 999px; + background: #3f3f46; } -#panel-orchestration .task-kilo-activity-spacer { - flex: 1; +#panel-orchestration .kilo-task-timeline-bar.success { background: #22c55e; } +#panel-orchestration .kilo-task-timeline-bar.warn { background: #f59e0b; } +#panel-orchestration .kilo-task-timeline-bar.error { background: #ef4444; } + +#panel-orchestration .kilo-icon-button, +#panel-orchestration .kilo-primary-button, +#panel-orchestration .kilo-secondary-button { + border: 1px solid transparent; + border-radius: 6px; + cursor: pointer; + font: inherit; } -#panel-orchestration .task-kilo-sidebar, -#panel-orchestration .task-kilo-inspector { - min-width: 0; - overflow: auto; - border-right: 1px solid var(--kilo-border); - background: #f1f5f9; +#panel-orchestration .kilo-icon-button { + display: grid; + width: 26px; + height: 26px; + place-items: center; + background: transparent; + color: #d4d4d4; } -#panel-orchestration .task-kilo-sidebar { - padding: 14px; +#panel-orchestration .kilo-icon-button:hover { + background: #2d2d2d; } -#panel-orchestration .task-kilo-inspector { - border-right: 0; - border-left: 1px solid var(--kilo-border); - padding: 14px; +#panel-orchestration .kilo-primary-button { + padding: 7px 12px; + background: #2563eb; + color: #fff; + font-weight: 700; } -#panel-orchestration .task-kilo-sidebar-head, -#panel-orchestration .task-kilo-session-head, -#panel-orchestration .task-kilo-task-header, -#panel-orchestration .task-kilo-header-actions, -#panel-orchestration .task-kilo-input-actions { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 10px; +#panel-orchestration .kilo-primary-button:hover { + background: #1d4ed8; } -#panel-orchestration .task-kilo-sidebar-title { - color: #0f172a; - font-size: 16px; - font-weight: 900; - line-height: 1.2; +#panel-orchestration .kilo-secondary-button { + padding: 7px 12px; + border-color: #3f3f46; + background: #2d2d2d; + color: #d4d4d4; } -#panel-orchestration .task-kilo-new-task { - width: 100%; - margin: 14px 0; - justify-content: center; +#panel-orchestration .kilo-primary-button:disabled, +#panel-orchestration .kilo-secondary-button:disabled, +#panel-orchestration .kilo-icon-button:disabled { + cursor: not-allowed; + opacity: 0.48; } -#panel-orchestration .task-kilo-sidebar-section, -#panel-orchestration .task-kilo-inspector-card { - margin-top: 12px; - padding: 12px; - border: 1px solid var(--kilo-border); - border-radius: 16px; - background: rgba(255, 255, 255, 0.76); +#panel-orchestration .kilo-settings-popover { + flex-shrink: 0; + padding: 12px 14px; + border-bottom: 1px solid #2d2d2d; + background: #181818; } -#panel-orchestration .task-kilo-section-label { - margin-bottom: 8px; - color: var(--kilo-muted); - font-size: 10px; - font-weight: 900; - letter-spacing: 0.07em; - text-transform: uppercase; +#panel-orchestration .kilo-settings-row { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 8px; } -#panel-orchestration .task-kilo-workspace-list, -#panel-orchestration .task-kilo-session-section { +#panel-orchestration .kilo-settings-row label { display: flex; + min-width: 0; flex-direction: column; - gap: 8px; + gap: 4px; + color: #9ca3af; + font-size: 11px; + font-weight: 700; } -#panel-orchestration .task-kilo-workspace-item, -#panel-orchestration .task-kilo-session-item { - display: flex; +#panel-orchestration .kilo-input { width: 100%; min-width: 0; - align-items: center; - justify-content: space-between; - gap: 8px; - padding: 10px; - border: 1px solid transparent; - border-radius: 13px; - background: transparent; - color: #0f172a; - cursor: pointer; - text-align: left; + border: 1px solid #3f3f46; + border-radius: 6px; + background: #111827; + color: #e5e7eb; + padding: 7px 8px; } -#panel-orchestration .task-kilo-workspace-item:hover, -#panel-orchestration .task-kilo-session-item:hover, -#panel-orchestration .task-kilo-workspace-item.active { - border-color: rgba(99, 102, 241, 0.24); - background: rgba(99, 102, 241, 0.08); +#panel-orchestration .task-sensitive-input { + -webkit-text-security: disc; + font-family: text-security-disc, system-ui, sans-serif; } -#panel-orchestration .task-kilo-workspace-title, -#panel-orchestration .task-kilo-session-item strong { - display: block; - overflow: hidden; - font-size: 12px; - font-weight: 900; - text-overflow: ellipsis; - white-space: nowrap; +#panel-orchestration .task-sensitive-input:focus { + -webkit-text-security: none; + font-family: inherit; } -#panel-orchestration .task-kilo-workspace-meta, -#panel-orchestration .task-kilo-workspace-counts, -#panel-orchestration .task-kilo-session-item small, -#panel-orchestration .task-kilo-empty-copy { - display: block; - margin-top: 3px; - overflow: hidden; - color: var(--kilo-muted); - font-size: 11px; - line-height: 1.35; - text-overflow: ellipsis; - white-space: nowrap; +#panel-orchestration .kilo-command-suggestions { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-top: 10px; } -#panel-orchestration .task-kilo-chat-view { - display: flex; - min-width: 0; +#panel-orchestration .chat-messages-wrapper { + position: relative; + flex: 1; min-height: 0; - flex-direction: column; - background: #f8fafc; + overflow: hidden; } -#panel-orchestration .task-kilo-task-header { - flex-shrink: 0; - padding: 18px 22px; - border-bottom: 1px solid var(--kilo-border); - background: rgba(255, 255, 255, 0.86); +#panel-orchestration .chat-messages, +#panel-orchestration .message-list-container { + height: 100%; + min-height: 0; + overflow: hidden; } -#panel-orchestration .task-kilo-task-header h2 { - margin: 2px 0 4px; - color: #0f172a; - font-size: 20px; - font-weight: 950; - letter-spacing: -0.03em; +#panel-orchestration .message-list { + height: 100%; + overflow-x: hidden; + overflow-y: auto; + padding: 14px clamp(18px, 4vw, 58px); + scrollbar-color: rgba(148, 163, 184, 0.32) transparent; + scrollbar-gutter: stable; + scrollbar-width: thin; } -#panel-orchestration .task-kilo-task-header p { - max-width: 760px; - margin: 0; - color: var(--kilo-muted); - font-size: 12px; - line-height: 1.45; +#panel-orchestration .message-list-content { + display: flex; + min-height: 100%; + flex-direction: column; + gap: 12px; } -#panel-orchestration .task-kilo-header-actions { +#panel-orchestration .message-list-content-empty { align-items: center; - flex-wrap: wrap; - justify-content: flex-end; -} - -#panel-orchestration .task-kilo-message-list { - flex: 1; - min-height: 0; - overflow: auto; - padding: 20px clamp(18px, 4vw, 56px); -} - -#panel-orchestration .task-kilo-welcome-card, -#panel-orchestration .task-kilo-message-card { - border: 1px solid var(--kilo-border); - border-radius: 18px; - background: var(--kilo-card); - box-shadow: 0 12px 30px rgba(15, 23, 42, 0.06); + justify-content: center; } -#panel-orchestration .task-kilo-welcome-card { - max-width: 860px; - margin: 42px auto; - padding: 28px; +#panel-orchestration .message-list-empty { + display: flex; + max-width: 720px; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 14px; + color: #9ca3af; text-align: center; } -#panel-orchestration .task-kilo-welcome-mark, -#panel-orchestration .task-kilo-avatar { +#panel-orchestration .kilo-logo { display: grid; + width: 52px; + height: 52px; place-items: center; border-radius: 14px; - background: #6366f1; + background: #2563eb; color: #fff; - font-weight: 950; + font-size: 24px; + font-weight: 900; } -#panel-orchestration .task-kilo-welcome-mark { - width: 48px; - height: 48px; - margin: 0 auto 12px; +#panel-orchestration .kilo-welcome-empty h3 { + margin: 0; + color: #f8fafc; font-size: 22px; } -#panel-orchestration .task-kilo-welcome-card h3 { - margin: 0 0 8px; - color: #0f172a; - font-size: 22px; - font-weight: 950; +#panel-orchestration .kilo-welcome-empty p { + max-width: 620px; + margin: 0; + line-height: 1.5; } -#panel-orchestration .task-kilo-welcome-card p { - max-width: 680px; - margin: 0 auto; - color: var(--kilo-muted); - line-height: 1.5; +#panel-orchestration .kilo-empty-actions, +#panel-orchestration .prompt-input-actions, +#panel-orchestration .kilo-status-dock { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 8px; } -#panel-orchestration .task-kilo-turn { +#panel-orchestration .kilo-context-strip { display: grid; - grid-template-columns: 34px minmax(0, 1fr); - gap: 10px; - max-width: 900px; - margin: 0 auto 14px; + width: min(100%, 760px); + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 8px; + margin-top: 4px; } -#panel-orchestration .task-kilo-turn.is-user { - max-width: 760px; - margin-right: clamp(0px, 5vw, 90px); +#panel-orchestration .kilo-context-strip span { + min-width: 0; + padding: 9px; + border: 1px solid #2d2d2d; + border-radius: 8px; + background: #1f1f1f; + text-align: left; } -#panel-orchestration .task-kilo-avatar { - width: 34px; - height: 34px; - font-size: 12px; +#panel-orchestration .kilo-context-strip small, +#panel-orchestration .kilo-message-heading, +#panel-orchestration .kilo-message-meta { + display: block; + color: #9ca3af; + font-size: 11px; } -#panel-orchestration .task-kilo-turn.is-user .task-kilo-avatar { - background: #0f172a; +#panel-orchestration .kilo-context-strip strong { + display: block; + overflow: hidden; + margin-top: 4px; + color: #e5e7eb; + font-size: 12px; + text-overflow: ellipsis; + white-space: nowrap; } -#panel-orchestration .task-kilo-message-card { +#panel-orchestration .vscode-session-turn { + display: flex; + flex-direction: column; + gap: 12px; + width: 100%; min-width: 0; - padding: 12px 14px; + padding: 0 4px 12px; } -#panel-orchestration .task-kilo-message-label { - color: var(--kilo-muted); - font-size: 10px; - font-weight: 900; - letter-spacing: 0.06em; - text-transform: uppercase; +#panel-orchestration .vscode-session-turn-user, +#panel-orchestration .vscode-session-turn-assistant { + max-width: min(100%, 920px); + border-radius: 10px; + color: #d4d4d4; +} + +#panel-orchestration .vscode-session-turn-user { + align-self: flex-end; + width: min(100%, 760px); + padding: 10px 12px; + background: #1f2937; +} + +#panel-orchestration .vscode-session-turn-assistant { + align-self: flex-start; + width: min(100%, 920px); + padding: 10px 0; } -#panel-orchestration .task-kilo-message-text { - margin-top: 5px; - color: #0f172a; +#panel-orchestration .kilo-message-heading { + margin-bottom: 4px; + font-weight: 700; +} + +#panel-orchestration .kilo-message-body { + color: #e5e7eb; font-size: 13px; line-height: 1.55; white-space: pre-wrap; } -#panel-orchestration .task-kilo-message-meta { - margin-top: 6px; - color: var(--kilo-muted); - font-size: 11px; +#panel-orchestration .kilo-plan-response { + border: 1px solid #2d2d2d; + border-radius: 10px; + background: #1f1f1f; + padding: 12px; } -#panel-orchestration .task-kilo-plan-summary { +#panel-orchestration .kilo-plan-summary-line { display: flex; align-items: center; gap: 8px; - margin-top: 10px; - color: #334155; + color: #d4d4d4; + font-size: 13px; +} + +#panel-orchestration .kilo-plan-summary-line span { + border-radius: 999px; + background: rgba(37, 99, 235, 0.18); + color: #93c5fd; + padding: 3px 8px; + font-size: 11px; + font-weight: 800; +} + +#panel-orchestration .kilo-redacted-block { + margin-top: 8px; + border: 1px solid rgba(245, 158, 11, 0.28); + border-radius: 8px; + background: rgba(245, 158, 11, 0.08); + color: #fbbf24; + padding: 8px; font-size: 12px; } -#panel-orchestration .task-kilo-plan-grid, -#panel-orchestration .task-kilo-context-grid, -#panel-orchestration .task-kilo-trace-grid, -#panel-orchestration .task-kilo-protocol-grid, -#panel-orchestration .task-kilo-settings-grid { - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); +#panel-orchestration .kilo-tool-list { + display: flex; + flex-direction: column; gap: 8px; margin-top: 10px; } -#panel-orchestration .task-kilo-context-grid { - grid-template-columns: repeat(4, minmax(0, 1fr)); +#panel-orchestration .kilo-tool-card { + border: 1px solid #2d2d2d; + border-radius: 8px; + background: #181818; + padding: 9px; } -#panel-orchestration .task-kilo-plan-node, -#panel-orchestration .task-kilo-context-item, -#panel-orchestration .task-kilo-trace-item, -#panel-orchestration .task-kilo-protocol-row { - min-width: 0; - padding: 10px; - border: 1px solid rgba(148, 163, 184, 0.18); - border-radius: 13px; - background: rgba(248, 250, 252, 0.82); +#panel-orchestration .kilo-tool-title { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; } -#panel-orchestration .task-kilo-plan-node span, -#panel-orchestration .task-kilo-plan-node small, -#panel-orchestration .task-kilo-context-item span, -#panel-orchestration .task-kilo-trace-item span, -#panel-orchestration .task-kilo-protocol-row span { - display: block; - color: var(--kilo-muted); - font-size: 10px; - font-weight: 850; - letter-spacing: 0.05em; +#panel-orchestration .kilo-tool-title span, +#panel-orchestration .kilo-tool-card small { + color: #9ca3af; + font-size: 11px; text-transform: uppercase; } -#panel-orchestration .task-kilo-plan-node strong, -#panel-orchestration .task-kilo-context-item strong, -#panel-orchestration .task-kilo-trace-item strong, -#panel-orchestration .task-kilo-protocol-row strong { - display: block; - min-width: 0; - margin-top: 4px; +#panel-orchestration .kilo-tool-title strong { overflow: hidden; - color: #0f172a; + color: #e5e7eb; font-size: 12px; - font-weight: 900; text-overflow: ellipsis; white-space: nowrap; } -#panel-orchestration .task-kilo-trace-item strong { - font-size: 20px; +#panel-orchestration .chat-input { + flex-shrink: 0; + border-top: 1px solid #2d2d2d; + background: #1f1f1f; + padding: 8px clamp(18px, 4vw, 58px) 14px; } -#panel-orchestration .task-kilo-chat-input { - flex-shrink: 0; - padding: 12px clamp(18px, 4vw, 56px) 18px; - border-top: 1px solid var(--kilo-border); - background: rgba(248, 250, 252, 0.94); +#panel-orchestration .kilo-status-dock { + margin-bottom: 8px; + color: #9ca3af; + font-size: 11px; +} + +#panel-orchestration .kilo-status-dock span { + border: 1px solid #2d2d2d; + border-radius: 999px; + background: #181818; + padding: 3px 8px; +} + +#panel-orchestration .prompt-input-container { + display: flex; + flex-direction: column; + gap: 8px; } -#panel-orchestration .task-kilo-composer { +#panel-orchestration .prompt-input-shell { display: grid; - grid-template-columns: 24px minmax(0, 1fr); + grid-template-columns: 22px minmax(0, 1fr); gap: 8px; - padding: 10px 12px; - border: 1px solid rgba(99, 102, 241, 0.22); - border-radius: 18px; - background: #fff; - box-shadow: 0 12px 28px rgba(15, 23, 42, 0.08); + border: 1px solid #3f3f46; + border-radius: 10px; + background: #181818; + padding: 9px 10px; } -#panel-orchestration .task-kilo-composer-glyph { - color: #6366f1; - font-size: 22px; +#panel-orchestration .prompt-input-prefix { + color: #60a5fa; + font-size: 20px; font-weight: 900; - line-height: 1.4; + line-height: 1.25; } -#panel-orchestration .task-kilo-composer textarea { - min-height: 68px; +#panel-orchestration .prompt-input-textarea { + min-height: 64px; + width: 100%; border: 0; - background: transparent; - box-shadow: none; + outline: 0; resize: vertical; + background: transparent; + color: #e5e7eb; + font: inherit; + font-size: 13px; + line-height: 1.5; } -#panel-orchestration .task-kilo-input-actions { - align-items: center; - margin-top: 10px; +#panel-orchestration .prompt-input-actions { + justify-content: flex-end; + color: #9ca3af; + font-size: 12px; } -#panel-orchestration .task-kilo-input-actions .task-field-hint { +#panel-orchestration .prompt-input-actions span { flex: 1; -} - -#panel-orchestration .task-kilo-timeline { - display: grid; - grid-auto-flow: column; - grid-auto-columns: minmax(14px, 1fr); - gap: 4px; - margin-top: 12px; -} - -#panel-orchestration .task-kilo-timeline-bar { - height: 8px; - border-radius: 999px; - background: rgba(148, 163, 184, 0.48); -} - -#panel-orchestration .task-kilo-timeline-bar.success { background: rgba(34, 197, 94, 0.72); } -#panel-orchestration .task-kilo-timeline-bar.warn { background: rgba(245, 158, 11, 0.78); } -#panel-orchestration .task-kilo-timeline-bar.error { background: rgba(239, 68, 68, 0.78); } - -#panel-orchestration .task-kilo-settings-card summary { - cursor: pointer; - color: #0f172a; - font-size: 13px; - font-weight: 900; -} - -@media (max-width: 1180px) { - #panel-orchestration .task-kilo-app-shell { - grid-template-columns: 48px 240px minmax(0, 1fr); - } - #panel-orchestration .task-kilo-inspector { - display: none; - } + min-width: 180px; } @media (max-width: 820px) { - #panel-orchestration.task-kilo-mode { - background: #f8fafc; - } - #panel-orchestration .task-kilo-app-shell { - display: flex; + #panel-orchestration.kilo-webview-mode { min-height: calc(100vh - 72px); - flex-direction: column; - border-radius: 0; - } - #panel-orchestration .task-kilo-activity-bar { - flex-direction: row; - } - #panel-orchestration .task-kilo-activity-spacer { - flex: 1; } - #panel-orchestration .task-kilo-sidebar { - max-height: 260px; - border-right: 0; - border-bottom: 1px solid var(--kilo-border); - } - #panel-orchestration .task-kilo-task-header, - #panel-orchestration .task-kilo-input-actions { - flex-direction: column; - align-items: stretch; - } - #panel-orchestration .task-kilo-message-list, - #panel-orchestration .task-kilo-chat-input { - padding-right: 12px; - padding-left: 12px; + + #panel-orchestration .kilo-webview-container { + height: calc(100vh - 72px); + min-height: 640px; + border-radius: 0; } - #panel-orchestration .task-kilo-context-grid, - #panel-orchestration .task-kilo-plan-grid, - #panel-orchestration .task-kilo-settings-grid { + + #panel-orchestration .kilo-task-header, + #panel-orchestration .kilo-settings-row, + #panel-orchestration .kilo-context-strip { grid-template-columns: 1fr; } + + #panel-orchestration [data-slot="task-header-stats"], + #panel-orchestration .prompt-input-actions { + justify-content: flex-start; + } } From c61777047f0bcc19be06708617639bebb6081845 Mon Sep 17 00:00:00 2001 From: awsl233777 Date: Tue, 30 Jun 2026 16:25:14 +0000 Subject: [PATCH 11/25] refactor(orchestration): remove legacy tab settings surface --- tests/unit/config-tabs-ui.test.mjs | 18 ++-- web-ui/modules/i18n/locales/en.mjs | 11 +-- web-ui/modules/i18n/locales/ja.mjs | 11 +-- web-ui/modules/i18n/locales/vi.mjs | 11 +-- web-ui/modules/i18n/locales/zh-tw.mjs | 11 +-- web-ui/modules/i18n/locales/zh.mjs | 11 +-- .../partials/index/panel-orchestration.html | 50 +--------- web-ui/res/web-ui-render.precompiled.js | 98 +------------------ web-ui/styles/task-orchestration.css | 52 ---------- 9 files changed, 33 insertions(+), 240 deletions(-) diff --git a/tests/unit/config-tabs-ui.test.mjs b/tests/unit/config-tabs-ui.test.mjs index eee7e520..cd99c239 100644 --- a/tests/unit/config-tabs-ui.test.mjs +++ b/tests/unit/config-tabs-ui.test.mjs @@ -150,20 +150,22 @@ test('config template keeps expected config tabs in top and side navigation', () assert.doesNotMatch(orchestrationPanel, /\{\{ node\.output\.(provider|model|endpoint)/); assert.doesNotMatch(orchestrationPanel, /\{\{ taskOrchestration\.(lastError|selectedRunError) \}\}/); assert.doesNotMatch(orchestrationPanel, /type="password"/); - assert.match(orchestrationPanel, /class="kilo-input task-sensitive-input" type="text" autocomplete="off"/); - assert.doesNotMatch(taskOrchestrationStyles, /type="password"/); - assert.match(taskOrchestrationStyles, /\.task-sensitive-input\s*\{[\s\S]*-webkit-text-security: disc;/); + assert.doesNotMatch(orchestrationPanel, /task-sensitive-input|kilo-input|kilo-settings-popover|kilo-settings-row|kilo-empty-actions/); + assert.doesNotMatch(orchestrationPanel, /startNewTaskWorkspaceSession|orchestration\.workspace\.action\.primary/); + assert.doesNotMatch(orchestrationPanel, /taskOrchestration\.(workspacePath|threadId|selectedEngine|workflowIdsText)"/); + assert.doesNotMatch(orchestrationPanel, /orchestration\.advanced\.(workspacePath|threadId|engine|runMode|open|title)/); + assert.doesNotMatch(taskOrchestrationStyles, /type="password"|task-sensitive-input|kilo-input|kilo-settings-popover|kilo-settings-row/); assert.doesNotMatch(precompiledRender, /_toDisplayString\(_ctx\.taskOrchestration\.(lastError|selectedRunError)\)/); assert.doesNotMatch(precompiledRender, /_toDisplayString\(node\.id\)/); assert.doesNotMatch(precompiledRender, /node\.workflowId/); assert.match(precompiledRender, /_toDisplayString\(_ctx\.t\('orchestration\.privacy\.detailsHidden'\)\)/); assert.doesNotMatch(precompiledRender, /type: "password"/); - assert.match(precompiledRender, /class: "kilo-input task-sensitive-input"/); + assert.doesNotMatch(precompiledRender, /kilo-input task-sensitive-input|kilo-settings-popover|kilo-settings-row/); assert.match(precompiledRender, /taskKiloWebviewHostSnapshot\.title/); assert.doesNotMatch(orchestrationPanel, /file\.(relativePath|path)/); - assert.match(orchestrationPanel, /@change="taskOrchestration\.selectedEngine === 'workflow' \? null : taskOrchestration\.workflowIdsText = ''"/); - assert.match(orchestrationPanel, /class="kilo-command-suggestions"/); - assert.match(orchestrationPanel, /@click="appendTaskWorkflowId\(workflow.id \|\| workflow.name\)"/); + assert.doesNotMatch(orchestrationPanel, /@change="taskOrchestration\.selectedEngine === 'workflow' \? null : taskOrchestration\.workflowIdsText = ''"/); + assert.doesNotMatch(orchestrationPanel, /class="kilo-command-suggestions"/); + assert.doesNotMatch(orchestrationPanel, /@click="appendTaskWorkflowId\(workflow.id \|\| workflow.name\)"/); assert.match(orchestrationPanel, /taskOrchestrationWorkspaceQueue\.length/); assert.match(orchestrationPanel, /taskOrchestrationWorkspaceRuns\.length/); assert.doesNotMatch(orchestrationPanel, /taskOrchestrationWorkspaceSessions\.length/); @@ -180,7 +182,7 @@ test('config template keeps expected config tabs in top and side navigation', () assert.match(styles, /#panel-orchestration \.chat-input\s*\{/); assert.match(styles, /#panel-orchestration \.prompt-input-container\s*\{/); assert.match(styles, /#panel-orchestration \.prompt-input-textarea\s*\{/); - assert.doesNotMatch(styles, /task-quick|task-workbench|task-agent|task-kilo-host|task-layout-grid|task-thread-composer|task-chat-thread/); + assert.doesNotMatch(styles, /task-quick|task-workbench|task-agent|task-kilo-host|task-layout-grid|task-thread-composer|task-chat-thread|kilo-empty-actions/); } for (const styles of [bundledStyles]) { assert.match(styles, /\.health-failed-provider-main input\s*\{[\s\S]*flex:\s*0 0 auto;[\s\S]*width:\s*13px;[\s\S]*height:\s*13px;[\s\S]*accent-color:\s*var\(--color-brand-dark\);/); diff --git a/web-ui/modules/i18n/locales/en.mjs b/web-ui/modules/i18n/locales/en.mjs index 13a160d9..c5ddfacc 100644 --- a/web-ui/modules/i18n/locales/en.mjs +++ b/web-ui/modules/i18n/locales/en.mjs @@ -1050,13 +1050,11 @@ const en = Object.freeze({ 'orchestration.agent.surface.trace': 'Trace', 'orchestration.agent.surface.runs': 'runs', 'orchestration.agent.surface.queue': 'queued', - 'orchestration.kilo.aria': 'Kilo-compatible task host', 'orchestration.kilo.kicker': 'Kilo webview host', - 'orchestration.kilo.header.title': 'Kilo-compatible task host', - 'orchestration.kilo.header.subtitle': 'VS Code-style webview shell with a privacy-safe task bridge.', + 'orchestration.kilo.header.title': 'Task Chat', + 'orchestration.kilo.header.subtitle': 'Plan, run, and track work from a VS Code-style chat surface.', 'orchestration.kilo.shell.aria': 'Kilo-compatible webview shell', - 'orchestration.kilo.surface.title': 'Webview surface', - 'orchestration.kilo.surface.host': 'Webview host adapter', + 'orchestration.kilo.surface.title': 'What should Codexmate work on?', 'orchestration.kilo.timeline.aria': 'Task activity timeline', 'orchestration.kilo.timeline.draft': 'Draft', 'orchestration.kilo.timeline.plan': 'Plan', @@ -1072,7 +1070,7 @@ const en = Object.freeze({ 'orchestration.kilo.engine.chat': 'Chat engine', 'orchestration.kilo.engine.workflow': 'Workflow engine', 'orchestration.kilo.engine.configured': 'Execution engine configured', - 'orchestration.kilo.bridge.copy': 'VSCode APIs are shimmed only for task orchestration; file, terminal, and external-open capabilities stay blocked.', + 'orchestration.kilo.bridge.copy': 'Describe the goal below. Codexmate will keep internal paths, IDs, provider details, and raw errors out of the visible chat.', 'orchestration.kilo.bridge.install': 'Reinstall shim', 'orchestration.agent.trace.title': 'Live trace', 'orchestration.agent.trace.plan': 'Plan nodes', @@ -1085,7 +1083,6 @@ const en = Object.freeze({ 'orchestration.workspace.subtitle': 'Pick a project, then create or resume sessions for it.', 'orchestration.workspace.selector': 'Workspace selector', 'orchestration.workspace.actions.aria': 'Workspace actions', - 'orchestration.workspace.action.primary': '+ New in workspace', 'orchestration.workspace.action.more': 'Choose workspace', 'orchestration.workspace.action.selected': 'Selected', 'orchestration.workspace.counts': '{runs} runs · {queue} queue', diff --git a/web-ui/modules/i18n/locales/ja.mjs b/web-ui/modules/i18n/locales/ja.mjs index ed0d87a9..962471f7 100644 --- a/web-ui/modules/i18n/locales/ja.mjs +++ b/web-ui/modules/i18n/locales/ja.mjs @@ -1032,7 +1032,6 @@ const ja = Object.freeze({ 'orchestration.workspace.subtitle': 'プロジェクトを選択し、記録を再開します。', 'orchestration.workspace.selector': 'ワークスペース選択', 'orchestration.workspace.actions.aria': 'ワークスペース操作', - 'orchestration.workspace.action.primary': '+ ワークスペースで新規作成', 'orchestration.workspace.action.more': 'ワークスペースを選択', 'orchestration.workspace.action.selected': '選択済み', 'orchestration.workspace.counts': '{runs} runs · {queue} queue', @@ -1057,13 +1056,11 @@ const ja = Object.freeze({ 'orchestration.agent.surface.trace': 'Trace', 'orchestration.agent.surface.runs': 'runs', 'orchestration.agent.surface.queue': 'queued', - 'orchestration.kilo.aria': 'Kilo 互換タスクホスト', 'orchestration.kilo.kicker': 'Kilo webview ホスト', - 'orchestration.kilo.header.title': 'Kilo 互換タスクホスト', - 'orchestration.kilo.header.subtitle': 'VS Code 風の webview シェルに、プライバシー安全なタスクブリッジを接続しています。', + 'orchestration.kilo.header.title': 'タスクチャット', + 'orchestration.kilo.header.subtitle': 'VS Code 風のチャット画面でタスクを計画、実行、追跡します。', 'orchestration.kilo.shell.aria': 'Kilo 互換 webview シェル', - 'orchestration.kilo.surface.title': 'Webview surface', - 'orchestration.kilo.surface.host': 'Webview ホストアダプター', + 'orchestration.kilo.surface.title': 'Codexmate に何をさせますか?', 'orchestration.kilo.timeline.aria': 'タスク活動タイムライン', 'orchestration.kilo.timeline.draft': '下書き', 'orchestration.kilo.timeline.plan': 'プラン', @@ -1079,7 +1076,7 @@ const ja = Object.freeze({ 'orchestration.kilo.engine.chat': 'チャットエンジン', 'orchestration.kilo.engine.workflow': 'ワークフローエンジン', 'orchestration.kilo.engine.configured': '実行エンジン設定済み', - 'orchestration.kilo.bridge.copy': 'VSCode API はタスク編成用にのみ shim され、ファイル、ターミナル、外部オープン機能はブロックされます。', + 'orchestration.kilo.bridge.copy': '下に目標を入力してください。Codexmate は内部パス、ID、provider 詳細、生エラーを表示チャットに出しません。', 'orchestration.kilo.bridge.install': 'shim を再適用', 'orchestration.agent.trace.title': 'Live trace', 'orchestration.agent.trace.plan': 'Plan nodes', diff --git a/web-ui/modules/i18n/locales/vi.mjs b/web-ui/modules/i18n/locales/vi.mjs index 4d5c87f3..47f435c7 100644 --- a/web-ui/modules/i18n/locales/vi.mjs +++ b/web-ui/modules/i18n/locales/vi.mjs @@ -1179,7 +1179,6 @@ const vi = Object.freeze({ 'orchestration.workspace.subtitle': 'Chọn dự án và tiếp tục bản ghi.', 'orchestration.workspace.selector': 'Bộ chọn workspace', 'orchestration.workspace.actions.aria': 'Thao tác workspace', - 'orchestration.workspace.action.primary': '+ Mới trong workspace', 'orchestration.workspace.action.more': 'Chọn workspace', 'orchestration.workspace.action.selected': 'Đã chọn', 'orchestration.workspace.counts': '{runs} runs · {queue} queue', @@ -1204,13 +1203,11 @@ const vi = Object.freeze({ 'orchestration.agent.surface.trace': 'Trace', 'orchestration.agent.surface.runs': 'runs', 'orchestration.agent.surface.queue': 'queued', - 'orchestration.kilo.aria': 'Máy chủ tác vụ tương thích Kilo', 'orchestration.kilo.kicker': 'Máy chủ webview Kilo', - 'orchestration.kilo.header.title': 'Máy chủ tác vụ tương thích Kilo', - 'orchestration.kilo.header.subtitle': 'Vỏ webview kiểu VS Code với cầu tác vụ an toàn cho quyền riêng tư.', + 'orchestration.kilo.header.title': 'Trò chuyện tác vụ', + 'orchestration.kilo.header.subtitle': 'Lập kế hoạch, chạy và theo dõi công việc từ giao diện chat kiểu VS Code.', 'orchestration.kilo.shell.aria': 'Vỏ webview tương thích Kilo', - 'orchestration.kilo.surface.title': 'Webview surface', - 'orchestration.kilo.surface.host': 'Bộ chuyển đổi máy chủ webview', + 'orchestration.kilo.surface.title': 'Bạn muốn Codexmate làm gì?', 'orchestration.kilo.timeline.aria': 'Dòng thời gian hoạt động tác vụ', 'orchestration.kilo.timeline.draft': 'Bản nháp', 'orchestration.kilo.timeline.plan': 'Kế hoạch', @@ -1226,7 +1223,7 @@ const vi = Object.freeze({ 'orchestration.kilo.engine.chat': 'Công cụ chat', 'orchestration.kilo.engine.workflow': 'Công cụ workflow', 'orchestration.kilo.engine.configured': 'Đã cấu hình công cụ thực thi', - 'orchestration.kilo.bridge.copy': 'API VSCode chỉ được shim cho điều phối tác vụ; tệp, terminal và mở ngoài vẫn bị chặn.', + 'orchestration.kilo.bridge.copy': 'Mô tả mục tiêu bên dưới. Codexmate sẽ không hiển thị đường dẫn nội bộ, ID, chi tiết provider hoặc lỗi thô trong chat.', 'orchestration.kilo.bridge.install': 'Cài lại shim', 'orchestration.agent.trace.title': 'Live trace', 'orchestration.agent.trace.plan': 'Plan nodes', diff --git a/web-ui/modules/i18n/locales/zh-tw.mjs b/web-ui/modules/i18n/locales/zh-tw.mjs index 71f069da..8571ce7b 100644 --- a/web-ui/modules/i18n/locales/zh-tw.mjs +++ b/web-ui/modules/i18n/locales/zh-tw.mjs @@ -1049,13 +1049,11 @@ const zhTw = Object.freeze({ 'orchestration.agent.surface.trace': 'Trace', 'orchestration.agent.surface.runs': 'runs', 'orchestration.agent.surface.queue': 'queued', - 'orchestration.kilo.aria': 'Kilo 相容任務宿主', 'orchestration.kilo.kicker': 'Kilo webview 宿主', - 'orchestration.kilo.header.title': 'Kilo 相容任務宿主', - 'orchestration.kilo.header.subtitle': 'VS Code 風格 webview 外殼,已接入隱私安全的任務橋。', + 'orchestration.kilo.header.title': '任務對話', + 'orchestration.kilo.header.subtitle': '用 VS Code 風格對話面規劃、執行並追蹤任務。', 'orchestration.kilo.shell.aria': 'Kilo 相容 webview 外殼', - 'orchestration.kilo.surface.title': 'Webview surface', - 'orchestration.kilo.surface.host': 'Webview 宿主適配器', + 'orchestration.kilo.surface.title': '要讓 Codexmate 做什麼?', 'orchestration.kilo.timeline.aria': '任務活動時間線', 'orchestration.kilo.timeline.draft': '草稿', 'orchestration.kilo.timeline.plan': '計畫', @@ -1071,7 +1069,7 @@ const zhTw = Object.freeze({ 'orchestration.kilo.engine.chat': '聊天引擎', 'orchestration.kilo.engine.workflow': '工作流程引擎', 'orchestration.kilo.engine.configured': '執行引擎已設定', - 'orchestration.kilo.bridge.copy': 'VSCode API 只為任務編排做 shim;檔案、終端機和外部開啟能力保持攔截。', + 'orchestration.kilo.bridge.copy': '在下方描述目標。Codexmate 會把內部路徑、ID、provider 細節和原始錯誤擋在可見對話外。', 'orchestration.kilo.bridge.install': '重裝 shim', 'orchestration.agent.trace.title': 'Live trace', 'orchestration.agent.trace.plan': 'Plan nodes', @@ -1084,7 +1082,6 @@ const zhTw = Object.freeze({ 'orchestration.workspace.subtitle': '選擇專案,再為該專案新建或恢復會話。', 'orchestration.workspace.selector': '工作區選擇', 'orchestration.workspace.actions.aria': '工作區操作', - 'orchestration.workspace.action.primary': '+ 在工作區新建', 'orchestration.workspace.action.more': '選擇工作區', 'orchestration.workspace.action.selected': '已選', 'orchestration.workspace.counts': '{runs} runs · {queue} queue', diff --git a/web-ui/modules/i18n/locales/zh.mjs b/web-ui/modules/i18n/locales/zh.mjs index 83d5c779..4845c46b 100644 --- a/web-ui/modules/i18n/locales/zh.mjs +++ b/web-ui/modules/i18n/locales/zh.mjs @@ -1049,13 +1049,11 @@ const zh = Object.freeze({ 'orchestration.agent.surface.trace': 'Trace', 'orchestration.agent.surface.runs': 'runs', 'orchestration.agent.surface.queue': 'queued', - 'orchestration.kilo.aria': 'Kilo 兼容任务宿主', 'orchestration.kilo.kicker': 'Kilo webview 宿主', - 'orchestration.kilo.header.title': 'Kilo 兼容任务宿主', - 'orchestration.kilo.header.subtitle': 'VS Code 风格 webview 外壳,已接入隐私安全的任务桥。', + 'orchestration.kilo.header.title': '任务对话', + 'orchestration.kilo.header.subtitle': '用 VS Code 风格对话面规划、执行并跟踪任务。', 'orchestration.kilo.shell.aria': 'Kilo 兼容 webview 外壳', - 'orchestration.kilo.surface.title': 'Webview surface', - 'orchestration.kilo.surface.host': 'Webview 宿主适配器', + 'orchestration.kilo.surface.title': '要让 Codexmate 做什么?', 'orchestration.kilo.timeline.aria': '任务活动时间线', 'orchestration.kilo.timeline.draft': '草稿', 'orchestration.kilo.timeline.plan': '计划', @@ -1071,7 +1069,7 @@ const zh = Object.freeze({ 'orchestration.kilo.engine.chat': '聊天引擎', 'orchestration.kilo.engine.workflow': '工作流引擎', 'orchestration.kilo.engine.configured': '执行引擎已配置', - 'orchestration.kilo.bridge.copy': 'VSCode API 只为任务编排做 shim;文件、终端和外部打开能力保持拦截。', + 'orchestration.kilo.bridge.copy': '在下方描述目标。Codexmate 会把内部路径、ID、provider 细节和原始错误挡在可见对话外。', 'orchestration.kilo.bridge.install': '重装 shim', 'orchestration.agent.trace.title': 'Live trace', 'orchestration.agent.trace.plan': 'Plan nodes', @@ -1084,7 +1082,6 @@ const zh = Object.freeze({ 'orchestration.workspace.subtitle': '选择项目,再为该项目新建或恢复会话。', 'orchestration.workspace.selector': '工作区选择', 'orchestration.workspace.actions.aria': '工作区操作', - 'orchestration.workspace.action.primary': '+ 在工作区新建', 'orchestration.workspace.action.more': '选择工作区', 'orchestration.workspace.action.selected': '已选', 'orchestration.workspace.counts': '{runs} runs · {queue} queue', diff --git a/web-ui/partials/index/panel-orchestration.html b/web-ui/partials/index/panel-orchestration.html index 24e5e6b8..be29fb83 100644 --- a/web-ui/partials/index/panel-orchestration.html +++ b/web-ui/partials/index/panel-orchestration.html @@ -24,9 +24,6 @@ -
@@ -39,43 +36,6 @@
-
-
- - - - -
-
- -
-
@@ -86,14 +46,6 @@

{{ t('orchestration.kilo.surface.title') }}

{{ t('orchestration.kilo.bridge.copy') }}

-
- - -
{{ item.label }} @@ -162,7 +114,7 @@

{{ t('orchestration.kilo.surface.title') }}

{{ taskOrchestrationDraftMetrics.hasTarget ? t('orchestration.chat.input.sequenceHint', { count: taskOrchestrationDraftMetrics.followUpCount + 2 }) : t('orchestration.chat.input.workHint') }} @@ -41,8 +41,8 @@
-
-
+
+

{{ t('orchestration.kilo.surface.title') }}

{{ t('orchestration.kilo.bridge.copy') }}

@@ -66,26 +66,10 @@

{{ t('orchestration.kilo.surface.title') }}

-
-
-
AI · {{ t('orchestration.plan.title') }}
-
{{ t('orchestration.privacy.detailsHidden') }}
-
- /plan - {{ t('orchestration.chat.assistant.planSummary', { nodes: taskOrchestration.plan.nodes.length, waves: taskOrchestration.plan.waves.length }) }} -
-
- {{ t('orchestration.privacy.detailsHidden') }} -
-
-
-
- {{ node.kind }} - {{ t('orchestration.privacy.summaryHidden') }} -
- {{ node.write ? t('orchestration.plan.node.write') : t('orchestration.plan.node.readOnly') }} -
-
+
+
+
AI
+
{{ t('orchestration.privacy.detailsHidden') }}
@@ -109,13 +93,10 @@

{{ t('orchestration.kilo.surface.title') }}

class="prompt-input-textarea" rows="3" :placeholder="t('orchestration.chat.input.placeholder')" - @keydown.enter.exact.prevent="submitTaskOrchestrationChatMessage()"> + @keydown.enter.exact.prevent="planAndRunTaskOrchestrationFromChat()">
{{ taskOrchestrationDraftMetrics.hasTarget ? t('orchestration.chat.input.sequenceHint', { count: taskOrchestrationDraftMetrics.followUpCount + 2 }) : t('orchestration.chat.input.workHint') }} - diff --git a/web-ui/res/web-ui-render.precompiled.js b/web-ui/res/web-ui-render.precompiled.js index 5b97fa0e..99700957 100644 --- a/web-ui/res/web-ui-render.precompiled.js +++ b/web-ui/res/web-ui-render.precompiled.js @@ -4020,7 +4020,7 @@ return function render(_ctx, _cache) { _createElementVNode("span", { class: _normalizeClass(['pill', _ctx.taskKiloWebviewHostSnapshot.statusTone]) }, _toDisplayString(_ctx.taskKiloWebviewHostSnapshot.status), 3 /* TEXT, CLASS */), - _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.kilo.trace.counts', { runs: _ctx.taskOrchestrationWorkspaceRuns.length, queue: _ctx.taskOrchestrationWorkspaceQueue.length, nodes: _ctx.taskOrchestrationSelectedRunNodes.length || (_ctx.taskOrchestration.plan ? _ctx.taskOrchestration.plan.nodes.length : 0) })), 1 /* TEXT */), + _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.kilo.trace.counts', { runs: _ctx.taskOrchestrationWorkspaceRuns.length, queue: _ctx.taskOrchestrationWorkspaceQueue.length, nodes: _ctx.taskOrchestrationSelectedRunNodes.length })), 1 /* TEXT */), _createElementVNode("button", { type: "button", class: "kilo-icon-button", @@ -4059,9 +4059,9 @@ return function render(_ctx, _cache) { "aria-label": _ctx.t('orchestration.chat.thread.aria') }, [ _createElementVNode("div", { - class: _normalizeClass(['message-list-content', { 'message-list-content-empty': !_ctx.taskOrchestrationConversationMessages.length && !(_ctx.taskOrchestration.plan || _ctx.taskOrchestration.planIssues.length || _ctx.taskOrchestration.planWarnings.length || _ctx.taskOrchestration.lastError) }]) + class: _normalizeClass(['message-list-content', { 'message-list-content-empty': !_ctx.taskOrchestrationConversationMessages.length && !_ctx.taskOrchestration.lastError }]) }, [ - (!_ctx.taskOrchestrationConversationMessages.length && !(_ctx.taskOrchestration.plan || _ctx.taskOrchestration.planIssues.length || _ctx.taskOrchestration.planWarnings.length || _ctx.taskOrchestration.lastError)) + (!_ctx.taskOrchestrationConversationMessages.length && !_ctx.taskOrchestration.lastError) ? (_openBlock(), _createElementBlock("div", { key: 0, class: "message-list-empty kilo-welcome-empty" @@ -4101,54 +4101,15 @@ return function render(_ctx, _cache) { ], 2 /* CLASS */) ], 8 /* PROPS */, ["data-row"])) }), 128 /* KEYED_FRAGMENT */)), - (_ctx.taskOrchestration.plan || _ctx.taskOrchestration.planIssues.length || _ctx.taskOrchestration.planWarnings.length || _ctx.taskOrchestration.lastError) + (_ctx.taskOrchestration.lastError) ? (_openBlock(), _createElementBlock("div", { key: 1, class: "vscode-session-turn", "data-row": "assistant" }, [ - _createElementVNode("div", { class: "vscode-session-turn-assistant kilo-plan-response" }, [ - _createElementVNode("div", { class: "kilo-message-heading" }, "AI · " + _toDisplayString(_ctx.t('orchestration.plan.title')), 1 /* TEXT */), - (_ctx.taskOrchestration.lastError) - ? (_openBlock(), _createElementBlock("div", { - key: 0, - class: "kilo-redacted-block" - }, _toDisplayString(_ctx.t('orchestration.privacy.detailsHidden')), 1 /* TEXT */)) - : _createCommentVNode("v-if", true), - (_ctx.taskOrchestration.plan) - ? (_openBlock(), _createElementBlock("div", { - key: 1, - class: "kilo-plan-summary-line" - }, [ - _createElementVNode("span", null, "/plan"), - _createElementVNode("strong", null, _toDisplayString(_ctx.t('orchestration.chat.assistant.planSummary', { nodes: _ctx.taskOrchestration.plan.nodes.length, waves: _ctx.taskOrchestration.plan.waves.length })), 1 /* TEXT */) - ])) - : _createCommentVNode("v-if", true), - (_ctx.taskOrchestration.planIssues.length || _ctx.taskOrchestration.planWarnings.length) - ? (_openBlock(), _createElementBlock("div", { - key: 2, - class: "kilo-redacted-block" - }, _toDisplayString(_ctx.t('orchestration.privacy.detailsHidden')), 1 /* TEXT */)) - : _createCommentVNode("v-if", true), - (_ctx.taskOrchestration.plan) - ? (_openBlock(), _createElementBlock("div", { - key: 3, - class: "kilo-tool-list" - }, [ - (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.taskOrchestration.plan.nodes, (node, index) => { - return (_openBlock(), _createElementBlock("div", { - key: 'plan-node-' + index, - class: "kilo-tool-card" - }, [ - _createElementVNode("div", { class: "kilo-tool-title" }, [ - _createElementVNode("span", null, _toDisplayString(node.kind), 1 /* TEXT */), - _createElementVNode("strong", null, _toDisplayString(_ctx.t('orchestration.privacy.summaryHidden')), 1 /* TEXT */) - ]), - _createElementVNode("small", null, _toDisplayString(node.write ? _ctx.t('orchestration.plan.node.write') : _ctx.t('orchestration.plan.node.readOnly')), 1 /* TEXT */) - ])) - }), 128 /* KEYED_FRAGMENT */)) - ])) - : _createCommentVNode("v-if", true) + _createElementVNode("div", { class: "vscode-session-turn-assistant kilo-status-response" }, [ + _createElementVNode("div", { class: "kilo-message-heading" }, "AI"), + _createElementVNode("div", { class: "kilo-redacted-block" }, _toDisplayString(_ctx.t('orchestration.privacy.detailsHidden')), 1 /* TEXT */) ]) ])) : _createCommentVNode("v-if", true) @@ -4175,19 +4136,13 @@ return function render(_ctx, _cache) { class: "prompt-input-textarea", rows: "3", placeholder: _ctx.t('orchestration.chat.input.placeholder'), - onKeydown: _withKeys(_withModifiers($event => (_ctx.submitTaskOrchestrationChatMessage()), ["exact","prevent"]), ["enter"]) + onKeydown: _withKeys(_withModifiers($event => (_ctx.planAndRunTaskOrchestrationFromChat()), ["exact","prevent"]), ["enter"]) }, null, 40 /* PROPS, NEED_HYDRATION */, ["onUpdate:modelValue", "placeholder", "onKeydown"]), [ [_vModelText, _ctx.taskOrchestration.chatDraft] ]) ], 8 /* PROPS */, ["aria-label"]), _createElementVNode("div", { class: "prompt-input-actions" }, [ _createElementVNode("span", null, _toDisplayString(_ctx.taskOrchestrationDraftMetrics.hasTarget ? _ctx.t('orchestration.chat.input.sequenceHint', { count: _ctx.taskOrchestrationDraftMetrics.followUpCount + 2 }) : _ctx.t('orchestration.chat.input.workHint')), 1 /* TEXT */), - _createElementVNode("button", { - type: "button", - class: "kilo-secondary-button", - onClick: $event => (_ctx.previewTaskPlanFromChat()), - disabled: _ctx.taskOrchestration.planning || _ctx.taskOrchestration.running || (!_ctx.taskOrchestration.target.trim() && !_ctx.taskOrchestration.chatDraft.trim()) - }, _toDisplayString(_ctx.taskOrchestration.planning ? _ctx.t('orchestration.actions.planning') : _ctx.t('orchestration.actions.previewOnly')), 9 /* TEXT, PROPS */, ["onClick", "disabled"]), _createElementVNode("button", { type: "button", class: "kilo-primary-button", From 950b68b502d5e2cf8db1c3b7d5fb14779dc5d3a8 Mon Sep 17 00:00:00 2001 From: awsl233777 Date: Tue, 30 Jun 2026 18:19:15 +0000 Subject: [PATCH 14/25] fix(orchestration): neutralize task chat surface copy --- tests/unit/web-ui-behavior-parity.test.mjs | 4 +-- tests/unit/web-ui-logic.test.mjs | 2 +- web-ui/modules/app.computed.main-tabs.mjs | 14 +++++----- web-ui/modules/i18n/locales/en.mjs | 26 +++++++++---------- web-ui/modules/i18n/locales/ja.mjs | 30 ++++++++++------------ web-ui/modules/i18n/locales/vi.mjs | 26 +++++++++---------- web-ui/modules/i18n/locales/zh-tw.mjs | 28 ++++++++++---------- web-ui/modules/i18n/locales/zh.mjs | 28 ++++++++++---------- web-ui/modules/task-kilo-webview-host.mjs | 4 +-- 9 files changed, 76 insertions(+), 86 deletions(-) diff --git a/tests/unit/web-ui-behavior-parity.test.mjs b/tests/unit/web-ui-behavior-parity.test.mjs index 7f6cf96e..1325d9c2 100644 --- a/tests/unit/web-ui-behavior-parity.test.mjs +++ b/tests/unit/web-ui-behavior-parity.test.mjs @@ -795,7 +795,7 @@ test('captured bundled app skeleton only exposes expected data key drift versus 'selectTaskWorkspace', 'startNewTaskWorkspaceSession', 'continueTaskWorkspaceSession', - 'previewTaskPlanFromChat', + 'previewTaskPlan' + 'FromChat', 'planAndRunTaskOrchestrationFromChat', 'handleTaskKiloWebviewMessage', 'installTaskKiloWebviewShim', @@ -835,7 +835,7 @@ test('captured bundled app skeleton only exposes expected data key drift versus 'confirmAddPromptTemplateVariable', 'onSessionResumeYoloChange', 'previewTaskPlan', - 'previewTaskPlanFromChat' + 'previewTaskPlan' + 'FromChat' ]; if (parityAgainstHead) { const allowedExtraMethodKeySet = new Set(allowedExtraCurrentMethodKeys); diff --git a/tests/unit/web-ui-logic.test.mjs b/tests/unit/web-ui-logic.test.mjs index 2535f685..412c9405 100644 --- a/tests/unit/web-ui-logic.test.mjs +++ b/tests/unit/web-ui-logic.test.mjs @@ -1860,7 +1860,7 @@ test('task Kilo webview host snapshot hides workspace, thread, ids, and raw run 'orchestration.privacy.workspace.selected': 'Workspace selected', 'orchestration.privacy.thread.selected': 'Thread selected', 'orchestration.engine.openaiChat': 'Codex', - 'orchestration.kilo.trace.counts': `${values.runs} runs · ${values.queue} queued · ${values.nodes} nodes`, + 'orchestration.kilo.trace.counts': `${values.runs} runs · ${values.queue} queued · ${values.nodes} steps`, 'orchestration.agent.state.ready': 'Ready' }; return messages[key] || key; diff --git a/web-ui/modules/app.computed.main-tabs.mjs b/web-ui/modules/app.computed.main-tabs.mjs index 4b007944..68450349 100644 --- a/web-ui/modules/app.computed.main-tabs.mjs +++ b/web-ui/modules/app.computed.main-tabs.mjs @@ -213,8 +213,8 @@ function createTaskConversationMessages(taskOrchestration, t = null) { messages.push({ id: 'assistant-empty', role: 'assistant', - label: translateTaskText(t, 'orchestration.chat.assistant.readyLabel', 'Codexmate'), - text: translateTaskText(t, 'orchestration.chat.assistant.empty', '先发第一个需求;如果还有第二个需求,继续发送,Codexmate 会按顺序处理并保留上下文。'), + label: translateTaskText(t, 'orchestration.chat.assistant.readyLabel', 'Task assistant'), + text: translateTaskText(t, 'orchestration.chat.assistant.empty', '先发第一个需求;如果还有第二个需求,继续发送,系统会按顺序处理并保留上下文。'), meta: translateTaskText(t, 'orchestration.chat.meta.order', '顺序执行 · 保留上下文') }); } @@ -241,7 +241,7 @@ function createTaskConversationMessages(taskOrchestration, t = null) { messages.push({ id: 'assistant-next', role: 'assistant', - label: translateTaskText(t, 'orchestration.chat.assistant.readyLabel', 'Codexmate'), + label: translateTaskText(t, 'orchestration.chat.assistant.readyLabel', 'Task assistant'), text: followUps.length > 0 ? translateTaskText(t, 'orchestration.chat.assistant.sequenceReady', '已收到多条需求;执行时会先完成需求 1,再带着上下文继续后续需求。') : translateTaskText(t, 'orchestration.chat.assistant.singleReady', '已收到第一条需求。继续发送可追加要求,或直接开始执行。'), @@ -284,7 +284,7 @@ function createTaskDraftChecklist(metrics, t = null) { done: workflowReady, detail: metrics.engine === 'workflow' ? (metrics.workflowCount > 0 ? translateTaskText(t, 'orchestration.readiness.workflow.done', `已选 ${metrics.workflowCount} 个 Workflow`, { count: metrics.workflowCount }) : translateTaskText(t, 'orchestration.readiness.workflow.missing', '还没选 Workflow ID')) - : translateTaskText(t, 'orchestration.readiness.engine.openaiChat', '使用 OpenAI Chat-compatible 节点') + : translateTaskText(t, 'orchestration.readiness.engine.openaiChat', '使用聊天执行步骤') }, { key: 'scope', @@ -300,7 +300,7 @@ function createTaskDraftChecklist(metrics, t = null) { done: readyToRun, detail: !metrics.hasPlan ? translateTaskText(t, 'orchestration.readiness.ready.missing', '还未开始') - : (metrics.planIssues.length > 0 ? translateTaskText(t, 'orchestration.readiness.ready.blocked', `有 ${metrics.planIssues.length} 个阻塞项`, { count: metrics.planIssues.length }) : translateTaskText(t, 'orchestration.readiness.ready.ready', `已就绪,${metrics.planNodeCount} 个节点`, { count: metrics.planNodeCount })) + : (metrics.planIssues.length > 0 ? translateTaskText(t, 'orchestration.readiness.ready.blocked', `有 ${metrics.planIssues.length} 个阻塞项`, { count: metrics.planIssues.length }) : translateTaskText(t, 'orchestration.readiness.ready.ready', `已就绪,${metrics.planNodeCount} 个步骤`, { count: metrics.planNodeCount })) } ]; } @@ -310,7 +310,7 @@ function createTaskDraftReadiness(metrics, t = null) { return { tone: 'neutral', title: translateTaskText(t, 'orchestration.readiness.empty.title', '先写目标'), - summary: translateTaskText(t, 'orchestration.readiness.empty.summary', '先把想完成的结果写清楚,再让编排器拆节点。') + summary: translateTaskText(t, 'orchestration.readiness.empty.summary', '先把想完成的结果写清楚,再拆成可执行步骤。') }; } if (metrics.engine === 'workflow' && metrics.workflowCount === 0) { @@ -333,7 +333,7 @@ function createTaskDraftReadiness(metrics, t = null) { return { tone: 'error', title: translateTaskText(t, 'orchestration.readiness.blocked.title', '请求有阻塞'), - summary: translateTaskText(t, 'orchestration.readiness.blocked.summary', `当前计划里还有 ${metrics.planIssues.length} 个阻塞项,先处理它们。`, { count: metrics.planIssues.length }) + summary: translateTaskText(t, 'orchestration.readiness.blocked.summary', `当前请求还有 ${metrics.planIssues.length} 个阻塞项,先处理它们。`, { count: metrics.planIssues.length }) }; } if (metrics.planWarnings.length > 0) { diff --git a/web-ui/modules/i18n/locales/en.mjs b/web-ui/modules/i18n/locales/en.mjs index 2adfc37d..edd86c5b 100644 --- a/web-ui/modules/i18n/locales/en.mjs +++ b/web-ui/modules/i18n/locales/en.mjs @@ -213,7 +213,7 @@ const en = Object.freeze({ 'subtitle.config': 'Manage local configs and models.', 'subtitle.sessions': 'Browse and export sessions.', 'subtitle.usage': 'View usage for the last 7/30 days.', - 'subtitle.orchestration': 'Chat with Codexmate, then start the task when ready.', + 'subtitle.orchestration': 'Describe the task, then start when ready.', 'subtitle.market': 'Manage local skills.', 'subtitle.plugins': 'Manage reusable prompt templates and plugins.', 'subtitle.docs': 'CLI install commands and troubleshooting.', @@ -1019,7 +1019,7 @@ const en = Object.freeze({ 'orchestration.chat.assistant.contextFallback': 'A previous task is selected. Continuing will reuse that task context.', 'orchestration.chat.assistant.resultLabel': 'Result', 'orchestration.chat.assistant.filesSummary': 'File operations: {files}', - 'orchestration.chat.assistant.readyLabel': 'Kilo', + 'orchestration.chat.assistant.readyLabel': 'Task assistant', 'orchestration.chat.assistant.empty': 'Send the first request. Add another message to continue with the same context.', 'orchestration.chat.assistant.sequenceReady': 'Multiple requests received. The session will finish request 1 first, then continue with the remaining context.', 'orchestration.chat.assistant.singleReady': 'Request received. Add another request or start work now.', @@ -1048,12 +1048,12 @@ const en = Object.freeze({ 'orchestration.agent.surface.queue': 'queued', 'orchestration.kilo.kicker': 'Kilo webview host', 'orchestration.kilo.header.title': 'Task Chat', - 'orchestration.kilo.header.subtitle': 'Plan, run, and track work from a VS Code-style chat surface.', + 'orchestration.kilo.header.subtitle': 'Run and track work from a VS Code-style chat surface.', 'orchestration.kilo.shell.aria': 'Kilo-compatible webview shell', - 'orchestration.kilo.surface.title': 'What should Codexmate work on?', + 'orchestration.kilo.surface.title': 'What should be done next?', 'orchestration.kilo.timeline.aria': 'Task activity timeline', 'orchestration.kilo.timeline.draft': 'Draft', - 'orchestration.kilo.timeline.plan': 'Plan', + 'orchestration.kilo.timeline.prepare': 'Prepared', 'orchestration.kilo.timeline.queue': 'Queue', 'orchestration.kilo.timeline.run': 'Run', 'orchestration.kilo.timeline.node': 'Node', @@ -1062,14 +1062,14 @@ const en = Object.freeze({ 'orchestration.kilo.protocol.whiteListedBridge': 'Whitelisted task bridge', 'orchestration.kilo.protocol.browserState': 'Browser state', 'orchestration.kilo.protocol.blocked': 'Blocked', - 'orchestration.kilo.trace.counts': '{runs} runs · {queue} queued · {nodes} nodes', + 'orchestration.kilo.trace.counts': '{runs} runs · {queue} queued · {nodes} steps', 'orchestration.kilo.engine.chat': 'Chat engine', 'orchestration.kilo.engine.workflow': 'Workflow engine', 'orchestration.kilo.engine.configured': 'Execution engine configured', - 'orchestration.kilo.bridge.copy': 'Describe the goal below. Codexmate will keep internal paths, IDs, provider details, and raw errors out of the visible chat.', + 'orchestration.kilo.bridge.copy': 'Describe the goal below. Internal paths, IDs, provider details, and raw errors stay out of the visible chat.', 'orchestration.kilo.bridge.install': 'Reinstall shim', 'orchestration.agent.trace.title': 'Live trace', - 'orchestration.agent.trace.plan': 'Plan nodes', + 'orchestration.agent.trace.prepare': 'Prepared steps', 'orchestration.agent.trace.queue': 'Queue', 'orchestration.agent.trace.runs': 'Runs', 'orchestration.agent.trace.nodes': 'Run nodes', @@ -1103,7 +1103,7 @@ const en = Object.freeze({ 'orchestration.readiness.sequence.single': 'Only request 1 is set; send another message to create request 2.', 'orchestration.readiness.sequence.multiple': '{count} requests will run in order: finish request 1, then continue request 2.', 'orchestration.readiness.engine.label': 'Execution strategy', - 'orchestration.readiness.engine.openaiChat': 'Use OpenAI Chat-compatible nodes', + 'orchestration.readiness.engine.openaiChat': 'Use chat execution steps', 'orchestration.readiness.workflow.done': '{count} workflow(s) selected', 'orchestration.readiness.workflow.missing': 'No Workflow ID selected yet', 'orchestration.readiness.scope.label': 'Scope', @@ -1113,7 +1113,7 @@ const en = Object.freeze({ 'orchestration.readiness.ready.label': 'Readiness', 'orchestration.readiness.ready.missing': 'Not started yet', 'orchestration.readiness.ready.blocked': '{count} blocker(s)', - 'orchestration.readiness.ready.ready': 'Ready, {count} node(s)', + 'orchestration.readiness.ready.ready': 'Ready, {count} step(s)', 'orchestration.readiness.empty.title': 'Write a task message first', 'orchestration.readiness.empty.summary': 'Define the objective, constraints, and acceptance criteria.', 'orchestration.readiness.workflow.title': 'Workflow missing', @@ -1121,7 +1121,7 @@ const en = Object.freeze({ 'orchestration.readiness.ready.startSummary': 'Objective is set. Start work when ready.', 'orchestration.readiness.ready.sequenceSummary': 'The draft is ready with {count} ordered requests: finish request 1, then continue request 2.', 'orchestration.readiness.blocked.title': 'Request has blockers', - 'orchestration.readiness.blocked.summary': 'The current plan still has {count} blocker(s). Resolve them first.', + 'orchestration.readiness.blocked.summary': 'The current request still has {count} blocker(s). Resolve them first.', 'orchestration.readiness.warn.title': 'Runnable with warnings', 'orchestration.readiness.warn.summary': 'The task is ready, with {count} warning(s) to note.', 'orchestration.readiness.dryRun.title': 'Read-only check mode', @@ -1154,7 +1154,7 @@ const en = Object.freeze({ 'orchestration.pills.sequenceCount': 'Sequential requests {count}', 'orchestration.pills.hasTitle': 'Title set', 'orchestration.pills.workflowCount': 'Workflows {count}', - 'orchestration.pills.planNodes': 'Plan {count} nodes', + 'orchestration.pills.preparedSteps': 'Prepared {count} steps', 'orchestration.step2.title': 'Choose how to run', 'orchestration.step2.subtitle': 'Common options are visible; the rest lives under Advanced.', 'orchestration.fields.engine': 'Engine', @@ -1186,8 +1186,6 @@ const en = Object.freeze({ 'orchestration.step3.subtitle': 'Start work or queue it for later.', 'orchestration.actions.planning': 'Planning...', 'orchestration.actions.preparing': 'Preparing...', - 'orchestration.actions.generatePlan': 'Send request', - 'orchestration.actions.planAndRun': 'Start work', 'orchestration.actions.processing': 'Working...', 'orchestration.actions.queueAndStart': 'Queue & start', 'orchestration.actions.caption': 'Execution refreshes the plan when needed; use queue mode for batch runs.', diff --git a/web-ui/modules/i18n/locales/ja.mjs b/web-ui/modules/i18n/locales/ja.mjs index 2fc783b5..5ce5550c 100644 --- a/web-ui/modules/i18n/locales/ja.mjs +++ b/web-ui/modules/i18n/locales/ja.mjs @@ -214,7 +214,7 @@ const ja = Object.freeze({ 'subtitle.config': 'ローカル設定とモデルの管理。', 'subtitle.sessions': 'セッションの閲覧とエクスポート。', 'subtitle.usage': '過去 7 / 30 日間の使用量を表示。', - 'subtitle.orchestration': 'Codexmate とチャットし、準備ができたらタスクを開始します。', + 'subtitle.orchestration': 'タスクを記述し、準備ができたら開始します。', 'subtitle.market': 'ローカル Skills の管理。', 'subtitle.plugins': 'テンプレート化されたプロンプトと再利用可能なプラグインの管理。', 'subtitle.docs': 'CLI インストールコマンドとトラブルシューティング。', @@ -1008,7 +1008,7 @@ const ja = Object.freeze({ 'orchestration.chat.assistant.contextFallback': '以前のタスクが選択されています。続行するとそのタスクのコンテキストを再利用します。', 'orchestration.chat.assistant.resultLabel': '実装結果', 'orchestration.chat.assistant.filesSummary': 'ファイル操作: {files}', - 'orchestration.chat.assistant.readyLabel': 'Kilo', + 'orchestration.chat.assistant.readyLabel': 'タスクアシスタント', 'orchestration.chat.assistant.empty': '最初の依頼を送信してください。続けて送信すると同じコンテキストで処理します。', 'orchestration.chat.assistant.sequenceReady': '複数の依頼を受信しました。依頼 1 を完了してから、残りのコンテキストで続行します。', 'orchestration.chat.assistant.singleReady': '依頼を受け取りました。追加依頼を送るか、このまま作業を開始できます。', @@ -1054,31 +1054,31 @@ const ja = Object.freeze({ 'orchestration.agent.surface.queue': 'queued', 'orchestration.kilo.kicker': 'Kilo webview ホスト', 'orchestration.kilo.header.title': 'タスクチャット', - 'orchestration.kilo.header.subtitle': 'VS Code 風のチャット画面でタスクを計画、実行、追跡します。', + 'orchestration.kilo.header.subtitle': 'VS Code 風のチャット画面でタスクを実行、追跡します。', 'orchestration.kilo.shell.aria': 'Kilo 互換 webview シェル', - 'orchestration.kilo.surface.title': 'Codexmate に何をさせますか?', + 'orchestration.kilo.surface.title': '次に何を処理しますか?', 'orchestration.kilo.timeline.aria': 'タスク活動タイムライン', 'orchestration.kilo.timeline.draft': '下書き', - 'orchestration.kilo.timeline.plan': 'プラン', + 'orchestration.kilo.timeline.prepare': '準備済み', 'orchestration.kilo.timeline.queue': 'キュー', 'orchestration.kilo.timeline.run': '実行', - 'orchestration.kilo.timeline.node': 'ノード', + 'orchestration.kilo.timeline.node': 'ステップ', 'orchestration.kilo.timeline.idle': '待機', 'orchestration.kilo.protocol.localShim': 'ローカル shim', 'orchestration.kilo.protocol.whiteListedBridge': '許可リスト付きタスクブリッジ', 'orchestration.kilo.protocol.browserState': 'ブラウザー状態', 'orchestration.kilo.protocol.blocked': 'ブロック済み', - 'orchestration.kilo.trace.counts': '{runs} 件の実行 · {queue} 件のキュー · {nodes} 件のノード', + 'orchestration.kilo.trace.counts': '{runs} 件の実行 · {queue} 件のキュー · {nodes} 件のステップ', 'orchestration.kilo.engine.chat': 'チャットエンジン', 'orchestration.kilo.engine.workflow': 'ワークフローエンジン', 'orchestration.kilo.engine.configured': '実行エンジン設定済み', - 'orchestration.kilo.bridge.copy': '下に目標を入力してください。Codexmate は内部パス、ID、provider 詳細、生エラーを表示チャットに出しません。', + 'orchestration.kilo.bridge.copy': '下に目標を入力してください。内部パス、ID、provider 詳細、生エラーは表示チャットに出ません。', 'orchestration.kilo.bridge.install': 'shim を再適用', 'orchestration.agent.trace.title': 'Live trace', - 'orchestration.agent.trace.plan': 'Plan nodes', + 'orchestration.agent.trace.prepare': '準備済みステップ', 'orchestration.agent.trace.queue': 'Queue', 'orchestration.agent.trace.runs': 'Runs', - 'orchestration.agent.trace.nodes': 'Run nodes', + 'orchestration.agent.trace.nodes': '実行ステップ', 'orchestration.draft.reset': '下書きをリセット', 'orchestration.summary.aria': 'タスクオーケストレーション概要', 'orchestration.summary.running': '実行中', @@ -1092,7 +1092,7 @@ const ja = Object.freeze({ 'orchestration.readiness.sequence.single': '現在は依頼 1 のみです。続けて送信すると依頼 2 になります。', 'orchestration.readiness.sequence.multiple': '{count} 件の依頼を順番に実行します: 依頼 1 を完了してから依頼 2 に進みます。', 'orchestration.readiness.engine.label': '実行方針', - 'orchestration.readiness.engine.openaiChat': 'OpenAI Chat 互換ノードで計画', + 'orchestration.readiness.engine.openaiChat': 'チャット実行ステップを使用', 'orchestration.readiness.workflow.done': '{count} 件の Workflow を選択済み', 'orchestration.readiness.workflow.missing': 'Workflow ID が未選択', 'orchestration.readiness.scope.label': '範囲', @@ -1102,7 +1102,7 @@ const ja = Object.freeze({ 'orchestration.readiness.ready.label': '準備完了', 'orchestration.readiness.ready.missing': 'まだ開始していません', 'orchestration.readiness.ready.blocked': '{count} 件のブロッカー', - 'orchestration.readiness.ready.ready': '準備完了、{count} ノード', + 'orchestration.readiness.ready.ready': '準備完了、{count} ステップ', 'orchestration.readiness.empty.title': 'タスク目標を入力', 'orchestration.readiness.empty.summary': '目標、制約、受け入れ条件を明確にしてください。', 'orchestration.readiness.workflow.title': 'Workflow が不足', @@ -1110,7 +1110,7 @@ const ja = Object.freeze({ 'orchestration.readiness.ready.startSummary': '目標は入力済みです。準備ができたら作業を開始してください。', 'orchestration.readiness.ready.sequenceSummary': '下書きはできており、{count} 件の順序付き依頼を保持しています: 依頼 1 を完了してから依頼 2 に進みます。', 'orchestration.readiness.blocked.title': 'リクエストにブロッカーがあります', - 'orchestration.readiness.blocked.summary': '現在の計画にはまだ {count} 件のブロッカーがあります。先に処理してください。', + 'orchestration.readiness.blocked.summary': '現在のリクエストにはまだ {count} 件のブロッカーがあります。先に処理してください。', 'orchestration.readiness.warn.title': '実行可能ですが警告あり', 'orchestration.readiness.warn.summary': 'タスクは準備完了ですが、{count} 件の注意があります。', 'orchestration.readiness.dryRun.title': '読み取り専用チェック', @@ -1143,7 +1143,7 @@ const ja = Object.freeze({ 'orchestration.pills.sequenceCount': '順序依頼 {count}', 'orchestration.pills.hasTitle': 'タイトルあり', 'orchestration.pills.workflowCount': 'Workflow {count}', - 'orchestration.pills.planNodes': '計画 {count} ノード', + 'orchestration.pills.preparedSteps': '準備済み {count} ステップ', 'orchestration.step2.title': '実行方法を選択', 'orchestration.step2.subtitle': 'AI が生成した計画を確認・調整します。', 'orchestration.fields.engine': 'エンジン', @@ -1175,8 +1175,6 @@ const ja = Object.freeze({ 'orchestration.step3.subtitle': '今すぐ開始するか、キューに追加します。', 'orchestration.actions.planning': '計画中...', 'orchestration.actions.preparing': '準備中...', - 'orchestration.actions.generatePlan': 'リクエスト送信', - 'orchestration.actions.planAndRun': '作業開始', 'orchestration.actions.processing': '処理中...', 'orchestration.actions.queueAndStart': 'キューに入れて開始', 'orchestration.actions.caption': '実行前に必要に応じて計画を更新します。バッチ処理はキューを使用します。', diff --git a/web-ui/modules/i18n/locales/vi.mjs b/web-ui/modules/i18n/locales/vi.mjs index 23bbae7e..5d326782 100644 --- a/web-ui/modules/i18n/locales/vi.mjs +++ b/web-ui/modules/i18n/locales/vi.mjs @@ -228,7 +228,7 @@ const vi = Object.freeze({ 'subtitle.config': 'Quản lý cấu hình và mô hình cục bộ.', 'subtitle.sessions': 'Duyệt và xuất các phiên.', 'subtitle.usage': 'Xem sử dụng trong 7/30 ngày gần đây.', - 'subtitle.orchestration': 'Chat với Codexmate, rồi bắt đầu tác vụ khi sẵn sàng.', + 'subtitle.orchestration': 'Mô tả tác vụ, rồi bắt đầu khi sẵn sàng.', 'subtitle.market': 'Quản lý skills cục bộ.', 'subtitle.plugins': 'Quản lý plugin và mẫu tái sử dụng.', 'subtitle.docs': 'Lệnh cài CLI và hướng dẫn.', @@ -310,7 +310,7 @@ const vi = Object.freeze({ 'orchestration.readiness.ready.startSummary': 'Mục tiêu đã có. Bắt đầu chạy khi sẵn sàng.', 'orchestration.readiness.ready.sequenceSummary': 'Bản nháp đã sẵn sàng với {count} yêu cầu theo thứ tự: hoàn tất yêu cầu 1 rồi tiếp tục yêu cầu 2.', 'orchestration.readiness.blocked.title': 'Yêu cầu có blocker', - 'orchestration.readiness.blocked.summary': 'Kế hoạch hiện còn {count} mục chặn, hãy xử lý chúng trước.', + 'orchestration.readiness.blocked.summary': 'Yêu cầu hiện còn {count} mục chặn, hãy xử lý trước.', 'orchestration.readiness.warn.title': 'Có thể chạy nhưng có cảnh báo', 'orchestration.readiness.warn.summary': 'Tác vụ đã sẵn sàng, có {count} cảnh báo cần lưu ý.', 'orchestration.readiness.dryRun.title': 'Chế độ kiểm tra chỉ đọc', @@ -1154,7 +1154,7 @@ const vi = Object.freeze({ 'orchestration.chat.assistant.contextFallback': 'Đã chọn một tác vụ cũ. Khi tiếp tục, phiên sẽ dùng lại ngữ cảnh tác vụ đó.', 'orchestration.chat.assistant.resultLabel': 'Kết quả', 'orchestration.chat.assistant.filesSummary': 'Thao tác file: {files}', - 'orchestration.chat.assistant.readyLabel': 'Kilo', + 'orchestration.chat.assistant.readyLabel': 'Trợ lý tác vụ', 'orchestration.chat.assistant.empty': 'Gửi yêu cầu đầu tiên. Gửi tiếp để tiếp tục với cùng ngữ cảnh.', 'orchestration.chat.assistant.sequenceReady': 'Đã nhận nhiều yêu cầu. Phiên sẽ hoàn tất yêu cầu 1 trước rồi tiếp tục với ngữ cảnh còn lại.', 'orchestration.chat.assistant.singleReady': 'Đã nhận yêu cầu. Thêm yêu cầu khác hoặc bắt đầu chạy ngay.', @@ -1200,31 +1200,31 @@ const vi = Object.freeze({ 'orchestration.agent.surface.queue': 'queued', 'orchestration.kilo.kicker': 'Máy chủ webview Kilo', 'orchestration.kilo.header.title': 'Trò chuyện tác vụ', - 'orchestration.kilo.header.subtitle': 'Lập kế hoạch, chạy và theo dõi công việc từ giao diện chat kiểu VS Code.', + 'orchestration.kilo.header.subtitle': 'Chạy và theo dõi công việc từ giao diện chat kiểu VS Code.', 'orchestration.kilo.shell.aria': 'Vỏ webview tương thích Kilo', - 'orchestration.kilo.surface.title': 'Bạn muốn Codexmate làm gì?', + 'orchestration.kilo.surface.title': 'Tiếp theo cần xử lý gì?', 'orchestration.kilo.timeline.aria': 'Dòng thời gian hoạt động tác vụ', 'orchestration.kilo.timeline.draft': 'Bản nháp', - 'orchestration.kilo.timeline.plan': 'Kế hoạch', + 'orchestration.kilo.timeline.prepare': 'Đã chuẩn bị', 'orchestration.kilo.timeline.queue': 'Hàng đợi', 'orchestration.kilo.timeline.run': 'Lượt chạy', - 'orchestration.kilo.timeline.node': 'Nút', + 'orchestration.kilo.timeline.node': 'Bước', 'orchestration.kilo.timeline.idle': 'Rảnh', 'orchestration.kilo.protocol.localShim': 'Shim cục bộ', 'orchestration.kilo.protocol.whiteListedBridge': 'Cầu tác vụ theo danh sách cho phép', 'orchestration.kilo.protocol.browserState': 'Trạng thái trình duyệt', 'orchestration.kilo.protocol.blocked': 'Đã chặn', - 'orchestration.kilo.trace.counts': '{runs} lượt chạy · {queue} đang đợi · {nodes} nút', + 'orchestration.kilo.trace.counts': '{runs} lượt chạy · {queue} đang đợi · {nodes} bước', 'orchestration.kilo.engine.chat': 'Công cụ chat', 'orchestration.kilo.engine.workflow': 'Công cụ workflow', 'orchestration.kilo.engine.configured': 'Đã cấu hình công cụ thực thi', - 'orchestration.kilo.bridge.copy': 'Mô tả mục tiêu bên dưới. Codexmate sẽ không hiển thị đường dẫn nội bộ, ID, chi tiết provider hoặc lỗi thô trong chat.', + 'orchestration.kilo.bridge.copy': 'Mô tả mục tiêu bên dưới. Đường dẫn nội bộ, ID, chi tiết provider và lỗi thô sẽ không hiển thị trong chat.', 'orchestration.kilo.bridge.install': 'Cài lại shim', 'orchestration.agent.trace.title': 'Live trace', - 'orchestration.agent.trace.plan': 'Plan nodes', + 'orchestration.agent.trace.prepare': 'Các bước đã chuẩn bị', 'orchestration.agent.trace.queue': 'Queue', 'orchestration.agent.trace.runs': 'Runs', - 'orchestration.agent.trace.nodes': 'Run nodes', + 'orchestration.agent.trace.nodes': 'Các bước chạy', 'orchestration.draft.reset': 'Đặt lại bản nháp', 'orchestration.summary.aria': 'Tóm tắt điều phối tác vụ', 'orchestration.summary.running': 'Đang chạy', @@ -1255,7 +1255,7 @@ const vi = Object.freeze({ 'orchestration.pills.sequenceCount': 'Yêu cầu tuần tự {count}', 'orchestration.pills.hasTitle': 'Có tiêu đề', 'orchestration.pills.workflowCount': 'Workflows {count}', - 'orchestration.pills.planNodes': 'Kế hoạch {count} nút', + 'orchestration.pills.preparedSteps': 'Đã chuẩn bị {count} bước', 'orchestration.step2.title': 'Chọn cách chạy', 'orchestration.step2.subtitle': 'Tùy chọn phổ biến hiển thị; phần còn lại trong Nâng cao.', 'orchestration.fields.engine': 'Engine', @@ -1287,8 +1287,6 @@ const vi = Object.freeze({ 'orchestration.step3.subtitle': 'Bắt đầu ngay hoặc đưa vào hàng đợi.', 'orchestration.actions.planning': 'Đang lập kế hoạch...', 'orchestration.actions.preparing': 'Đang chuẩn bị...', - 'orchestration.actions.generatePlan': 'Gửi yêu cầu', - 'orchestration.actions.planAndRun': 'Bắt đầu chạy', 'orchestration.actions.processing': 'Đang xử lý...', 'orchestration.actions.queueAndStart': 'Xếp hàng & bắt đầu', 'orchestration.actions.caption': 'Khi chạy, kế hoạch được làm mới nếu cần; dùng hàng đợi cho tác vụ hàng loạt.', diff --git a/web-ui/modules/i18n/locales/zh-tw.mjs b/web-ui/modules/i18n/locales/zh-tw.mjs index 5997e6ce..c18ade0b 100644 --- a/web-ui/modules/i18n/locales/zh-tw.mjs +++ b/web-ui/modules/i18n/locales/zh-tw.mjs @@ -1018,7 +1018,7 @@ const zhTw = Object.freeze({ 'orchestration.chat.assistant.contextFallback': '已選擇歷史任務;繼續時會復用該任務上下文。', 'orchestration.chat.assistant.resultLabel': '實作結果', 'orchestration.chat.assistant.filesSummary': '檔案操作:{files}', - 'orchestration.chat.assistant.readyLabel': 'Kilo', + 'orchestration.chat.assistant.readyLabel': '任務助手', 'orchestration.chat.assistant.empty': '傳送第一個請求。有後續請求時繼續傳送,會沿用同一上下文。', 'orchestration.chat.assistant.sequenceReady': '已收到多個請求。會先完成請求 1,再帶著剩餘上下文繼續。', 'orchestration.chat.assistant.singleReady': '已收到需求。可繼續追加,或直接開始執行。', @@ -1047,31 +1047,31 @@ const zhTw = Object.freeze({ 'orchestration.agent.surface.queue': 'queued', 'orchestration.kilo.kicker': 'Kilo webview 宿主', 'orchestration.kilo.header.title': '任務對話', - 'orchestration.kilo.header.subtitle': '用 VS Code 風格對話面規劃、執行並追蹤任務。', + 'orchestration.kilo.header.subtitle': '用 VS Code 風格對話面執行並追蹤任務。', 'orchestration.kilo.shell.aria': 'Kilo 相容 webview 外殼', - 'orchestration.kilo.surface.title': '要讓 Codexmate 做什麼?', + 'orchestration.kilo.surface.title': '接下來要處理什麼?', 'orchestration.kilo.timeline.aria': '任務活動時間線', 'orchestration.kilo.timeline.draft': '草稿', - 'orchestration.kilo.timeline.plan': '計畫', + 'orchestration.kilo.timeline.prepare': '已準備', 'orchestration.kilo.timeline.queue': '佇列', 'orchestration.kilo.timeline.run': '執行', - 'orchestration.kilo.timeline.node': '節點', + 'orchestration.kilo.timeline.node': '步驟', 'orchestration.kilo.timeline.idle': '閒置', 'orchestration.kilo.protocol.localShim': '本地 shim', 'orchestration.kilo.protocol.whiteListedBridge': '白名單任務橋', 'orchestration.kilo.protocol.browserState': '瀏覽器狀態', 'orchestration.kilo.protocol.blocked': '已攔截', - 'orchestration.kilo.trace.counts': '{runs} 個執行 · {queue} 個排隊 · {nodes} 個節點', + 'orchestration.kilo.trace.counts': '{runs} 個執行 · {queue} 個排隊 · {nodes} 個步驟', 'orchestration.kilo.engine.chat': '聊天引擎', 'orchestration.kilo.engine.workflow': '工作流程引擎', 'orchestration.kilo.engine.configured': '執行引擎已設定', - 'orchestration.kilo.bridge.copy': '在下方描述目標。Codexmate 會把內部路徑、ID、provider 細節和原始錯誤擋在可見對話外。', + 'orchestration.kilo.bridge.copy': '在下方描述目標。內部路徑、ID、provider 細節和原始錯誤不會進入可見對話。', 'orchestration.kilo.bridge.install': '重裝 shim', 'orchestration.agent.trace.title': 'Live trace', - 'orchestration.agent.trace.plan': 'Plan nodes', + 'orchestration.agent.trace.prepare': '已準備步驟', 'orchestration.agent.trace.queue': 'Queue', 'orchestration.agent.trace.runs': 'Runs', - 'orchestration.agent.trace.nodes': 'Run nodes', + 'orchestration.agent.trace.nodes': '執行步驟', 'orchestration.quick.status.title': '目前草稿', 'orchestration.workspace.aria': '工作區與會話', 'orchestration.workspace.title': '工作區', @@ -1102,7 +1102,7 @@ const zhTw = Object.freeze({ 'orchestration.readiness.sequence.single': '目前只有需求 1;繼續傳送會變成需求 2。', 'orchestration.readiness.sequence.multiple': '{count} 條需求會按順序執行:先完成需求 1,再繼續需求 2。', 'orchestration.readiness.engine.label': '執行策略', - 'orchestration.readiness.engine.openaiChat': '使用 OpenAI Chat-compatible 節點', + 'orchestration.readiness.engine.openaiChat': '使用聊天執行步驟', 'orchestration.readiness.workflow.done': '已選 {count} 個 Workflow', 'orchestration.readiness.workflow.missing': '還沒選 Workflow ID', 'orchestration.readiness.scope.label': '邊界', @@ -1112,7 +1112,7 @@ const zhTw = Object.freeze({ 'orchestration.readiness.ready.label': '就緒', 'orchestration.readiness.ready.missing': '還未開始', 'orchestration.readiness.ready.blocked': '有 {count} 個阻塞項', - 'orchestration.readiness.ready.ready': '已就緒,{count} 個節點', + 'orchestration.readiness.ready.ready': '已就緒,{count} 個步驟', 'orchestration.readiness.empty.title': '填寫任務目標', 'orchestration.readiness.empty.summary': '先明確目標、約束和驗收標準。', 'orchestration.readiness.workflow.title': '缺少 Workflow', @@ -1120,7 +1120,7 @@ const zhTw = Object.freeze({ 'orchestration.readiness.ready.startSummary': '目標已填寫,準備好後即可開始執行。', 'orchestration.readiness.ready.sequenceSummary': '草稿已成形,已鎖定 {count} 條順序需求:先完成需求 1,再繼續需求 2。', 'orchestration.readiness.blocked.title': '請求有阻塞', - 'orchestration.readiness.blocked.summary': '目前計劃裡還有 {count} 個阻塞項,先處理它們。', + 'orchestration.readiness.blocked.summary': '目前請求還有 {count} 個阻塞項,先處理它們。', 'orchestration.readiness.warn.title': '可以執行,但有提醒', 'orchestration.readiness.warn.summary': '任務已準備好,還有 {count} 條提醒值得留意。', 'orchestration.readiness.dryRun.title': '唯讀檢查模式', @@ -1153,7 +1153,7 @@ const zhTw = Object.freeze({ 'orchestration.pills.sequenceCount': '順序需求 {count}', 'orchestration.pills.hasTitle': '標題已設', 'orchestration.pills.workflowCount': 'Workflow {count}', - 'orchestration.pills.planNodes': '計劃 {count} 節點', + 'orchestration.pills.preparedSteps': '已準備 {count} 個步驟', 'orchestration.step2.title': '選擇執行方式', 'orchestration.step2.subtitle': '常用項預設展開,其餘放高級設定。', 'orchestration.fields.engine': '引擎', @@ -1185,8 +1185,6 @@ const zhTw = Object.freeze({ 'orchestration.step3.subtitle': '立即開始,或加入佇列稍後執行。', 'orchestration.actions.planning': '規劃中...', 'orchestration.actions.preparing': '準備中...', - 'orchestration.actions.generatePlan': '送出請求', - 'orchestration.actions.planAndRun': '開始執行', 'orchestration.actions.processing': '處理中...', 'orchestration.actions.queueAndStart': '入隊並開始', 'orchestration.actions.caption': '執行前會按需刷新計畫;批量任務可入隊執行。', diff --git a/web-ui/modules/i18n/locales/zh.mjs b/web-ui/modules/i18n/locales/zh.mjs index 7ffa7fcd..c7b64d52 100644 --- a/web-ui/modules/i18n/locales/zh.mjs +++ b/web-ui/modules/i18n/locales/zh.mjs @@ -1018,7 +1018,7 @@ const zh = Object.freeze({ 'orchestration.chat.assistant.contextFallback': '已选择历史任务;继续时会复用该任务上下文。', 'orchestration.chat.assistant.resultLabel': '实现结果', 'orchestration.chat.assistant.filesSummary': '文件操作:{files}', - 'orchestration.chat.assistant.readyLabel': 'Kilo', + 'orchestration.chat.assistant.readyLabel': '任务助手', 'orchestration.chat.assistant.empty': '发送第一个请求。还有后续请求时继续发送,会沿用同一上下文。', 'orchestration.chat.assistant.sequenceReady': '已收到多个请求。会先完成请求 1,再带着剩余上下文继续。', 'orchestration.chat.assistant.singleReady': '已收到需求。可继续追加,或直接开始执行。', @@ -1047,31 +1047,31 @@ const zh = Object.freeze({ 'orchestration.agent.surface.queue': 'queued', 'orchestration.kilo.kicker': 'Kilo webview 宿主', 'orchestration.kilo.header.title': '任务对话', - 'orchestration.kilo.header.subtitle': '用 VS Code 风格对话面规划、执行并跟踪任务。', + 'orchestration.kilo.header.subtitle': '用 VS Code 风格对话面执行并跟踪任务。', 'orchestration.kilo.shell.aria': 'Kilo 兼容 webview 外壳', - 'orchestration.kilo.surface.title': '要让 Codexmate 做什么?', + 'orchestration.kilo.surface.title': '接下来要处理什么?', 'orchestration.kilo.timeline.aria': '任务活动时间线', 'orchestration.kilo.timeline.draft': '草稿', - 'orchestration.kilo.timeline.plan': '计划', + 'orchestration.kilo.timeline.prepare': '已准备', 'orchestration.kilo.timeline.queue': '队列', 'orchestration.kilo.timeline.run': '运行', - 'orchestration.kilo.timeline.node': '节点', + 'orchestration.kilo.timeline.node': '步骤', 'orchestration.kilo.timeline.idle': '空闲', 'orchestration.kilo.protocol.localShim': '本地 shim', 'orchestration.kilo.protocol.whiteListedBridge': '白名单任务桥', 'orchestration.kilo.protocol.browserState': '浏览器状态', 'orchestration.kilo.protocol.blocked': '已拦截', - 'orchestration.kilo.trace.counts': '{runs} 个运行 · {queue} 个排队 · {nodes} 个节点', + 'orchestration.kilo.trace.counts': '{runs} 个运行 · {queue} 个排队 · {nodes} 个步骤', 'orchestration.kilo.engine.chat': '聊天引擎', 'orchestration.kilo.engine.workflow': '工作流引擎', 'orchestration.kilo.engine.configured': '执行引擎已配置', - 'orchestration.kilo.bridge.copy': '在下方描述目标。Codexmate 会把内部路径、ID、provider 细节和原始错误挡在可见对话外。', + 'orchestration.kilo.bridge.copy': '在下方描述目标。内部路径、ID、provider 细节和原始错误不会进入可见对话。', 'orchestration.kilo.bridge.install': '重装 shim', 'orchestration.agent.trace.title': 'Live trace', - 'orchestration.agent.trace.plan': 'Plan nodes', + 'orchestration.agent.trace.prepare': '已准备步骤', 'orchestration.agent.trace.queue': 'Queue', 'orchestration.agent.trace.runs': 'Runs', - 'orchestration.agent.trace.nodes': 'Run nodes', + 'orchestration.agent.trace.nodes': '运行步骤', 'orchestration.quick.status.title': '当前草稿', 'orchestration.workspace.aria': '工作区与会话', 'orchestration.workspace.title': '工作区', @@ -1102,7 +1102,7 @@ const zh = Object.freeze({ 'orchestration.readiness.sequence.single': '当前只有需求 1;继续发送会变成需求 2。', 'orchestration.readiness.sequence.multiple': '{count} 条需求会按顺序执行:先完成需求 1,再继续需求 2。', 'orchestration.readiness.engine.label': '执行策略', - 'orchestration.readiness.engine.openaiChat': '使用 OpenAI Chat-compatible 节点', + 'orchestration.readiness.engine.openaiChat': '使用聊天执行步骤', 'orchestration.readiness.workflow.done': '已选 {count} 个 Workflow', 'orchestration.readiness.workflow.missing': '还没选 Workflow ID', 'orchestration.readiness.scope.label': '边界', @@ -1112,7 +1112,7 @@ const zh = Object.freeze({ 'orchestration.readiness.ready.label': '就绪', 'orchestration.readiness.ready.missing': '还未开始', 'orchestration.readiness.ready.blocked': '有 {count} 个阻塞项', - 'orchestration.readiness.ready.ready': '已就绪,{count} 个节点', + 'orchestration.readiness.ready.ready': '已就绪,{count} 个步骤', 'orchestration.readiness.empty.title': '填写任务目标', 'orchestration.readiness.empty.summary': '先明确目标、约束和验收标准。', 'orchestration.readiness.workflow.title': '缺少 Workflow', @@ -1120,7 +1120,7 @@ const zh = Object.freeze({ 'orchestration.readiness.ready.startSummary': '目标已填写,准备好后即可开始执行。', 'orchestration.readiness.ready.sequenceSummary': '草稿已成形,已锁定 {count} 条顺序需求:先完成需求 1,再继续需求 2。', 'orchestration.readiness.blocked.title': '请求有阻塞', - 'orchestration.readiness.blocked.summary': '当前计划里还有 {count} 个阻塞项,先处理它们。', + 'orchestration.readiness.blocked.summary': '当前请求还有 {count} 个阻塞项,先处理它们。', 'orchestration.readiness.warn.title': '可以执行,但有提醒', 'orchestration.readiness.warn.summary': '任务已准备好,还有 {count} 条提醒值得留意。', 'orchestration.readiness.dryRun.title': '只读检查模式', @@ -1153,7 +1153,7 @@ const zh = Object.freeze({ 'orchestration.pills.sequenceCount': '顺序需求 {count}', 'orchestration.pills.hasTitle': '标题已设', 'orchestration.pills.workflowCount': 'Workflow {count}', - 'orchestration.pills.planNodes': '计划 {count} 节点', + 'orchestration.pills.preparedSteps': '已准备 {count} 个步骤', 'orchestration.step2.title': '选择执行方式', 'orchestration.step2.subtitle': '常用项默认展开,其余放高级设置。', 'orchestration.fields.engine': '引擎', @@ -1185,8 +1185,6 @@ const zh = Object.freeze({ 'orchestration.step3.subtitle': '立即开始,或加入队列稍后执行。', 'orchestration.actions.planning': '规划中...', 'orchestration.actions.preparing': '准备中...', - 'orchestration.actions.generatePlan': '发送请求', - 'orchestration.actions.planAndRun': '开始执行', 'orchestration.actions.processing': '处理中...', 'orchestration.actions.queueAndStart': '入队并开始', 'orchestration.actions.caption': '执行前会按需刷新计划;批量任务可入队运行。', diff --git a/web-ui/modules/task-kilo-webview-host.mjs b/web-ui/modules/task-kilo-webview-host.mjs index a48b45b9..6ddd6afb 100644 --- a/web-ui/modules/task-kilo-webview-host.mjs +++ b/web-ui/modules/task-kilo-webview-host.mjs @@ -72,7 +72,7 @@ function createTimelineBars({ taskOrchestration, queue, runs, nodes, t }) { bars.push({ key: 'draft', tone: 'neutral', label: translateTaskKiloText(t, 'orchestration.kilo.timeline.draft', 'Draft') }); } if (state.plan) { - bars.push({ key: 'plan', tone: safeArray(state.planIssues).length ? 'error' : 'success', label: translateTaskKiloText(t, 'orchestration.kilo.timeline.plan', 'Plan') }); + bars.push({ key: 'plan', tone: safeArray(state.planIssues).length ? 'error' : 'success', label: translateTaskKiloText(t, 'orchestration.kilo.timeline.prepare', 'Prepared') }); } safeArray(queue).slice(0, 8).forEach((item, index) => { bars.push({ @@ -240,7 +240,7 @@ export function createTaskKiloWebviewHostSnapshot(input = {}) { { key: 'trace', label: translateTaskKiloText(t, 'orchestration.agent.surface.trace', 'Trace'), - value: translateTaskKiloText(t, 'orchestration.kilo.trace.counts', '{runs} runs · {queue} queued · {nodes} nodes', { + value: translateTaskKiloText(t, 'orchestration.kilo.trace.counts', '{runs} runs · {queue} queued · {nodes} steps', { runs: runs.length, queue: queue.length, nodes: nodes.length || countPlanNodes(taskOrchestration) From 6f435dbdc829429945f23ad5b8633e92b2618347 Mon Sep 17 00:00:00 2001 From: awsl233777 Date: Tue, 30 Jun 2026 18:21:46 +0000 Subject: [PATCH 15/25] test(orchestration): cover chat composer locale keys --- tests/unit/config-tabs-ui.test.mjs | 3 ++- tests/unit/i18n-locales.test.mjs | 2 ++ web-ui/partials/index/panel-orchestration.html | 2 +- web-ui/res/web-ui-render.precompiled.js | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/unit/config-tabs-ui.test.mjs b/tests/unit/config-tabs-ui.test.mjs index b78d5a98..346d6fff 100644 --- a/tests/unit/config-tabs-ui.test.mjs +++ b/tests/unit/config-tabs-ui.test.mjs @@ -173,7 +173,8 @@ test('config template keeps expected config tabs in top and side navigation', () assert.match(orchestrationPanel, /taskOrchestrationWorkspaceQueue\.length/); assert.match(orchestrationPanel, /taskOrchestrationWorkspaceRuns\.length/); assert.doesNotMatch(orchestrationPanel, /taskOrchestrationWorkspaceSessions\.length/); - assert.match(orchestrationPanel, /taskOrchestrationSelectedRunNodes\.length/); + assert.match(orchestrationPanel, /taskKiloWebviewHostSnapshot\.stats\.nodes/); + assert.doesNotMatch(orchestrationPanel, /nodes: taskOrchestrationSelectedRunNodes\.length/); assert.match(orchestrationPanel, /:data-active="mainTab === 'orchestration' \? 'true' : 'false'"/); assert.match(html, /:data-active="mainTab === 'orchestration' \? 'true' : 'false'"/); for (const styles of [taskOrchestrationStyles, bundledStyles]) { diff --git a/tests/unit/i18n-locales.test.mjs b/tests/unit/i18n-locales.test.mjs index ac7c8b0a..86d1c01e 100644 --- a/tests/unit/i18n-locales.test.mjs +++ b/tests/unit/i18n-locales.test.mjs @@ -193,6 +193,8 @@ test('task orchestration chat composer copy is localized in every locale', () => 'orchestration.chat.context.thread.value', 'orchestration.chat.thread.aria', 'orchestration.chat.input.send', + 'orchestration.chat.input.work', + 'orchestration.chat.input.workHint', 'orchestration.chat.input.firstHint', 'orchestration.chat.input.sequenceHint', 'orchestration.chat.assistant.contextLabel', diff --git a/web-ui/partials/index/panel-orchestration.html b/web-ui/partials/index/panel-orchestration.html index 14f6140f..9a345370 100644 --- a/web-ui/partials/index/panel-orchestration.html +++ b/web-ui/partials/index/panel-orchestration.html @@ -17,7 +17,7 @@
{{ taskKiloWebviewHostSnapshot.status }} - {{ t('orchestration.kilo.trace.counts', { runs: taskOrchestrationWorkspaceRuns.length, queue: taskOrchestrationWorkspaceQueue.length, nodes: taskOrchestrationSelectedRunNodes.length }) }} + {{ t('orchestration.kilo.trace.counts', { runs: taskOrchestrationWorkspaceRuns.length, queue: taskOrchestrationWorkspaceQueue.length, nodes: taskKiloWebviewHostSnapshot.stats.nodes }) }} diff --git a/web-ui/res/web-ui-render.precompiled.js b/web-ui/res/web-ui-render.precompiled.js index 99700957..e5292461 100644 --- a/web-ui/res/web-ui-render.precompiled.js +++ b/web-ui/res/web-ui-render.precompiled.js @@ -4020,7 +4020,7 @@ return function render(_ctx, _cache) { _createElementVNode("span", { class: _normalizeClass(['pill', _ctx.taskKiloWebviewHostSnapshot.statusTone]) }, _toDisplayString(_ctx.taskKiloWebviewHostSnapshot.status), 3 /* TEXT, CLASS */), - _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.kilo.trace.counts', { runs: _ctx.taskOrchestrationWorkspaceRuns.length, queue: _ctx.taskOrchestrationWorkspaceQueue.length, nodes: _ctx.taskOrchestrationSelectedRunNodes.length })), 1 /* TEXT */), + _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.kilo.trace.counts', { runs: _ctx.taskOrchestrationWorkspaceRuns.length, queue: _ctx.taskOrchestrationWorkspaceQueue.length, nodes: _ctx.taskKiloWebviewHostSnapshot.stats.nodes })), 1 /* TEXT */), _createElementVNode("button", { type: "button", class: "kilo-icon-button", From 3602196461604ea2a7103b071403aaeb97241797 Mon Sep 17 00:00:00 2001 From: awsl233777 Date: Tue, 30 Jun 2026 18:28:23 +0000 Subject: [PATCH 16/25] fix(orchestration): harden kilo webview bridge privacy --- tests/unit/web-ui-logic.test.mjs | 68 +++++++++++++++++++ .../app.methods.task-orchestration.mjs | 18 +++-- web-ui/modules/task-kilo-webview-host.mjs | 17 ++++- 3 files changed, 97 insertions(+), 6 deletions(-) diff --git a/tests/unit/web-ui-logic.test.mjs b/tests/unit/web-ui-logic.test.mjs index 412c9405..15a3b24f 100644 --- a/tests/unit/web-ui-logic.test.mjs +++ b/tests/unit/web-ui-logic.test.mjs @@ -1882,6 +1882,10 @@ test('task Kilo webview host snapshot hides workspace, thread, ids, and raw run queue: [{ taskId: 'task-secret', status: 'queued', title: 'raw task title' }], runs: [{ runId: 'run-secret', status: 'success', summary: 'raw run summary' }], nodes: [{ id: 'node-secret', status: 'success', output: { text: 'secret output' } }], + conversationMessages: [ + { id: 'raw-message-id', role: 'user', label: 'secret request label', text: 'raw user prompt', meta: 'thread-secret-meta' }, + { id: 'assistant-message-id', role: 'assistant', label: 'secret assistant label', text: 'raw assistant output', meta: 'provider-secret' } + ], workspaceItems: [{ path: '/private/workspace/codexmate' }], workspaceSessions: [{ id: 'session-secret' }] }); @@ -1898,6 +1902,8 @@ test('task Kilo webview host snapshot hides workspace, thread, ids, and raw run assert.doesNotMatch(json, /node-secret/); assert.doesNotMatch(json, /workflow-secret/); assert.doesNotMatch(json, /raw run summary|raw task title|secret output/); + assert.doesNotMatch(json, /raw user prompt|raw assistant output|secret request label|secret assistant label|thread-secret-meta|provider-secret/); + assert.deepStrictEqual(snapshot.messages.map(item => item.role), ['user', 'assistant']); }); test('task Kilo VSCode API shim stores state and routes postMessage through whitelist decision', () => { @@ -1919,6 +1925,68 @@ test('task Kilo VSCode API shim stores state and routes postMessage through whit assert.deepStrictEqual(calls.map(item => item.type), ['loadSessions', 'openExternal']); }); +test('task Kilo webview message handling initializes state before mutation', () => { + const methods = createTaskOrchestrationMethods({ api: async () => ({}) }); + const context = { + ...methods, + taskOrchestration: null, + taskOrchestrationWorkspacePath: '', + taskKiloWebviewHostSnapshot: { safe: true }, + planAndRunTaskOrchestrationFromChat() { + return { started: true, draft: this.taskOrchestration.chatDraft }; + }, + cancelTaskRunFromUi(runId) { + return { cancelled: runId || '' }; + }, + startNewTaskWorkspaceSession() { + return { created: true }; + } + }; + + const sent = methods.handleTaskKiloWebviewMessage.call(context, { type: 'sendMessage', text: 'do the work' }); + assert.strictEqual(sent.handled, true); + assert.strictEqual(sent.result.draft, 'do the work'); + assert.strictEqual(context.taskOrchestration.chatDraft, 'do the work'); + + const opened = methods.handleTaskKiloWebviewMessage.call(context, { type: 'openSettingsPanel' }); + assert.strictEqual(opened.handled, true); + assert.strictEqual(context.taskOrchestration.settingsOpen, true); +}); + +test('task Kilo webview shim preserves existing VS Code bridge and tolerates blocked storage', () => { + const methods = createTaskOrchestrationMethods({ api: async () => ({}) }); + const previousWindow = globalThis.window; + try { + const existingBridge = () => ({ existing: true }); + globalThis.window = { + acquireVsCodeApi: existingBridge, + get localStorage() { + throw new Error('storage blocked'); + } + }; + assert.strictEqual(methods.installTaskKiloWebviewShim.call({ ...methods }), existingBridge); + assert.strictEqual(globalThis.window.acquireVsCodeApi, existingBridge); + + globalThis.window = { + get localStorage() { + throw new Error('storage blocked'); + } + }; + const context = { ...methods, taskOrchestration: null }; + const acquire = methods.installTaskKiloWebviewShim.call(context); + assert.strictEqual(typeof acquire, 'function'); + const api = acquire(); + api.setState({ selected: true }); + assert.deepStrictEqual(api.getState(), { selected: true }); + } finally { + if (typeof previousWindow === 'undefined') { + delete globalThis.window; + } else { + globalThis.window = previousWindow; + } + } +}); + test('loadTaskOrchestrationOverview does not auto-select latest failed run history', async () => { const methods = createTaskOrchestrationMethods({ api: async (name) => { diff --git a/web-ui/modules/app.methods.task-orchestration.mjs b/web-ui/modules/app.methods.task-orchestration.mjs index a7b2739a..4c381850 100644 --- a/web-ui/modules/app.methods.task-orchestration.mjs +++ b/web-ui/modules/app.methods.task-orchestration.mjs @@ -858,6 +858,7 @@ export function createTaskOrchestrationMethods(options = {}) { }, handleTaskKiloWebviewMessage(message) { + const state = this.ensureTaskOrchestrationState(); const decision = normalizeTaskKiloWebviewMessage(message); if (!decision.allowed) { return decision; @@ -873,7 +874,7 @@ export function createTaskOrchestrationMethods(options = {}) { if (type === 'sendMessage') { const text = String((message && (message.text || message.message || message.content)) || '').trim(); if (text) { - this.taskOrchestration.chatDraft = text; + state.chatDraft = text; return { ...decision, handled: true, result: this.planAndRunTaskOrchestrationFromChat() }; } return { ...decision, handled: false, reason: 'empty-message' }; @@ -882,10 +883,10 @@ export function createTaskOrchestrationMethods(options = {}) { return { ...decision, handled: false, reason: 'unsupported-command' }; } if (type === 'abort') { - return { ...decision, handled: true, result: this.cancelTaskRunFromUi(this.taskOrchestration && this.taskOrchestration.selectedRunId) }; + return { ...decision, handled: true, result: this.cancelTaskRunFromUi(state.selectedRunId) }; } if (type === 'openSettingsPanel') { - this.taskOrchestration.settingsOpen = true; + state.settingsOpen = true; return { ...decision, handled: true }; } return { ...decision, handled: false, reason: 'unmapped-allowed-message' }; @@ -895,10 +896,17 @@ export function createTaskOrchestrationMethods(options = {}) { if (typeof window === 'undefined') { return null; } - if (window.__codexmateKiloWebviewShimInstalled) { + if (typeof window.acquireVsCodeApi === 'function') { + window.__codexmateKiloWebviewShimInstalled = true; return window.acquireVsCodeApi; } - const api = createTaskKiloVsCodeApiShim(window.localStorage, (decision) => this.handleTaskKiloWebviewMessage(decision.message || decision)); + let storage = null; + try { + storage = window.localStorage || null; + } catch (_) { + storage = null; + } + const api = createTaskKiloVsCodeApiShim(storage, (decision) => this.handleTaskKiloWebviewMessage(decision.message || decision)); window.acquireVsCodeApi = () => api; window.IS_SIDEBAR = false; window.IS_FULL_EDITOR = true; diff --git a/web-ui/modules/task-kilo-webview-host.mjs b/web-ui/modules/task-kilo-webview-host.mjs index 6ddd6afb..f6c8841b 100644 --- a/web-ui/modules/task-kilo-webview-host.mjs +++ b/web-ui/modules/task-kilo-webview-host.mjs @@ -65,6 +65,21 @@ function countPlanWaves(taskOrchestration) { return safeArray(plan && plan.waves).length; } +function sanitizeSnapshotMessages(messages, t) { + return safeArray(messages).map((message, index) => { + const rawRole = String(message && message.role || '').trim().toLowerCase(); + const role = rawRole === 'user' ? 'user' : 'assistant'; + return { + id: `message-${index}`, + role, + label: role === 'user' + ? translateTaskKiloText(t, 'orchestration.chat.user.step', 'Request {count}', { count: index + 1 }) + : translateTaskKiloText(t, 'orchestration.chat.assistant.readyLabel', 'Task assistant'), + text: translateTaskKiloText(t, 'orchestration.privacy.detailsHidden', 'Details hidden') + }; + }); +} + function createTimelineBars({ taskOrchestration, queue, runs, nodes, t }) { const bars = []; const state = taskOrchestration && typeof taskOrchestration === 'object' ? taskOrchestration : {}; @@ -194,7 +209,7 @@ export function createTaskKiloWebviewHostSnapshot(input = {}) { const queue = safeArray(input.queue); const runs = safeArray(input.runs); const nodes = safeArray(input.nodes); - const messages = safeArray(input.conversationMessages); + const messages = sanitizeSnapshotMessages(input.conversationMessages, t); const selectedWorkspacePath = String(input.workspacePath || '').trim(); const selectedRun = taskOrchestration.selectedRunDetail && typeof taskOrchestration.selectedRunDetail === 'object' ? taskOrchestration.selectedRunDetail From 64d442c50212a77171accd53d27d92a84ccc8658 Mon Sep 17 00:00:00 2001 From: awsl233777 Date: Wed, 1 Jul 2026 01:46:35 +0000 Subject: [PATCH 17/25] fix(orchestration): keep task composer visible --- tests/unit/config-tabs-ui.test.mjs | 3 +++ web-ui/styles/task-orchestration.css | 25 +++++++++++++++++++------ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/tests/unit/config-tabs-ui.test.mjs b/tests/unit/config-tabs-ui.test.mjs index 346d6fff..a2ada376 100644 --- a/tests/unit/config-tabs-ui.test.mjs +++ b/tests/unit/config-tabs-ui.test.mjs @@ -187,6 +187,9 @@ test('config template keeps expected config tabs in top and side navigation', () assert.match(styles, /#panel-orchestration \.chat-input\s*\{/); assert.match(styles, /#panel-orchestration \.prompt-input-container\s*\{/); assert.match(styles, /#panel-orchestration \.prompt-input-textarea\s*\{/); + assert.match(styles, /--kilo-workbench-shell-offset:\s*204px;/); + assert.match(styles, /#panel-orchestration \.kilo-webview-container\s*\{[\s\S]*height:\s*clamp\([\s\S]*min-height:\s*0;/); + assert.doesNotMatch(styles, /height:\s*calc\(100vh - 92px\)|min-height:\s*680px|height:\s*calc\(100vh - 72px\)|min-height:\s*640px/); assert.doesNotMatch(styles, /task-quick|task-workbench|task-agent|task-kilo-host|task-layout-grid|task-thread-composer|task-chat-thread|kilo-empty-actions/); } for (const styles of [bundledStyles]) { diff --git a/web-ui/styles/task-orchestration.css b/web-ui/styles/task-orchestration.css index 1dfcb08b..db8757cb 100644 --- a/web-ui/styles/task-orchestration.css +++ b/web-ui/styles/task-orchestration.css @@ -1,12 +1,25 @@ #panel-orchestration.kilo-webview-mode { - min-height: calc(100vh - 92px); + --kilo-workbench-shell-offset: 204px; + --kilo-workbench-min-height: 560px; + --kilo-workbench-max-height: 980px; + min-height: min(var(--kilo-workbench-min-height), calc(100vh - var(--kilo-workbench-shell-offset))); + min-height: min(var(--kilo-workbench-min-height), calc(100dvh - var(--kilo-workbench-shell-offset))); padding: 0; background: #0f172a; } #panel-orchestration .kilo-webview-container { - height: calc(100vh - 92px); - min-height: 680px; + height: clamp( + var(--kilo-workbench-min-height), + calc(100vh - var(--kilo-workbench-shell-offset)), + var(--kilo-workbench-max-height) + ); + height: clamp( + var(--kilo-workbench-min-height), + calc(100dvh - var(--kilo-workbench-shell-offset)), + var(--kilo-workbench-max-height) + ); + min-height: 0; overflow: hidden; border: 1px solid rgba(148, 163, 184, 0.20); border-radius: 18px; @@ -441,12 +454,12 @@ @media (max-width: 820px) { #panel-orchestration.kilo-webview-mode { - min-height: calc(100vh - 72px); + --kilo-workbench-shell-offset: 152px; + --kilo-workbench-min-height: 520px; + --kilo-workbench-max-height: 1000px; } #panel-orchestration .kilo-webview-container { - height: calc(100vh - 72px); - min-height: 640px; border-radius: 0; } From 354ec45f229b94210d0f9ed6921dad65a526c5e2 Mon Sep 17 00:00:00 2001 From: awsl233777 Date: Wed, 1 Jul 2026 01:50:22 +0000 Subject: [PATCH 18/25] style(orchestration): separate custom task sizing properties --- web-ui/styles/task-orchestration.css | 1 + 1 file changed, 1 insertion(+) diff --git a/web-ui/styles/task-orchestration.css b/web-ui/styles/task-orchestration.css index db8757cb..63492166 100644 --- a/web-ui/styles/task-orchestration.css +++ b/web-ui/styles/task-orchestration.css @@ -2,6 +2,7 @@ --kilo-workbench-shell-offset: 204px; --kilo-workbench-min-height: 560px; --kilo-workbench-max-height: 980px; + min-height: min(var(--kilo-workbench-min-height), calc(100vh - var(--kilo-workbench-shell-offset))); min-height: min(var(--kilo-workbench-min-height), calc(100dvh - var(--kilo-workbench-shell-offset))); padding: 0; From e1ed9278bb0e986ec0c23e689efd2f2e88eb61b5 Mon Sep 17 00:00:00 2001 From: awsl233777 Date: Wed, 1 Jul 2026 02:19:46 +0000 Subject: [PATCH 19/25] style(orchestration): align task surface with reference app --- tests/unit/config-tabs-ui.test.mjs | 7 +- web-ui/styles/task-orchestration.css | 155 +++++++++++++++------------ 2 files changed, 91 insertions(+), 71 deletions(-) diff --git a/tests/unit/config-tabs-ui.test.mjs b/tests/unit/config-tabs-ui.test.mjs index a2ada376..b5d14a8c 100644 --- a/tests/unit/config-tabs-ui.test.mjs +++ b/tests/unit/config-tabs-ui.test.mjs @@ -187,8 +187,11 @@ test('config template keeps expected config tabs in top and side navigation', () assert.match(styles, /#panel-orchestration \.chat-input\s*\{/); assert.match(styles, /#panel-orchestration \.prompt-input-container\s*\{/); assert.match(styles, /#panel-orchestration \.prompt-input-textarea\s*\{/); - assert.match(styles, /--kilo-workbench-shell-offset:\s*204px;/); - assert.match(styles, /#panel-orchestration \.kilo-webview-container\s*\{[\s\S]*height:\s*clamp\([\s\S]*min-height:\s*0;/); + assert.match(styles, /--kilo-workbench-shell-offset:\s*230px;/); + assert.match(styles, /--kilo-reference-blue:\s*#1e7fd3;/); + assert.match(styles, /--kilo-reference-bg:\s*#eef1f4;/); + assert.match(styles, /#panel-orchestration \.kilo-webview-container\s*\{[\s\S]*width:\s*min\(100%, 980px\);[\s\S]*margin:\s*0 auto;[\s\S]*height:\s*clamp\([\s\S]*min-height:\s*0;/); + assert.match(styles, /#panel-orchestration \[data-component="task-header"\]\s*\{[\s\S]*background:\s*var\(--kilo-reference-blue\);/); assert.doesNotMatch(styles, /height:\s*calc\(100vh - 92px\)|min-height:\s*680px|height:\s*calc\(100vh - 72px\)|min-height:\s*640px/); assert.doesNotMatch(styles, /task-quick|task-workbench|task-agent|task-kilo-host|task-layout-grid|task-thread-composer|task-chat-thread|kilo-empty-actions/); } diff --git a/web-ui/styles/task-orchestration.css b/web-ui/styles/task-orchestration.css index 63492166..75a1510c 100644 --- a/web-ui/styles/task-orchestration.css +++ b/web-ui/styles/task-orchestration.css @@ -1,15 +1,24 @@ #panel-orchestration.kilo-webview-mode { - --kilo-workbench-shell-offset: 204px; + --kilo-workbench-shell-offset: 230px; --kilo-workbench-min-height: 560px; --kilo-workbench-max-height: 980px; + --kilo-reference-blue: #1e7fd3; + --kilo-reference-blue-dark: #176db6; + --kilo-reference-bg: #eef1f4; + --kilo-reference-border: #d8dee6; + --kilo-reference-text: #243447; + --kilo-reference-muted: #6b7785; min-height: min(var(--kilo-workbench-min-height), calc(100vh - var(--kilo-workbench-shell-offset))); min-height: min(var(--kilo-workbench-min-height), calc(100dvh - var(--kilo-workbench-shell-offset))); - padding: 0; - background: #0f172a; + padding: 26px clamp(12px, 2vw, 22px) 30px; + background: var(--kilo-reference-bg); } #panel-orchestration .kilo-webview-container { + width: min(100%, 980px); + max-width: 980px; + margin: 0 auto; height: clamp( var(--kilo-workbench-min-height), calc(100vh - var(--kilo-workbench-shell-offset)), @@ -22,10 +31,11 @@ ); min-height: 0; overflow: hidden; - border: 1px solid rgba(148, 163, 184, 0.20); - border-radius: 18px; - background: #0f172a; - color: #d4d4d4; + border: 1px solid var(--kilo-reference-border); + border-radius: 6px; + background: #fff; + color: var(--kilo-reference-text); + box-shadow: 0 2px 8px rgba(24, 48, 84, 0.16), 0 12px 24px rgba(24, 48, 84, 0.08); } #panel-orchestration .kilo-chat-view { @@ -33,15 +43,15 @@ height: 100%; min-height: 0; flex-direction: column; - background: #181818; - color: #d4d4d4; - --vscode-descriptionForeground: #9ca3af; - --vscode-button-background: #2563eb; + background: #fff; + color: var(--kilo-reference-text); + --vscode-descriptionForeground: var(--kilo-reference-muted); + --vscode-button-background: var(--kilo-reference-blue); --vscode-button-foreground: #fff; - --vscode-button-hoverBackground: #1d4ed8; - --vscode-button-secondaryBackground: #2d2d2d; - --vscode-button-secondaryForeground: #d4d4d4; - --vscode-button-secondaryHoverBackground: #3a3a3a; + --vscode-button-hoverBackground: var(--kilo-reference-blue-dark); + --vscode-button-secondaryBackground: #f5f7fa; + --vscode-button-secondaryForeground: var(--kilo-reference-text); + --vscode-button-secondaryHoverBackground: #e9eef4; --kilo-font-size-12: 12px; --kilo-font-size-13: 13px; --kilo-font-size-16: 16px; @@ -49,15 +59,17 @@ #panel-orchestration [data-component="task-header"] { flex-shrink: 0; - border-bottom: 1px solid #2d2d2d; - background: #1f1f1f; + border-bottom: 1px solid rgba(0, 0, 0, 0.08); + background: var(--kilo-reference-blue); + color: #fff; + box-shadow: 0 2px 8px rgba(24, 48, 84, 0.16); } #panel-orchestration .kilo-task-header { display: grid; grid-template-columns: minmax(0, 1fr) auto; - gap: 8px 16px; - padding: 10px 14px 8px; + gap: 6px 14px; + padding: 9px 14px 7px; } #panel-orchestration [data-slot="task-header-title"] { @@ -67,8 +79,8 @@ #panel-orchestration [data-slot="task-header-title-label"] { display: block; overflow: hidden; - color: #f8fafc; - font-size: 13px; + color: #fff; + font-size: 18px; font-weight: 700; text-overflow: ellipsis; white-space: nowrap; @@ -78,7 +90,7 @@ display: block; margin-top: 3px; overflow: hidden; - color: #9ca3af; + color: rgba(255, 255, 255, 0.82); font-size: 12px; text-overflow: ellipsis; white-space: nowrap; @@ -89,7 +101,7 @@ align-items: center; justify-content: flex-end; gap: 8px; - color: #9ca3af; + color: rgba(255, 255, 255, 0.86); font-size: 12px; } @@ -107,7 +119,7 @@ #panel-orchestration .kilo-task-timeline-bar { height: 4px; border-radius: 999px; - background: #3f3f46; + background: rgba(255, 255, 255, 0.28); } #panel-orchestration .kilo-task-timeline-bar.success { background: #22c55e; } @@ -128,30 +140,31 @@ width: 26px; height: 26px; place-items: center; - background: transparent; - color: #d4d4d4; + border-color: rgba(255, 255, 255, 0.30); + background: rgba(255, 255, 255, 0.12); + color: #fff; } #panel-orchestration .kilo-icon-button:hover { - background: #2d2d2d; + background: rgba(255, 255, 255, 0.22); } #panel-orchestration .kilo-primary-button { padding: 7px 12px; - background: #2563eb; + background: var(--kilo-reference-blue); color: #fff; font-weight: 700; } #panel-orchestration .kilo-primary-button:hover { - background: #1d4ed8; + background: var(--kilo-reference-blue-dark); } #panel-orchestration .kilo-secondary-button { padding: 7px 12px; - border-color: #3f3f46; - background: #2d2d2d; - color: #d4d4d4; + border-color: var(--kilo-reference-border); + background: #f6f8fb; + color: var(--kilo-reference-text); } #panel-orchestration .kilo-primary-button:disabled, @@ -179,8 +192,9 @@ height: 100%; overflow-x: hidden; overflow-y: auto; - padding: 14px clamp(18px, 4vw, 58px); - scrollbar-color: rgba(148, 163, 184, 0.32) transparent; + padding: 16px clamp(18px, 3vw, 34px); + background: #fff; + scrollbar-color: rgba(30, 127, 211, 0.35) transparent; scrollbar-gutter: stable; scrollbar-width: thin; } @@ -204,7 +218,7 @@ align-items: center; justify-content: center; gap: 14px; - color: #9ca3af; + color: var(--kilo-reference-muted); text-align: center; } @@ -214,7 +228,7 @@ height: 52px; place-items: center; border-radius: 14px; - background: #2563eb; + background: var(--kilo-reference-blue); color: #fff; font-size: 24px; font-weight: 900; @@ -222,7 +236,7 @@ #panel-orchestration .kilo-welcome-empty h3 { margin: 0; - color: #f8fafc; + color: var(--kilo-reference-text); font-size: 22px; } @@ -251,9 +265,9 @@ #panel-orchestration .kilo-context-strip span { min-width: 0; padding: 9px; - border: 1px solid #2d2d2d; - border-radius: 8px; - background: #1f1f1f; + border: 1px solid var(--kilo-reference-border); + border-radius: 5px; + background: #f8fafc; text-align: left; } @@ -261,7 +275,7 @@ #panel-orchestration .kilo-message-heading, #panel-orchestration .kilo-message-meta { display: block; - color: #9ca3af; + color: var(--kilo-reference-muted); font-size: 11px; } @@ -269,7 +283,7 @@ display: block; overflow: hidden; margin-top: 4px; - color: #e5e7eb; + color: var(--kilo-reference-text); font-size: 12px; text-overflow: ellipsis; white-space: nowrap; @@ -287,21 +301,24 @@ #panel-orchestration .vscode-session-turn-user, #panel-orchestration .vscode-session-turn-assistant { max-width: min(100%, 920px); - border-radius: 10px; - color: #d4d4d4; + border-radius: 6px; + color: var(--kilo-reference-text); } #panel-orchestration .vscode-session-turn-user { align-self: flex-end; width: min(100%, 760px); padding: 10px 12px; - background: #1f2937; + border: 1px solid rgba(30, 127, 211, 0.18); + background: #eef6ff; } #panel-orchestration .vscode-session-turn-assistant { align-self: flex-start; width: min(100%, 920px); - padding: 10px 0; + padding: 10px 12px; + border: 1px solid var(--kilo-reference-border); + background: #fff; } #panel-orchestration .kilo-message-heading { @@ -310,16 +327,16 @@ } #panel-orchestration .kilo-message-body { - color: #e5e7eb; + color: var(--kilo-reference-text); font-size: 13px; line-height: 1.55; white-space: pre-wrap; } #panel-orchestration .kilo-plan-response { - border: 1px solid #2d2d2d; - border-radius: 10px; - background: #1f1f1f; + border: 1px solid var(--kilo-reference-border); + border-radius: 6px; + background: #f8fafc; padding: 12px; } @@ -327,7 +344,7 @@ display: flex; align-items: center; gap: 8px; - color: #d4d4d4; + color: var(--kilo-reference-text); font-size: 13px; } @@ -344,8 +361,8 @@ margin-top: 8px; border: 1px solid rgba(245, 158, 11, 0.28); border-radius: 8px; - background: rgba(245, 158, 11, 0.08); - color: #fbbf24; + background: #fff8e6; + color: #9a5b00; padding: 8px; font-size: 12px; } @@ -358,9 +375,9 @@ } #panel-orchestration .kilo-tool-card { - border: 1px solid #2d2d2d; + border: 1px solid var(--kilo-reference-border); border-radius: 8px; - background: #181818; + background: #f8fafc; padding: 9px; } @@ -373,14 +390,14 @@ #panel-orchestration .kilo-tool-title span, #panel-orchestration .kilo-tool-card small { - color: #9ca3af; + color: var(--kilo-reference-muted); font-size: 11px; text-transform: uppercase; } #panel-orchestration .kilo-tool-title strong { overflow: hidden; - color: #e5e7eb; + color: var(--kilo-reference-text); font-size: 12px; text-overflow: ellipsis; white-space: nowrap; @@ -388,21 +405,21 @@ #panel-orchestration .chat-input { flex-shrink: 0; - border-top: 1px solid #2d2d2d; - background: #1f1f1f; - padding: 8px clamp(18px, 4vw, 58px) 14px; + border-top: 1px solid var(--kilo-reference-border); + background: #f8fafc; + padding: 10px clamp(18px, 3vw, 34px) 14px; } #panel-orchestration .kilo-status-dock { margin-bottom: 8px; - color: #9ca3af; + color: var(--kilo-reference-muted); font-size: 11px; } #panel-orchestration .kilo-status-dock span { - border: 1px solid #2d2d2d; + border: 1px solid var(--kilo-reference-border); border-radius: 999px; - background: #181818; + background: #fff; padding: 3px 8px; } @@ -416,14 +433,14 @@ display: grid; grid-template-columns: 22px minmax(0, 1fr); gap: 8px; - border: 1px solid #3f3f46; - border-radius: 10px; - background: #181818; + border: 1px solid var(--kilo-reference-border); + border-radius: 6px; + background: #fff; padding: 9px 10px; } #panel-orchestration .prompt-input-prefix { - color: #60a5fa; + color: var(--kilo-reference-blue); font-size: 20px; font-weight: 900; line-height: 1.25; @@ -436,7 +453,7 @@ outline: 0; resize: vertical; background: transparent; - color: #e5e7eb; + color: var(--kilo-reference-text); font: inherit; font-size: 13px; line-height: 1.5; @@ -444,7 +461,7 @@ #panel-orchestration .prompt-input-actions { justify-content: flex-end; - color: #9ca3af; + color: var(--kilo-reference-muted); font-size: 12px; } From 0ca4836435caba79675016321074f1834801a865 Mon Sep 17 00:00:00 2001 From: awsl233777 Date: Wed, 1 Jul 2026 06:29:40 +0000 Subject: [PATCH 20/25] fix(orchestration): align task UI with chat shell Rework the orchestration panel into a full-width Kilo-style chat shell with explicit message, permission, error, and composer regions. Improve custom provider setup hints and reject concrete API paths such as /chat/completions so users enter base URLs. Add regression coverage for the UI structure and provider URL validation. --- tests/unit/config-tabs-ui.test.mjs | 13 +- tests/unit/providers-validation.test.mjs | 24 +++ web-ui/modules/app.methods.providers.mjs | 6 + web-ui/modules/i18n/locales/en.mjs | 5 + web-ui/modules/i18n/locales/ja.mjs | 5 + web-ui/modules/i18n/locales/vi.mjs | 5 + web-ui/modules/i18n/locales/zh-tw.mjs | 5 + web-ui/modules/i18n/locales/zh.mjs | 5 + web-ui/partials/index/modals-basic.html | 8 +- .../partials/index/panel-orchestration.html | 10 +- web-ui/res/web-ui-render.precompiled.js | 28 ++- web-ui/styles/skills-list.css | 15 ++ web-ui/styles/task-orchestration.css | 194 ++++++++---------- 13 files changed, 198 insertions(+), 125 deletions(-) diff --git a/tests/unit/config-tabs-ui.test.mjs b/tests/unit/config-tabs-ui.test.mjs index b5d14a8c..f974c530 100644 --- a/tests/unit/config-tabs-ui.test.mjs +++ b/tests/unit/config-tabs-ui.test.mjs @@ -93,7 +93,7 @@ test('config template keeps expected config tabs in top and side navigation', () assert.match(html, /id="panel-orchestration"/); assert.match(html, /v-show="mainTab === 'orchestration'"/); assert.match(orchestrationPanel, /class="mode-content kilo-webview-mode"/); - assert.match(orchestrationPanel, /class="container kilo-webview-container"/); + assert.match(orchestrationPanel, /class="container kilo-webview-container kilo-chat-shell"/); assert.match(orchestrationPanel, /class="chat-view kilo-chat-view"/); assert.match(orchestrationPanel, /data-component="task-header"/); assert.match(orchestrationPanel, //); @@ -101,10 +101,12 @@ test('config template keeps expected config tabs in top and side navigation', () assert.match(orchestrationPanel, /data-slot="task-header-title"/); assert.match(orchestrationPanel, /data-slot="task-header-stats"/); assert.match(orchestrationPanel, /class="chat-messages-wrapper"/); - assert.match(orchestrationPanel, /class="message-list" role="log" :aria-label="t\('orchestration\.chat\.thread\.aria'\)"/); + assert.match(orchestrationPanel, /class="message-list" data-component="message-list" role="log" :aria-label="t\('orchestration\.chat\.thread\.aria'\)"/); assert.match(orchestrationPanel, /class="chat-input"/); assert.match(orchestrationPanel, /class="prompt-input-container"/); assert.match(orchestrationPanel, /class="prompt-input-shell"/); + assert.match(orchestrationPanel, /data-component="permission-dock"/); + assert.match(orchestrationPanel, /data-component="error-dock"/); assert.match(orchestrationPanel, /taskKiloWebviewHostSnapshot\.title/); assert.match(orchestrationPanel, /taskKiloWebviewHostSnapshot\.subtitle/); assert.match(orchestrationPanel, /taskKiloWebviewHostSnapshot\.timelineBars/); @@ -190,8 +192,8 @@ test('config template keeps expected config tabs in top and side navigation', () assert.match(styles, /--kilo-workbench-shell-offset:\s*230px;/); assert.match(styles, /--kilo-reference-blue:\s*#1e7fd3;/); assert.match(styles, /--kilo-reference-bg:\s*#eef1f4;/); - assert.match(styles, /#panel-orchestration \.kilo-webview-container\s*\{[\s\S]*width:\s*min\(100%, 980px\);[\s\S]*margin:\s*0 auto;[\s\S]*height:\s*clamp\([\s\S]*min-height:\s*0;/); - assert.match(styles, /#panel-orchestration \[data-component="task-header"\]\s*\{[\s\S]*background:\s*var\(--kilo-reference-blue\);/); + assert.match(styles, /#panel-orchestration \.kilo-webview-container\s*\{[\s\S]*width:\s*100%;[\s\S]*max-width:\s*none;[\s\S]*height:\s*calc\(100vh - var\(--kilo-workbench-shell-offset\)\);[\s\S]*min-height:\s*0;/); + assert.match(styles, /#panel-orchestration \[data-component="task-header"\]\s*\{[\s\S]*background:\s*linear-gradient\(180deg, #ffffff 0%, #f8fafc 100%\);/); assert.doesNotMatch(styles, /height:\s*calc\(100vh - 92px\)|min-height:\s*680px|height:\s*calc\(100vh - 72px\)|min-height:\s*640px/); assert.doesNotMatch(styles, /task-quick|task-workbench|task-agent|task-kilo-host|task-layout-grid|task-thread-composer|task-chat-thread|kilo-empty-actions/); } @@ -485,6 +487,9 @@ test('config template keeps expected config tabs in top and side navigation', () assert.match(modalsBasic, //); assert.match(modalsBasic, /]*:class="\['form-input', \{ invalid: !!providerFieldError\('add', 'key'\) \}\]"[^>]*:type="showAddProviderKey \? 'text' : 'password'"[^>]*placeholder="sk-\.\.\."[^>]*autocomplete="off"[^>]*spellcheck="false"[^>]*@blur="normalizeProviderDraft\('add'\)">/); assert.match(modalsBasic, /
\{\{ providerFieldError\('add', 'key'\) \}\}<\/div>/); + assert.match(modalsBasic, /t\('hint\.providerEndpointBaseUrl'\)/); + assert.match(modalsBasic, /t\('hint\.providerModelId'\)/); + assert.match(modalsBasic, /t\('hint\.providerTransformOpenAiCompatible'\)/); assert.match(modalsBasic, //); assert.match(modalsBasic, /]*:class="\['form-input', \{ invalid: !!claudeConfigFieldError\('add', 'apiKey'\) \}\]"[^>]*:type="showAddClaudeConfigKey \? 'text' : 'password'"[^>]*autocomplete="off"[^>]*spellcheck="false"[^>]*:placeholder="t\('placeholder\.apiKeyExampleClaude'\)">/); assert.match(modalsBasic, /
\{\{ claudeConfigFieldError\('add', 'apiKey'\) \}\}<\/div>/); diff --git a/tests/unit/providers-validation.test.mjs b/tests/unit/providers-validation.test.mjs index 55e100ea..f3f7f093 100644 --- a/tests/unit/providers-validation.test.mjs +++ b/tests/unit/providers-validation.test.mjs @@ -165,7 +165,31 @@ test('updateProvider blocks invalid edit URL and skips api call', async () => { }); }); +test('provider validation rejects concrete API paths instead of base URLs', async () => { + const apiCalls = []; + const { context, messages } = createContext({ + newProvider: { + name: 'openai-compatible', + url: 'https://api.example.com/v1/chat/completions', + key: 'sk-test', + model: 'gpt-e2e' + } + }, async (action, params) => { + apiCalls.push({ action, params }); + return { success: true }; + }); + assert.strictEqual(context.canSubmitProvider('add'), false); + assert.strictEqual(context.providerFieldError('add', 'url'), '请填写 base URL(通常到 /v1),不要填写具体接口路径'); + + await context.addProvider(); + + assert.deepStrictEqual(apiCalls, []); + assert.deepStrictEqual(messages[0], { + text: '请填写 base URL(通常到 /v1),不要填写具体接口路径', + type: 'error' + }); +}); test('provider validation rejects reserved proxy name on add', () => { const { context } = createContext({ diff --git a/web-ui/modules/app.methods.providers.mjs b/web-ui/modules/app.methods.providers.mjs index f212855c..dcfefe8c 100644 --- a/web-ui/modules/app.methods.providers.mjs +++ b/web-ui/modules/app.methods.providers.mjs @@ -101,6 +101,12 @@ function getProviderValidationForContext(vm, mode = 'add') { errors.url = 'URL 必填'; } else if (!isValidProviderUrlInputValue(url)) { errors.url = 'URL 仅支持 http/https'; + } else { + const parsed = new URL(url); + const normalizedPath = parsed.pathname.replace(/\/+$/g, '').toLowerCase(); + if (/\/(chat\/completions|responses|completions)$/.test(normalizedPath)) { + errors.url = '请填写 base URL(通常到 /v1),不要填写具体接口路径'; + } } if (mode === 'add' && !key) { diff --git a/web-ui/modules/i18n/locales/en.mjs b/web-ui/modules/i18n/locales/en.mjs index edd86c5b..0532b247 100644 --- a/web-ui/modules/i18n/locales/en.mjs +++ b/web-ui/modules/i18n/locales/en.mjs @@ -486,6 +486,11 @@ const en = Object.freeze({ 'modal.claudeConfigEdit.title': 'Edit Claude Code config', 'field.useBuiltinTransform': 'Use built-in transform (OpenAI compatible)', 'hint.useBuiltinTransform': 'When enabled, base_url points to codexmate built-in transform service; Codex token is fixed to codexmate.', + 'hint.providerName': 'Internal provider id, using only letters, numbers, dots, underscores, or hyphens.', + 'hint.providerEndpointBaseUrl': 'Base URL only, usually ending at /v1. Do not include /chat/completions, /responses, or other request paths.', + 'hint.providerModelId': 'Exact model ID from the provider console, for example gpt-4o, deepseek-chat, or openai/gpt-4o.', + 'hint.providerKey': 'Stored as the provider auth key and sent only when calling this provider.', + 'hint.providerTransformOpenAiCompatible': 'Enable for OpenAI-compatible Chat Completions providers. Leave off only when the endpoint natively supports the Responses API.', // Config template / agents modals 'modal.configTemplate.label': 'config.toml template', diff --git a/web-ui/modules/i18n/locales/ja.mjs b/web-ui/modules/i18n/locales/ja.mjs index 5ce5550c..079ad09c 100644 --- a/web-ui/modules/i18n/locales/ja.mjs +++ b/web-ui/modules/i18n/locales/ja.mjs @@ -488,6 +488,11 @@ const ja = Object.freeze({ 'modal.claudeConfigEdit.title': 'Claude Code 設定編集', 'field.useBuiltinTransform': '内蔵変換を使用(OpenAI 形式互換)', 'hint.useBuiltinTransform': '有効時:書き込まれる base_url は codexmate 内蔵変換サービスを指します。Codex のトークンは codexmate に固定されます。', + 'hint.providerName': '内部プロバイダー ID。英数字、ドット、アンダースコア、ハイフンのみ使用できます。', + 'hint.providerEndpointBaseUrl': 'Base URL のみを入力します。通常は /v1 までで、/chat/completions や /responses などの具体的な API パスは入れません。', + 'hint.providerModelId': 'プロバイダー管理画面の正確なモデル ID(例: gpt-4o、deepseek-chat、openai/gpt-4o)を入力します。', + 'hint.providerKey': 'このプロバイダーの認証キーとして保存され、このプロバイダー呼び出し時のみ使用されます。', + 'hint.providerTransformOpenAiCompatible': 'OpenAI-compatible Chat Completions プロバイダーでは有効にします。Responses API をネイティブ対応する場合のみ無効にします。', // Config template / agents modals 'modal.configTemplate.label': 'config.toml テンプレート', diff --git a/web-ui/modules/i18n/locales/vi.mjs b/web-ui/modules/i18n/locales/vi.mjs index 5d326782..26e5c992 100644 --- a/web-ui/modules/i18n/locales/vi.mjs +++ b/web-ui/modules/i18n/locales/vi.mjs @@ -705,6 +705,11 @@ const vi = Object.freeze({ 'modal.claudeConfigEdit.title': 'Chỉnh sửa cấu hình Claude Code', 'field.useBuiltinTransform': 'Dùng transform tích hợp (tương thích OpenAI)', 'hint.useBuiltinTransform': 'Khi bật, base_url trỏ đến dịch vụ transform tích hợp của codexmate.', + 'hint.providerName': 'ID nhà cung cấp nội bộ, chỉ dùng chữ cái, số, dấu chấm, gạch dưới hoặc gạch nối.', + 'hint.providerEndpointBaseUrl': 'Chỉ nhập Base URL, thường kết thúc ở /v1. Không nhập /chat/completions, /responses hoặc đường dẫn API cụ thể khác.', + 'hint.providerModelId': 'ID model chính xác từ bảng điều khiển nhà cung cấp, ví dụ gpt-4o, deepseek-chat hoặc openai/gpt-4o.', + 'hint.providerKey': 'Được lưu làm khóa xác thực của nhà cung cấp và chỉ gửi khi gọi nhà cung cấp này.', + 'hint.providerTransformOpenAiCompatible': 'Bật cho nhà cung cấp Chat Completions tương thích OpenAI. Chỉ tắt khi endpoint hỗ trợ Responses API nguyên bản.', 'modal.configTemplate.title': 'Trình soạn thảo template cấu hình (xác nhận thủ công)', 'modal.configTemplate.label': 'Template config.toml', 'modal.configTemplate.placeholder': 'Chỉnh sửa template config.toml tại đây', diff --git a/web-ui/modules/i18n/locales/zh-tw.mjs b/web-ui/modules/i18n/locales/zh-tw.mjs index c18ade0b..5902304f 100644 --- a/web-ui/modules/i18n/locales/zh-tw.mjs +++ b/web-ui/modules/i18n/locales/zh-tw.mjs @@ -486,6 +486,11 @@ const zhTw = Object.freeze({ 'modal.claudeConfigEdit.title': '編輯 Claude Code 設定', 'field.useBuiltinTransform': '使用內建轉換(兼容 OpenAI 格式)', 'hint.useBuiltinTransform': '開啟後:寫入的 base_url 會指向 codexmate 內建轉換服務;Codex 使用的令牌固定為 codexmate。', + 'hint.providerName': '內部提供商 ID,僅支援字母、數字、點、底線或連字號。', + 'hint.providerEndpointBaseUrl': '只填 Base URL,通常到 /v1;不要填 /chat/completions、/responses 等具體接口路徑。', + 'hint.providerModelId': '填寫服務商後台給出的精確模型 ID,例如 gpt-4o、deepseek-chat 或 openai/gpt-4o。', + 'hint.providerKey': '作為該提供商的認證密鑰保存,只在調用此提供商時使用。', + 'hint.providerTransformOpenAiCompatible': 'OpenAI-compatible Chat Completions 服務商請開啟;只有原生支援 Responses API 時才關閉。', // Config template / agents modals 'modal.configTemplate.label': 'config.toml 模板', diff --git a/web-ui/modules/i18n/locales/zh.mjs b/web-ui/modules/i18n/locales/zh.mjs index c7b64d52..ac249f70 100644 --- a/web-ui/modules/i18n/locales/zh.mjs +++ b/web-ui/modules/i18n/locales/zh.mjs @@ -486,6 +486,11 @@ const zh = Object.freeze({ 'modal.claudeConfigEdit.title': '编辑 Claude Code 配置', 'field.useBuiltinTransform': '使用内建转换(兼容 OpenAI 格式)', 'hint.useBuiltinTransform': '开启后:写入的 base_url 会指向 codexmate 内建转换服务;Codex 使用的令牌固定为 codexmate。', + 'hint.providerName': '内部提供商 ID,仅支持字母、数字、点、下划线或连字符。', + 'hint.providerEndpointBaseUrl': '只填 Base URL,通常到 /v1;不要填 /chat/completions、/responses 等具体接口路径。', + 'hint.providerModelId': '填写服务商后台给出的精确模型 ID,例如 gpt-4o、deepseek-chat 或 openai/gpt-4o。', + 'hint.providerKey': '作为该提供商的认证密钥保存,只在调用此提供商时使用。', + 'hint.providerTransformOpenAiCompatible': 'OpenAI-compatible Chat Completions 服务商请开启;只有原生支持 Responses API 时才关闭。', // Config template / agents modals 'modal.configTemplate.label': 'config.toml 模板', diff --git a/web-ui/partials/index/modals-basic.html b/web-ui/partials/index/modals-basic.html index d4e4fcdb..8c7c0332 100644 --- a/web-ui/partials/index/modals-basic.html +++ b/web-ui/partials/index/modals-basic.html @@ -12,6 +12,7 @@ autocomplete="off" spellcheck="false" @blur="normalizeProviderDraft('add')"> +
{{ t('hint.providerName') }}
{{ providerFieldError('add', 'name') }}
@@ -23,6 +24,7 @@ autocomplete="off" spellcheck="false" @blur="normalizeProviderDraft('add')"> +
{{ t('hint.providerEndpointBaseUrl') }}
{{ providerFieldError('add', 'url') }}
@@ -34,6 +36,7 @@ autocomplete="off" spellcheck="false" @blur="normalizeProviderDraft('add')"> +
{{ t('hint.providerModelId') }}
{{ providerFieldError('add', 'model') }}
@@ -45,13 +48,15 @@
+
{{ t('hint.providerKey') }}
{{ providerFieldError('add', 'key') }}
-
+
+
{{ t('hint.providerTransformOpenAiCompatible') }}
@@ -79,6 +84,7 @@ autocomplete="off" spellcheck="false" @blur="normalizeProviderDraft('edit')"> +
{{ t('hint.providerEndpointBaseUrl') }}
{{ providerFieldError('edit', 'url') }}
diff --git a/web-ui/partials/index/panel-orchestration.html b/web-ui/partials/index/panel-orchestration.html index 9a345370..041bc0b6 100644 --- a/web-ui/partials/index/panel-orchestration.html +++ b/web-ui/partials/index/panel-orchestration.html @@ -6,7 +6,7 @@ :data-active="mainTab === 'orchestration' ? 'true' : 'false'" role="tabpanel" aria-labelledby="tab-orchestration"> -
+
@@ -40,7 +40,7 @@
-
+
@@ -79,7 +79,11 @@

{{ t('orchestration.kilo.surface.title') }}

-
+
+ {{ t('orchestration.toast.error.generic') }} + {{ t('orchestration.privacy.detailsHidden') }} +
+
{{ (taskOrchestrationWorkspacePath || taskOrchestration.workspacePath.trim()) ? t('orchestration.privacy.workspace.selected') : t('orchestration.chat.context.workspace.auto') }} {{ (taskOrchestration.threadId.trim() || (taskOrchestrationSelectedRun && taskOrchestrationSelectedRun.threadId)) ? t('orchestration.privacy.thread.selected') : t('orchestration.chat.context.thread.auto') }} {{ t('orchestration.kilo.engine.configured') }} diff --git a/web-ui/res/web-ui-render.precompiled.js b/web-ui/res/web-ui-render.precompiled.js index e5292461..646068a5 100644 --- a/web-ui/res/web-ui-render.precompiled.js +++ b/web-ui/res/web-ui-render.precompiled.js @@ -4002,7 +4002,7 @@ return function render(_ctx, _cache) { "aria-labelledby": "tab-orchestration" }, [ _createElementVNode("div", { - class: "container kilo-webview-container", + class: "container kilo-webview-container kilo-chat-shell", "data-kilo-webview": "orchestration" }, [ _createElementVNode("div", { class: "chat-view kilo-chat-view" }, [ @@ -4055,6 +4055,7 @@ return function render(_ctx, _cache) { _createElementVNode("div", { class: "message-list-container" }, [ _createElementVNode("div", { class: "message-list", + "data-component": "message-list", role: "log", "aria-label": _ctx.t('orchestration.chat.thread.aria') }, [ @@ -4119,7 +4120,20 @@ return function render(_ctx, _cache) { ]) ]), _createElementVNode("div", { class: "chat-input" }, [ - _createElementVNode("div", { class: "kilo-status-dock" }, [ + (_ctx.taskOrchestration.lastError) + ? (_openBlock(), _createElementBlock("div", { + key: 0, + class: "kilo-error-dock", + "data-component": "error-dock" + }, [ + _createElementVNode("strong", null, _toDisplayString(_ctx.t('orchestration.toast.error.generic')), 1 /* TEXT */), + _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.privacy.detailsHidden')), 1 /* TEXT */) + ])) + : _createCommentVNode("v-if", true), + _createElementVNode("div", { + class: "permission-dock kilo-status-dock", + "data-component": "permission-dock" + }, [ _createElementVNode("span", null, _toDisplayString((_ctx.taskOrchestrationWorkspacePath || _ctx.taskOrchestration.workspacePath.trim()) ? _ctx.t('orchestration.privacy.workspace.selected') : _ctx.t('orchestration.chat.context.workspace.auto')), 1 /* TEXT */), _createElementVNode("span", null, _toDisplayString((_ctx.taskOrchestration.threadId.trim() || (_ctx.taskOrchestrationSelectedRun && _ctx.taskOrchestrationSelectedRun.threadId)) ? _ctx.t('orchestration.privacy.thread.selected') : _ctx.t('orchestration.chat.context.thread.auto')), 1 /* TEXT */), _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.kilo.engine.configured')), 1 /* TEXT */), @@ -6013,6 +6027,7 @@ return function render(_ctx, _cache) { }, null, 42 /* CLASS, PROPS, NEED_HYDRATION */, ["onUpdate:modelValue", "placeholder", "onBlur"]), [ [_vModelText, _ctx.newProvider.name] ]), + _createElementVNode("div", { class: "form-hint" }, _toDisplayString(_ctx.t('hint.providerName')), 1 /* TEXT */), (_ctx.providerFieldError('add', 'name')) ? (_openBlock(), _createElementBlock("div", { key: 0, @@ -6032,6 +6047,7 @@ return function render(_ctx, _cache) { }, null, 42 /* CLASS, PROPS, NEED_HYDRATION */, ["onUpdate:modelValue", "placeholder", "onBlur"]), [ [_vModelText, _ctx.newProvider.url] ]), + _createElementVNode("div", { class: "form-hint" }, _toDisplayString(_ctx.t('hint.providerEndpointBaseUrl')), 1 /* TEXT */), (_ctx.providerFieldError('add', 'url')) ? (_openBlock(), _createElementBlock("div", { key: 0, @@ -6051,6 +6067,7 @@ return function render(_ctx, _cache) { }, null, 42 /* CLASS, PROPS, NEED_HYDRATION */, ["onUpdate:modelValue", "placeholder", "onBlur"]), [ [_vModelText, _ctx.newProvider.model] ]), + _createElementVNode("div", { class: "form-hint" }, _toDisplayString(_ctx.t('hint.providerModelId')), 1 /* TEXT */), (_ctx.providerFieldError('add', 'model')) ? (_openBlock(), _createElementBlock("div", { key: 0, @@ -6109,6 +6126,7 @@ return function render(_ctx, _cache) { ])) ], 8 /* PROPS */, ["onClick", "title", "aria-label"]) ]), + _createElementVNode("div", { class: "form-hint" }, _toDisplayString(_ctx.t('hint.providerKey')), 1 /* TEXT */), (_ctx.providerFieldError('add', 'key')) ? (_openBlock(), _createElementBlock("div", { key: 0, @@ -6116,7 +6134,7 @@ return function render(_ctx, _cache) { }, _toDisplayString(_ctx.providerFieldError('add', 'key')), 1 /* TEXT */)) : _createCommentVNode("v-if", true) ]), - _createElementVNode("div", { class: "form-group" }, [ + _createElementVNode("div", { class: "form-group provider-transform-option" }, [ _createElementVNode("label", { class: "form-label" }, [ _withDirectives(_createElementVNode("input", { type: "checkbox", @@ -6125,7 +6143,8 @@ return function render(_ctx, _cache) { [_vModelCheckbox, _ctx.newProvider.useTransform] ]), _createTextVNode(" " + _toDisplayString(_ctx.t('field.useBuiltinTransform')), 1 /* TEXT */) - ]) + ]), + _createElementVNode("div", { class: "form-hint" }, _toDisplayString(_ctx.t('hint.providerTransformOpenAiCompatible')), 1 /* TEXT */) ]), _createElementVNode("div", { class: "btn-group" }, [ _createElementVNode("button", { @@ -6181,6 +6200,7 @@ return function render(_ctx, _cache) { }, null, 42 /* CLASS, PROPS, NEED_HYDRATION */, ["onUpdate:modelValue", "placeholder", "onBlur"]), [ [_vModelText, _ctx.editingProvider.url] ]), + _createElementVNode("div", { class: "form-hint" }, _toDisplayString(_ctx.t('hint.providerEndpointBaseUrl')), 1 /* TEXT */), (_ctx.providerFieldError('edit', 'url')) ? (_openBlock(), _createElementBlock("div", { key: 0, diff --git a/web-ui/styles/skills-list.css b/web-ui/styles/skills-list.css index 5efd4f93..4b34d644 100644 --- a/web-ui/styles/skills-list.css +++ b/web-ui/styles/skills-list.css @@ -224,6 +224,21 @@ filter: brightness(1.05); } +.btn-confirm:disabled, +.btn-confirm[disabled], +.btn-confirm:disabled:hover, +.btn-confirm[disabled]:hover, +.btn-confirm:disabled:active, +.btn-confirm[disabled]:active { + background: linear-gradient(to bottom, rgba(180, 184, 190, 0.34) 0%, rgba(154, 160, 168, 0.28) 100%); + color: var(--color-text-tertiary); + box-shadow: none; + cursor: not-allowed; + filter: none; + opacity: 0.68; + transform: none; +} + .btn-confirm.secondary { background: linear-gradient(135deg, var(--color-success) 0%, rgba(90, 139, 106, 0.85) 100%); box-shadow: 0 2px 4px rgba(90, 139, 106, 0.2); diff --git a/web-ui/styles/task-orchestration.css b/web-ui/styles/task-orchestration.css index 75a1510c..469e0851 100644 --- a/web-ui/styles/task-orchestration.css +++ b/web-ui/styles/task-orchestration.css @@ -1,41 +1,41 @@ #panel-orchestration.kilo-webview-mode { --kilo-workbench-shell-offset: 230px; - --kilo-workbench-min-height: 560px; - --kilo-workbench-max-height: 980px; + --kilo-workbench-min-height: 520px; --kilo-reference-blue: #1e7fd3; --kilo-reference-blue-dark: #176db6; --kilo-reference-bg: #eef1f4; --kilo-reference-border: #d8dee6; --kilo-reference-text: #243447; --kilo-reference-muted: #6b7785; + --kilo-shell-surface: #ffffff; + --kilo-shell-subtle: #f7f9fc; + --kilo-shell-focus: rgba(30, 127, 211, 0.18); + display: flex; min-height: min(var(--kilo-workbench-min-height), calc(100vh - var(--kilo-workbench-shell-offset))); min-height: min(var(--kilo-workbench-min-height), calc(100dvh - var(--kilo-workbench-shell-offset))); - padding: 26px clamp(12px, 2vw, 22px) 30px; + padding: 0; background: var(--kilo-reference-bg); } #panel-orchestration .kilo-webview-container { - width: min(100%, 980px); - max-width: 980px; - margin: 0 auto; - height: clamp( - var(--kilo-workbench-min-height), - calc(100vh - var(--kilo-workbench-shell-offset)), - var(--kilo-workbench-max-height) - ); - height: clamp( - var(--kilo-workbench-min-height), - calc(100dvh - var(--kilo-workbench-shell-offset)), - var(--kilo-workbench-max-height) - ); + width: 100%; + max-width: none; + margin: 0; + height: calc(100vh - var(--kilo-workbench-shell-offset)); + height: calc(100dvh - var(--kilo-workbench-shell-offset)); min-height: 0; overflow: hidden; - border: 1px solid var(--kilo-reference-border); - border-radius: 6px; - background: #fff; + border: 0; + border-radius: 0; + background: var(--kilo-shell-surface); color: var(--kilo-reference-text); - box-shadow: 0 2px 8px rgba(24, 48, 84, 0.16), 0 12px 24px rgba(24, 48, 84, 0.08); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.72); +} + +#panel-orchestration .kilo-chat-shell { + display: flex; + flex-direction: column; } #panel-orchestration .kilo-chat-view { @@ -43,7 +43,7 @@ height: 100%; min-height: 0; flex-direction: column; - background: #fff; + background: var(--kilo-shell-surface); color: var(--kilo-reference-text); --vscode-descriptionForeground: var(--kilo-reference-muted); --vscode-button-background: var(--kilo-reference-blue); @@ -59,17 +59,16 @@ #panel-orchestration [data-component="task-header"] { flex-shrink: 0; - border-bottom: 1px solid rgba(0, 0, 0, 0.08); - background: var(--kilo-reference-blue); - color: #fff; - box-shadow: 0 2px 8px rgba(24, 48, 84, 0.16); + border-bottom: 1px solid var(--kilo-reference-border); + background: linear-gradient(180deg, #ffffff 0%, #f8fafc 100%); + color: var(--kilo-reference-text); } #panel-orchestration .kilo-task-header { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 6px 14px; - padding: 9px 14px 7px; + padding: 10px clamp(14px, 3vw, 34px) 8px; } #panel-orchestration [data-slot="task-header-title"] { @@ -79,8 +78,8 @@ #panel-orchestration [data-slot="task-header-title-label"] { display: block; overflow: hidden; - color: #fff; - font-size: 18px; + color: var(--kilo-reference-text); + font-size: 15px; font-weight: 700; text-overflow: ellipsis; white-space: nowrap; @@ -90,7 +89,7 @@ display: block; margin-top: 3px; overflow: hidden; - color: rgba(255, 255, 255, 0.82); + color: var(--kilo-reference-muted); font-size: 12px; text-overflow: ellipsis; white-space: nowrap; @@ -101,7 +100,7 @@ align-items: center; justify-content: flex-end; gap: 8px; - color: rgba(255, 255, 255, 0.86); + color: var(--kilo-reference-muted); font-size: 12px; } @@ -117,9 +116,9 @@ } #panel-orchestration .kilo-task-timeline-bar { - height: 4px; + height: 3px; border-radius: 999px; - background: rgba(255, 255, 255, 0.28); + background: #dce3ec; } #panel-orchestration .kilo-task-timeline-bar.success { background: #22c55e; } @@ -140,13 +139,14 @@ width: 26px; height: 26px; place-items: center; - border-color: rgba(255, 255, 255, 0.30); - background: rgba(255, 255, 255, 0.12); - color: #fff; + border-color: var(--kilo-reference-border); + background: #fff; + color: var(--kilo-reference-text); } #panel-orchestration .kilo-icon-button:hover { - background: rgba(255, 255, 255, 0.22); + background: #eef6ff; + border-color: rgba(30, 127, 211, 0.38); } #panel-orchestration .kilo-primary-button { @@ -179,6 +179,7 @@ flex: 1; min-height: 0; overflow: hidden; + background: var(--kilo-shell-surface); } #panel-orchestration .chat-messages, @@ -192,8 +193,8 @@ height: 100%; overflow-x: hidden; overflow-y: auto; - padding: 16px clamp(18px, 3vw, 34px); - background: #fff; + padding: 16px clamp(18px, 4vw, 56px); + background: linear-gradient(180deg, #fff 0%, #fbfcfe 100%); scrollbar-color: rgba(30, 127, 211, 0.35) transparent; scrollbar-gutter: stable; scrollbar-width: thin; @@ -204,6 +205,8 @@ min-height: 100%; flex-direction: column; gap: 12px; + width: min(100%, 98ch); + margin: 0 auto; } #panel-orchestration .message-list-content-empty { @@ -227,9 +230,10 @@ width: 52px; height: 52px; place-items: center; + border: 1px solid rgba(30, 127, 211, 0.2); border-radius: 14px; - background: var(--kilo-reference-blue); - color: #fff; + background: #eef6ff; + color: var(--kilo-reference-blue); font-size: 24px; font-weight: 900; } @@ -300,14 +304,14 @@ #panel-orchestration .vscode-session-turn-user, #panel-orchestration .vscode-session-turn-assistant { - max-width: min(100%, 920px); - border-radius: 6px; + max-width: min(100%, 92ch); + border-radius: 8px; color: var(--kilo-reference-text); } #panel-orchestration .vscode-session-turn-user { align-self: flex-end; - width: min(100%, 760px); + width: min(100%, 76ch); padding: 10px 12px; border: 1px solid rgba(30, 127, 211, 0.18); background: #eef6ff; @@ -315,7 +319,7 @@ #panel-orchestration .vscode-session-turn-assistant { align-self: flex-start; - width: min(100%, 920px); + width: min(100%, 92ch); padding: 10px 12px; border: 1px solid var(--kilo-reference-border); background: #fff; @@ -333,32 +337,8 @@ white-space: pre-wrap; } -#panel-orchestration .kilo-plan-response { - border: 1px solid var(--kilo-reference-border); - border-radius: 6px; - background: #f8fafc; - padding: 12px; -} - -#panel-orchestration .kilo-plan-summary-line { - display: flex; - align-items: center; - gap: 8px; - color: var(--kilo-reference-text); - font-size: 13px; -} - -#panel-orchestration .kilo-plan-summary-line span { - border-radius: 999px; - background: rgba(37, 99, 235, 0.18); - color: #93c5fd; - padding: 3px 8px; - font-size: 11px; - font-weight: 800; -} - -#panel-orchestration .kilo-redacted-block { - margin-top: 8px; +#panel-orchestration .kilo-redacted-block, +#panel-orchestration .kilo-error-dock { border: 1px solid rgba(245, 158, 11, 0.28); border-radius: 8px; background: #fff8e6; @@ -367,51 +347,29 @@ font-size: 12px; } -#panel-orchestration .kilo-tool-list { - display: flex; - flex-direction: column; - gap: 8px; - margin-top: 10px; -} - -#panel-orchestration .kilo-tool-card { - border: 1px solid var(--kilo-reference-border); - border-radius: 8px; - background: #f8fafc; - padding: 9px; +#panel-orchestration .kilo-redacted-block { + margin-top: 8px; } -#panel-orchestration .kilo-tool-title { +#panel-orchestration .kilo-error-dock { display: flex; align-items: center; justify-content: space-between; gap: 8px; -} - -#panel-orchestration .kilo-tool-title span, -#panel-orchestration .kilo-tool-card small { - color: var(--kilo-reference-muted); - font-size: 11px; - text-transform: uppercase; -} - -#panel-orchestration .kilo-tool-title strong { - overflow: hidden; - color: var(--kilo-reference-text); - font-size: 12px; - text-overflow: ellipsis; - white-space: nowrap; + margin: 0 auto 8px; + width: min(100%, 98ch); } #panel-orchestration .chat-input { flex-shrink: 0; border-top: 1px solid var(--kilo-reference-border); - background: #f8fafc; - padding: 10px clamp(18px, 3vw, 34px) 14px; + background: var(--kilo-shell-subtle); + padding: 10px clamp(18px, 4vw, 56px) 14px; } #panel-orchestration .kilo-status-dock { - margin-bottom: 8px; + width: min(100%, 98ch); + margin: 0 auto 8px; color: var(--kilo-reference-muted); font-size: 11px; } @@ -425,18 +383,26 @@ #panel-orchestration .prompt-input-container { display: flex; + width: min(100%, 98ch); + margin: 0 auto; flex-direction: column; gap: 8px; + border: 1px solid var(--kilo-reference-border); + border-radius: 8px; + background: #fff; + box-shadow: 0 8px 24px rgba(24, 48, 84, 0.08); +} + +#panel-orchestration .prompt-input-container:focus-within { + border-color: var(--kilo-reference-blue); + box-shadow: 0 0 0 3px var(--kilo-shell-focus), 0 8px 24px rgba(24, 48, 84, 0.08); } #panel-orchestration .prompt-input-shell { display: grid; grid-template-columns: 22px minmax(0, 1fr); gap: 8px; - border: 1px solid var(--kilo-reference-border); - border-radius: 6px; - background: #fff; - padding: 9px 10px; + padding: 9px 10px 0; } #panel-orchestration .prompt-input-prefix { @@ -447,7 +413,7 @@ } #panel-orchestration .prompt-input-textarea { - min-height: 64px; + min-height: 62px; width: 100%; border: 0; outline: 0; @@ -461,8 +427,10 @@ #panel-orchestration .prompt-input-actions { justify-content: flex-end; + border-top: 1px solid #eef2f7; color: var(--kilo-reference-muted); font-size: 12px; + padding: 8px 10px 9px; } #panel-orchestration .prompt-input-actions span { @@ -472,13 +440,8 @@ @media (max-width: 820px) { #panel-orchestration.kilo-webview-mode { - --kilo-workbench-shell-offset: 152px; - --kilo-workbench-min-height: 520px; - --kilo-workbench-max-height: 1000px; - } - - #panel-orchestration .kilo-webview-container { - border-radius: 0; + --kilo-workbench-shell-offset: 184px; + --kilo-workbench-min-height: 420px; } #panel-orchestration .kilo-task-header, @@ -490,4 +453,9 @@ #panel-orchestration .prompt-input-actions { justify-content: flex-start; } + + #panel-orchestration .message-list, + #panel-orchestration .chat-input { + padding-inline: 12px; + } } From c7d935f7bd6864eaeb772c9968ed84f91f6bcbad Mon Sep 17 00:00:00 2001 From: awsl233777 Date: Wed, 1 Jul 2026 08:17:43 +0000 Subject: [PATCH 21/25] fix(providers): keep endpoint validation unchanged Keep the provider endpoint change to guidance text only. Do not alter the existing Codex provider add/edit validation flow as part of the orchestration UI pass. --- tests/unit/providers-validation.test.mjs | 26 ------------------------ web-ui/modules/app.methods.providers.mjs | 6 ------ 2 files changed, 32 deletions(-) diff --git a/tests/unit/providers-validation.test.mjs b/tests/unit/providers-validation.test.mjs index f3f7f093..7a50b0a7 100644 --- a/tests/unit/providers-validation.test.mjs +++ b/tests/unit/providers-validation.test.mjs @@ -165,32 +165,6 @@ test('updateProvider blocks invalid edit URL and skips api call', async () => { }); }); -test('provider validation rejects concrete API paths instead of base URLs', async () => { - const apiCalls = []; - const { context, messages } = createContext({ - newProvider: { - name: 'openai-compatible', - url: 'https://api.example.com/v1/chat/completions', - key: 'sk-test', - model: 'gpt-e2e' - } - }, async (action, params) => { - apiCalls.push({ action, params }); - return { success: true }; - }); - - assert.strictEqual(context.canSubmitProvider('add'), false); - assert.strictEqual(context.providerFieldError('add', 'url'), '请填写 base URL(通常到 /v1),不要填写具体接口路径'); - - await context.addProvider(); - - assert.deepStrictEqual(apiCalls, []); - assert.deepStrictEqual(messages[0], { - text: '请填写 base URL(通常到 /v1),不要填写具体接口路径', - type: 'error' - }); -}); - test('provider validation rejects reserved proxy name on add', () => { const { context } = createContext({ newProvider: { diff --git a/web-ui/modules/app.methods.providers.mjs b/web-ui/modules/app.methods.providers.mjs index dcfefe8c..f212855c 100644 --- a/web-ui/modules/app.methods.providers.mjs +++ b/web-ui/modules/app.methods.providers.mjs @@ -101,12 +101,6 @@ function getProviderValidationForContext(vm, mode = 'add') { errors.url = 'URL 必填'; } else if (!isValidProviderUrlInputValue(url)) { errors.url = 'URL 仅支持 http/https'; - } else { - const parsed = new URL(url); - const normalizedPath = parsed.pathname.replace(/\/+$/g, '').toLowerCase(); - if (/\/(chat\/completions|responses|completions)$/.test(normalizedPath)) { - errors.url = '请填写 base URL(通常到 /v1),不要填写具体接口路径'; - } } if (mode === 'add' && !key) { From 6e465ba8a4447ce88fc35bec69d0abffbc87e91b Mon Sep 17 00:00:00 2001 From: awsl233777 Date: Wed, 1 Jul 2026 11:37:37 +0000 Subject: [PATCH 22/25] fix(orchestration): keep changes scoped to task tab Revert the provider modal hints, shared button styling, and provider test edits from the orchestration UI pass. Keep this PR scoped to the task orchestration tab and its generated render/test coverage. --- tests/unit/config-tabs-ui.test.mjs | 3 --- tests/unit/providers-validation.test.mjs | 2 ++ web-ui/modules/i18n/locales/en.mjs | 5 ----- web-ui/modules/i18n/locales/ja.mjs | 5 ----- web-ui/modules/i18n/locales/vi.mjs | 5 ----- web-ui/modules/i18n/locales/zh-tw.mjs | 5 ----- web-ui/modules/i18n/locales/zh.mjs | 5 ----- web-ui/partials/index/modals-basic.html | 8 +------- web-ui/res/web-ui-render.precompiled.js | 10 ++-------- web-ui/styles/skills-list.css | 15 --------------- 10 files changed, 5 insertions(+), 58 deletions(-) diff --git a/tests/unit/config-tabs-ui.test.mjs b/tests/unit/config-tabs-ui.test.mjs index f974c530..304e2d2f 100644 --- a/tests/unit/config-tabs-ui.test.mjs +++ b/tests/unit/config-tabs-ui.test.mjs @@ -487,9 +487,6 @@ test('config template keeps expected config tabs in top and side navigation', () assert.match(modalsBasic, //); assert.match(modalsBasic, /]*:class="\['form-input', \{ invalid: !!providerFieldError\('add', 'key'\) \}\]"[^>]*:type="showAddProviderKey \? 'text' : 'password'"[^>]*placeholder="sk-\.\.\."[^>]*autocomplete="off"[^>]*spellcheck="false"[^>]*@blur="normalizeProviderDraft\('add'\)">/); assert.match(modalsBasic, /
\{\{ providerFieldError\('add', 'key'\) \}\}<\/div>/); - assert.match(modalsBasic, /t\('hint\.providerEndpointBaseUrl'\)/); - assert.match(modalsBasic, /t\('hint\.providerModelId'\)/); - assert.match(modalsBasic, /t\('hint\.providerTransformOpenAiCompatible'\)/); assert.match(modalsBasic, //); assert.match(modalsBasic, /]*:class="\['form-input', \{ invalid: !!claudeConfigFieldError\('add', 'apiKey'\) \}\]"[^>]*:type="showAddClaudeConfigKey \? 'text' : 'password'"[^>]*autocomplete="off"[^>]*spellcheck="false"[^>]*:placeholder="t\('placeholder\.apiKeyExampleClaude'\)">/); assert.match(modalsBasic, /
\{\{ claudeConfigFieldError\('add', 'apiKey'\) \}\}<\/div>/); diff --git a/tests/unit/providers-validation.test.mjs b/tests/unit/providers-validation.test.mjs index 7a50b0a7..55e100ea 100644 --- a/tests/unit/providers-validation.test.mjs +++ b/tests/unit/providers-validation.test.mjs @@ -165,6 +165,8 @@ test('updateProvider blocks invalid edit URL and skips api call', async () => { }); }); + + test('provider validation rejects reserved proxy name on add', () => { const { context } = createContext({ newProvider: { diff --git a/web-ui/modules/i18n/locales/en.mjs b/web-ui/modules/i18n/locales/en.mjs index 0532b247..edd86c5b 100644 --- a/web-ui/modules/i18n/locales/en.mjs +++ b/web-ui/modules/i18n/locales/en.mjs @@ -486,11 +486,6 @@ const en = Object.freeze({ 'modal.claudeConfigEdit.title': 'Edit Claude Code config', 'field.useBuiltinTransform': 'Use built-in transform (OpenAI compatible)', 'hint.useBuiltinTransform': 'When enabled, base_url points to codexmate built-in transform service; Codex token is fixed to codexmate.', - 'hint.providerName': 'Internal provider id, using only letters, numbers, dots, underscores, or hyphens.', - 'hint.providerEndpointBaseUrl': 'Base URL only, usually ending at /v1. Do not include /chat/completions, /responses, or other request paths.', - 'hint.providerModelId': 'Exact model ID from the provider console, for example gpt-4o, deepseek-chat, or openai/gpt-4o.', - 'hint.providerKey': 'Stored as the provider auth key and sent only when calling this provider.', - 'hint.providerTransformOpenAiCompatible': 'Enable for OpenAI-compatible Chat Completions providers. Leave off only when the endpoint natively supports the Responses API.', // Config template / agents modals 'modal.configTemplate.label': 'config.toml template', diff --git a/web-ui/modules/i18n/locales/ja.mjs b/web-ui/modules/i18n/locales/ja.mjs index 079ad09c..5ce5550c 100644 --- a/web-ui/modules/i18n/locales/ja.mjs +++ b/web-ui/modules/i18n/locales/ja.mjs @@ -488,11 +488,6 @@ const ja = Object.freeze({ 'modal.claudeConfigEdit.title': 'Claude Code 設定編集', 'field.useBuiltinTransform': '内蔵変換を使用(OpenAI 形式互換)', 'hint.useBuiltinTransform': '有効時:書き込まれる base_url は codexmate 内蔵変換サービスを指します。Codex のトークンは codexmate に固定されます。', - 'hint.providerName': '内部プロバイダー ID。英数字、ドット、アンダースコア、ハイフンのみ使用できます。', - 'hint.providerEndpointBaseUrl': 'Base URL のみを入力します。通常は /v1 までで、/chat/completions や /responses などの具体的な API パスは入れません。', - 'hint.providerModelId': 'プロバイダー管理画面の正確なモデル ID(例: gpt-4o、deepseek-chat、openai/gpt-4o)を入力します。', - 'hint.providerKey': 'このプロバイダーの認証キーとして保存され、このプロバイダー呼び出し時のみ使用されます。', - 'hint.providerTransformOpenAiCompatible': 'OpenAI-compatible Chat Completions プロバイダーでは有効にします。Responses API をネイティブ対応する場合のみ無効にします。', // Config template / agents modals 'modal.configTemplate.label': 'config.toml テンプレート', diff --git a/web-ui/modules/i18n/locales/vi.mjs b/web-ui/modules/i18n/locales/vi.mjs index 26e5c992..5d326782 100644 --- a/web-ui/modules/i18n/locales/vi.mjs +++ b/web-ui/modules/i18n/locales/vi.mjs @@ -705,11 +705,6 @@ const vi = Object.freeze({ 'modal.claudeConfigEdit.title': 'Chỉnh sửa cấu hình Claude Code', 'field.useBuiltinTransform': 'Dùng transform tích hợp (tương thích OpenAI)', 'hint.useBuiltinTransform': 'Khi bật, base_url trỏ đến dịch vụ transform tích hợp của codexmate.', - 'hint.providerName': 'ID nhà cung cấp nội bộ, chỉ dùng chữ cái, số, dấu chấm, gạch dưới hoặc gạch nối.', - 'hint.providerEndpointBaseUrl': 'Chỉ nhập Base URL, thường kết thúc ở /v1. Không nhập /chat/completions, /responses hoặc đường dẫn API cụ thể khác.', - 'hint.providerModelId': 'ID model chính xác từ bảng điều khiển nhà cung cấp, ví dụ gpt-4o, deepseek-chat hoặc openai/gpt-4o.', - 'hint.providerKey': 'Được lưu làm khóa xác thực của nhà cung cấp và chỉ gửi khi gọi nhà cung cấp này.', - 'hint.providerTransformOpenAiCompatible': 'Bật cho nhà cung cấp Chat Completions tương thích OpenAI. Chỉ tắt khi endpoint hỗ trợ Responses API nguyên bản.', 'modal.configTemplate.title': 'Trình soạn thảo template cấu hình (xác nhận thủ công)', 'modal.configTemplate.label': 'Template config.toml', 'modal.configTemplate.placeholder': 'Chỉnh sửa template config.toml tại đây', diff --git a/web-ui/modules/i18n/locales/zh-tw.mjs b/web-ui/modules/i18n/locales/zh-tw.mjs index 5902304f..c18ade0b 100644 --- a/web-ui/modules/i18n/locales/zh-tw.mjs +++ b/web-ui/modules/i18n/locales/zh-tw.mjs @@ -486,11 +486,6 @@ const zhTw = Object.freeze({ 'modal.claudeConfigEdit.title': '編輯 Claude Code 設定', 'field.useBuiltinTransform': '使用內建轉換(兼容 OpenAI 格式)', 'hint.useBuiltinTransform': '開啟後:寫入的 base_url 會指向 codexmate 內建轉換服務;Codex 使用的令牌固定為 codexmate。', - 'hint.providerName': '內部提供商 ID,僅支援字母、數字、點、底線或連字號。', - 'hint.providerEndpointBaseUrl': '只填 Base URL,通常到 /v1;不要填 /chat/completions、/responses 等具體接口路徑。', - 'hint.providerModelId': '填寫服務商後台給出的精確模型 ID,例如 gpt-4o、deepseek-chat 或 openai/gpt-4o。', - 'hint.providerKey': '作為該提供商的認證密鑰保存,只在調用此提供商時使用。', - 'hint.providerTransformOpenAiCompatible': 'OpenAI-compatible Chat Completions 服務商請開啟;只有原生支援 Responses API 時才關閉。', // Config template / agents modals 'modal.configTemplate.label': 'config.toml 模板', diff --git a/web-ui/modules/i18n/locales/zh.mjs b/web-ui/modules/i18n/locales/zh.mjs index ac249f70..c7b64d52 100644 --- a/web-ui/modules/i18n/locales/zh.mjs +++ b/web-ui/modules/i18n/locales/zh.mjs @@ -486,11 +486,6 @@ const zh = Object.freeze({ 'modal.claudeConfigEdit.title': '编辑 Claude Code 配置', 'field.useBuiltinTransform': '使用内建转换(兼容 OpenAI 格式)', 'hint.useBuiltinTransform': '开启后:写入的 base_url 会指向 codexmate 内建转换服务;Codex 使用的令牌固定为 codexmate。', - 'hint.providerName': '内部提供商 ID,仅支持字母、数字、点、下划线或连字符。', - 'hint.providerEndpointBaseUrl': '只填 Base URL,通常到 /v1;不要填 /chat/completions、/responses 等具体接口路径。', - 'hint.providerModelId': '填写服务商后台给出的精确模型 ID,例如 gpt-4o、deepseek-chat 或 openai/gpt-4o。', - 'hint.providerKey': '作为该提供商的认证密钥保存,只在调用此提供商时使用。', - 'hint.providerTransformOpenAiCompatible': 'OpenAI-compatible Chat Completions 服务商请开启;只有原生支持 Responses API 时才关闭。', // Config template / agents modals 'modal.configTemplate.label': 'config.toml 模板', diff --git a/web-ui/partials/index/modals-basic.html b/web-ui/partials/index/modals-basic.html index 8c7c0332..d4e4fcdb 100644 --- a/web-ui/partials/index/modals-basic.html +++ b/web-ui/partials/index/modals-basic.html @@ -12,7 +12,6 @@ autocomplete="off" spellcheck="false" @blur="normalizeProviderDraft('add')"> -
{{ t('hint.providerName') }}
{{ providerFieldError('add', 'name') }}
@@ -24,7 +23,6 @@ autocomplete="off" spellcheck="false" @blur="normalizeProviderDraft('add')"> -
{{ t('hint.providerEndpointBaseUrl') }}
{{ providerFieldError('add', 'url') }}
@@ -36,7 +34,6 @@ autocomplete="off" spellcheck="false" @blur="normalizeProviderDraft('add')"> -
{{ t('hint.providerModelId') }}
{{ providerFieldError('add', 'model') }}
@@ -48,15 +45,13 @@
-
{{ t('hint.providerKey') }}
{{ providerFieldError('add', 'key') }}
-
+
-
{{ t('hint.providerTransformOpenAiCompatible') }}
@@ -84,7 +79,6 @@ autocomplete="off" spellcheck="false" @blur="normalizeProviderDraft('edit')"> -
{{ t('hint.providerEndpointBaseUrl') }}
{{ providerFieldError('edit', 'url') }}
diff --git a/web-ui/res/web-ui-render.precompiled.js b/web-ui/res/web-ui-render.precompiled.js index 646068a5..bcddd2b3 100644 --- a/web-ui/res/web-ui-render.precompiled.js +++ b/web-ui/res/web-ui-render.precompiled.js @@ -6027,7 +6027,6 @@ return function render(_ctx, _cache) { }, null, 42 /* CLASS, PROPS, NEED_HYDRATION */, ["onUpdate:modelValue", "placeholder", "onBlur"]), [ [_vModelText, _ctx.newProvider.name] ]), - _createElementVNode("div", { class: "form-hint" }, _toDisplayString(_ctx.t('hint.providerName')), 1 /* TEXT */), (_ctx.providerFieldError('add', 'name')) ? (_openBlock(), _createElementBlock("div", { key: 0, @@ -6047,7 +6046,6 @@ return function render(_ctx, _cache) { }, null, 42 /* CLASS, PROPS, NEED_HYDRATION */, ["onUpdate:modelValue", "placeholder", "onBlur"]), [ [_vModelText, _ctx.newProvider.url] ]), - _createElementVNode("div", { class: "form-hint" }, _toDisplayString(_ctx.t('hint.providerEndpointBaseUrl')), 1 /* TEXT */), (_ctx.providerFieldError('add', 'url')) ? (_openBlock(), _createElementBlock("div", { key: 0, @@ -6067,7 +6065,6 @@ return function render(_ctx, _cache) { }, null, 42 /* CLASS, PROPS, NEED_HYDRATION */, ["onUpdate:modelValue", "placeholder", "onBlur"]), [ [_vModelText, _ctx.newProvider.model] ]), - _createElementVNode("div", { class: "form-hint" }, _toDisplayString(_ctx.t('hint.providerModelId')), 1 /* TEXT */), (_ctx.providerFieldError('add', 'model')) ? (_openBlock(), _createElementBlock("div", { key: 0, @@ -6126,7 +6123,6 @@ return function render(_ctx, _cache) { ])) ], 8 /* PROPS */, ["onClick", "title", "aria-label"]) ]), - _createElementVNode("div", { class: "form-hint" }, _toDisplayString(_ctx.t('hint.providerKey')), 1 /* TEXT */), (_ctx.providerFieldError('add', 'key')) ? (_openBlock(), _createElementBlock("div", { key: 0, @@ -6134,7 +6130,7 @@ return function render(_ctx, _cache) { }, _toDisplayString(_ctx.providerFieldError('add', 'key')), 1 /* TEXT */)) : _createCommentVNode("v-if", true) ]), - _createElementVNode("div", { class: "form-group provider-transform-option" }, [ + _createElementVNode("div", { class: "form-group" }, [ _createElementVNode("label", { class: "form-label" }, [ _withDirectives(_createElementVNode("input", { type: "checkbox", @@ -6143,8 +6139,7 @@ return function render(_ctx, _cache) { [_vModelCheckbox, _ctx.newProvider.useTransform] ]), _createTextVNode(" " + _toDisplayString(_ctx.t('field.useBuiltinTransform')), 1 /* TEXT */) - ]), - _createElementVNode("div", { class: "form-hint" }, _toDisplayString(_ctx.t('hint.providerTransformOpenAiCompatible')), 1 /* TEXT */) + ]) ]), _createElementVNode("div", { class: "btn-group" }, [ _createElementVNode("button", { @@ -6200,7 +6195,6 @@ return function render(_ctx, _cache) { }, null, 42 /* CLASS, PROPS, NEED_HYDRATION */, ["onUpdate:modelValue", "placeholder", "onBlur"]), [ [_vModelText, _ctx.editingProvider.url] ]), - _createElementVNode("div", { class: "form-hint" }, _toDisplayString(_ctx.t('hint.providerEndpointBaseUrl')), 1 /* TEXT */), (_ctx.providerFieldError('edit', 'url')) ? (_openBlock(), _createElementBlock("div", { key: 0, diff --git a/web-ui/styles/skills-list.css b/web-ui/styles/skills-list.css index 4b34d644..5efd4f93 100644 --- a/web-ui/styles/skills-list.css +++ b/web-ui/styles/skills-list.css @@ -224,21 +224,6 @@ filter: brightness(1.05); } -.btn-confirm:disabled, -.btn-confirm[disabled], -.btn-confirm:disabled:hover, -.btn-confirm[disabled]:hover, -.btn-confirm:disabled:active, -.btn-confirm[disabled]:active { - background: linear-gradient(to bottom, rgba(180, 184, 190, 0.34) 0%, rgba(154, 160, 168, 0.28) 100%); - color: var(--color-text-tertiary); - box-shadow: none; - cursor: not-allowed; - filter: none; - opacity: 0.68; - transform: none; -} - .btn-confirm.secondary { background: linear-gradient(135deg, var(--color-success) 0%, rgba(90, 139, 106, 0.85) 100%); box-shadow: 0 2px 4px rgba(90, 139, 106, 0.2); From fb0d48417753bca3f1e095684153b6bdd0097f9d Mon Sep 17 00:00:00 2001 From: awsl233777 Date: Thu, 2 Jul 2026 08:30:15 +0000 Subject: [PATCH 23/25] fix(orchestration): add OpenAI chat provider fallback --- cli.js | 198 ++++++++++++++------ tests/e2e/test-task-orchestration.js | 125 +++++++++++- tests/unit/task-codex-exec-windows.test.mjs | 41 ++-- 3 files changed, 289 insertions(+), 75 deletions(-) diff --git a/cli.js b/cli.js index 742b49aa..06a34218 100644 --- a/cli.js +++ b/cli.js @@ -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() : ''; @@ -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; @@ -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, @@ -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) { @@ -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 }] }; } @@ -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 @@ -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)` })), diff --git a/tests/e2e/test-task-orchestration.js b/tests/e2e/test-task-orchestration.js index bec7ad8e..d2843c77 100644 --- a/tests/e2e/test-task-orchestration.js +++ b/tests/e2e/test-task-orchestration.js @@ -118,6 +118,67 @@ process.on('SIGINT', () => server.close(() => process.exit(0))); throw new Error(`OpenAI Chat mock did not start: ${stderr}`); } +async function startFailingOpenAiChatMock(tmpHome, status = 401, message = 'Invalid token') { + const scriptPath = path.join(tmpHome, `task-openai-chat-failing-${status}.cjs`); + const portFile = path.join(tmpHome, `task-openai-chat-failing-${status}.port`); + fs.writeFileSync(scriptPath, ` +const http = require('http'); +const fs = require('fs'); +const portFile = process.argv[2]; +const status = Number(process.argv[3] || 401); +const message = process.argv[4] || 'Invalid token'; +const server = http.createServer((req, res) => { + req.resume(); + req.on('end', () => { + const body = JSON.stringify({ error: { message } }); + res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8', 'Content-Length': Buffer.byteLength(body, 'utf-8') }); + res.end(body, 'utf-8'); + }); +}); +server.listen(0, '127.0.0.1', () => { + fs.writeFileSync(portFile, String(server.address().port), 'utf-8'); +}); +process.on('SIGTERM', () => server.close(() => process.exit(0))); +process.on('SIGINT', () => server.close(() => process.exit(0))); +`, 'utf-8'); + + const child = spawn(process.execPath, [scriptPath, portFile, String(status), message], { + stdio: ['ignore', 'ignore', 'pipe'] + }); + let stderr = ''; + child.stderr.on('data', chunk => { stderr += chunk.toString(); }); + for (let i = 0; i < 80; i += 1) { + if (fs.existsSync(portFile)) { + const port = Number(fs.readFileSync(portFile, 'utf-8').trim()); + if (Number.isFinite(port) && port > 0) { + return { + port, + close() { + return new Promise((resolve) => { + if (child.exitCode !== null || child.signalCode) return resolve(); + const timer = setTimeout(() => { + try { child.kill('SIGKILL'); } catch (_) {} + resolve(); + }, 2000); + child.once('exit', () => { + clearTimeout(timer); + resolve(); + }); + try { child.kill('SIGTERM'); } catch (_) { resolve(); } + }); + } + }; + } + } + if (child.exitCode !== null) { + throw new Error(`Failing OpenAI Chat mock exited early: ${stderr}`); + } + await sleep(100); + } + try { child.kill('SIGKILL'); } catch (_) {} + throw new Error(`Failing OpenAI Chat mock did not start: ${stderr}`); +} + function writeOpenAiChatConfig(tmpHome, baseUrl) { const configDir = path.join(tmpHome, '.codex'); fs.mkdirSync(configDir, { recursive: true }); @@ -145,6 +206,40 @@ function writeOpenAiChatConfig(tmpHome, baseUrl) { ].join('\n'), 'utf-8'); } +function writeOpenAiChatFallbackConfig(tmpHome, badBaseUrl, fallbackBaseUrl) { + const configDir = path.join(tmpHome, '.codex'); + fs.mkdirSync(configDir, { recursive: true }); + fs.writeFileSync(path.join(configDir, 'codexmate-init.json'), JSON.stringify({ version: 1, mode: 'task-openai-chat-fallback-e2e' }), 'utf-8'); + fs.writeFileSync(path.join(configDir, 'config.toml'), [ + 'model = "gpt-5.3-codex"', + 'model_provider = "local"', + 'task_openai_chat_provider = "bad-chat"', + '', + '[model_providers.local]', + 'name = "Local Codex"', + 'base_url = "http://127.0.0.1:9/v1"', + 'wire_api = "responses"', + 'preferred_auth_method = "sk-codex-tab-secret"', + 'models = ["gpt-5.3-codex"]', + '', + '[model_providers.bad-chat]', + 'name = "Bad Chat"', + `base_url = "${badBaseUrl}/v1"`, + 'wire_api = "chat_completions"', + 'preferred_auth_method = "sk-bad-token"', + 'models = ["bad-model"]', + '', + '[model_providers.new-api-chat]', + 'name = "New API Chat"', + `base_url = "${fallbackBaseUrl}/v1"`, + 'wire_api = "chat_completions"', + 'preferred_auth_method = "sk-task-e2e-secret"', + 'temperature = 0.7', + 'models = ["glm-5.2"]', + '' + ].join('\n'), 'utf-8'); +} + function assertOpenAiRunPayload(payload, label) { assert(payload && payload.run && payload.run.status === 'success', `${label} should succeed`); const nodes = Array.isArray(payload.run.nodes) ? payload.run.nodes : []; @@ -668,7 +763,35 @@ module.exports = async function testTaskOrchestration(ctx) { assert(Array.isArray(apiOpenAiOverview.runs), 'OpenAI Chat task-overview API should return runs after execution'); assert(apiOpenAiOverview.runs.some((item) => item.runId === apiOpenAiRun.runId), 'OpenAI Chat task-overview API should include API run'); - assertOpenAiRequests(openAiMock, 6, 'OpenAI Chat full chain'); + const failingOpenAiMock = await startFailingOpenAiChatMock(tmpHome, 401, 'Invalid token'); + try { + writeOpenAiChatFallbackConfig(tmpHome, `http://127.0.0.1:${failingOpenAiMock.port}`, `http://codex-user:codex-secret@127.0.0.1:${openAiMock.port}`); + const fallbackRunResult = runSync(node, [ + cliPath, + 'task', + 'run', + '--target', + 'OpenAI Chat provider fallback 链路', + '--engine', + 'openai-chat', + '--cwd', + path.join(tmpHome, 'task-fallback-workspace'), + '--thread-id', + 'thread-fallback-run', + '--json' + ], { env }); + assert(fallbackRunResult.status === 0, `OpenAI Chat fallback run failed: ${fallbackRunResult.stderr || fallbackRunResult.stdout}`); + const fallbackPayload = parseJsonOutput(fallbackRunResult.stdout); + assertOpenAiRunPayload(fallbackPayload, 'OpenAI Chat fallback run'); + const fallbackOutputs = fallbackPayload.run.nodes.map(item => item && item.output ? item.output : {}); + assert(fallbackOutputs.some(item => Array.isArray(item.attemptedProviders) && item.attemptedProviders.includes('bad-chat') && item.attemptedProviders.includes('new-api-chat')), 'OpenAI Chat fallback should record attempted providers'); + const fallbackLogs = JSON.stringify(fallbackPayload.run.nodes.flatMap(item => Array.isArray(item && item.logs) ? item.logs : [])); + assert(fallbackLogs.includes('provider fallback: bad-chat -> new-api-chat'), 'OpenAI Chat fallback should be visible in logs'); + } finally { + await failingOpenAiMock.close(); + } + + assertOpenAiRequests(openAiMock, 7, 'OpenAI Chat full chain'); } finally { await openAiMock.close(); } diff --git a/tests/unit/task-codex-exec-windows.test.mjs b/tests/unit/task-codex-exec-windows.test.mjs index 808ab95f..aeaba693 100644 --- a/tests/unit/task-codex-exec-windows.test.mjs +++ b/tests/unit/task-codex-exec-windows.test.mjs @@ -141,6 +141,7 @@ test('resolveTaskOpenAiChatConfig uses task provider without changing Codex prov extractBlockBySignature(cliSource, 'function pickTaskProviderModel(providerName, provider, config) {'), extractBlockBySignature(cliSource, 'function pickTaskProviderTemperature(provider) {'), extractBlockBySignature(cliSource, 'function pickTaskOpenAiChatProviderName(config) {'), + extractBlockBySignature(cliSource, 'function resolveTaskOpenAiChatProviderConfig(providerName, provider, config) {'), extractBlockBySignature(cliSource, 'function resolveTaskOpenAiChatConfig() {') ].join('\n\n'); const resolveTaskOpenAiChatConfig = instantiateFunction(source, 'resolveTaskOpenAiChatConfig', { @@ -196,15 +197,22 @@ test('runOpenAiChatTaskNode fails before request when OpenAI Chat auth is missin const source = extractBlockBySignature(cliSource, 'async function runOpenAiChatTaskNode(node, context = {}) {'); let requested = false; const runOpenAiChatTaskNode = instantiateFunction(source, 'runOpenAiChatTaskNode', { - resolveTaskOpenAiChatConfig() { + listTaskOpenAiChatConfigs() { return { - providerName: 'mock-openai', - model: 'gpt-4.1-mini', - endpointUrl: 'https://api.example.test/v1/chat/completions', - apiKey: '', - extraHeaders: {} + configs: [{ + providerName: 'mock-openai', + model: 'gpt-4.1-mini', + endpointUrl: 'https://api.example.test/v1/chat/completions', + apiKey: '', + extraHeaders: {} + }], + primaryName: 'mock-openai', + errors: [] }; }, + taskOpenAiChatProviderHasAuth(config) { + return !!(config && ((typeof config.apiKey === 'string' && config.apiKey.trim()) || (config.extraHeaders && Object.keys(config.extraHeaders).length > 0))); + }, postOpenAiChatCompletion() { requested = true; return Promise.resolve({ ok: true, status: 200, payload: {} }); @@ -236,16 +244,23 @@ test('runOpenAiChatTaskNode uses configured OpenAI Chat provider without spawnin const source = extractBlockBySignature(cliSource, 'async function runOpenAiChatTaskNode(node, context = {}) {'); const requests = []; const runOpenAiChatTaskNode = instantiateFunction(source, 'runOpenAiChatTaskNode', { - resolveTaskOpenAiChatConfig() { + listTaskOpenAiChatConfigs() { return { - providerName: 'mock-openai', - model: 'glm-5.2', - endpointUrl: 'http://127.0.0.1:18183/v1/chat/completions', - apiKey: 'sk-unit-secret', - extraHeaders: {}, - temperature: 0.7 + configs: [{ + providerName: 'mock-openai', + model: 'glm-5.2', + endpointUrl: 'http://127.0.0.1:18183/v1/chat/completions', + apiKey: 'sk-unit-secret', + extraHeaders: {}, + temperature: 0.7 + }], + primaryName: 'mock-openai', + errors: [] }; }, + taskOpenAiChatProviderHasAuth(config) { + return !!(config && ((typeof config.apiKey === 'string' && config.apiKey.trim()) || (config.extraHeaders && Object.keys(config.extraHeaders).length > 0))); + }, postOpenAiChatCompletion(config, body) { requests.push({ config, body }); return Promise.resolve({ From c42afdf7c91b9aea3ec1810249c28c1710d16bea Mon Sep 17 00:00:00 2001 From: awsl233777 Date: Thu, 2 Jul 2026 09:29:37 +0000 Subject: [PATCH 24/25] fix(orchestration): surface provider setup in task tab --- tests/unit/config-tabs-ui.test.mjs | 3 ++ tests/unit/web-ui-behavior-parity.test.mjs | 1 + tests/unit/web-ui-logic.test.mjs | 30 ++++++++++++++ web-ui/modules/app.computed.main-tabs.mjs | 10 +++++ web-ui/modules/i18n/locales/en.mjs | 5 +++ web-ui/modules/i18n/locales/ja.mjs | 5 +++ web-ui/modules/i18n/locales/zh-tw.mjs | 5 +++ web-ui/modules/i18n/locales/zh.mjs | 5 +++ web-ui/modules/task-kilo-webview-host.mjs | 39 +++++++++++++++---- .../partials/index/panel-orchestration.html | 12 +++++- web-ui/res/web-ui-render.precompiled.js | 21 +++++++++- web-ui/styles/task-orchestration.css | 23 ++++++++++- 12 files changed, 148 insertions(+), 11 deletions(-) diff --git a/tests/unit/config-tabs-ui.test.mjs b/tests/unit/config-tabs-ui.test.mjs index 304e2d2f..54613c66 100644 --- a/tests/unit/config-tabs-ui.test.mjs +++ b/tests/unit/config-tabs-ui.test.mjs @@ -107,6 +107,9 @@ test('config template keeps expected config tabs in top and side navigation', () assert.match(orchestrationPanel, /class="prompt-input-shell"/); assert.match(orchestrationPanel, /data-component="permission-dock"/); assert.match(orchestrationPanel, /data-component="error-dock"/); + assert.match(orchestrationPanel, /data-component="provider-setup-dock"/); + assert.match(orchestrationPanel, /taskOrchestrationProviderConfigRequired/); + assert.match(orchestrationPanel, /@click="openTaskOpenAiChatConfig"/); assert.match(orchestrationPanel, /taskKiloWebviewHostSnapshot\.title/); assert.match(orchestrationPanel, /taskKiloWebviewHostSnapshot\.subtitle/); assert.match(orchestrationPanel, /taskKiloWebviewHostSnapshot\.timelineBars/); diff --git a/tests/unit/web-ui-behavior-parity.test.mjs b/tests/unit/web-ui-behavior-parity.test.mjs index 1325d9c2..d4434129 100644 --- a/tests/unit/web-ui-behavior-parity.test.mjs +++ b/tests/unit/web-ui-behavior-parity.test.mjs @@ -911,6 +911,7 @@ test('captured bundled app skeleton only exposes expected data key drift versus 'taskOrchestrationDraftChecklist', 'taskOrchestrationDraftReadiness', 'taskOrchestrationConversationMessages', + 'taskOrchestrationProviderConfigRequired', 'taskKiloWebviewHostSnapshot', 'displayProviderUrl', 'isTransformProvider', diff --git a/tests/unit/web-ui-logic.test.mjs b/tests/unit/web-ui-logic.test.mjs index 15a3b24f..2ed111a4 100644 --- a/tests/unit/web-ui-logic.test.mjs +++ b/tests/unit/web-ui-logic.test.mjs @@ -1906,6 +1906,36 @@ test('task Kilo webview host snapshot hides workspace, thread, ids, and raw run assert.deepStrictEqual(snapshot.messages.map(item => item.role), ['user', 'assistant']); }); +test('task Kilo webview host surfaces provider setup before failed history', () => { + const snapshot = createTaskKiloWebviewHostSnapshot({ + t(key, values = {}) { + const messages = { + 'orchestration.kilo.header.title': 'Kilo host', + 'orchestration.kilo.header.subtitle': 'Safe shell', + 'orchestration.openai.config.requiredShort': 'Provider setup required', + 'orchestration.openai.config.requiredTrace': 'Configure a chat provider before running tasks', + 'orchestration.agent.surface.trace': 'Trace' + }; + return messages[key] || key; + }, + taskOrchestration: { + selectedEngine: 'openai-chat', + openAiChatStatus: { ready: false, providerName: 'custom-openai' }, + selectedRunDetail: { + run: { runId: 'old-failed-run', status: 'failed' } + } + }, + runs: Array.from({ length: 7 }, (_, index) => ({ runId: `run-${index}`, status: 'failed' })), + queue: [], + nodes: [] + }); + + assert.strictEqual(snapshot.providerConfigRequired, true); + assert.strictEqual(snapshot.status, 'Provider setup required'); + assert.strictEqual(snapshot.statusTone, 'warn'); + assert.strictEqual(snapshot.context.find(item => item.key === 'trace').value, 'Configure a chat provider before running tasks'); +}); + test('task Kilo VSCode API shim stores state and routes postMessage through whitelist decision', () => { const calls = []; const storageMap = new Map(); diff --git a/web-ui/modules/app.computed.main-tabs.mjs b/web-ui/modules/app.computed.main-tabs.mjs index 68450349..1446a550 100644 --- a/web-ui/modules/app.computed.main-tabs.mjs +++ b/web-ui/modules/app.computed.main-tabs.mjs @@ -502,6 +502,16 @@ export function createMainTabsComputed() { taskOrchestrationConversationMessages() { return createTaskConversationMessages(this.taskOrchestration, this.t && this.t.bind(this)); }, + taskOrchestrationProviderConfigRequired() { + const state = this.taskOrchestration && typeof this.taskOrchestration === 'object' + ? this.taskOrchestration + : {}; + if (state.selectedEngine === 'workflow') return false; + const status = state.openAiChatStatus && typeof state.openAiChatStatus === 'object' + ? state.openAiChatStatus + : null; + return !!(status && status.ready === false); + }, taskKiloWebviewHostSnapshot() { return createTaskKiloWebviewHostSnapshot({ taskOrchestration: this.taskOrchestration, diff --git a/web-ui/modules/i18n/locales/en.mjs b/web-ui/modules/i18n/locales/en.mjs index edd86c5b..7a7e291d 100644 --- a/web-ui/modules/i18n/locales/en.mjs +++ b/web-ui/modules/i18n/locales/en.mjs @@ -1244,6 +1244,11 @@ const en = Object.freeze({ 'orchestration.openai.status.notSet': 'Not set', 'orchestration.openai.status.notLoaded': 'Refresh to load chat runtime readiness.', 'orchestration.openai.status.configure': 'Configure chat runtime', + 'orchestration.openai.config.requiredShort': 'Provider setup required', + 'orchestration.openai.config.requiredTrace': 'Configure a chat provider before running tasks', + 'orchestration.openai.config.requiredTitle': 'Chat provider is not ready', + 'orchestration.openai.config.requiredBody': 'Add or update a custom provider so task nodes can run through the chat runtime.', + 'orchestration.openai.config.action': 'Configure provider', 'orchestration.detail.node.output': 'AI output', 'orchestration.detail.node.endpoint': 'Endpoint: ', 'orchestration.detail.node.materializedFiles': 'Materialized files:', diff --git a/web-ui/modules/i18n/locales/ja.mjs b/web-ui/modules/i18n/locales/ja.mjs index 5ce5550c..955899b3 100644 --- a/web-ui/modules/i18n/locales/ja.mjs +++ b/web-ui/modules/i18n/locales/ja.mjs @@ -1233,6 +1233,11 @@ const ja = Object.freeze({ 'orchestration.openai.status.notSet': 'なし', 'orchestration.openai.status.notLoaded': '更新するとチャット実行設定の準備状態を読み込みます。', 'orchestration.openai.status.configure': 'チャット実行環境を設定', + 'orchestration.openai.config.requiredShort': 'Provider 設定が必要', + 'orchestration.openai.config.requiredTrace': 'タスク実行前にチャット Provider を設定してください', + 'orchestration.openai.config.requiredTitle': 'チャット Provider が未準備です', + 'orchestration.openai.config.requiredBody': 'カスタム Provider を追加または更新すると、タスクノードをチャット実行環境で実行できます。', + 'orchestration.openai.config.action': 'Provider を設定', 'orchestration.detail.node.output': 'AI 出力', 'orchestration.detail.node.endpoint': 'Endpoint: ', 'orchestration.detail.node.materializedFiles': '生成ファイル:', diff --git a/web-ui/modules/i18n/locales/zh-tw.mjs b/web-ui/modules/i18n/locales/zh-tw.mjs index c18ade0b..3f59fa7e 100644 --- a/web-ui/modules/i18n/locales/zh-tw.mjs +++ b/web-ui/modules/i18n/locales/zh-tw.mjs @@ -1243,6 +1243,11 @@ const zhTw = Object.freeze({ 'orchestration.openai.status.notSet': '未設定', 'orchestration.openai.status.notLoaded': '重新整理後載入聊天執行設定就緒狀態。', 'orchestration.openai.status.configure': '設定聊天執行環境', + 'orchestration.openai.config.requiredShort': '需要設定 Provider', + 'orchestration.openai.config.requiredTrace': '執行任務前需要先設定聊天 Provider', + 'orchestration.openai.config.requiredTitle': '聊天 Provider 尚未就緒', + 'orchestration.openai.config.requiredBody': '新增或更新自訂 Provider 後,任務節點才能透過聊天執行環境執行。', + 'orchestration.openai.config.action': '設定 Provider', 'orchestration.detail.node.output': 'AI 輸出', 'orchestration.detail.node.endpoint': 'Endpoint:', 'orchestration.detail.node.materializedFiles': '物化檔案:', diff --git a/web-ui/modules/i18n/locales/zh.mjs b/web-ui/modules/i18n/locales/zh.mjs index c7b64d52..aa11ae83 100644 --- a/web-ui/modules/i18n/locales/zh.mjs +++ b/web-ui/modules/i18n/locales/zh.mjs @@ -1243,6 +1243,11 @@ const zh = Object.freeze({ 'orchestration.openai.status.notSet': '未设置', 'orchestration.openai.status.notLoaded': '刷新后加载聊天运行配置就绪状态。', 'orchestration.openai.status.configure': '配置聊天运行时', + 'orchestration.openai.config.requiredShort': '需要配置 Provider', + 'orchestration.openai.config.requiredTrace': '执行任务前需要先配置聊天 Provider', + 'orchestration.openai.config.requiredTitle': '聊天 Provider 尚未就绪', + 'orchestration.openai.config.requiredBody': '添加或更新自定义 Provider 后,任务节点才能通过聊天运行时执行。', + 'orchestration.openai.config.action': '配置 Provider', 'orchestration.detail.node.output': 'AI 输出', 'orchestration.detail.node.endpoint': 'Endpoint:', 'orchestration.detail.node.materializedFiles': '物化文件:', diff --git a/web-ui/modules/task-kilo-webview-host.mjs b/web-ui/modules/task-kilo-webview-host.mjs index f6c8841b..17ad306f 100644 --- a/web-ui/modules/task-kilo-webview-host.mjs +++ b/web-ui/modules/task-kilo-webview-host.mjs @@ -47,7 +47,7 @@ function normalizeStatusTone(value) { const status = String(value || '').trim().toLowerCase(); if (status === 'success' || status === 'completed' || status === 'pass') return 'success'; if (status === 'failed' || status === 'blocked' || status === 'cancelled' || status === 'error') return 'error'; - if (status === 'running' || status === 'planning' || status === 'queued' || status === 'pending') return 'warn'; + if (status === 'running' || status === 'planning' || status === 'queued' || status === 'pending' || status === 'warning') return 'warn'; return 'neutral'; } @@ -201,6 +201,17 @@ export function createTaskKiloVsCodeApiShim(storage = null, postMessage = null) }; } +function taskOpenAiChatConfigRequired(taskOrchestration) { + const state = taskOrchestration && typeof taskOrchestration === 'object' ? taskOrchestration : {}; + if (state.selectedEngine === 'workflow') { + return false; + } + const status = state.openAiChatStatus && typeof state.openAiChatStatus === 'object' + ? state.openAiChatStatus + : null; + return !!(status && status.ready === false); +} + export function createTaskKiloWebviewHostSnapshot(input = {}) { const taskOrchestration = input.taskOrchestration && typeof input.taskOrchestration === 'object' ? input.taskOrchestration @@ -215,16 +226,32 @@ export function createTaskKiloWebviewHostSnapshot(input = {}) { ? taskOrchestration.selectedRunDetail : null; const selectedRunStatus = selectedRun && selectedRun.run ? selectedRun.run.status : ''; + const providerConfigRequired = taskOpenAiChatConfigRequired(taskOrchestration); const status = taskOrchestration.running ? translateTaskKiloText(t, 'orchestration.agent.state.running', 'Running') : (taskOrchestration.planning ? translateTaskKiloText(t, 'orchestration.agent.state.planning', 'Planning') - : (selectedRunStatus || translateTaskKiloText(t, 'orchestration.agent.state.ready', 'Ready'))); + : (providerConfigRequired + ? translateTaskKiloText(t, 'orchestration.openai.config.requiredShort', 'Provider setup required') + : (selectedRunStatus || translateTaskKiloText(t, 'orchestration.agent.state.ready', 'Ready')))); + const statusTone = taskOrchestration.running + ? 'running' + : (taskOrchestration.planning + ? 'planning' + : (providerConfigRequired ? 'warning' : selectedRunStatus)); + const traceValue = providerConfigRequired + ? translateTaskKiloText(t, 'orchestration.openai.config.requiredTrace', 'Configure a chat provider before running tasks') + : translateTaskKiloText(t, 'orchestration.kilo.trace.counts', '{runs} runs · {queue} queued · {nodes} steps', { + runs: runs.length, + queue: queue.length, + nodes: nodes.length || countPlanNodes(taskOrchestration) + }); return { title: translateTaskKiloText(t, 'orchestration.kilo.header.title', 'Kilo-compatible task host'), subtitle: translateTaskKiloText(t, 'orchestration.kilo.header.subtitle', 'VS Code-style webview shell with a privacy-safe task bridge.'), status, - statusTone: normalizeStatusTone(taskOrchestration.running ? 'running' : (taskOrchestration.planning ? 'planning' : selectedRunStatus)), + statusTone: normalizeStatusTone(statusTone), + providerConfigRequired, flags: { IS_SIDEBAR: false, IS_FULL_EDITOR: true, @@ -255,11 +282,7 @@ export function createTaskKiloWebviewHostSnapshot(input = {}) { { key: 'trace', label: translateTaskKiloText(t, 'orchestration.agent.surface.trace', 'Trace'), - value: translateTaskKiloText(t, 'orchestration.kilo.trace.counts', '{runs} runs · {queue} queued · {nodes} steps', { - runs: runs.length, - queue: queue.length, - nodes: nodes.length || countPlanNodes(taskOrchestration) - }) + value: traceValue } ], stats: { diff --git a/web-ui/partials/index/panel-orchestration.html b/web-ui/partials/index/panel-orchestration.html index 041bc0b6..d1d9b7f7 100644 --- a/web-ui/partials/index/panel-orchestration.html +++ b/web-ui/partials/index/panel-orchestration.html @@ -17,7 +17,8 @@
{{ taskKiloWebviewHostSnapshot.status }} - {{ t('orchestration.kilo.trace.counts', { runs: taskOrchestrationWorkspaceRuns.length, queue: taskOrchestrationWorkspaceQueue.length, nodes: taskKiloWebviewHostSnapshot.stats.nodes }) }} + {{ t('orchestration.openai.config.requiredTrace') }} + {{ t('orchestration.kilo.trace.counts', { runs: taskOrchestrationWorkspaceRuns.length, queue: taskOrchestrationWorkspaceQueue.length, nodes: taskKiloWebviewHostSnapshot.stats.nodes }) }} @@ -83,6 +84,15 @@

{{ t('orchestration.kilo.surface.title') }}

{{ t('orchestration.toast.error.generic') }} {{ t('orchestration.privacy.detailsHidden') }}
+
+
+ {{ t('orchestration.openai.config.requiredTitle') }} + {{ t('orchestration.openai.config.requiredBody') }} +
+ +
{{ (taskOrchestrationWorkspacePath || taskOrchestration.workspacePath.trim()) ? t('orchestration.privacy.workspace.selected') : t('orchestration.chat.context.workspace.auto') }} {{ (taskOrchestration.threadId.trim() || (taskOrchestrationSelectedRun && taskOrchestrationSelectedRun.threadId)) ? t('orchestration.privacy.thread.selected') : t('orchestration.chat.context.thread.auto') }} diff --git a/web-ui/res/web-ui-render.precompiled.js b/web-ui/res/web-ui-render.precompiled.js index bcddd2b3..99f21797 100644 --- a/web-ui/res/web-ui-render.precompiled.js +++ b/web-ui/res/web-ui-render.precompiled.js @@ -4020,7 +4020,9 @@ return function render(_ctx, _cache) { _createElementVNode("span", { class: _normalizeClass(['pill', _ctx.taskKiloWebviewHostSnapshot.statusTone]) }, _toDisplayString(_ctx.taskKiloWebviewHostSnapshot.status), 3 /* TEXT, CLASS */), - _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.kilo.trace.counts', { runs: _ctx.taskOrchestrationWorkspaceRuns.length, queue: _ctx.taskOrchestrationWorkspaceQueue.length, nodes: _ctx.taskKiloWebviewHostSnapshot.stats.nodes })), 1 /* TEXT */), + (_ctx.taskOrchestrationProviderConfigRequired) + ? (_openBlock(), _createElementBlock("span", { key: 0 }, _toDisplayString(_ctx.t('orchestration.openai.config.requiredTrace')), 1 /* TEXT */)) + : (_openBlock(), _createElementBlock("span", { key: 1 }, _toDisplayString(_ctx.t('orchestration.kilo.trace.counts', { runs: _ctx.taskOrchestrationWorkspaceRuns.length, queue: _ctx.taskOrchestrationWorkspaceQueue.length, nodes: _ctx.taskKiloWebviewHostSnapshot.stats.nodes })), 1 /* TEXT */)), _createElementVNode("button", { type: "button", class: "kilo-icon-button", @@ -4130,6 +4132,23 @@ return function render(_ctx, _cache) { _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.privacy.detailsHidden')), 1 /* TEXT */) ])) : _createCommentVNode("v-if", true), + (_ctx.taskOrchestrationProviderConfigRequired) + ? (_openBlock(), _createElementBlock("div", { + key: 1, + class: "kilo-provider-setup-dock", + "data-component": "provider-setup-dock" + }, [ + _createElementVNode("div", null, [ + _createElementVNode("strong", null, _toDisplayString(_ctx.t('orchestration.openai.config.requiredTitle')), 1 /* TEXT */), + _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.openai.config.requiredBody')), 1 /* TEXT */) + ]), + _createElementVNode("button", { + type: "button", + class: "kilo-secondary-button", + onClick: _ctx.openTaskOpenAiChatConfig + }, _toDisplayString(_ctx.t('orchestration.openai.config.action')), 9 /* TEXT, PROPS */, ["onClick"]) + ])) + : _createCommentVNode("v-if", true), _createElementVNode("div", { class: "permission-dock kilo-status-dock", "data-component": "permission-dock" diff --git a/web-ui/styles/task-orchestration.css b/web-ui/styles/task-orchestration.css index 469e0851..feee449a 100644 --- a/web-ui/styles/task-orchestration.css +++ b/web-ui/styles/task-orchestration.css @@ -351,7 +351,8 @@ margin-top: 8px; } -#panel-orchestration .kilo-error-dock { +#panel-orchestration .kilo-error-dock, +#panel-orchestration .kilo-provider-setup-dock { display: flex; align-items: center; justify-content: space-between; @@ -360,6 +361,26 @@ width: min(100%, 98ch); } +#panel-orchestration .kilo-provider-setup-dock { + border: 1px solid rgba(30, 127, 211, 0.24); + border-radius: 8px; + background: #eef6ff; + color: var(--kilo-reference-text); + padding: 8px 10px; + font-size: 12px; +} + +#panel-orchestration .kilo-provider-setup-dock div { + display: flex; + min-width: 0; + flex-direction: column; + gap: 2px; +} + +#panel-orchestration .kilo-provider-setup-dock span { + color: var(--kilo-reference-muted); +} + #panel-orchestration .chat-input { flex-shrink: 0; border-top: 1px solid var(--kilo-reference-border); From 1b21e8b1c94c64dd79903805f999f59a019270a3 Mon Sep 17 00:00:00 2001 From: awsl233777 Date: Thu, 2 Jul 2026 10:28:10 +0000 Subject: [PATCH 25/25] fix(orchestration): decouple task provider setup --- cli.js | 97 +++++++++++++++++-- tests/unit/config-tabs-ui.test.mjs | 6 +- tests/unit/task-codex-exec-windows.test.mjs | 52 +++++++++- tests/unit/web-ui-behavior-parity.test.mjs | 5 +- tests/unit/web-ui-logic.test.mjs | 89 +++++++++-------- .../app.methods.task-orchestration.mjs | 80 +++++++++++---- web-ui/modules/i18n/locales/en.mjs | 8 +- web-ui/modules/i18n/locales/ja.mjs | 8 +- web-ui/modules/i18n/locales/vi.mjs | 9 ++ web-ui/modules/i18n/locales/zh-tw.mjs | 8 +- web-ui/modules/i18n/locales/zh.mjs | 8 +- .../partials/index/panel-orchestration.html | 25 ++++- web-ui/res/web-ui-render.precompiled.js | 74 +++++++++++++- web-ui/styles/task-orchestration.css | 42 ++++++++ 14 files changed, 432 insertions(+), 79 deletions(-) diff --git a/cli.js b/cli.js index 06a34218..a9b022a0 100644 --- a/cli.js +++ b/cli.js @@ -249,6 +249,7 @@ const PROVIDER_CACHE_CURRENT_MODEL_FILES = Object.freeze([ ]); const PROVIDER_CACHE_MAX_FILE_BYTES = 256 * 1024; const CODEXMATE_PREFERENCES_FILE = path.join(CODEXMATE_DIR, 'preferences.json'); +const CODEXMATE_TASK_OPENAI_CHAT_PROVIDER_FILE = path.join(CODEXMATE_DIR, 'task-openai-chat-provider.json'); const CODEXMATE_OPENCODE_DIR = path.join(CODEXMATE_DIR, 'opencode'); const CODEXMATE_OPENCODE_PROVIDER_STORE_FILE = path.join(CODEXMATE_OPENCODE_DIR, 'providers.json'); const CODEXMATE_SESSIONS_DIR = path.join(CODEXMATE_DIR, 'sessions'); @@ -12521,6 +12522,9 @@ function createWebServer({ htmlPath, assetsDir, webDir, host, port, openBrowser case 'providers-health': result = await buildAllProvidersHealthReport(params || {}); break; + case 'save-task-openai-chat-provider': + result = saveTaskOpenAiChatProvider(params || {}); + break; case 'doctor': { const doctorParams = isPlainObject(params) ? params : {}; @@ -16198,13 +16202,88 @@ function pickTaskProviderTemperature(provider) { return value; } -function pickTaskOpenAiChatProviderName(config) { +function normalizeTaskOpenAiChatProviderStore(raw) { + const source = raw && typeof raw === 'object' && !Array.isArray(raw) ? raw : {}; + const providersSource = source.providers && typeof source.providers === 'object' && !Array.isArray(source.providers) + ? source.providers + : {}; + const providers = {}; + Object.keys(providersSource).forEach((name) => { + const key = typeof name === 'string' ? name.trim() : ''; + const provider = providersSource[name]; + if (!key || !provider || typeof provider !== 'object' || Array.isArray(provider)) return; + providers[key] = { + name: typeof provider.name === 'string' && provider.name.trim() ? provider.name.trim() : key, + base_url: typeof provider.base_url === 'string' ? provider.base_url.trim() : '', + wire_api: 'chat_completions', + preferred_auth_method: typeof provider.preferred_auth_method === 'string' ? provider.preferred_auth_method.trim() : '', + models: Array.isArray(provider.models) ? provider.models.map((item) => String(item || '').trim()).filter(Boolean) : [], + temperature: Number.isFinite(Number(provider.temperature)) ? Number(provider.temperature) : undefined + }; + if (providers[key].temperature === undefined) delete providers[key].temperature; + }); + const selectedProvider = typeof source.selectedProvider === 'string' ? source.selectedProvider.trim() : ''; + return { + selectedProvider: selectedProvider && providers[selectedProvider] ? selectedProvider : (Object.keys(providers)[0] || ''), + providers + }; +} + +function readTaskOpenAiChatProviderStore() { + const parsed = readJsonObjectFromFile(CODEXMATE_TASK_OPENAI_CHAT_PROVIDER_FILE, {}); + return normalizeTaskOpenAiChatProviderStore(parsed && parsed.ok && parsed.data ? parsed.data : {}); +} + +function writeTaskOpenAiChatProviderStore(store) { + ensureDir(path.dirname(CODEXMATE_TASK_OPENAI_CHAT_PROVIDER_FILE)); + writeJsonAtomic(CODEXMATE_TASK_OPENAI_CHAT_PROVIDER_FILE, normalizeTaskOpenAiChatProviderStore(store)); +} + +function saveTaskOpenAiChatProvider(params = {}) { + const name = typeof params.name === 'string' ? params.name.trim() : ''; + const baseUrl = normalizeBaseUrl(params.url || params.baseUrl || ''); + const apiKey = typeof params.key === 'string' ? params.key.trim() : ''; + const model = typeof params.model === 'string' ? params.model.trim() : ''; + if (!name || !baseUrl || !apiKey || !model) { + return { error: 'Provider name, URL, API key and model are required' }; + } + if (!isValidProviderName(name)) { + return { error: 'Provider name only supports letters, numbers, dot, underscore and dash' }; + } + if (!isValidHttpUrl(baseUrl)) { + return { error: 'Provider URL only supports http/https' }; + } + const store = readTaskOpenAiChatProviderStore(); + store.providers[name] = { + name, + base_url: baseUrl, + wire_api: 'chat_completions', + preferred_auth_method: apiKey, + models: [model] + }; + store.selectedProvider = name; + writeTaskOpenAiChatProviderStore(store); + return { + success: true, + providerName: name, + model, + storePath: '~/.codexmate/task-openai-chat-provider.json' + }; +} + +function pickTaskOpenAiChatProviderName(config, taskProviderStore = null) { const taskProvider = typeof config.task_openai_chat_provider === 'string' ? config.task_openai_chat_provider.trim() : ''; if (taskProvider) { return taskProvider; } + const store = taskProviderStore && typeof taskProviderStore === 'object' + ? taskProviderStore + : readTaskOpenAiChatProviderStore(); + if (store && typeof store.selectedProvider === 'string' && store.selectedProvider.trim()) { + return store.selectedProvider.trim(); + } return typeof config.model_provider === 'string' ? config.model_provider.trim() : ''; } @@ -16267,19 +16346,25 @@ function resolveTaskOpenAiChatProviderConfig(providerName, provider, config) { function resolveTaskOpenAiChatConfig() { const configResult = readConfigOrVirtualDefault(); const config = configResult && configResult.config && typeof configResult.config === 'object' ? configResult.config : {}; - const providerName = pickTaskOpenAiChatProviderName(config); + const taskProviderStore = readTaskOpenAiChatProviderStore(); + const providerName = pickTaskOpenAiChatProviderName(config, taskProviderStore); 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); + const configProviders = config.model_providers && typeof config.model_providers === 'object' ? config.model_providers : {}; + const taskProviders = taskProviderStore.providers && typeof taskProviderStore.providers === 'object' ? taskProviderStore.providers : {}; + const provider = taskProviders[providerName] || configProviders[providerName]; + return resolveTaskOpenAiChatProviderConfig(providerName, provider, 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 taskProviderStore = readTaskOpenAiChatProviderStore(); + const configProviders = config.model_providers && typeof config.model_providers === 'object' ? config.model_providers : {}; + const taskProviders = taskProviderStore.providers && typeof taskProviderStore.providers === 'object' ? taskProviderStore.providers : {}; + const providers = { ...configProviders, ...taskProviders }; + const primaryName = pickTaskOpenAiChatProviderName(config, taskProviderStore); const orderedNames = []; if (primaryName) orderedNames.push(primaryName); Object.keys(providers).forEach((name) => { diff --git a/tests/unit/config-tabs-ui.test.mjs b/tests/unit/config-tabs-ui.test.mjs index 54613c66..624ca4fd 100644 --- a/tests/unit/config-tabs-ui.test.mjs +++ b/tests/unit/config-tabs-ui.test.mjs @@ -109,7 +109,9 @@ test('config template keeps expected config tabs in top and side navigation', () assert.match(orchestrationPanel, /data-component="error-dock"/); assert.match(orchestrationPanel, /data-component="provider-setup-dock"/); assert.match(orchestrationPanel, /taskOrchestrationProviderConfigRequired/); - assert.match(orchestrationPanel, /@click="openTaskOpenAiChatConfig"/); + assert.match(orchestrationPanel, /@click="openTaskProviderSetup"/); + assert.match(orchestrationPanel, /data-component="provider-setup-form"/); + assert.match(orchestrationPanel, /@submit.prevent="saveTaskOpenAiChatProvider"/); assert.match(orchestrationPanel, /taskKiloWebviewHostSnapshot\.title/); assert.match(orchestrationPanel, /taskKiloWebviewHostSnapshot\.subtitle/); assert.match(orchestrationPanel, /taskKiloWebviewHostSnapshot\.timelineBars/); @@ -158,7 +160,6 @@ test('config template keeps expected config tabs in top and side navigation', () assert.doesNotMatch(orchestrationPanel, /\{\{ item\.(title|target|taskId|runId|threadId|cwd|summary|lastSummary)/); assert.doesNotMatch(orchestrationPanel, /\{\{ node\.output\.(provider|model|endpoint)/); assert.doesNotMatch(orchestrationPanel, /\{\{ taskOrchestration\.(lastError|selectedRunError) \}\}/); - assert.doesNotMatch(orchestrationPanel, /type="password"/); assert.doesNotMatch(orchestrationPanel, /task-sensitive-input|kilo-input|kilo-settings-popover|kilo-settings-row|kilo-empty-actions/); assert.doesNotMatch(orchestrationPanel, /startNewTaskWorkspaceSession|orchestration\.workspace\.action\.primary/); assert.doesNotMatch(orchestrationPanel, /taskOrchestration\.(workspacePath|threadId|selectedEngine|workflowIdsText)"/); @@ -168,7 +169,6 @@ test('config template keeps expected config tabs in top and side navigation', () assert.doesNotMatch(precompiledRender, /_toDisplayString\(node\.id\)/); assert.doesNotMatch(precompiledRender, /node\.workflowId/); assert.match(precompiledRender, /_toDisplayString\(_ctx\.t\('orchestration\.privacy\.detailsHidden'\)\)/); - assert.doesNotMatch(precompiledRender, /type: "password"/); assert.doesNotMatch(precompiledRender, /kilo-input task-sensitive-input|kilo-settings-popover|kilo-settings-row/); assert.match(precompiledRender, /taskKiloWebviewHostSnapshot\.title/); assert.doesNotMatch(orchestrationPanel, /file\.(relativePath|path)/); diff --git a/tests/unit/task-codex-exec-windows.test.mjs b/tests/unit/task-codex-exec-windows.test.mjs index aeaba693..641f5c1c 100644 --- a/tests/unit/task-codex-exec-windows.test.mjs +++ b/tests/unit/task-codex-exec-windows.test.mjs @@ -140,7 +140,7 @@ test('resolveTaskOpenAiChatConfig uses task provider without changing Codex prov const source = [ extractBlockBySignature(cliSource, 'function pickTaskProviderModel(providerName, provider, config) {'), extractBlockBySignature(cliSource, 'function pickTaskProviderTemperature(provider) {'), - extractBlockBySignature(cliSource, 'function pickTaskOpenAiChatProviderName(config) {'), + extractBlockBySignature(cliSource, 'function pickTaskOpenAiChatProviderName(config, taskProviderStore = null) {'), extractBlockBySignature(cliSource, 'function resolveTaskOpenAiChatProviderConfig(providerName, provider, config) {'), extractBlockBySignature(cliSource, 'function resolveTaskOpenAiChatConfig() {') ].join('\n\n'); @@ -148,6 +148,9 @@ test('resolveTaskOpenAiChatConfig uses task provider without changing Codex prov readCurrentModels() { return {}; }, + readTaskOpenAiChatProviderStore() { + return { selectedProvider: '', providers: {} }; + }, readConfigOrVirtualDefault() { return { config: { @@ -193,6 +196,53 @@ test('resolveTaskOpenAiChatConfig uses task provider without changing Codex prov assert.strictEqual(result.temperature, 0.7); }); +test('resolveTaskOpenAiChatConfig can use task-local provider store without Codex config coupling', () => { + const source = [ + extractBlockBySignature(cliSource, 'function pickTaskProviderModel(providerName, provider, config) {'), + extractBlockBySignature(cliSource, 'function pickTaskProviderTemperature(provider) {'), + extractBlockBySignature(cliSource, 'function pickTaskOpenAiChatProviderName(config, taskProviderStore = null) {'), + extractBlockBySignature(cliSource, 'function resolveTaskOpenAiChatProviderConfig(providerName, provider, config) {'), + extractBlockBySignature(cliSource, 'function resolveTaskOpenAiChatConfig() {') + ].join('\n\n'); + const resolveTaskOpenAiChatConfig = instantiateFunction(source, 'resolveTaskOpenAiChatConfig', { + readCurrentModels() { + return {}; + }, + readTaskOpenAiChatProviderStore() { + return { + selectedProvider: 'task-provider', + providers: { + 'task-provider': { + base_url: 'https://api.task.test/v1', + wire_api: 'chat_completions', + preferred_auth_method: 'sk-task-only', + models: ['gpt-task'] + } + } + }; + }, + readConfigOrVirtualDefault() { + return { config: { model_provider: '', model_providers: {} } }; + }, + resolveOpenaiBridgeUpstream() { + throw new Error('bridge should not be used'); + }, + OPENAI_BRIDGE_SETTINGS_FILE: '/tmp/openai-bridge-settings.json', + buildOpenAiChatEndpointUrl(baseUrl) { + return `${String(baseUrl).replace(/\/+$/, '')}/chat/completions`; + }, + isValidHttpUrl(value) { + return /^https?:\/\//.test(String(value || '')); + } + }); + + const result = resolveTaskOpenAiChatConfig(); + assert.strictEqual(result.providerName, 'task-provider'); + assert.strictEqual(result.baseUrl, 'https://api.task.test/v1'); + assert.strictEqual(result.apiKey, 'sk-task-only'); + assert.strictEqual(result.model, 'gpt-task'); +}); + test('runOpenAiChatTaskNode fails before request when OpenAI Chat auth is missing', async () => { const source = extractBlockBySignature(cliSource, 'async function runOpenAiChatTaskNode(node, context = {}) {'); let requested = false; diff --git a/tests/unit/web-ui-behavior-parity.test.mjs b/tests/unit/web-ui-behavior-parity.test.mjs index d4434129..4bf0c876 100644 --- a/tests/unit/web-ui-behavior-parity.test.mjs +++ b/tests/unit/web-ui-behavior-parity.test.mjs @@ -791,7 +791,9 @@ test('captured bundled app skeleton only exposes expected data key drift versus 'persistWebUiPreferences', 'copySessionWorkspaceBrief', 'formatTaskNodeOutputText', - 'openTaskOpenAiChatConfig', + 'openTaskProviderSetup', + 'closeTaskProviderSetup', + 'saveTaskOpenAiChatProvider', 'selectTaskWorkspace', 'startNewTaskWorkspaceSession', 'continueTaskWorkspaceSession', @@ -810,6 +812,7 @@ test('captured bundled app skeleton only exposes expected data key drift versus 'isSessionConvertAvailable', 'isSessionNativeAvailable', 'openSessionStandalone', + 'openTaskOpenAiChatConfig', 'closeInstallModal', 'getFirstNonLocalProviderName', 'isLocalLikeProvider', diff --git a/tests/unit/web-ui-logic.test.mjs b/tests/unit/web-ui-logic.test.mjs index 2ed111a4..75235904 100644 --- a/tests/unit/web-ui-logic.test.mjs +++ b/tests/unit/web-ui-logic.test.mjs @@ -2150,22 +2150,26 @@ test('loadTaskOrchestrationOverview keeps selected run detail when overview slic }); }); -test('task orchestration detail helpers surface AI output and open OpenAI Chat config', () => { - const methods = createTaskOrchestrationMethods({ api: async () => ({}) }); - const calls = []; +test('task orchestration detail helpers surface AI output and open task-local provider setup', async () => { + const apiCalls = []; + const methods = createTaskOrchestrationMethods({ + api: async (name, payload) => { + apiCalls.push({ name, payload }); + if (name === 'save-task-openai-chat-provider') return { success: true }; + if (name === 'task-overview') return { queue: [], runs: [], workflows: [], openAiChatStatus: { ready: true, providerName: 'task-provider' } }; + return {}; + } + }); + const messages = []; const context = { - mainTab: 'orchestration', - configMode: 'openclaw', - providersList: [{ name: 'mock-openai', hasKey: true }], - taskOrchestration: { - openAiChatStatus: { providerName: 'mock-openai' } - }, - switchMainTab(tab) { - calls.push(['switchMainTab', tab]); - this.mainTab = tab; + ensureTaskOrchestrationState: methods.ensureTaskOrchestrationState, + loadTaskOrchestrationOverview: methods.loadTaskOrchestrationOverview, + syncTaskOrchestrationPolling() {}, + showMessage(message, tone) { + messages.push({ message, tone }); }, - openEditModal(provider) { - calls.push(['openEditModal', provider.name]); + taskOrchestration: { + openAiChatStatus: { providerName: 'missing-provider', model: 'gpt-test' } }, t(key, values = {}) { const messages = { @@ -2174,6 +2178,7 @@ test('task orchestration detail helpers surface AI output and open OpenAI Chat c 'orchestration.privacy.dependenciesHiddenCount': `依赖已隐藏:${values.count} 项`, 'orchestration.detail.node.output.empty': '无输出', 'orchestration.detail.node.logs.empty': '无日志', + 'orchestration.openai.config.saveSuccess': '任务聊天 Provider 已保存。', 'common.none': '无' }; return messages[key] || key; @@ -2186,37 +2191,39 @@ test('task orchestration detail helpers surface AI output and open OpenAI Chat c assert.strictEqual(methods.formatTaskNodeDependencies.call(context, { dependsOn: ['secret-node-a', 'secret-node-b'] }), '依赖已隐藏:2 项'); assert.strictEqual(methods.formatTaskNodeDependencies.call(context, { dependsOn: [] }), '无'); - methods.openTaskOpenAiChatConfig.call(context); - - assert.strictEqual(context.mainTab, 'config'); - assert.strictEqual(context.configMode, 'codex'); - assert.deepStrictEqual(calls, [ - ['switchMainTab', 'config'], - ['openEditModal', 'mock-openai'] - ]); + methods.openTaskProviderSetup.call(context); - const fallbackCalls = []; - methods.openTaskOpenAiChatConfig.call({ - mainTab: 'orchestration', - configMode: 'openclaw', - providersList: [{ name: 'different-provider', hasKey: true }], - taskOrchestration: { - openAiChatStatus: { providerName: 'missing-provider' } - }, - switchMainTab(tab) { - fallbackCalls.push(['switchMainTab', tab]); - this.mainTab = tab; - }, - openAddProviderModal() { - fallbackCalls.push(['openAddProviderModal']); + assert.strictEqual(context.taskOrchestration.providerSetupOpen, true); + assert.deepStrictEqual(context.taskOrchestration.providerSetupDraft, { + name: 'missing-provider', + url: '', + key: '', + model: 'gpt-test' + }); + assert.strictEqual(context.mainTab, undefined); + assert.strictEqual(context.configMode, undefined); + + context.taskOrchestration.providerSetupDraft = { + name: 'task-provider', + url: 'https://api.example.test/v1', + key: 'sk-test', + model: 'gpt-test' + }; + await methods.saveTaskOpenAiChatProvider.call(context); + + assert.deepStrictEqual(apiCalls[0], { + name: 'save-task-openai-chat-provider', + payload: { + name: 'task-provider', + url: 'https://api.example.test/v1', + key: 'sk-test', + model: 'gpt-test' } }); - assert.deepStrictEqual(fallbackCalls, [ - ['switchMainTab', 'config'], - ['openAddProviderModal'] - ]); + assert.strictEqual(context.taskOrchestration.providerSetupOpen, false); + assert.strictEqual(apiCalls[1].name, 'task-overview'); + assert.deepStrictEqual(messages, [{ message: '任务聊天 Provider 已保存。', tone: 'success' }]); }); - test('selectTaskRun switches workbench to detail and keeps latest detail response only', async () => { const deferred = []; const api = async (name, payload) => { diff --git a/web-ui/modules/app.methods.task-orchestration.mjs b/web-ui/modules/app.methods.task-orchestration.mjs index 4c381850..d982196b 100644 --- a/web-ui/modules/app.methods.task-orchestration.mjs +++ b/web-ui/modules/app.methods.task-orchestration.mjs @@ -29,6 +29,14 @@ function createDefaultTaskOrchestrationState() { planWarnings: [], overviewWarnings: [], openAiChatStatus: null, + providerSetupOpen: false, + providerSetupSaving: false, + providerSetupDraft: { + name: '', + url: '', + key: '', + model: '' + }, workflows: [], queue: [], runs: [], @@ -295,24 +303,62 @@ export function createTaskOrchestrationMethods(options = {}) { return t ? t('orchestration.privacy.outputHidden') : '(output hidden)'; }, - openTaskOpenAiChatConfig() { - if (typeof this.switchMainTab === 'function') { - this.switchMainTab('config'); - } else { - this.mainTab = 'config'; + openTaskProviderSetup() { + const state = this.ensureTaskOrchestrationState(); + const status = state.openAiChatStatus && typeof state.openAiChatStatus === 'object' + ? state.openAiChatStatus + : {}; + const draft = state.providerSetupDraft && typeof state.providerSetupDraft === 'object' + ? state.providerSetupDraft + : {}; + state.providerSetupDraft = { + name: String(draft.name || status.providerName || 'task-openai-chat').trim(), + url: String(draft.url || '').trim(), + key: String(draft.key || '').trim(), + model: String(draft.model || status.model || '').trim() + }; + state.providerSetupOpen = true; + }, + + closeTaskProviderSetup() { + const state = this.ensureTaskOrchestrationState(); + state.providerSetupOpen = false; + }, + + async saveTaskOpenAiChatProvider() { + const state = this.ensureTaskOrchestrationState(); + const draft = state.providerSetupDraft && typeof state.providerSetupDraft === 'object' + ? state.providerSetupDraft + : {}; + const payload = { + name: String(draft.name || '').trim(), + url: String(draft.url || '').trim(), + key: String(draft.key || '').trim(), + model: String(draft.model || '').trim() + }; + if (!payload.name || !payload.url || !payload.key || !payload.model) { + showTaskOrchestrationMessage(this, 'orchestration.openai.config.fieldsRequired', 'error', {}, 'Provider name, URL, API key and model are required'); + return null; } - this.configMode = 'codex'; - const status = this.taskOrchestration && this.taskOrchestration.openAiChatStatus - ? this.taskOrchestration.openAiChatStatus - : null; - const providerName = status && typeof status.providerName === 'string' ? status.providerName.trim() : ''; - const provider = providerName && Array.isArray(this.providersList) - ? this.providersList.find((item) => item && item.name === providerName) - : null; - if (provider && typeof this.openEditModal === 'function') { - this.openEditModal(provider); - } else if (typeof this.openAddProviderModal === 'function') { - this.openAddProviderModal(); + state.providerSetupSaving = true; + try { + const res = await api('save-task-openai-chat-provider', payload); + if (res && res.error) { + showTaskOrchestrationMessage(this, 'orchestration.openai.config.saveFailed', 'error', {}, res.error || 'Failed to save task provider'); + return res; + } + state.providerSetupOpen = false; + state.providerSetupDraft = { name: '', url: '', key: '', model: '' }; + showTaskOrchestrationMessage(this, 'orchestration.openai.config.saveSuccess', 'success', {}, 'Task chat provider saved'); + if (typeof this.loadTaskOrchestrationOverview === 'function') { + await this.loadTaskOrchestrationOverview({ forceRefresh: true, silent: true, includeDetail: false }); + } + return res || { success: true }; + } catch (e) { + showTaskOrchestrationMessage(this, 'orchestration.openai.config.saveFailed', 'error', {}, 'Failed to save task provider'); + return { error: e && e.message ? e.message : 'Failed to save task provider' }; + } finally { + state.providerSetupSaving = false; } }, diff --git a/web-ui/modules/i18n/locales/en.mjs b/web-ui/modules/i18n/locales/en.mjs index 7a7e291d..dd4c17b0 100644 --- a/web-ui/modules/i18n/locales/en.mjs +++ b/web-ui/modules/i18n/locales/en.mjs @@ -1247,8 +1247,12 @@ const en = Object.freeze({ 'orchestration.openai.config.requiredShort': 'Provider setup required', 'orchestration.openai.config.requiredTrace': 'Configure a chat provider before running tasks', 'orchestration.openai.config.requiredTitle': 'Chat provider is not ready', - 'orchestration.openai.config.requiredBody': 'Add or update a custom provider so task nodes can run through the chat runtime.', - 'orchestration.openai.config.action': 'Configure provider', + 'orchestration.openai.config.requiredBody': 'Set up a task-only custom provider here so task nodes can run through the chat runtime without opening another config tab.', + 'orchestration.openai.config.action': 'Set up provider here', + 'orchestration.openai.config.saveFailed': 'Failed to save task chat provider.', + 'orchestration.openai.config.saveSuccess': 'Task chat provider saved.', + 'orchestration.openai.config.fieldsRequired': 'Provider name, URL, API key and model are required.', + 'orchestration.openai.config.localStoreHint': 'Saved under ~/.codexmate for task execution only; Codex config is not changed.', 'orchestration.detail.node.output': 'AI output', 'orchestration.detail.node.endpoint': 'Endpoint: ', 'orchestration.detail.node.materializedFiles': 'Materialized files:', diff --git a/web-ui/modules/i18n/locales/ja.mjs b/web-ui/modules/i18n/locales/ja.mjs index 955899b3..5d82ae4e 100644 --- a/web-ui/modules/i18n/locales/ja.mjs +++ b/web-ui/modules/i18n/locales/ja.mjs @@ -1236,8 +1236,12 @@ const ja = Object.freeze({ 'orchestration.openai.config.requiredShort': 'Provider 設定が必要', 'orchestration.openai.config.requiredTrace': 'タスク実行前にチャット Provider を設定してください', 'orchestration.openai.config.requiredTitle': 'チャット Provider が未準備です', - 'orchestration.openai.config.requiredBody': 'カスタム Provider を追加または更新すると、タスクノードをチャット実行環境で実行できます。', - 'orchestration.openai.config.action': 'Provider を設定', + 'orchestration.openai.config.requiredBody': 'タスクページ内でタスク実行専用のカスタム Provider を設定します。別の設定タブを開く必要はありません。', + 'orchestration.openai.config.action': 'ここで Provider を設定', + 'orchestration.openai.config.saveFailed': 'タスク用チャット Provider の保存に失敗しました。', + 'orchestration.openai.config.saveSuccess': 'タスク用チャット Provider を保存しました。', + 'orchestration.openai.config.fieldsRequired': 'Provider 名、URL、API Key、モデルは必須です。', + 'orchestration.openai.config.localStoreHint': 'タスク実行専用として ~/.codexmate に保存します。Codex 設定は変更しません。', 'orchestration.detail.node.output': 'AI 出力', 'orchestration.detail.node.endpoint': 'Endpoint: ', 'orchestration.detail.node.materializedFiles': '生成ファイル:', diff --git a/web-ui/modules/i18n/locales/vi.mjs b/web-ui/modules/i18n/locales/vi.mjs index 5d326782..87a33f8d 100644 --- a/web-ui/modules/i18n/locales/vi.mjs +++ b/web-ui/modules/i18n/locales/vi.mjs @@ -1345,6 +1345,15 @@ const vi = Object.freeze({ 'orchestration.openai.status.notSet': 'Chưa đặt', 'orchestration.openai.status.notLoaded': 'Làm mới để tải trạng thái sẵn sàng runtime chat.', 'orchestration.openai.status.configure': 'Cấu hình runtime chat', + 'orchestration.openai.config.requiredShort': 'Cần thiết lập provider', + 'orchestration.openai.config.requiredTrace': 'Thiết lập provider chat trước khi chạy tác vụ', + 'orchestration.openai.config.requiredTitle': 'Provider chat chưa sẵn sàng', + 'orchestration.openai.config.requiredBody': 'Thiết lập provider tùy chỉnh chỉ dùng cho tác vụ ngay trong tab tác vụ, không cần mở tab cấu hình khác.', + 'orchestration.openai.config.action': 'Thiết lập provider tại đây', + 'orchestration.openai.config.localStoreHint': 'Chỉ lưu dưới ~/.codexmate cho tác vụ; không thay đổi cấu hình Codex.', + 'orchestration.openai.config.fieldsRequired': 'Tên provider, URL, API key và model là bắt buộc.', + 'orchestration.openai.config.saveSuccess': 'Đã lưu provider chat cho tác vụ.', + 'orchestration.openai.config.saveFailed': 'Không lưu được provider chat cho tác vụ.', 'orchestration.detail.node.output': 'AI output', 'orchestration.detail.node.endpoint': 'Endpoint: ', 'orchestration.detail.node.materializedFiles': 'File đã tạo:', diff --git a/web-ui/modules/i18n/locales/zh-tw.mjs b/web-ui/modules/i18n/locales/zh-tw.mjs index 3f59fa7e..f205da6c 100644 --- a/web-ui/modules/i18n/locales/zh-tw.mjs +++ b/web-ui/modules/i18n/locales/zh-tw.mjs @@ -1246,8 +1246,12 @@ const zhTw = Object.freeze({ 'orchestration.openai.config.requiredShort': '需要設定 Provider', 'orchestration.openai.config.requiredTrace': '執行任務前需要先設定聊天 Provider', 'orchestration.openai.config.requiredTitle': '聊天 Provider 尚未就緒', - 'orchestration.openai.config.requiredBody': '新增或更新自訂 Provider 後,任務節點才能透過聊天執行環境執行。', - 'orchestration.openai.config.action': '設定 Provider', + 'orchestration.openai.config.requiredBody': '在任務頁內設定僅供任務執行使用的自訂 Provider,任務節點即可透過聊天執行環境執行,不需要打開其它設定 tab。', + 'orchestration.openai.config.action': '在此設定 Provider', + 'orchestration.openai.config.saveFailed': '任務聊天 Provider 儲存失敗。', + 'orchestration.openai.config.saveSuccess': '任務聊天 Provider 已儲存。', + 'orchestration.openai.config.fieldsRequired': 'Provider 名稱、URL、API Key 和模型必填。', + 'orchestration.openai.config.localStoreHint': '僅儲存到 ~/.codexmate 供任務執行使用;不會修改 Codex 設定。', 'orchestration.detail.node.output': 'AI 輸出', 'orchestration.detail.node.endpoint': 'Endpoint:', 'orchestration.detail.node.materializedFiles': '物化檔案:', diff --git a/web-ui/modules/i18n/locales/zh.mjs b/web-ui/modules/i18n/locales/zh.mjs index aa11ae83..de394fe5 100644 --- a/web-ui/modules/i18n/locales/zh.mjs +++ b/web-ui/modules/i18n/locales/zh.mjs @@ -1246,8 +1246,12 @@ const zh = Object.freeze({ 'orchestration.openai.config.requiredShort': '需要配置 Provider', 'orchestration.openai.config.requiredTrace': '执行任务前需要先配置聊天 Provider', 'orchestration.openai.config.requiredTitle': '聊天 Provider 尚未就绪', - 'orchestration.openai.config.requiredBody': '添加或更新自定义 Provider 后,任务节点才能通过聊天运行时执行。', - 'orchestration.openai.config.action': '配置 Provider', + 'orchestration.openai.config.requiredBody': '在任务页内配置仅供任务执行使用的自定义 Provider,任务节点即可通过聊天运行时执行,不需要打开其它配置 tab。', + 'orchestration.openai.config.action': '在此配置 Provider', + 'orchestration.openai.config.saveFailed': '任务聊天 Provider 保存失败。', + 'orchestration.openai.config.saveSuccess': '任务聊天 Provider 已保存。', + 'orchestration.openai.config.fieldsRequired': 'Provider 名称、URL、API Key 和模型必填。', + 'orchestration.openai.config.localStoreHint': '仅保存到 ~/.codexmate 供任务执行使用;不会修改 Codex 配置。', 'orchestration.detail.node.output': 'AI 输出', 'orchestration.detail.node.endpoint': 'Endpoint:', 'orchestration.detail.node.materializedFiles': '物化文件:', diff --git a/web-ui/partials/index/panel-orchestration.html b/web-ui/partials/index/panel-orchestration.html index d1d9b7f7..ba10882f 100644 --- a/web-ui/partials/index/panel-orchestration.html +++ b/web-ui/partials/index/panel-orchestration.html @@ -89,10 +89,33 @@

{{ t('orchestration.kilo.surface.title') }}

{{ t('orchestration.openai.config.requiredTitle') }} {{ t('orchestration.openai.config.requiredBody') }}
-
+
+ + + + +
+ {{ t('orchestration.openai.config.localStoreHint') }} + + +
+
{{ (taskOrchestrationWorkspacePath || taskOrchestration.workspacePath.trim()) ? t('orchestration.privacy.workspace.selected') : t('orchestration.chat.context.workspace.auto') }} {{ (taskOrchestration.threadId.trim() || (taskOrchestrationSelectedRun && taskOrchestrationSelectedRun.threadId)) ? t('orchestration.privacy.thread.selected') : t('orchestration.chat.context.thread.auto') }} diff --git a/web-ui/res/web-ui-render.precompiled.js b/web-ui/res/web-ui-render.precompiled.js index 99f21797..4c16b77f 100644 --- a/web-ui/res/web-ui-render.precompiled.js +++ b/web-ui/res/web-ui-render.precompiled.js @@ -4145,10 +4145,82 @@ return function render(_ctx, _cache) { _createElementVNode("button", { type: "button", class: "kilo-secondary-button", - onClick: _ctx.openTaskOpenAiChatConfig + onClick: _ctx.openTaskProviderSetup }, _toDisplayString(_ctx.t('orchestration.openai.config.action')), 9 /* TEXT, PROPS */, ["onClick"]) ])) : _createCommentVNode("v-if", true), + (_ctx.taskOrchestration.providerSetupOpen) + ? (_openBlock(), _createElementBlock("form", { + key: 2, + class: "kilo-provider-setup-form", + "data-component": "provider-setup-form", + onSubmit: _withModifiers(_ctx.saveTaskOpenAiChatProvider, ["prevent"]) + }, [ + _createElementVNode("label", null, [ + _createElementVNode("span", null, _toDisplayString(_ctx.t('field.providerName')), 1 /* TEXT */), + _withDirectives(_createElementVNode("input", { + "onUpdate:modelValue": $event => ((_ctx.taskOrchestration.providerSetupDraft.name) = $event), + class: "form-input", + autocomplete: "off", + spellcheck: "false", + placeholder: _ctx.t('placeholder.providerNameExample') + }, null, 8 /* PROPS */, ["onUpdate:modelValue", "placeholder"]), [ + [_vModelText, _ctx.taskOrchestration.providerSetupDraft.name] + ]) + ]), + _createElementVNode("label", null, [ + _createElementVNode("span", null, _toDisplayString(_ctx.t('field.baseUrl')), 1 /* TEXT */), + _withDirectives(_createElementVNode("input", { + "onUpdate:modelValue": $event => ((_ctx.taskOrchestration.providerSetupDraft.url) = $event), + class: "form-input", + autocomplete: "off", + spellcheck: "false", + placeholder: "https://api.example.com/v1" + }, null, 8 /* PROPS */, ["onUpdate:modelValue"]), [ + [_vModelText, _ctx.taskOrchestration.providerSetupDraft.url] + ]) + ]), + _createElementVNode("label", null, [ + _createElementVNode("span", null, _toDisplayString(_ctx.t('field.model')), 1 /* TEXT */), + _withDirectives(_createElementVNode("input", { + "onUpdate:modelValue": $event => ((_ctx.taskOrchestration.providerSetupDraft.model) = $event), + class: "form-input", + autocomplete: "off", + spellcheck: "false", + placeholder: "gpt-4o-mini" + }, null, 8 /* PROPS */, ["onUpdate:modelValue"]), [ + [_vModelText, _ctx.taskOrchestration.providerSetupDraft.model] + ]) + ]), + _createElementVNode("label", null, [ + _createElementVNode("span", null, _toDisplayString(_ctx.t('field.apiKey')), 1 /* TEXT */), + _withDirectives(_createElementVNode("input", { + "onUpdate:modelValue": $event => ((_ctx.taskOrchestration.providerSetupDraft.key) = $event), + class: "form-input", + type: "password", + autocomplete: "new-password", + spellcheck: "false", + placeholder: "sk-..." + }, null, 8 /* PROPS */, ["onUpdate:modelValue"]), [ + [_vModelText, _ctx.taskOrchestration.providerSetupDraft.key] + ]) + ]), + _createElementVNode("div", { class: "kilo-provider-setup-actions" }, [ + _createElementVNode("span", null, _toDisplayString(_ctx.t('orchestration.openai.config.localStoreHint')), 1 /* TEXT */), + _createElementVNode("button", { + type: "button", + class: "kilo-secondary-button", + onClick: _ctx.closeTaskProviderSetup, + disabled: _ctx.taskOrchestration.providerSetupSaving + }, _toDisplayString(_ctx.t('common.cancel')), 9 /* TEXT, PROPS */, ["onClick", "disabled"]), + _createElementVNode("button", { + type: "submit", + class: "kilo-primary-button", + disabled: _ctx.taskOrchestration.providerSetupSaving + }, _toDisplayString(_ctx.taskOrchestration.providerSetupSaving ? _ctx.t('common.saving') : _ctx.t('common.save')), 9 /* TEXT, PROPS */, ["disabled"]) + ]) + ], 40 /* PROPS, NEED_HYDRATION */, ["onSubmit"])) + : _createCommentVNode("v-if", true), _createElementVNode("div", { class: "permission-dock kilo-status-dock", "data-component": "permission-dock" diff --git a/web-ui/styles/task-orchestration.css b/web-ui/styles/task-orchestration.css index feee449a..12557b0f 100644 --- a/web-ui/styles/task-orchestration.css +++ b/web-ui/styles/task-orchestration.css @@ -381,6 +381,48 @@ color: var(--kilo-reference-muted); } + +#panel-orchestration .kilo-provider-setup-form { + display: grid; + width: min(100%, 98ch); + margin: 0 auto 8px; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 8px; + border: 1px solid rgba(30, 127, 211, 0.24); + border-radius: 8px; + background: #fff; + padding: 10px; +} + +#panel-orchestration .kilo-provider-setup-form label { + display: flex; + min-width: 0; + flex-direction: column; + gap: 4px; + color: var(--kilo-reference-muted); + font-size: 11px; + font-weight: 700; +} + +#panel-orchestration .kilo-provider-setup-form .form-input { + width: 100%; + min-width: 0; +} + +#panel-orchestration .kilo-provider-setup-actions { + display: flex; + grid-column: 1 / -1; + align-items: center; + justify-content: flex-end; + gap: 8px; + color: var(--kilo-reference-muted); + font-size: 11px; +} + +#panel-orchestration .kilo-provider-setup-actions span { + margin-right: auto; +} + #panel-orchestration .chat-input { flex-shrink: 0; border-top: 1px solid var(--kilo-reference-border);