codexmate 0.0.20 → 0.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/README.md +289 -152
  2. package/README.zh.md +321 -0
  3. package/cli/agents-files.js +224 -0
  4. package/cli/archive-helpers.js +446 -0
  5. package/cli/auth-profiles.js +359 -0
  6. package/cli/builtin-proxy.js +1044 -0
  7. package/cli/claude-proxy.js +998 -0
  8. package/cli/config-bootstrap.js +384 -0
  9. package/cli/openai-bridge.js +950 -0
  10. package/cli/openclaw-config.js +629 -0
  11. package/cli/session-usage.concurrent.js +28 -0
  12. package/cli/session-usage.js +112 -0
  13. package/cli/session-usage.models.js +176 -0
  14. package/cli/skills.js +1141 -0
  15. package/cli/zip-commands.js +510 -0
  16. package/cli.js +9408 -9719
  17. package/lib/cli-models-utils.js +109 -1
  18. package/lib/cli-path-utils.js +69 -0
  19. package/lib/cli-sessions.js +386 -0
  20. package/lib/download-artifacts.js +77 -0
  21. package/lib/task-orchestrator.js +869 -0
  22. package/package.json +14 -10
  23. package/res/logo.png +0 -0
  24. package/res/vue.global.prod.js +13 -0
  25. package/web-ui/app.js +193 -15
  26. package/web-ui/index.html +5 -1
  27. package/web-ui/logic.agents-diff.mjs +1 -1
  28. package/web-ui/logic.claude.mjs +60 -0
  29. package/web-ui/logic.runtime.mjs +11 -7
  30. package/web-ui/logic.sessions.mjs +372 -21
  31. package/web-ui/modules/api.mjs +22 -1
  32. package/web-ui/modules/app.computed.dashboard.mjs +23 -10
  33. package/web-ui/modules/app.computed.index.mjs +4 -0
  34. package/web-ui/modules/app.computed.main-tabs.mjs +198 -0
  35. package/web-ui/modules/app.computed.session.mjs +521 -9
  36. package/web-ui/modules/app.methods.agents.mjs +62 -11
  37. package/web-ui/modules/app.methods.codex-config.mjs +189 -34
  38. package/web-ui/modules/app.methods.index.mjs +7 -1
  39. package/web-ui/modules/app.methods.install.mjs +24 -20
  40. package/web-ui/modules/app.methods.navigation.mjs +142 -1
  41. package/web-ui/modules/app.methods.openclaw-core.mjs +339 -39
  42. package/web-ui/modules/app.methods.openclaw-editing.mjs +39 -4
  43. package/web-ui/modules/app.methods.openclaw-persist.mjs +122 -4
  44. package/web-ui/modules/app.methods.providers.mjs +192 -53
  45. package/web-ui/modules/app.methods.session-actions.mjs +99 -19
  46. package/web-ui/modules/app.methods.session-browser.mjs +196 -5
  47. package/web-ui/modules/app.methods.session-timeline.mjs +22 -15
  48. package/web-ui/modules/app.methods.session-trash.mjs +3 -0
  49. package/web-ui/modules/app.methods.startup-claude.mjs +70 -71
  50. package/web-ui/modules/app.methods.task-orchestration.mjs +471 -0
  51. package/web-ui/modules/config-mode.computed.mjs +2 -0
  52. package/web-ui/modules/config-template-confirm-pref.mjs +33 -0
  53. package/web-ui/modules/i18n.mjs +1609 -0
  54. package/web-ui/modules/plugins.computed.mjs +220 -0
  55. package/web-ui/modules/plugins.methods.mjs +620 -0
  56. package/web-ui/modules/plugins.storage.mjs +37 -0
  57. package/web-ui/partials/index/layout-footer.html +1 -57
  58. package/web-ui/partials/index/layout-header.html +299 -175
  59. package/web-ui/partials/index/modal-config-template-agents.html +79 -29
  60. package/web-ui/partials/index/modal-confirm-toast.html +1 -1
  61. package/web-ui/partials/index/modal-health-check.html +14 -14
  62. package/web-ui/partials/index/modal-openclaw-config.html +47 -42
  63. package/web-ui/partials/index/modal-skills.html +130 -114
  64. package/web-ui/partials/index/modals-basic.html +71 -102
  65. package/web-ui/partials/index/panel-config-claude.html +50 -12
  66. package/web-ui/partials/index/panel-config-codex.html +34 -37
  67. package/web-ui/partials/index/panel-config-openclaw.html +10 -16
  68. package/web-ui/partials/index/panel-docs.html +147 -0
  69. package/web-ui/partials/index/panel-market.html +38 -38
  70. package/web-ui/partials/index/panel-orchestration.html +397 -0
  71. package/web-ui/partials/index/panel-plugins.html +243 -0
  72. package/web-ui/partials/index/panel-sessions.html +51 -146
  73. package/web-ui/partials/index/panel-settings.html +188 -96
  74. package/web-ui/partials/index/panel-usage.html +353 -0
  75. package/web-ui/session-helpers.mjs +221 -10
  76. package/web-ui/styles/base-theme.css +120 -229
  77. package/web-ui/styles/controls-forms.css +59 -51
  78. package/web-ui/styles/docs-panel.css +247 -0
  79. package/web-ui/styles/layout-shell.css +394 -128
  80. package/web-ui/styles/modals-core.css +18 -3
  81. package/web-ui/styles/navigation-panels.css +184 -183
  82. package/web-ui/styles/plugins-panel.css +518 -0
  83. package/web-ui/styles/responsive.css +102 -62
  84. package/web-ui/styles/sessions-list.css +13 -27
  85. package/web-ui/styles/sessions-preview.css +13 -7
  86. package/web-ui/styles/sessions-toolbar-trash.css +25 -0
  87. package/web-ui/styles/sessions-usage.css +581 -6
  88. package/web-ui/styles/settings-panel.css +166 -0
  89. package/web-ui/styles/skills-list.css +16 -11
  90. package/web-ui/styles/skills-market.css +63 -2
  91. package/web-ui/styles/task-orchestration.css +776 -0
  92. package/web-ui/styles/titles-cards.css +67 -66
  93. package/web-ui/styles.css +4 -0
  94. package/README.en.md +0 -259
  95. package/res/screenshot.png +0 -0
  96. package/res/vue.global.js +0 -18552
@@ -7,20 +7,20 @@
7
7
  <div class="selector-section market-overview-section">
8
8
  <div class="selector-header market-overview-header">
9
9
  <div>
10
- <span class="selector-title">Skills 概览</span>
11
- <div class="skills-panel-note">聚焦本地 skills:切换目标、查看已装项、跨应用导入与 ZIP 分发。</div>
10
+ <span class="selector-title">{{ t('market.title') }}</span>
11
+ <div class="skills-panel-note">{{ t('market.subtitle') }}</div>
12
12
  </div>
13
13
  <div class="settings-tab-actions market-header-actions">
14
14
  <button type="button" class="btn-tool btn-tool-compact" @click="loadSkillsMarketOverview({ forceRefresh: true, silent: false })" :disabled="loading || !!initError || skillsMarketBusy">
15
- {{ skillsMarketLoading ? '刷新中...' : '刷新概览' }}
15
+ {{ skillsMarketLoading ? t('market.refreshing') : t('market.refresh') }}
16
16
  </button>
17
17
  <button type="button" class="btn-tool btn-tool-compact" @click="openSkillsManager" :disabled="loading || !!initError || skillsMarketBusy">
18
- 打开 Skills 管理
18
+ {{ t('market.openManager') }}
19
19
  </button>
20
20
  </div>
21
21
  </div>
22
22
 
23
- <div class="market-target-switch" role="group" aria-label="选择 Skills 安装目标">
23
+ <div class="market-target-switch" role="group" :aria-label="t('market.target.aria')">
24
24
  <button
25
25
  type="button"
26
26
  :class="['market-target-chip', { active: skillsTargetApp === 'codex' }]"
@@ -43,27 +43,27 @@
43
43
 
44
44
  <div class="skills-summary-strip market-summary-strip">
45
45
  <div class="skills-summary-item">
46
- <span class="skills-summary-label">安装目标</span>
46
+ <span class="skills-summary-label">{{ t('market.summary.target') }}</span>
47
47
  <strong class="skills-summary-value">{{ skillsTargetLabel }}</strong>
48
48
  </div>
49
49
  <div class="skills-summary-item">
50
- <span class="skills-summary-label">本地总数</span>
50
+ <span class="skills-summary-label">{{ t('market.summary.total') }}</span>
51
51
  <strong class="skills-summary-value">{{ skillsList.length }}</strong>
52
52
  </div>
53
53
  <div class="skills-summary-item">
54
- <span class="skills-summary-label">含 SKILL.md</span>
54
+ <span class="skills-summary-label">{{ t('market.summary.configured') }}</span>
55
55
  <strong class="skills-summary-value">{{ skillsConfiguredCount }}</strong>
56
56
  </div>
57
57
  <div class="skills-summary-item">
58
- <span class="skills-summary-label">缺少 SKILL.md</span>
58
+ <span class="skills-summary-label">{{ t('market.summary.missing') }}</span>
59
59
  <strong class="skills-summary-value">{{ skillsMissingSkillFileCount }}</strong>
60
60
  </div>
61
61
  <div class="skills-summary-item">
62
- <span class="skills-summary-label">可导入</span>
62
+ <span class="skills-summary-label">{{ t('market.summary.importable') }}</span>
63
63
  <strong class="skills-summary-value">{{ skillsImportList.length }}</strong>
64
64
  </div>
65
65
  <div class="skills-summary-item">
66
- <span class="skills-summary-label">可直接导入</span>
66
+ <span class="skills-summary-label">{{ t('market.summary.importableDirect') }}</span>
67
67
  <strong class="skills-summary-value">{{ skillsImportConfiguredCount }}</strong>
68
68
  </div>
69
69
  </div>
@@ -73,15 +73,15 @@
73
73
  <div class="skills-panel market-panel">
74
74
  <div class="skills-panel-header">
75
75
  <div class="skills-panel-title-wrap">
76
- <div class="skills-panel-title">已安装 Skills</div>
77
- <div class="skills-panel-note">展示当前目录前 6 项;更多操作请进管理弹窗。</div>
76
+ <div class="skills-panel-title">{{ t('market.installed.title') }}</div>
77
+ <div class="skills-panel-note">{{ t('market.installed.note') }}</div>
78
78
  </div>
79
79
  <button type="button" class="btn-mini" @click="refreshSkillsList({ silent: false })" :disabled="loading || !!initError || skillsMarketBusy">
80
- {{ skillsLoading ? '刷新中...' : '刷新本地' }}
80
+ {{ skillsLoading ? t('market.local.refreshing') : t('market.local.refresh') }}
81
81
  </button>
82
82
  </div>
83
- <div v-if="skillsLoading && !skillsMarketLocalLoadedOnce" class="skills-empty-state">正在加载本地 Skills...</div>
84
- <div v-else-if="skillsList.length === 0" class="skills-empty-state">当前暂无已安装 skill,可通过 ZIP 或跨应用导入补充。</div>
83
+ <div v-if="skillsLoading && !skillsMarketLocalLoadedOnce" class="skills-empty-state">{{ t('market.local.loading') }}</div>
84
+ <div v-else-if="skillsList.length === 0" class="skills-empty-state">{{ t('market.local.empty') }}</div>
85
85
  <div v-else class="market-preview-list">
86
86
  <div v-for="skill in skillsMarketInstalledPreview" :key="'market-local-' + skill.name" class="market-preview-item">
87
87
  <div class="market-preview-main">
@@ -89,7 +89,7 @@
89
89
  <div class="market-preview-meta">{{ skill.description || skill.path }}</div>
90
90
  </div>
91
91
  <span :class="['pill', skill.hasSkillFile ? 'configured' : 'empty']">
92
- {{ skill.hasSkillFile ? '已验证' : '待补 SKILL.md' }}
92
+ {{ skill.hasSkillFile ? t('market.pill.verified') : t('market.pill.missingSkill') }}
93
93
  </span>
94
94
  </div>
95
95
  </div>
@@ -98,15 +98,15 @@
98
98
  <div class="skills-panel market-panel">
99
99
  <div class="skills-panel-header">
100
100
  <div class="skills-panel-title-wrap">
101
- <div class="skills-panel-title">可导入来源</div>
102
- <div class="skills-panel-note">扫描其他应用中的未托管 skill,确认后批量导入到当前 {{ skillsTargetLabel }}。</div>
101
+ <div class="skills-panel-title">{{ t('market.import.title') }}</div>
102
+ <div class="skills-panel-note">{{ t('market.import.note', { target: skillsTargetLabel }) }}</div>
103
103
  </div>
104
104
  <button type="button" class="btn-mini" @click="scanImportableSkills({ silent: false })" :disabled="loading || !!initError || skillsMarketBusy">
105
- {{ skillsScanningImports ? '扫描中...' : '扫描来源' }}
105
+ {{ skillsScanningImports ? t('market.import.scanning') : t('market.import.scan') }}
106
106
  </button>
107
107
  </div>
108
- <div v-if="skillsScanningImports && !skillsMarketImportLoadedOnce" class="skills-empty-state">正在扫描可导入 skill...</div>
109
- <div v-else-if="skillsImportList.length === 0" class="skills-empty-state">暂未扫描到可导入 skill,可点击“扫描来源”重试。</div>
108
+ <div v-if="skillsScanningImports && !skillsMarketImportLoadedOnce" class="skills-empty-state">{{ t('market.import.loading') }}</div>
109
+ <div v-else-if="skillsImportList.length === 0" class="skills-empty-state">{{ t('market.import.empty') }}</div>
110
110
  <div v-else class="market-preview-list">
111
111
  <div v-for="skill in skillsMarketImportPreview" :key="'market-import-' + buildSkillImportKey(skill)" class="market-preview-item">
112
112
  <div class="market-preview-main">
@@ -114,7 +114,7 @@
114
114
  <div class="market-preview-meta">{{ skill.sourceLabel }} · {{ skill.sourcePath }}</div>
115
115
  </div>
116
116
  <span :class="['pill', skill.hasSkillFile ? 'configured' : 'empty']">
117
- {{ skill.hasSkillFile ? '可直接导入' : '缺少 SKILL.md' }}
117
+ {{ skill.hasSkillFile ? t('market.pill.importableDirect') : t('market.pill.importMissing') }}
118
118
  </span>
119
119
  </div>
120
120
  </div>
@@ -123,22 +123,22 @@
123
123
  <div class="skills-panel market-panel market-actions-panel">
124
124
  <div class="skills-panel-header">
125
125
  <div class="skills-panel-title-wrap">
126
- <div class="skills-panel-title">分发入口</div>
127
- <div class="skills-panel-note">本地管理、跨应用导入与 ZIP 导入,均作用于当前安装目标。</div>
126
+ <div class="skills-panel-title">{{ t('market.actions.title') }}</div>
127
+ <div class="skills-panel-note">{{ t('market.actions.note') }}</div>
128
128
  </div>
129
129
  </div>
130
130
  <div class="market-action-grid">
131
131
  <button type="button" class="market-action-card" @click="openSkillsManager" :disabled="loading || !!initError || skillsMarketBusy">
132
- <span class="market-action-title">本地 Skills 管理</span>
133
- <span class="market-action-copy">查看并管理当前 {{ skillsTargetLabel }} 的已装 skills</span>
132
+ <span class="market-action-title">{{ t('market.action.manage.title') }}</span>
133
+ <span class="market-action-copy">{{ t('market.action.manage.copy', { target: skillsTargetLabel }) }}</span>
134
134
  </button>
135
135
  <button type="button" class="market-action-card" @click="scanImportableSkills({ silent: false })" :disabled="loading || !!initError || skillsMarketBusy">
136
- <span class="market-action-title">跨应用导入</span>
137
- <span class="market-action-copy">扫描其他应用并导入到当前 {{ skillsTargetLabel }}</span>
136
+ <span class="market-action-title">{{ t('market.action.crossImport.title') }}</span>
137
+ <span class="market-action-copy">{{ t('market.action.crossImport.copy', { target: skillsTargetLabel }) }}</span>
138
138
  </button>
139
139
  <button type="button" class="market-action-card" @click="triggerSkillsZipImport" :disabled="loading || !!initError || skillsMarketBusy">
140
- <span class="market-action-title">ZIP 导入</span>
141
- <span class="market-action-copy">从 ZIP 安装到当前目标</span>
140
+ <span class="market-action-title">{{ t('market.action.zipImport.title') }}</span>
141
+ <span class="market-action-copy">{{ t('market.action.zipImport.copy') }}</span>
142
142
  </button>
143
143
  </div>
144
144
  </div>
@@ -146,26 +146,26 @@
146
146
  <div class="skills-panel market-panel market-panel-wide">
147
147
  <div class="skills-panel-header">
148
148
  <div class="skills-panel-title-wrap">
149
- <div class="skills-panel-title">市场说明</div>
149
+ <div class="skills-panel-title">{{ t('market.help.title') }}</div>
150
150
  </div>
151
151
  </div>
152
152
  <div class="market-preview-list">
153
153
  <div class="market-preview-item">
154
154
  <div class="market-preview-main">
155
- <div class="market-preview-title">目标切换</div>
156
- <div class="market-preview-meta">切换 Codex / Claude Code 后,后续操作都会落到当前 {{ skillsTargetLabel }} 目录。</div>
155
+ <div class="market-preview-title">{{ t('market.help.target.title') }}</div>
156
+ <div class="market-preview-meta">{{ t('market.help.target.copy', { target: skillsTargetLabel }) }}</div>
157
157
  </div>
158
158
  </div>
159
159
  <div class="market-preview-item">
160
160
  <div class="market-preview-main">
161
- <div class="market-preview-title">跨应用导入</div>
162
- <div class="market-preview-meta">扫描 `Codex`、`Claude Code` 与 `Agents` 中未托管的 skills,并导入到当前宿主。</div>
161
+ <div class="market-preview-title">{{ t('market.help.crossImport.title') }}</div>
162
+ <div class="market-preview-meta">{{ t('market.help.crossImport.copy') }}</div>
163
163
  </div>
164
164
  </div>
165
165
  <div class="market-preview-item">
166
166
  <div class="market-preview-main">
167
- <div class="market-preview-title">ZIP 分发</div>
168
- <div class="market-preview-meta">通过压缩包分发技能目录,保持本地可控,不依赖外部目录服务。</div>
167
+ <div class="market-preview-title">{{ t('market.help.zipImport.title') }}</div>
168
+ <div class="market-preview-meta">{{ t('market.help.zipImport.copy') }}</div>
169
169
  </div>
170
170
  </div>
171
171
  </div>
@@ -0,0 +1,397 @@
1
+ <div
2
+ v-if="taskOrchestrationTabEnabled"
3
+ v-show="mainTab === 'orchestration'"
4
+ class="mode-content"
5
+ id="panel-orchestration"
6
+ role="tabpanel"
7
+ aria-labelledby="tab-orchestration">
8
+ <section class="selector-section task-hero-card">
9
+ <div class="task-hero-main">
10
+ <div>
11
+ <div class="task-hero-kicker">Task orchestration</div>
12
+ <div class="selector-title">把需求拆成可执行步骤</div>
13
+ <div class="skills-panel-note task-hero-copy">先写目标,再预览,再执行。</div>
14
+ </div>
15
+ <div class="task-hero-actions settings-tab-actions task-header-actions">
16
+ <button type="button" class="btn-tool btn-tool-compact" @click="loadTaskOrchestrationOverview({ forceRefresh: true, includeDetail: true })" :disabled="taskOrchestration.loading">
17
+ {{ taskOrchestration.loading ? '刷新中...' : '刷新' }}
18
+ </button>
19
+ <button type="button" class="btn-tool btn-tool-compact" @click="resetTaskOrchestrationDraft()" :disabled="taskOrchestration.running || taskOrchestration.queueAdding || taskOrchestration.planning">
20
+ 重置草稿
21
+ </button>
22
+ </div>
23
+ </div>
24
+ <div v-if="taskOrchestrationQueueStats.running || taskOrchestrationQueueStats.queued || taskOrchestration.runs.length" class="task-hero-meta-strip" aria-label="任务编排概览">
25
+ <div class="task-hero-meta">运行中 <strong>{{ taskOrchestrationQueueStats.running }}</strong></div>
26
+ <div class="task-hero-meta">排队中 <strong>{{ taskOrchestrationQueueStats.queued }}</strong></div>
27
+ <div class="task-hero-meta">运行记录 <strong>{{ taskOrchestration.runs.length }}</strong></div>
28
+ </div>
29
+ </section>
30
+
31
+ <div class="task-layout-grid task-layout-grid-primary">
32
+ <section class="selector-section task-compose-flow-card">
33
+ <div class="task-flow-section task-flow-section-compact">
34
+ <div class="task-flow-head">
35
+ <div class="task-flow-step">1</div>
36
+ <div>
37
+ <div class="task-flow-title">先把结果写清楚</div>
38
+ <div class="task-flow-copy">只写会影响执行的内容。</div>
39
+ </div>
40
+ </div>
41
+
42
+ <details v-if="!taskOrchestration.target.trim()" class="task-template-panel">
43
+ <summary class="task-advanced-summary">快捷示例</summary>
44
+ <div class="task-template-block">
45
+ <div class="task-template-chip-group">
46
+ <button type="button" class="task-template-chip" @click="taskOrchestration.target = '修复当前 PR review 评论,并补对应回归测试'; taskOrchestration.selectedEngine = 'codex'; taskOrchestration.workflowIdsText = ''; taskOrchestration.notes = '不要改动无关模块;需要给出验证结果'; taskOrchestration.followUpsText = '继续处理新增 review 评论\n最后更新 PR 摘要'">修 review + 回归</button>
47
+ <button type="button" class="task-template-chip" @click="taskOrchestration.target = '先排查问题根因并给出执行计划,不直接修改代码'; taskOrchestration.selectedEngine = 'codex'; taskOrchestration.workflowIdsText = ''; taskOrchestration.notes = '优先定位根因、影响范围和风险点'; taskOrchestration.followUpsText = ''">只做排查规划</button>
48
+ <button type="button" class="task-template-chip" @click="taskOrchestration.target = '用 Workflow 跑一组固定检查并整理结果'; taskOrchestration.selectedEngine = 'workflow'; taskOrchestration.workflowIdsText = 'diagnose-config\nsafe-provider-switch'; taskOrchestration.notes = '输出统一结论,避免重复描述'; taskOrchestration.followUpsText = ''">Workflow 批处理</button>
49
+ </div>
50
+ </div>
51
+ </details>
52
+
53
+ <div class="selector-grid task-composer-grid task-composer-grid-primary">
54
+ <label class="selector-field task-field task-field-wide task-goal-field">
55
+ <span class="selector-label">目标</span>
56
+ <textarea v-model="taskOrchestration.target" class="task-textarea task-textarea-goal" rows="5" placeholder="例如:修复当前 PR review 评论,并补对应回归测试;不要改无关模块"></textarea>
57
+ <span class="task-field-hint">一句话写清结果、边界和验收标准就够了。</span>
58
+ </label>
59
+ </div>
60
+
61
+ <div class="task-draft-overview task-draft-inline">
62
+ <div class="task-draft-inline-head">
63
+ <span :class="['pill', taskOrchestrationDraftReadiness.tone]">{{ taskOrchestrationDraftReadiness.title }}</span>
64
+ <div class="task-readiness-copy task-draft-inline-copy">{{ taskOrchestrationDraftReadiness.summary }}</div>
65
+ </div>
66
+ <div class="task-config-strip">
67
+ <div class="task-config-pill">{{ taskOrchestration.selectedEngine === 'workflow' ? 'Workflow' : 'Codex' }}</div>
68
+ <div class="task-config-pill">{{ taskOrchestration.allowWrite ? '允许写入' : '只读' }}</div>
69
+ <div class="task-config-pill">{{ taskOrchestration.dryRun ? '仅预演' : '真实执行' }}</div>
70
+ <div v-if="taskOrchestration.title.trim()" class="task-config-pill">标题已设</div>
71
+ <div v-if="taskOrchestration.selectedEngine === 'workflow' && taskOrchestrationDraftMetrics.workflowCount > 0" class="task-config-pill">Workflow {{ taskOrchestrationDraftMetrics.workflowCount }}</div>
72
+ <div v-if="taskOrchestration.plan" class="task-config-pill">计划 {{ taskOrchestrationDraftMetrics.planNodeCount }} 节点</div>
73
+ </div>
74
+ </div>
75
+ </div>
76
+
77
+ <div class="task-flow-section task-flow-section-compact">
78
+ <div class="task-flow-head">
79
+ <div class="task-flow-step">2</div>
80
+ <div>
81
+ <div class="task-flow-title">选择执行方式</div>
82
+ <div class="task-flow-copy">常用项默认展开,其余放高级设置。</div>
83
+ </div>
84
+ </div>
85
+
86
+ <div class="selector-grid task-composer-grid task-composer-grid-compact">
87
+ <label class="selector-field">
88
+ <span class="selector-label">引擎</span>
89
+ <select v-model="taskOrchestration.selectedEngine">
90
+ <option value="codex">Codex</option>
91
+ <option value="workflow">Workflow</option>
92
+ </select>
93
+ </label>
94
+ <div class="selector-field task-toggle-card task-toggle-card-inline">
95
+ <span class="selector-label">运行模式</span>
96
+ <div class="task-toggle-inline">
97
+ <label class="task-toggle">
98
+ <input v-model="taskOrchestration.allowWrite" type="checkbox">
99
+ <span>允许写入</span>
100
+ </label>
101
+ <label class="task-toggle">
102
+ <input v-model="taskOrchestration.dryRun" type="checkbox">
103
+ <span>仅预演</span>
104
+ </label>
105
+ </div>
106
+ </div>
107
+ </div>
108
+
109
+ <details class="task-advanced-panel">
110
+ <summary class="task-advanced-summary">高级设置</summary>
111
+ <div class="selector-grid task-composer-grid task-composer-grid-secondary">
112
+ <label class="selector-field task-field-wide">
113
+ <span class="selector-label">标题</span>
114
+ <input v-model="taskOrchestration.title" type="text" placeholder="可选,默认从目标自动提取">
115
+ </label>
116
+ <label class="selector-field task-field-wide">
117
+ <span class="selector-label">说明</span>
118
+ <textarea v-model="taskOrchestration.notes" class="task-textarea" rows="3" placeholder="例如:不要重写现有架构,只做增量实现"></textarea>
119
+ <span class="task-field-hint">补边界、禁区、风格要求或验证要求。</span>
120
+ </label>
121
+ <label class="selector-field task-field-wide">
122
+ <span class="selector-label">后续动作(每行一条)</span>
123
+ <textarea v-model="taskOrchestration.followUpsText" class="task-textarea" rows="3" placeholder="例如:\n继续处理 review 评论\n最后补回归测试"></textarea>
124
+ </label>
125
+ <label class="selector-field">
126
+ <span class="selector-label">并发</span>
127
+ <input v-model="taskOrchestration.concurrency" type="number" min="1" max="8">
128
+ <span class="task-field-hint">复杂任务先从 1~2 开始更稳。</span>
129
+ </label>
130
+ <label class="selector-field">
131
+ <span class="selector-label">自动修复</span>
132
+ <input v-model="taskOrchestration.autoFixRounds" type="number" min="0" max="5">
133
+ <span class="task-field-hint">失败后自动再试几轮。</span>
134
+ </label>
135
+ <label v-if="taskOrchestration.selectedEngine === 'workflow'" class="selector-field task-field-wide">
136
+ <span class="selector-label">Workflow ID(每行一条)</span>
137
+ <textarea v-model="taskOrchestration.workflowIdsText" class="task-textarea" rows="3" placeholder="例如:\ndiagnose-config\nsafe-provider-switch"></textarea>
138
+ <span class="task-field-hint">仅 Workflow 模式需要。当前本地可用 {{ taskOrchestration.workflows.length }} 个。</span>
139
+ <div v-if="taskOrchestration.workflows.length" class="task-workflow-suggestions">
140
+ <button
141
+ v-for="workflow in taskOrchestration.workflows"
142
+ :key="workflow.id || workflow.name"
143
+ type="button"
144
+ class="task-workflow-chip"
145
+ @click="appendTaskWorkflowId(workflow.id || workflow.name)">
146
+ <span>{{ workflow.name || workflow.id }}</span>
147
+ <small v-if="workflow.stepCount">{{ workflow.stepCount }} 步</small>
148
+ </button>
149
+ </div>
150
+ </label>
151
+ </div>
152
+ </details>
153
+ </div>
154
+
155
+ <div class="task-flow-section task-flow-section-actions task-flow-section-compact">
156
+ <div class="task-flow-head">
157
+ <div class="task-flow-step">3</div>
158
+ <div>
159
+ <div class="task-flow-title">先预览,再执行</div>
160
+ <div class="task-flow-copy">先确认计划,再决定立即执行还是入队。</div>
161
+ </div>
162
+ </div>
163
+ <div class="task-action-row task-action-row-prominent">
164
+ <button type="button" class="btn-tool task-action-preview" @click="previewTaskPlan()" :disabled="taskOrchestration.planning || taskOrchestration.running || !taskOrchestration.target.trim()">
165
+ {{ taskOrchestration.planning ? '规划中...' : '预览计划' }}
166
+ </button>
167
+ <div class="task-action-row-right task-action-row-right-prominent">
168
+ <button type="button" class="btn-tool btn-primary" @click="runTaskOrchestration()" :disabled="taskOrchestration.running || !taskOrchestration.target.trim()">
169
+ {{ taskOrchestration.running ? '启动中...' : '立即执行' }}
170
+ </button>
171
+ <button type="button" class="btn-tool" @click="addTaskOrchestrationToQueue()" :disabled="taskOrchestration.queueAdding || !taskOrchestration.target.trim()">
172
+ {{ taskOrchestration.queueAdding ? '加入中...' : '加入队列' }}
173
+ </button>
174
+ <button v-if="taskOrchestration.queue.length" type="button" class="btn-tool" @click="startTaskQueueRunner()" :disabled="taskOrchestration.queueStarting">
175
+ {{ taskOrchestration.queueStarting ? '启动中...' : '开始队列' }}
176
+ </button>
177
+ </div>
178
+ </div>
179
+ <div class="task-action-caption">预览不会执行。批量任务再入队。</div>
180
+ </div>
181
+ </section>
182
+ </div>
183
+
184
+ <section
185
+ v-if="!(taskOrchestration.plan || taskOrchestration.planIssues.length || taskOrchestration.planWarnings.length || taskOrchestration.lastError || taskOrchestration.queue.length || taskOrchestration.runs.length || taskOrchestration.selectedRunId || taskOrchestration.selectedRunError)"
186
+ class="selector-section task-stage-card">
187
+ <div class="task-stage-empty">
188
+ <div>
189
+ <div class="selector-title">有内容时再展开工作区</div>
190
+ <div class="skills-panel-note">先写目标,再预览计划。</div>
191
+ </div>
192
+ <div class="task-stage-strip">
193
+ <div class="task-stage-pill">写目标</div>
194
+ <div class="task-stage-pill">预览</div>
195
+ <div class="task-stage-pill">执行或入队</div>
196
+ </div>
197
+ </div>
198
+ </section>
199
+
200
+ <div
201
+ v-else
202
+ class="task-layout-grid task-layout-grid-secondary">
203
+ <section
204
+ v-if="taskOrchestration.plan || taskOrchestration.planIssues.length || taskOrchestration.planWarnings.length || taskOrchestration.lastError"
205
+ class="selector-section task-plan-card">
206
+ <div v-if="taskOrchestration.lastError" class="task-issue-item">{{ taskOrchestration.lastError }}</div>
207
+ <div class="selector-header task-section-header">
208
+ <div>
209
+ <span class="selector-title">计划预览</span>
210
+ <div class="skills-panel-note">先确认节点、波次和依赖。</div>
211
+ </div>
212
+ </div>
213
+ <div v-if="taskOrchestration.planIssues.length" class="task-issues-list">
214
+ <div v-for="issue in taskOrchestration.planIssues" :key="issue.code + issue.message" class="task-issue-item">
215
+ {{ issue.message }}
216
+ </div>
217
+ </div>
218
+ <div v-if="taskOrchestration.planWarnings.length" class="task-warning-list">
219
+ <div v-for="warning in taskOrchestration.planWarnings" :key="warning" class="task-warning-item">
220
+ {{ warning }}
221
+ </div>
222
+ </div>
223
+ <template v-if="taskOrchestration.plan">
224
+ <div class="task-plan-summary-strip">
225
+ <div class="task-plan-summary-item">
226
+ <span class="task-plan-summary-label">节点数</span>
227
+ <strong>{{ taskOrchestration.plan.nodes.length }}</strong>
228
+ </div>
229
+ <div class="task-plan-summary-item">
230
+ <span class="task-plan-summary-label">波次</span>
231
+ <strong>{{ taskOrchestration.plan.waves.length }}</strong>
232
+ </div>
233
+ <div class="task-plan-summary-item">
234
+ <span class="task-plan-summary-label">引擎</span>
235
+ <strong>{{ taskOrchestration.plan.engine }}</strong>
236
+ </div>
237
+ </div>
238
+ <div class="task-wave-list">
239
+ <div v-for="wave in taskOrchestration.plan.waves" :key="wave.label" class="task-wave-card">
240
+ <div class="task-wave-title">{{ wave.label }}</div>
241
+ <div class="task-wave-nodes">{{ wave.nodeIds.join(', ') }}</div>
242
+ </div>
243
+ </div>
244
+ <div class="task-node-list">
245
+ <div v-for="node in taskOrchestration.plan.nodes" :key="node.id" class="task-node-card">
246
+ <div class="task-node-head">
247
+ <div>
248
+ <div class="task-node-title">{{ node.title || node.id }}</div>
249
+ <div class="task-node-meta">{{ node.id }} · {{ node.kind }}<span v-if="node.workflowId"> · {{ node.workflowId }}</span></div>
250
+ </div>
251
+ <span :class="['pill', node.write ? 'configured' : 'empty']">{{ node.write ? 'write' : 'read-only' }}</span>
252
+ </div>
253
+ <div class="task-node-deps">依赖:{{ formatTaskNodeDependencies(node) }}</div>
254
+ </div>
255
+ </div>
256
+ </template>
257
+ </section>
258
+
259
+ <section
260
+ v-if="taskOrchestration.queue.length || taskOrchestration.runs.length || taskOrchestration.selectedRunId || taskOrchestration.selectedRunError"
261
+ class="selector-section task-workbench-card">
262
+ <div class="selector-header task-section-header">
263
+ <div>
264
+ <span class="selector-title">执行工作台</span>
265
+ <div class="skills-panel-note">有内容时才展开。</div>
266
+ </div>
267
+ <div class="settings-tab-actions task-header-actions">
268
+ <button type="button" class="btn-tool btn-tool-compact" @click="loadTaskOrchestrationOverview({ forceRefresh: true, includeDetail: true })" :disabled="taskOrchestration.loading">
269
+ {{ taskOrchestration.loading ? '刷新中...' : '刷新' }}
270
+ </button>
271
+ <button v-if="taskOrchestration.queue.length" type="button" class="btn-tool btn-tool-compact" @click="startTaskQueueRunner()" :disabled="taskOrchestration.queueStarting">
272
+ {{ taskOrchestration.queueStarting ? '启动中...' : '开始队列' }}
273
+ </button>
274
+ </div>
275
+ </div>
276
+
277
+ <div
278
+ v-if="(taskOrchestration.queue.length ? 1 : 0) + (taskOrchestration.runs.length ? 1 : 0) + ((taskOrchestration.selectedRunId || taskOrchestration.selectedRunError) ? 1 : 0) > 1"
279
+ class="task-workbench-tabs"
280
+ role="group"
281
+ aria-label="任务编排工作台视图">
282
+ <button v-if="taskOrchestration.queue.length" type="button" class="task-workbench-tab" :class="{ active: taskOrchestration.workspaceTab === 'queue' }" @click="taskOrchestration.workspaceTab = 'queue'">队列 {{ taskOrchestration.queue.length }}</button>
283
+ <button v-if="taskOrchestration.runs.length" type="button" class="task-workbench-tab" :class="{ active: taskOrchestration.workspaceTab === 'runs' }" @click="taskOrchestration.workspaceTab = 'runs'">运行记录 {{ taskOrchestration.runs.length }}</button>
284
+ <button v-if="taskOrchestration.selectedRunId || taskOrchestration.selectedRunError" type="button" class="task-workbench-tab" :class="{ active: taskOrchestration.workspaceTab === 'detail' }" @click="taskOrchestration.workspaceTab = 'detail'">运行详情</button>
285
+ </div>
286
+
287
+ <div v-if="taskOrchestration.workspaceTab === 'queue' || (!taskOrchestration.runs.length && !taskOrchestration.selectedRunId && !taskOrchestration.selectedRunError)" class="task-workbench-panel">
288
+ <div v-if="!taskOrchestration.queue.length" class="task-empty-state">
289
+ <div class="task-empty-title">当前没有排队任务</div>
290
+ <div class="task-empty-copy">批量任务可先入队,再启动队列。</div>
291
+ </div>
292
+ <div v-else class="task-runtime-list">
293
+ <div
294
+ v-for="item in taskOrchestration.queue"
295
+ :key="item.taskId"
296
+ :class="['task-runtime-item', { active: item.lastRunId && taskOrchestration.selectedRunId === item.lastRunId, clickable: !!item.lastRunId }]"
297
+ :role="item.lastRunId ? 'button' : null"
298
+ :tabindex="item.lastRunId ? 0 : -1"
299
+ :aria-disabled="item.lastRunId ? null : 'true'"
300
+ @click="item.lastRunId ? (taskOrchestration.workspaceTab = 'detail', selectTaskRun(item.lastRunId)) : null"
301
+ @keydown.enter.self.prevent="item.lastRunId ? (taskOrchestration.workspaceTab = 'detail', selectTaskRun(item.lastRunId)) : null"
302
+ @keydown.space.self.prevent="item.lastRunId ? (taskOrchestration.workspaceTab = 'detail', selectTaskRun(item.lastRunId)) : null">
303
+ <div class="task-runtime-item-main">
304
+ <div class="task-runtime-item-title">{{ item.title || item.target || item.taskId }}</div>
305
+ <div class="task-runtime-item-meta">{{ item.taskId }} · {{ item.updatedAt || item.createdAt }}</div>
306
+ <div v-if="item.lastSummary" class="task-runtime-item-summary">{{ item.lastSummary }}</div>
307
+ </div>
308
+ <div class="task-runtime-item-actions">
309
+ <span :class="['pill', taskRunStatusTone(item.status)]">{{ item.status }}</span>
310
+ <button type="button" class="btn-mini" @click.stop="cancelTaskRunFromUi(item.taskId)" :disabled="item.status !== 'queued' && item.status !== 'running'">取消</button>
311
+ </div>
312
+ </div>
313
+ </div>
314
+ </div>
315
+
316
+ <div v-else-if="taskOrchestration.workspaceTab === 'runs' || (!taskOrchestration.queue.length && taskOrchestration.runs.length && !taskOrchestration.selectedRunId && !taskOrchestration.selectedRunError)" class="task-workbench-panel">
317
+ <div v-if="!taskOrchestration.runs.length" class="task-empty-state">
318
+ <div class="task-empty-title">还没有运行记录</div>
319
+ <div class="task-empty-copy">执行后会显示最近运行记录。</div>
320
+ </div>
321
+ <div v-else class="task-runtime-list">
322
+ <button
323
+ v-for="item in taskOrchestration.runs"
324
+ :key="item.runId"
325
+ type="button"
326
+ :class="['task-runtime-item', { active: taskOrchestration.selectedRunId === item.runId }]"
327
+ @click="taskOrchestration.workspaceTab = 'detail'; selectTaskRun(item.runId)">
328
+ <div class="task-runtime-item-main">
329
+ <div class="task-runtime-item-title">{{ item.title || item.taskId || item.runId }}</div>
330
+ <div class="task-runtime-item-meta">{{ item.runId }} · {{ item.durationMs || 0 }}ms</div>
331
+ <div v-if="item.summary" class="task-runtime-item-summary">{{ item.summary }}</div>
332
+ </div>
333
+ <div class="task-runtime-item-actions">
334
+ <span :class="['pill', taskRunStatusTone(item.status)]">{{ item.status }}</span>
335
+ </div>
336
+ </button>
337
+ </div>
338
+ </div>
339
+
340
+ <div v-else class="task-workbench-panel">
341
+ <div class="task-detail-toolbar settings-tab-actions">
342
+ <button type="button" class="btn-tool btn-tool-compact" @click="taskOrchestration.selectedRunId ? loadTaskRunDetail(taskOrchestration.selectedRunId) : null" :disabled="!taskOrchestration.selectedRunId || taskOrchestration.selectedRunLoading">
343
+ {{ taskOrchestration.selectedRunLoading ? '刷新中...' : '刷新详情' }}
344
+ </button>
345
+ <button type="button" class="btn-tool btn-tool-compact" @click="retryTaskRunFromUi(taskOrchestration.selectedRunId)" :disabled="!taskOrchestration.selectedRunId || taskOrchestration.retrying">
346
+ {{ taskOrchestration.retrying ? '重试中...' : '重试' }}
347
+ </button>
348
+ <button type="button" class="btn-tool btn-tool-compact" @click="cancelTaskRunFromUi(taskOrchestration.selectedRunId)" :disabled="!taskOrchestrationSelectedRun || !taskOrchestrationSelectedRun.run || !isTaskRunActive(taskOrchestrationSelectedRun.run.status)">
349
+ 取消
350
+ </button>
351
+ </div>
352
+
353
+ <div v-if="taskOrchestration.selectedRunError" class="task-issue-item">{{ taskOrchestration.selectedRunError }}</div>
354
+ <div v-if="!taskOrchestrationSelectedRun" class="task-empty-state">
355
+ <div class="task-empty-title">选择一条运行记录查看详情</div>
356
+ <div class="task-empty-copy">这里会显示节点状态、摘要和日志。</div>
357
+ </div>
358
+ <template v-else>
359
+ <div class="task-detail-summary-strip">
360
+ <div class="task-plan-summary-item">
361
+ <span class="task-plan-summary-label">状态</span>
362
+ <strong>{{ taskOrchestrationSelectedRun.run.status }}</strong>
363
+ </div>
364
+ <div class="task-plan-summary-item">
365
+ <span class="task-plan-summary-label">耗时</span>
366
+ <strong>{{ taskOrchestrationSelectedRun.run.durationMs || 0 }}ms</strong>
367
+ </div>
368
+ <div class="task-plan-summary-item">
369
+ <span class="task-plan-summary-label">节点数</span>
370
+ <strong>{{ taskOrchestrationSelectedRunNodes.length }}</strong>
371
+ </div>
372
+ <div class="task-plan-summary-item">
373
+ <span class="task-plan-summary-label">摘要</span>
374
+ <strong>{{ taskOrchestrationSelectedRun.run.summary || '暂无' }}</strong>
375
+ </div>
376
+ </div>
377
+ <div v-if="taskOrchestrationSelectedRun.run.error" class="task-issue-item">{{ taskOrchestrationSelectedRun.run.error }}</div>
378
+ <div class="task-node-list">
379
+ <div v-for="node in taskOrchestrationSelectedRunNodes" :key="node.id" class="task-node-card task-node-card-detail">
380
+ <div class="task-node-head">
381
+ <div>
382
+ <div class="task-node-title">{{ node.title || node.id }}</div>
383
+ <div class="task-node-meta">{{ node.id }} · attempts {{ node.attemptCount || 0 }} · auto-fix {{ node.autoFixRounds || 0 }}</div>
384
+ </div>
385
+ <span :class="['pill', taskRunStatusTone(node.status)]">{{ node.status }}</span>
386
+ </div>
387
+ <div v-if="node.summary" class="task-runtime-item-summary">{{ node.summary }}</div>
388
+ <div v-if="node.error && node.error !== node.summary" class="task-node-deps">错误:{{ node.error }}</div>
389
+ <div class="task-node-deps">依赖:{{ formatTaskNodeDependencies(node) }}</div>
390
+ <pre class="task-log-block">{{ formatTaskNodeLogs(node.logs) }}</pre>
391
+ </div>
392
+ </div>
393
+ </template>
394
+ </div>
395
+ </section>
396
+ </div>
397
+ </div>