@yancyyu/openhermit 1.6.40 → 1.6.42

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 (86) hide show
  1. package/dist-renderer/assets/{ProjectEditorOverlay-CemDOX-3.js → ProjectEditorOverlay-DlFQ6mai.js} +1 -1
  2. package/dist-renderer/assets/{TeamGraphOverlay-hPY770Db.js → TeamGraphOverlay-D2TPMPGR.js} +1 -1
  3. package/dist-renderer/assets/{_basePickBy-BHHrJT1i.js → _basePickBy-Cmd0RHLQ.js} +1 -1
  4. package/dist-renderer/assets/{_baseUniq-CWErBtke.js → _baseUniq-BI_iy8ea.js} +1 -1
  5. package/dist-renderer/assets/{arc-C_o2_Uv8.js → arc-NzW2mjTP.js} +1 -1
  6. package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-DUW0LI3t.js → architectureDiagram-VXUJARFQ-Bzq85AYv.js} +1 -1
  7. package/dist-renderer/assets/{blockDiagram-VD42YOAC-CWbCE9hQ.js → blockDiagram-VD42YOAC-D1PvYS-b.js} +1 -1
  8. package/dist-renderer/assets/{c4Diagram-YG6GDRKO-BjLadrfV.js → c4Diagram-YG6GDRKO-D49RKzPC.js} +1 -1
  9. package/dist-renderer/assets/channel-Ch7JrfUu.js +1 -0
  10. package/dist-renderer/assets/{chunk-4BX2VUAB-CPnvjZl9.js → chunk-4BX2VUAB-fmI_MQmQ.js} +1 -1
  11. package/dist-renderer/assets/{chunk-55IACEB6-OlL47yXQ.js → chunk-55IACEB6-Xsv9RCXZ.js} +1 -1
  12. package/dist-renderer/assets/{chunk-B4BG7PRW-DTasjbm8.js → chunk-B4BG7PRW-BE1KO8Um.js} +1 -1
  13. package/dist-renderer/assets/{chunk-DI55MBZ5-C5_Xaqkk.js → chunk-DI55MBZ5-tqJ7Mv7f.js} +1 -1
  14. package/dist-renderer/assets/{chunk-FMBD7UC4-NdoM4DMR.js → chunk-FMBD7UC4-DMD45MVJ.js} +1 -1
  15. package/dist-renderer/assets/{chunk-QN33PNHL-C8Fybejy.js → chunk-QN33PNHL-DOhGrz-q.js} +1 -1
  16. package/dist-renderer/assets/{chunk-QZHKN3VN-E98TYFXJ.js → chunk-QZHKN3VN-D8yDgJdD.js} +1 -1
  17. package/dist-renderer/assets/{chunk-TZMSLE5B-h4lFgkIq.js → chunk-TZMSLE5B-BcsEDu7A.js} +1 -1
  18. package/dist-renderer/assets/classDiagram-2ON5EDUG-z9I4AnFy.js +1 -0
  19. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-z9I4AnFy.js +1 -0
  20. package/dist-renderer/assets/clone-Dfi1Jx6l.js +1 -0
  21. package/dist-renderer/assets/{cose-bilkent-S5V4N54A-DtQ7fkrs.js → cose-bilkent-S5V4N54A-DlSqGHMX.js} +1 -1
  22. package/dist-renderer/assets/{dagre-6UL2VRFP-CN-nL_z4.js → dagre-6UL2VRFP-BTT9tSAx.js} +1 -1
  23. package/dist-renderer/assets/{diagram-PSM6KHXK-DVJtqmm-.js → diagram-PSM6KHXK-Du-U-mK2.js} +1 -1
  24. package/dist-renderer/assets/{diagram-QEK2KX5R-DlxHxyXh.js → diagram-QEK2KX5R-jFdHeKas.js} +1 -1
  25. package/dist-renderer/assets/{diagram-S2PKOQOG-7dpzO6x6.js → diagram-S2PKOQOG-DKLNK2bu.js} +1 -1
  26. package/dist-renderer/assets/{erDiagram-Q2GNP2WA-GP1TqsHi.js → erDiagram-Q2GNP2WA-CZxHgIIo.js} +1 -1
  27. package/dist-renderer/assets/{flowDiagram-NV44I4VS-C7ZLETuH.js → flowDiagram-NV44I4VS-v4XStCD0.js} +1 -1
  28. package/dist-renderer/assets/{ganttDiagram-JELNMOA3-CvPB68dH.js → ganttDiagram-JELNMOA3-DJjD_BEL.js} +1 -1
  29. package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-B5yOm3w7.js → gitGraphDiagram-V2S2FVAM-BNy-jr03.js} +1 -1
  30. package/dist-renderer/assets/{graph-smeyY1YZ.js → graph-DDTrn6je.js} +1 -1
  31. package/dist-renderer/assets/{index-IhmXZWqf.js → index-BBp78BAu.js} +1 -1
  32. package/dist-renderer/assets/{index-ChG4rE-E.js → index-BQrwHZ-k.js} +84 -84
  33. package/dist-renderer/assets/{index-CQaXUAua.js → index-CRKQSG9S.js} +1 -1
  34. package/dist-renderer/assets/{index-x_JkoDRH.js → index-D8_B-cfs.js} +1 -1
  35. package/dist-renderer/assets/{index-BJx8XvG1.js → index-DR6Wz52b.js} +1 -1
  36. package/dist-renderer/assets/{index-CajRpxO2.js → index-eotrJaYy.js} +1 -1
  37. package/dist-renderer/assets/{index-DUd0uw9C.css → index-iyjkpSus.css} +1 -1
  38. package/dist-renderer/assets/{infoDiagram-HS3SLOUP-D-hWRQGY.js → infoDiagram-HS3SLOUP-DqnOsuza.js} +1 -1
  39. package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-Bb6W8rUG.js → journeyDiagram-XKPGCS4Q-DTobaO1d.js} +1 -1
  40. package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-CnHdUX0q.js → kanban-definition-3W4ZIXB7-HbwVOvWc.js} +1 -1
  41. package/dist-renderer/assets/{layout-pqss_zkI.js → layout--VYmTcw2.js} +1 -1
  42. package/dist-renderer/assets/{linear-B1mFITNh.js → linear-BsJh89Mr.js} +1 -1
  43. package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-DTD9q7-D.js → mindmap-definition-VGOIOE7T-BZqUZePd.js} +1 -1
  44. package/dist-renderer/assets/{pieDiagram-ADFJNKIX-Df3mhrn7.js → pieDiagram-ADFJNKIX-B1q_nH6P.js} +1 -1
  45. package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-B1FZ09vH.js → quadrantDiagram-AYHSOK5B-UD8QhSEu.js} +1 -1
  46. package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-aEO78thZ.js → requirementDiagram-UZGBJVZJ-BA_i7Nw8.js} +1 -1
  47. package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-6Ui--jp-.js → sankeyDiagram-TZEHDZUN-CMTnX-2d.js} +1 -1
  48. package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-DF4Q1cAM.js → sequenceDiagram-WL72ISMW-BQXDB615.js} +1 -1
  49. package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-BqA2BI8C.js → stateDiagram-FKZM4ZOC-BAsPXy6X.js} +1 -1
  50. package/dist-renderer/assets/{stateDiagram-v2-4FDKWEC3-Cs2ZtUD2.js → stateDiagram-v2-4FDKWEC3-DTUIBfce.js} +1 -1
  51. package/dist-renderer/assets/{timeline-definition-IT6M3QCI-DoOkw_A8.js → timeline-definition-IT6M3QCI-BdasmVkC.js} +1 -1
  52. package/dist-renderer/assets/{treemap-GDKQZRPO-DUe26QdD.js → treemap-GDKQZRPO-BkKQqIui.js} +1 -1
  53. package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-BKCnj5Xn.js → xychartDiagram-PRI3JC2R-EAlPHOdx.js} +1 -1
  54. package/dist-renderer/index.html +2 -2
  55. package/package.json +1 -1
  56. package/src/main/server.ts +29 -12
  57. package/src/main/services/system-manager/BuiltinWorkflowSeeder.ts +139 -0
  58. package/src/main/services/teams-mvp/TeamWorkspaceService.ts +1 -1
  59. package/src/renderer/api/httpClient.ts +17 -15
  60. package/src/renderer/components/chat/SessionContextPanel/DirectoryTree/DirectoryTreeNode.tsx +1 -1
  61. package/src/renderer/components/chat/SessionContextPanel/items/ClaudeMdItem.tsx +1 -1
  62. package/src/renderer/components/chat/SessionContextPanel/items/MentionedFileItem.tsx +1 -1
  63. package/src/renderer/components/chat/SessionContextPanel/items/TaskCoordinationItem.tsx +1 -1
  64. package/src/renderer/components/chat/SessionContextPanel/items/ThinkingTextItem.tsx +1 -1
  65. package/src/renderer/components/chat/SessionContextPanel/items/ToolOutputItem.tsx +1 -1
  66. package/src/renderer/components/chat/SessionContextPanel/items/UserMessageItem.tsx +1 -1
  67. package/src/renderer/components/dashboard/CliStatusBanner.tsx +1 -1
  68. package/src/renderer/components/runtime/ProviderRuntimeBackendSelector.tsx +2 -2
  69. package/src/renderer/components/settings/SettingsTabs.tsx +1 -1
  70. package/src/renderer/components/settings/components/SettingsSelect.tsx +2 -2
  71. package/src/renderer/components/settings/components/SettingsToggle.tsx +1 -1
  72. package/src/renderer/components/settings/sections/AdvancedSection.tsx +3 -3
  73. package/src/renderer/components/settings/sections/GeneralSection.tsx +1 -1
  74. package/src/renderer/components/sidebar/taskFiltersState.ts +1 -1
  75. package/src/renderer/components/system-manager/SystemManagerView.tsx +1 -1
  76. package/src/renderer/components/team/TeamListView.tsx +1 -0
  77. package/src/renderer/components/team/activity/ActivityItem.tsx +1 -1
  78. package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +88 -29
  79. package/src/renderer/constants/teamColors.ts +5 -5
  80. package/src/renderer/index.css +1 -1
  81. package/src/renderer/utils/codemirrorTheme.ts +3 -3
  82. package/src/shared/types/team.ts +4 -0
  83. package/dist-renderer/assets/channel-DyP9YlCF.js +0 -1
  84. package/dist-renderer/assets/classDiagram-2ON5EDUG-BqffFTae.js +0 -1
  85. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-BqffFTae.js +0 -1
  86. package/dist-renderer/assets/clone-MPcKWs2O.js +0 -1
@@ -0,0 +1,139 @@
1
+ /**
2
+ * BuiltinWorkflowSeeder — 将内置 workflow 作为 Claude Code 自定义命令
3
+ * 复制到控制台工作空间的 `.claude/commands/` 目录。
4
+ *
5
+ * 内置 workflow 以代码常量形式内嵌,在控制台打开工作空间时自动复制到
6
+ * <workspace>/.claude/commands/ 目录,成为原生 `/doctor` 等斜杠命令。
7
+ */
8
+ import { mkdir, writeFile, stat } from 'node:fs/promises';
9
+ import os from 'node:os';
10
+ import path from 'node:path';
11
+
12
+ import { createLogger } from '@shared/utils/logger';
13
+
14
+ const logger = createLogger('BuiltinWorkflowSeeder');
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Builtin workflow definitions
18
+ // ---------------------------------------------------------------------------
19
+
20
+ const BUILTIN_WORKFLOWS: Record<string, string> = {
21
+ 'doctor.md': `# Hermit Doctor — 环境诊断
22
+
23
+ 你是一个 Hermit 运维诊断助手。请按以下步骤逐一检查当前环境,报告每项状态(✅ 正常 / ⚠️ 警告 / ❌ 异常),并在最后给出总结和建议。
24
+
25
+ ## 1. 基础环境
26
+
27
+ - 检查操作系统和版本
28
+ - 检查可用内存(\`free -h\` 或 \`vm_stat\`)
29
+ - 检查磁盘空间(\`df -h .\`)
30
+ - 检查文件描述符限制(\`ulimit -n\`)
31
+
32
+ ## 2. Claude Code CLI
33
+
34
+ - 检查 \`claude\` 是否在 PATH 中(\`which claude\`)
35
+ - 检查 Claude Code 版本(\`claude --version\`)
36
+ - 检查登录状态(\`claude auth status\` 或检查 \`~/.claude\` 目录)
37
+
38
+ ## 3. cc-connect 连通性
39
+
40
+ - 检查 cc-connect 进程是否运行(\`ps aux | grep cc-connect\`)
41
+ - 检查 cc-connect API 是否可达(\`curl -s http://127.0.0.1:6300/api/v1/status\`)
42
+ - 检查 cc-connect 配置文件是否存在(\`~/.cc-connect/config.toml\`)
43
+ - 列出已配置的项目数量
44
+
45
+ ## 4. Hermit 数据目录
46
+
47
+ - 检查 \`~/.hermit/\` 目录结构是否完整
48
+ - 检查 \`~/.hermit/teams/\` 下的团队数量和状态
49
+ - 检查是否有 pendingDelete 或 restartRequired 标记的团队
50
+ - 检查团队 team.json 是否有效(schemaVersion 字段)
51
+
52
+ ## 5. 会话健康
53
+
54
+ - 统计当前运行的 Claude 会话数量(\`ps aux | grep claude | grep -v grep\`)
55
+ - 检查是否有僵尸进程
56
+ - 检查内存占用是否异常(单个会话 > 200MB 为警告)
57
+
58
+ ## 6. 网络和端口
59
+
60
+ - 检查 Hermit 服务端口是否正常监听
61
+ - 检查 cc-connect 端口是否正常监听
62
+ - 检查 DNS 解析是否正常(\`nslookup api.anthropic.com\`)
63
+
64
+ ## 输出格式
65
+
66
+ 完成后请输出:
67
+
68
+ \`\`\`
69
+ 🔍 Hermit Doctor 诊断报告
70
+ =========================
71
+ 操作系统: ...
72
+ 内存: ... (可用 / 总计)
73
+ 磁盘: ... (可用 / 总计)
74
+
75
+ ✅/⚠️/❌ Claude Code CLI: ...
76
+ ✅/⚠️/❌ cc-connect: ...
77
+ ✅/⚠️/❌ Hermit 数据: ...
78
+ ✅/⚠️/❌ 会话健康: ...
79
+ ✅/⚠️/❌ 网络: ...
80
+
81
+ 📊 总结:
82
+ - 正常项: N
83
+ - 警告项: N
84
+ - 异常项: N
85
+
86
+ 💡 建议:
87
+ 1. ...
88
+ 2. ...
89
+ \`\`\`
90
+ `,
91
+ };
92
+
93
+ // ---------------------------------------------------------------------------
94
+ // Public API
95
+ // ---------------------------------------------------------------------------
96
+
97
+ function hermitHome(): string {
98
+ return process.env.HERMIT_HOME || path.join(os.homedir(), '.hermit');
99
+ }
100
+
101
+ /**
102
+ * 将内置命令文件复制到工作空间的 .claude/commands/ 目录。
103
+ * 如果已存在同名文件则跳过(尊重用户自定义)。
104
+ *
105
+ * @param workspaceDir 工作空间根目录
106
+ * @returns 实际复制的文件数量
107
+ */
108
+ export async function seedBuiltinWorkflows(workspaceDir: string): Promise<number> {
109
+ let copied = 0;
110
+ try {
111
+ const targetDir = path.join(workspaceDir, '.claude', 'commands');
112
+ await mkdir(targetDir, { recursive: true });
113
+
114
+ for (const [filename, content] of Object.entries(BUILTIN_WORKFLOWS)) {
115
+ const targetPath = path.join(targetDir, filename);
116
+ const exists = await stat(targetPath).then(() => true).catch(() => false);
117
+ if (exists) continue;
118
+
119
+ await writeFile(targetPath, content, 'utf-8');
120
+ copied++;
121
+ logger.info(`seeded builtin workflow: ${filename} → ${targetPath}`);
122
+ }
123
+ } catch (err) {
124
+ logger.warn('failed to seed builtin workflows:', err instanceof Error ? err.message : err);
125
+ }
126
+ return copied;
127
+ }
128
+
129
+ /**
130
+ * Ensure ~/.hermit/.claude/commands/ has the builtin commands.
131
+ * Called once at app startup as fallback.
132
+ */
133
+ export async function ensureGlobalWorkflows(): Promise<void> {
134
+ const globalWorkspace = hermitHome();
135
+ const copied = await seedBuiltinWorkflows(globalWorkspace);
136
+ if (copied > 0) {
137
+ logger.info(`seeded ${copied} builtin command(s) to ${path.join(globalWorkspace, '.claude', 'commands')}`);
138
+ }
139
+ }
@@ -205,7 +205,7 @@ export class TeamWorkspaceService {
205
205
  if (!input.bindProject) throw new Error('bindProject is required');
206
206
  if (!input.workDir) throw new Error('workDir is required');
207
207
 
208
- const slug = await this.createUniqueStorageSlug(input.displayName);
208
+ const slug = await this.createUniqueStorageSlug(input.bindProject);
209
209
  const root = teamRoot(slug);
210
210
 
211
211
  await fs.promises.mkdir(root, { recursive: true });
@@ -240,8 +240,8 @@ export class HttpAPIClient implements ElectronAPI {
240
240
  return JSON.parse(text, (key, value) => HttpAPIClient.reviveDates(key, value)) as T;
241
241
  }
242
242
 
243
- private async get<T>(path: string): Promise<T> {
244
- const { controller, timeout } = this.createTimeoutController(10_000);
243
+ private async get<T>(path: string, timeoutMs = 10_000): Promise<T> {
244
+ const { controller, timeout } = this.createTimeoutController(timeoutMs);
245
245
  try {
246
246
  const res = await fetch(`${this.baseUrl}${path}`, { signal: controller.signal });
247
247
  return this.parseJson<T>(res);
@@ -250,8 +250,8 @@ export class HttpAPIClient implements ElectronAPI {
250
250
  }
251
251
  }
252
252
 
253
- private async post<T>(path: string, body?: unknown): Promise<T> {
254
- const { controller, timeout } = this.createTimeoutController(10_000);
253
+ private async post<T>(path: string, body?: unknown, timeoutMs = 10_000): Promise<T> {
254
+ const { controller, timeout } = this.createTimeoutController(timeoutMs);
255
255
  try {
256
256
  const res = await fetch(`${this.baseUrl}${path}`, {
257
257
  method: 'POST',
@@ -280,8 +280,8 @@ export class HttpAPIClient implements ElectronAPI {
280
280
  }
281
281
  }
282
282
 
283
- private async del<T>(path: string, body?: unknown): Promise<T> {
284
- const { controller, timeout } = this.createTimeoutController(10_000);
283
+ private async del<T>(path: string, body?: unknown, timeoutMs = 10_000): Promise<T> {
284
+ const { controller, timeout } = this.createTimeoutController(timeoutMs);
285
285
  try {
286
286
  const res = await fetch(`${this.baseUrl}${path}`, {
287
287
  method: 'DELETE',
@@ -295,8 +295,8 @@ export class HttpAPIClient implements ElectronAPI {
295
295
  }
296
296
  }
297
297
 
298
- private async put<T>(path: string, body?: unknown): Promise<T> {
299
- const { controller, timeout } = this.createTimeoutController(10_000);
298
+ private async put<T>(path: string, body?: unknown, timeoutMs = 10_000): Promise<T> {
299
+ const { controller, timeout } = this.createTimeoutController(timeoutMs);
300
300
  try {
301
301
  const res = await fetch(`${this.baseUrl}${path}`, {
302
302
  method: 'PUT',
@@ -310,8 +310,8 @@ export class HttpAPIClient implements ElectronAPI {
310
310
  }
311
311
  }
312
312
 
313
- private async patch<T>(path: string, body?: unknown): Promise<T> {
314
- const { controller, timeout } = this.createTimeoutController(10_000);
313
+ private async patch<T>(path: string, body?: unknown, timeoutMs = 10_000): Promise<T> {
314
+ const { controller, timeout } = this.createTimeoutController(timeoutMs);
315
315
  try {
316
316
  const res = await fetch(`${this.baseUrl}${path}`, {
317
317
  method: 'PATCH',
@@ -325,8 +325,8 @@ export class HttpAPIClient implements ElectronAPI {
325
325
  }
326
326
  }
327
327
 
328
- private async delete<T>(path: string): Promise<T> {
329
- const { controller, timeout } = this.createTimeoutController(10_000);
328
+ private async delete<T>(path: string, timeoutMs = 10_000): Promise<T> {
329
+ const { controller, timeout } = this.createTimeoutController(timeoutMs);
330
330
  try {
331
331
  const res = await fetch(`${this.baseUrl}${path}`, {
332
332
  method: 'DELETE',
@@ -1093,11 +1093,11 @@ export class HttpAPIClient implements ElectronAPI {
1093
1093
  };
1094
1094
 
1095
1095
  teams: TeamsAPI = {
1096
- list: async (): Promise<TeamSummary[]> => this.get<TeamSummary[]>('/api/teams'),
1096
+ list: async (): Promise<TeamSummary[]> => this.get<TeamSummary[]>('/api/teams', 30_000),
1097
1097
  ensureSystemManager: async (): Promise<SystemManagerSummary> =>
1098
1098
  this.post<SystemManagerSummary>('/api/system-manager/ensure'),
1099
1099
  getData: async (teamName: string): Promise<TeamViewSnapshot> =>
1100
- this.get<TeamViewSnapshot>(`/api/teams/${encodeURIComponent(teamName)}/data`),
1100
+ this.get<TeamViewSnapshot>(`/api/teams/${encodeURIComponent(teamName)}/data`, 30_000),
1101
1101
  getTaskChangePresence: async (): Promise<
1102
1102
  Record<string, 'has_changes' | 'no_changes' | 'unknown'>
1103
1103
  > => {
@@ -1477,7 +1477,9 @@ export class HttpAPIClient implements ElectronAPI {
1477
1477
  },
1478
1478
  restartMember: async (teamName: string, memberName: string): Promise<void> => {
1479
1479
  await this.post(
1480
- `/api/teams/${encodeURIComponent(teamName)}/members/${encodeURIComponent(memberName)}/restart`
1480
+ `/api/teams/${encodeURIComponent(teamName)}/members/${encodeURIComponent(memberName)}/restart`,
1481
+ undefined,
1482
+ 30_000
1481
1483
  );
1482
1484
  },
1483
1485
  skipMemberForLaunch: async (teamName: string, memberName: string): Promise<void> => {
@@ -55,7 +55,7 @@ export const DirectoryTreeNode = ({
55
55
  type="button"
56
56
  className="cursor-pointer text-xs transition-opacity hover:opacity-80"
57
57
  style={{
58
- color: '#93c5fd',
58
+ color: '#a5b4fc',
59
59
  textDecoration: 'underline',
60
60
  textDecorationStyle: 'dotted' as const,
61
61
  textUnderlineOffset: '2px',
@@ -45,7 +45,7 @@ export const ClaudeMdItem = ({
45
45
  type="button"
46
46
  className="cursor-pointer text-xs transition-opacity hover:opacity-80"
47
47
  style={{
48
- color: '#93c5fd',
48
+ color: '#a5b4fc',
49
49
  textDecoration: 'underline',
50
50
  textDecorationStyle: 'dotted' as const,
51
51
  textUnderlineOffset: '2px',
@@ -60,7 +60,7 @@ export const MentionedFileItem = ({
60
60
  tabIndex={0}
61
61
  className="cursor-pointer text-xs transition-opacity hover:opacity-80"
62
62
  style={{
63
- color: '#93c5fd',
63
+ color: '#a5b4fc',
64
64
  textDecoration: 'underline',
65
65
  textDecorationStyle: 'dotted' as const,
66
66
  textUnderlineOffset: '2px',
@@ -40,7 +40,7 @@ export const TaskCoordinationItem = ({
40
40
  tabIndex={0}
41
41
  className="cursor-pointer text-xs transition-opacity hover:opacity-80"
42
42
  style={{
43
- color: '#93c5fd',
43
+ color: '#a5b4fc',
44
44
  textDecoration: 'underline',
45
45
  textDecorationStyle: 'dotted' as const,
46
46
  textUnderlineOffset: '2px',
@@ -49,7 +49,7 @@ export const ThinkingTextItem = ({
49
49
  tabIndex={0}
50
50
  className="cursor-pointer text-xs transition-opacity hover:opacity-80"
51
51
  style={{
52
- color: '#93c5fd',
52
+ color: '#a5b4fc',
53
53
  textDecoration: 'underline',
54
54
  textDecorationStyle: 'dotted' as const,
55
55
  textUnderlineOffset: '2px',
@@ -42,7 +42,7 @@ export const ToolOutputItem = ({
42
42
  tabIndex={0}
43
43
  className="cursor-pointer text-xs transition-opacity hover:opacity-80"
44
44
  style={{
45
- color: '#93c5fd',
45
+ color: '#a5b4fc',
46
46
  textDecoration: 'underline',
47
47
  textDecorationStyle: 'dotted' as const,
48
48
  textUnderlineOffset: '2px',
@@ -33,7 +33,7 @@ export const UserMessageItem = ({
33
33
  tabIndex={0}
34
34
  className="cursor-pointer text-xs transition-opacity hover:opacity-80"
35
35
  style={{
36
- color: '#93c5fd',
36
+ color: '#a5b4fc',
37
37
  textDecoration: 'underline',
38
38
  textDecorationStyle: 'dotted' as const,
39
39
  textUnderlineOffset: '2px',
@@ -962,7 +962,7 @@ const InstalledBanner = ({
962
962
  className="flex items-center gap-1 rounded-md border px-2 py-[3px] text-[10px] font-medium transition-colors hover:bg-white/5 disabled:opacity-50"
963
963
  style={{
964
964
  borderColor: 'rgba(99, 102, 241, 0.45)',
965
- color: '#93c5fd',
965
+ color: '#a5b4fc',
966
966
  }}
967
967
  title={`更新 Claude Code 到 v${cliStatus.latestVersion ?? 'latest'}`}
968
968
  >
@@ -190,7 +190,7 @@ export const ProviderRuntimeBackendSelector = ({
190
190
  <span
191
191
  className="shrink-0 rounded-full px-1.5 py-0.5 text-[10px]"
192
192
  style={{
193
- color: '#93c5fd',
193
+ color: '#a5b4fc',
194
194
  backgroundColor: 'rgba(99, 102, 241, 0.14)',
195
195
  }}
196
196
  >
@@ -258,7 +258,7 @@ export const ProviderRuntimeBackendSelector = ({
258
258
  <span
259
259
  className="rounded-full px-1.5 py-0.5 text-[10px]"
260
260
  style={{
261
- color: '#93c5fd',
261
+ color: '#a5b4fc',
262
262
  backgroundColor: 'rgba(99, 102, 241, 0.14)',
263
263
  }}
264
264
  >
@@ -75,7 +75,7 @@ export const SettingsTabs = ({
75
75
  }`}
76
76
  style={{
77
77
  borderLeft: index > 0 ? '1px solid var(--color-border-subtle)' : undefined,
78
- color: isActive ? '#67e8f9' : undefined,
78
+ color: isActive ? '#818cf8' : undefined,
79
79
  }}
80
80
  >
81
81
  <Icon className={`size-3 ${isActive ? 'opacity-90' : 'opacity-40'}`} />
@@ -86,13 +86,13 @@ export const SettingsSelect = <T extends string | number>({
86
86
  onClick={() => handleSelect(option.value)}
87
87
  className={`flex w-full items-center justify-between gap-3 px-3 py-2 text-left text-sm transition-colors duration-100 ${
88
88
  value === option.value
89
- ? 'bg-cyan-500/10 text-cyan-300'
89
+ ? 'bg-indigo-500/10 text-indigo-300'
90
90
  : 'hover:bg-white/5'
91
91
  } `}
92
92
  style={value !== option.value ? { color: 'var(--color-text-secondary)' } : undefined}
93
93
  >
94
94
  <span className="whitespace-nowrap">{option.label}</span>
95
- {value === option.value && <Check className="size-4 shrink-0 text-cyan-400" />}
95
+ {value === option.value && <Check className="size-4 shrink-0 text-indigo-400" />}
96
96
  </button>
97
97
  ))}
98
98
  </div>
@@ -29,7 +29,7 @@ export const SettingsToggle = ({
29
29
  onClick={handleClick}
30
30
  className="relative inline-flex h-5 w-9 shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-[var(--color-text)]/20 focus:ring-offset-2 focus:ring-offset-[var(--color-surface)]"
31
31
  style={{
32
- backgroundColor: enabled ? '#0891b2' : '#3f3f46',
32
+ backgroundColor: enabled ? '#6366f1' : '#3f3f46',
33
33
  opacity: disabled ? 0.5 : 1,
34
34
  cursor: disabled ? 'not-allowed' : 'pointer',
35
35
  }}
@@ -163,10 +163,10 @@ export const AdvancedSection = ({}: AdvancedSectionProps): React.JSX.Element =>
163
163
  <div className="flex flex-wrap gap-2 py-2">
164
164
  <button
165
165
  onClick={() => setCcConfigOpen(true)}
166
- className="flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium transition-all duration-150 hover:bg-cyan-500/5"
166
+ className="flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium transition-all duration-150 hover:bg-indigo-500/5"
167
167
  style={{
168
- borderColor: 'rgba(6, 182, 212, 0.3)',
169
- color: '#67e8f9',
168
+ borderColor: 'rgba(99, 102, 241, 0.3)',
169
+ color: '#818cf8',
170
170
  }}
171
171
  >
172
172
  <FileEdit className="size-4" />
@@ -446,7 +446,7 @@ export const GeneralSection = ({
446
446
  safeConfig.general.theme === opt.value
447
447
  ? {
448
448
  backgroundColor: 'rgba(6, 182, 212, 0.1)',
449
- color: '#67e8f9',
449
+ color: '#818cf8',
450
450
  }
451
451
  : undefined
452
452
  }
@@ -12,7 +12,7 @@ export type TaskStatusFilterId =
12
12
  | 'approved';
13
13
 
14
14
  export const STATUS_OPTIONS: { id: TaskStatusFilterId; label: string; color: string }[] = [
15
- { id: 'todo', label: 'TODO', color: '#3b82f6' },
15
+ { id: 'todo', label: 'TODO', color: '#6366f1' },
16
16
  { id: 'in_progress', label: 'IN PROGRESS', color: '#eab308' },
17
17
  { id: 'needs_fix', label: 'NEEDS FIXES', color: '#f43f5e' },
18
18
  { id: 'done', label: 'DONE', color: '#22c55e' },
@@ -91,7 +91,7 @@ export const SystemManagerView = ({
91
91
  yellow: '#fde68a',
92
92
  blue: 'var(--color-accent)',
93
93
  magenta: '#d8b4fe',
94
- cyan: '#67e8f9',
94
+ cyan: '#818cf8',
95
95
  white: 'var(--color-text)',
96
96
  },
97
97
  });
@@ -840,6 +840,7 @@ export const TeamListView = (): React.JSX.Element => {
840
840
  provisioningErrorsByTeam={provisioningErrorByTeam}
841
841
  clearProvisioningError={clearProvisioningError}
842
842
  existingTeamNames={teams.map((t) => t.teamName)}
843
+ existingBindProjects={teams.map((t) => t.bindProject).filter(Boolean) as string[]}
843
844
  provisioningTeamNames={provisioningTeamNames}
844
845
  activeTeams={activeTeams}
845
846
  initialData={copyData ?? undefined}
@@ -224,7 +224,7 @@ const SessionSourceBadge = ({ message }: { message: InboxMessage }): React.JSX.E
224
224
  title={`${platform}${label}${session?.key ? ` (${session.key})` : ''}`}
225
225
  style={{
226
226
  backgroundColor: 'rgba(99, 102, 241, 0.12)',
227
- color: '#93c5fd',
227
+ color: '#a5b4fc',
228
228
  }}
229
229
  >
230
230
  {platform}
@@ -76,6 +76,32 @@ function sanitizeTeamName(name: string): string {
76
76
  return result;
77
77
  }
78
78
 
79
+ /**
80
+ * Generate a unique ASCII project identifier from a display name.
81
+ * For Chinese names: produces "team-xxxx" (4-char random suffix).
82
+ * For ASCII names: produces a slugified version.
83
+ */
84
+ function generateBindProject(displayName: string): string {
85
+ const trimmed = displayName.trim();
86
+ if (!trimmed) return '';
87
+ // Try to extract ASCII parts from the name
88
+ const asciiParts = trimmed
89
+ .toLowerCase()
90
+ .normalize('NFKD')
91
+ .replace(/[̀-ͯ]/g, '')
92
+ .replace(/[^a-z0-9]+/g, '-')
93
+ .replace(/-+/g, '-')
94
+ .replace(/^-|-$/g, '');
95
+ const base = asciiParts || 'team';
96
+ const suffix = Math.random().toString(36).slice(2, 6);
97
+ return `${base}-${suffix}`;
98
+ }
99
+
100
+ /** Validate bindProject: ASCII lowercase alphanumeric, hyphens, underscores. */
101
+ function isValidBindProject(value: string): boolean {
102
+ return /^[a-z0-9][a-z0-9_-]{1,}$/.test(value);
103
+ }
104
+
79
105
  // ---------------------------------------------------------------------------
80
106
  // Wizard step types
81
107
  // ---------------------------------------------------------------------------
@@ -92,6 +118,7 @@ interface CreateTeamDialogProps {
92
118
  provisioningErrorsByTeam: Record<string, string | null>;
93
119
  clearProvisioningError?: (teamName?: string) => void;
94
120
  existingTeamNames: string[];
121
+ existingBindProjects?: string[];
95
122
  provisioningTeamNames?: string[];
96
123
  activeTeams?: ActiveTeamRef[];
97
124
  initialData?: unknown;
@@ -107,6 +134,7 @@ export const CreateTeamDialog = ({
107
134
  provisioningErrorsByTeam,
108
135
  clearProvisioningError,
109
136
  existingTeamNames,
137
+ existingBindProjects = [],
110
138
  provisioningTeamNames = [],
111
139
  activeTeams,
112
140
  defaultProjectPath,
@@ -137,6 +165,17 @@ export const CreateTeamDialog = ({
137
165
  const [selectedHarness, setSelectedHarness] = useState<CcAgentType>('claudecode');
138
166
  const [description, setDescription] = useState('');
139
167
 
168
+ // ── bindProject (ASCII unique identifier) ────────────────────────────
169
+ const [bindProject, setBindProject] = useState('');
170
+ const [bindProjectManuallyEdited, setBindProjectManuallyEdited] = useState(false);
171
+
172
+ // Auto-generate bindProject from displayName when not manually edited
173
+ useEffect(() => {
174
+ if (bindProjectManuallyEdited) return;
175
+ const auto = generateBindProject(teamName);
176
+ setBindProject(auto);
177
+ }, [teamName, bindProjectManuallyEdited]);
178
+
140
179
  // ── Projects (for path selector) ─────────────────────────────────────
141
180
  const [projects, setProjects] = useState<Project[]>([]);
142
181
  const [projectsLoading, setProjectsLoading] = useState(false);
@@ -152,19 +191,8 @@ export const CreateTeamDialog = ({
152
191
  const [fieldErrors, setFieldErrors] = useState<{ teamName?: string; cwd?: string }>({});
153
192
 
154
193
  // ── Name conflict detection ──────────────────────────────────────────
155
- const allTakenTeamNames = useMemo(
156
- () => [...new Set([...existingTeamNames, ...provisioningTeamNames])],
157
- [existingTeamNames, provisioningTeamNames]
158
- );
159
- const sanitizedTeamName = sanitizeTeamName(teamName);
160
- const trimmedInputName = teamName.trim().toLowerCase();
161
- // Check both slug (for ASCII names) and display name (for Chinese/Unicode names)
162
- // This prevents creating "产品经理团队" multiple times with different slugs (team-3, team-4...)
163
- const isNameTaken =
164
- existingTeamNames.includes(sanitizedTeamName) ||
165
- activeTeams?.some((t) => t.displayName.toLowerCase() === trimmedInputName) === true;
166
- const isNameProvisioning =
167
- (provisioningTeamNames.includes(sanitizedTeamName) && !isNameTaken);
194
+ const isBindProjectTaken = existingBindProjects.includes(bindProject);
195
+ const isNameProvisioning = provisioningTeamNames.includes(bindProject) && !isBindProjectTaken;
168
196
 
169
197
  const effectiveCwd =
170
198
  cwdMode === 'project'
@@ -267,8 +295,8 @@ export const CreateTeamDialog = ({
267
295
 
268
296
  // ── Clear provisioning error on open ─────────────────────────────────
269
297
  useEffect(() => {
270
- if (open && sanitizedTeamName) clearProvisioningError?.(sanitizedTeamName);
271
- }, [open, clearProvisioningError, sanitizedTeamName]);
298
+ if (open && bindProject) clearProvisioningError?.(bindProject);
299
+ }, [open, clearProvisioningError, bindProject]);
272
300
 
273
301
  // ── Reset state on close ─────────────────────────────────────────────
274
302
  const resetState = () => {
@@ -277,11 +305,14 @@ export const CreateTeamDialog = ({
277
305
  setIsSubmitting(false);
278
306
  setConflictDismissed(false);
279
307
  setSelectedProviderRef(null);
308
+ setBindProject('');
309
+ setBindProjectManuallyEdited(false);
280
310
  setStep('name');
281
311
  };
282
312
 
283
313
  const buildCreateRequest = (): TeamCreateRequest => ({
284
- teamName: sanitizedTeamName,
314
+ teamName: bindProject,
315
+ bindProject,
285
316
  displayName: teamName.trim() || undefined,
286
317
  description: description.trim() || undefined,
287
318
  color: teamColor || undefined,
@@ -295,12 +326,20 @@ export const CreateTeamDialog = ({
295
326
  });
296
327
 
297
328
  const validateCreateFields = (): boolean => {
298
- if (allTakenTeamNames.includes(sanitizedTeamName)) {
299
- setLocalError(isNameProvisioning ? '数字员工正在启动中' : '数字员工名称已存在');
329
+ if (!teamName.trim()) {
330
+ setLocalError('请输入数字员工名称');
300
331
  return false;
301
332
  }
302
- if (!sanitizedTeamName) {
303
- setLocalError('请输入数字员工名称');
333
+ if (!bindProject) {
334
+ setLocalError('请输入项目标识');
335
+ return false;
336
+ }
337
+ if (!isValidBindProject(bindProject)) {
338
+ setLocalError('项目标识只能包含小写英文字母、数字、连字符和下划线(至少2个字符,以字母或数字开头)');
339
+ return false;
340
+ }
341
+ if (isBindProjectTaken) {
342
+ setLocalError(isNameProvisioning ? '数字员工正在启动中' : `项目标识"${bindProject}"已存在,请换一个`);
304
343
  return false;
305
344
  }
306
345
  if (!effectiveCwd) {
@@ -399,11 +438,6 @@ export const CreateTeamDialog = ({
399
438
  placeholder="例如:产品助手 / 前端工程师"
400
439
  autoFocus
401
440
  />
402
- {isNameTaken && (
403
- <p className="text-[11px]" style={{ color: 'var(--field-error-text)' }}>
404
- 数字员工名称已存在
405
- </p>
406
- )}
407
441
  {isNameProvisioning && (
408
442
  <p className="text-[11px]" style={{ color: 'var(--warning-text)' }}>
409
443
  同名数字员工正在启动中
@@ -411,6 +445,31 @@ export const CreateTeamDialog = ({
411
445
  )}
412
446
  </div>
413
447
 
448
+ <div className="space-y-1.5">
449
+ <Label htmlFor="team-bind-project">项目标识</Label>
450
+ <Input
451
+ id="team-bind-project"
452
+ className={cn(
453
+ 'h-8 text-xs font-mono',
454
+ isBindProjectTaken && 'border-[var(--field-error-border)]'
455
+ )}
456
+ value={bindProject}
457
+ onChange={(e) => {
458
+ setBindProject(e.target.value.toLowerCase().replace(/[^a-z0-9_-]/g, ''));
459
+ setBindProjectManuallyEdited(true);
460
+ }}
461
+ placeholder="auto-generated-id"
462
+ />
463
+ {isBindProjectTaken && (
464
+ <p className="text-[11px]" style={{ color: 'var(--field-error-text)' }}>
465
+ 该项目标识已存在
466
+ </p>
467
+ )}
468
+ <p className="text-[11px] text-[var(--color-text-muted)]">
469
+ 用于 URL 路由和 cc-connect 项目绑定,仅限小写英文/数字/连字符
470
+ </p>
471
+ </div>
472
+
414
473
  <div className="space-y-1.5">
415
474
  <Label htmlFor="team-harness">Agent 类型</Label>
416
475
  <HarnessSelect
@@ -541,7 +600,7 @@ export const CreateTeamDialog = ({
541
600
  {localError}
542
601
  </p>
543
602
  )}
544
- {provisioningErrorsByTeam[sanitizedTeamName] && (
603
+ {provisioningErrorsByTeam[bindProject] && (
545
604
  <p
546
605
  className="mt-1 rounded border p-2 text-xs"
547
606
  style={{
@@ -550,7 +609,7 @@ export const CreateTeamDialog = ({
550
609
  backgroundColor: 'var(--field-error-bg)',
551
610
  }}
552
611
  >
553
- {provisioningErrorsByTeam[sanitizedTeamName]}
612
+ {provisioningErrorsByTeam[bindProject]}
554
613
  </p>
555
614
  )}
556
615
  </div>
@@ -572,7 +631,7 @@ export const CreateTeamDialog = ({
572
631
  <Button
573
632
  size="sm"
574
633
  onClick={() => {
575
- onOpenTeam(sanitizedTeamName, effectiveCwd || undefined, {
634
+ onOpenTeam(bindProject, effectiveCwd || undefined, {
576
635
  displayName: teamName.trim() || undefined,
577
636
  });
578
637
  clearDraft();
@@ -588,7 +647,7 @@ export const CreateTeamDialog = ({
588
647
  <Button variant="outline" size="sm" onClick={onClose}>
589
648
  取消
590
649
  </Button>
591
- <Button size="sm" disabled={!sanitizedTeamName || !effectiveCwd || isSubmitting} onClick={handleCreate}>
650
+ <Button size="sm" disabled={!teamName.trim() || !bindProject || !effectiveCwd || isSubmitting} onClick={handleCreate}>
592
651
  {isSubmitting ? '创建中...' : '创建数字员工'}
593
652
  </Button>
594
653
  </>