@yancyyu/openhermit 1.6.27 → 1.6.29

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 (101) hide show
  1. package/README.md +7 -1
  2. package/bin/hermit.mjs +2 -2
  3. package/dist-renderer/assets/ProjectEditorOverlay-CQm6jUR1.js +52 -0
  4. package/dist-renderer/assets/{TeamGraphOverlay-DVq8rt6_.js → TeamGraphOverlay-h0WDfifv.js} +1 -1
  5. package/dist-renderer/assets/{_basePickBy-ZbF0pKvS.js → _basePickBy-CgG_tjgX.js} +1 -1
  6. package/dist-renderer/assets/{_baseUniq-BBLBOeXc.js → _baseUniq-DwPTU9lP.js} +1 -1
  7. package/dist-renderer/assets/{arc-wGaEgkCf.js → arc-7nIrGRzY.js} +1 -1
  8. package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-BpMkdC35.js → architectureDiagram-VXUJARFQ-BYhA6Ev2.js} +1 -1
  9. package/dist-renderer/assets/{blockDiagram-VD42YOAC-C8Z1xhG4.js → blockDiagram-VD42YOAC-BVpZUGDg.js} +1 -1
  10. package/dist-renderer/assets/{c4Diagram-YG6GDRKO-CJmlw9LA.js → c4Diagram-YG6GDRKO-DsdreMQ9.js} +1 -1
  11. package/dist-renderer/assets/channel-C0SqeFU7.js +1 -0
  12. package/dist-renderer/assets/{chunk-4BX2VUAB-CHPHiRPP.js → chunk-4BX2VUAB-CcoAs7Jd.js} +1 -1
  13. package/dist-renderer/assets/{chunk-55IACEB6-DyVohOQb.js → chunk-55IACEB6-CGGAOoXd.js} +1 -1
  14. package/dist-renderer/assets/{chunk-B4BG7PRW-p5bffh_R.js → chunk-B4BG7PRW-FhpTEPvD.js} +1 -1
  15. package/dist-renderer/assets/{chunk-DI55MBZ5-BnfGPSUu.js → chunk-DI55MBZ5-DoYySbm1.js} +1 -1
  16. package/dist-renderer/assets/{chunk-FMBD7UC4-B6SCKseX.js → chunk-FMBD7UC4-e9l2tGHG.js} +1 -1
  17. package/dist-renderer/assets/{chunk-QN33PNHL-L12RvLBR.js → chunk-QN33PNHL-DeiXVTCy.js} +1 -1
  18. package/dist-renderer/assets/{chunk-QZHKN3VN-DeH1Kxge.js → chunk-QZHKN3VN-DC2UJLJM.js} +1 -1
  19. package/dist-renderer/assets/{chunk-TZMSLE5B-BWnjzSlI.js → chunk-TZMSLE5B-BHFD9eZI.js} +1 -1
  20. package/dist-renderer/assets/classDiagram-2ON5EDUG-DWew1HpM.js +1 -0
  21. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-DWew1HpM.js +1 -0
  22. package/dist-renderer/assets/clone-Dm-k63Yr.js +1 -0
  23. package/dist-renderer/assets/{cose-bilkent-S5V4N54A-BtzoT5fu.js → cose-bilkent-S5V4N54A-BdybQraU.js} +1 -1
  24. package/dist-renderer/assets/{dagre-6UL2VRFP-CBBvuoUD.js → dagre-6UL2VRFP-DdF3pwM3.js} +1 -1
  25. package/dist-renderer/assets/{diagram-PSM6KHXK-Be9BAKws.js → diagram-PSM6KHXK-B9Ldd3nh.js} +1 -1
  26. package/dist-renderer/assets/{diagram-QEK2KX5R-BDS4PI_i.js → diagram-QEK2KX5R-XEqkrbpu.js} +1 -1
  27. package/dist-renderer/assets/{diagram-S2PKOQOG-2Rameaq7.js → diagram-S2PKOQOG-CipwtY59.js} +1 -1
  28. package/dist-renderer/assets/{erDiagram-Q2GNP2WA-CSIzCEZD.js → erDiagram-Q2GNP2WA-BB-2ISGo.js} +1 -1
  29. package/dist-renderer/assets/{flowDiagram-NV44I4VS-ForEIVM5.js → flowDiagram-NV44I4VS-B8XmJ0u2.js} +1 -1
  30. package/dist-renderer/assets/{ganttDiagram-JELNMOA3-BJrli_xr.js → ganttDiagram-JELNMOA3-D-8XglBb.js} +1 -1
  31. package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-C_4GuLno.js → gitGraphDiagram-V2S2FVAM-DL4ChakD.js} +1 -1
  32. package/dist-renderer/assets/{graph-B1EAT_gw.js → graph-BiFNoBjP.js} +1 -1
  33. package/dist-renderer/assets/{index-eKRmS5kI.js → index-6m1ZAymG.js} +1 -1
  34. package/dist-renderer/assets/index-BhellmRb.css +1 -0
  35. package/dist-renderer/assets/{index-DYdseEwc.js → index-BowUl0Jb.js} +518 -514
  36. package/dist-renderer/assets/{index-DR602dwJ.js → index-Dp3kJTEe.js} +1 -1
  37. package/dist-renderer/assets/{index-Dwr5wu5x.js → index-TOpt_T7A.js} +1 -1
  38. package/dist-renderer/assets/{index-DOA_jbYb.js → index-qNBNjW4K.js} +1 -1
  39. package/dist-renderer/assets/{index-k4tnOFC5.js → index-vAykq1H1.js} +1 -1
  40. package/dist-renderer/assets/{infoDiagram-HS3SLOUP-DjI0uaMz.js → infoDiagram-HS3SLOUP-DRIBfHDi.js} +1 -1
  41. package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-jQ6Thae-.js → journeyDiagram-XKPGCS4Q-BOMiigU4.js} +1 -1
  42. package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-CKw6InbL.js → kanban-definition-3W4ZIXB7-DDxeyjod.js} +1 -1
  43. package/dist-renderer/assets/{layout-Dad20y3V.js → layout-DNANbrI4.js} +1 -1
  44. package/dist-renderer/assets/{linear-vMgo_2Cv.js → linear-DxEJi1yT.js} +1 -1
  45. package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-DYp6YoHL.js → mindmap-definition-VGOIOE7T-nBfGriW8.js} +1 -1
  46. package/dist-renderer/assets/{pieDiagram-ADFJNKIX-BytBecG9.js → pieDiagram-ADFJNKIX-Din5j6sV.js} +1 -1
  47. package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-RUaspLsc.js → quadrantDiagram-AYHSOK5B-DMVK2BEQ.js} +1 -1
  48. package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-rR2B1Use.js → requirementDiagram-UZGBJVZJ-6SC94Gg_.js} +1 -1
  49. package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-BJi5qYhq.js → sankeyDiagram-TZEHDZUN-CD2gghhu.js} +1 -1
  50. package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-BM-wggUb.js → sequenceDiagram-WL72ISMW-BnhkN7nZ.js} +1 -1
  51. package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-BqmcVjnj.js → stateDiagram-FKZM4ZOC-Bn8XdYX-.js} +1 -1
  52. package/dist-renderer/assets/{stateDiagram-v2-4FDKWEC3-By3JDVbB.js → stateDiagram-v2-4FDKWEC3-1b6sI1_g.js} +1 -1
  53. package/dist-renderer/assets/{timeline-definition-IT6M3QCI-szH0GUyk.js → timeline-definition-IT6M3QCI-CNs3RPoa.js} +1 -1
  54. package/dist-renderer/assets/treemap-GDKQZRPO-DU_yr827.js +162 -0
  55. package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-dwDpvw0w.js → xychartDiagram-PRI3JC2R-B8o5J2f3.js} +1 -1
  56. package/dist-renderer/index.html +2 -2
  57. package/package.json +1 -1
  58. package/src/main/server.ts +800 -163
  59. package/src/main/services/session-intelligence/SessionUsageParser.ts +446 -0
  60. package/src/main/services/session-intelligence/UsageTelemetryService.ts +252 -0
  61. package/src/main/services/teams-mvp/CollaborationBoardService.ts +310 -0
  62. package/src/main/services/teams-mvp/TaskDispatchService.ts +880 -95
  63. package/src/main/services/teams-mvp/TeamProvisioningService.ts +58 -19
  64. package/src/main/services/teams-mvp/TeamWorkspaceService.ts +25 -2
  65. package/src/main/services/teams-mvp/index.ts +3 -0
  66. package/src/renderer/App.tsx +5 -0
  67. package/src/renderer/api/httpClient.ts +67 -0
  68. package/src/renderer/components/dashboard/DashboardView.tsx +6 -105
  69. package/src/renderer/components/layout/PaneContent.tsx +2 -0
  70. package/src/renderer/components/layout/SortableTab.tsx +1 -0
  71. package/src/renderer/components/layout/TabBarActions.tsx +12 -12
  72. package/src/renderer/components/schedules/SchedulesView.tsx +54 -22
  73. package/src/renderer/components/settings/SettingsTabs.tsx +2 -2
  74. package/src/renderer/components/settings/sections/AdvancedSection.tsx +1 -1
  75. package/src/renderer/components/settings/sections/TaskBusSection.tsx +511 -81
  76. package/src/renderer/components/tasks/TasksView.tsx +343 -0
  77. package/src/renderer/components/team/TeamDetailView.tsx +20 -98
  78. package/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx +1 -1
  79. package/src/renderer/components/team/editor/EditorContextMenu.tsx +8 -23
  80. package/src/renderer/components/team/editor/EditorFileTree.tsx +0 -4
  81. package/src/renderer/components/team/editor/EditorSelectionMenu.tsx +1 -8
  82. package/src/renderer/components/team/editor/ProjectEditorOverlay.tsx +0 -10
  83. package/src/renderer/components/team/kanban/KanbanBoard.tsx +5 -1
  84. package/src/renderer/components/team/members/MemberDetailDialog.tsx +8 -33
  85. package/src/renderer/components/team/messages/MessageComposer.tsx +39 -3
  86. package/src/renderer/components/team/messages/MessagesPanel.tsx +72 -2
  87. package/src/renderer/components/team/messages/StatusBlock.tsx +2 -24
  88. package/src/renderer/components/team/schedule/ScheduleEmptyState.tsx +1 -1
  89. package/src/renderer/components/ui/MentionableTextarea.tsx +0 -1
  90. package/src/renderer/store/slices/scheduleSlice.ts +21 -0
  91. package/src/renderer/store/slices/teamSlice.ts +59 -23
  92. package/src/renderer/types/tabs.ts +1 -0
  93. package/src/shared/types/api.ts +29 -0
  94. package/src/shared/types/team.ts +109 -1
  95. package/dist-renderer/assets/ProjectEditorOverlay-BBwYdXPv.js +0 -57
  96. package/dist-renderer/assets/channel-DJUrwVrK.js +0 -1
  97. package/dist-renderer/assets/classDiagram-2ON5EDUG-blc3DrH7.js +0 -1
  98. package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-blc3DrH7.js +0 -1
  99. package/dist-renderer/assets/clone-BftjWakJ.js +0 -1
  100. package/dist-renderer/assets/index-CWpFqEvz.css +0 -1
  101. package/dist-renderer/assets/treemap-GDKQZRPO-BCMlh-Ex.js +0 -162
@@ -24,6 +24,26 @@ import {
24
24
  } from './TeamWorkspaceService';
25
25
 
26
26
  const logger = createLogger('TeamProvisioningService');
27
+ const TEAM_INSTRUCTIONS_BEGIN = '<!-- hermit:team-collaboration:start -->';
28
+ const TEAM_INSTRUCTIONS_END = '<!-- hermit:team-collaboration:end -->';
29
+
30
+ function removeSectionByHeading(content: string, heading: string): string {
31
+ const escapedHeading = heading.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
32
+ return content.replace(
33
+ new RegExp(`\\n{0,2}## ${escapedHeading}\\n[\\s\\S]*?(?=\\n## |\\s*$)`, 'g'),
34
+ ''
35
+ );
36
+ }
37
+
38
+ function removeManagedTeamInstructions(content: string): string {
39
+ let next = content.replace(
40
+ new RegExp(`\\n{0,2}${TEAM_INSTRUCTIONS_BEGIN}[\\s\\S]*?${TEAM_INSTRUCTIONS_END}\\n?`, 'g'),
41
+ '\n'
42
+ );
43
+ next = removeSectionByHeading(next, 'Agent Collaboration (Hermit)');
44
+ next = removeSectionByHeading(next, 'Cross-Team Task Dispatch (Hermit)');
45
+ return next.replace(/\n{3,}/g, '\n\n').trimEnd();
46
+ }
27
47
 
28
48
  export class TeamProvisioningService {
29
49
  private readonly workspace: TeamWorkspaceService;
@@ -77,8 +97,6 @@ export class TeamProvisioningService {
77
97
  }
78
98
  }
79
99
 
80
- await this.injectTeamInstructions(manifest.workDir, slug);
81
-
82
100
  return { slug, manifest };
83
101
  }
84
102
 
@@ -244,25 +262,33 @@ export class TeamProvisioningService {
244
262
 
245
263
  async injectTeamInstructions(workDir: string, teamSlug: string): Promise<void> {
246
264
  const mdPath = path.join(workDir, 'CLAUDE.md');
265
+ const teams = await this.workspace.listTeams().catch(() => []);
266
+ const availableTeams = teams
267
+ .filter((team) => team.slug !== teamSlug)
268
+ .map((team) => {
269
+ const label =
270
+ team.displayName && team.displayName !== team.slug
271
+ ? `${team.slug} (${team.displayName})`
272
+ : team.slug;
273
+ return team.description ? `- ${label}: ${team.description}` : `- ${label}`;
274
+ });
247
275
  const section = `
248
276
 
249
- ## Cross-Team Task Dispatch (Hermit)
277
+ ${TEAM_INSTRUCTIONS_BEGIN}
250
278
 
251
- You can dispatch tasks to other teams via the Hermit local API:
252
-
253
- - **List available teams**: \`curl -s http://127.0.0.1:5680/api/cross-team/targets\`
254
- - **Dispatch a task**: \`curl -s -X POST http://127.0.0.1:5680/api/cross-team/send -H 'Content-Type: application/json' -d '{"fromTeam":"${teamSlug}","toTeam":"TARGET_TEAM","subject":"Task title","description":"Optional description"}'\`
279
+ ## Hermit Team Context
255
280
 
256
281
  Current team slug: \`${teamSlug}\`
257
282
 
258
- When to dispatch:
259
- - Task requires access to a different codebase/project
260
- - Task explicitly mentions another team's domain
261
- - Task is blocked by work owned by another team
283
+ Available teams:
284
+ ${availableTeams.length > 0 ? availableTeams.join('\n') : '- No other teams currently registered.'}
285
+
286
+ Cross-team work is routed by Hermit itself. If the user mentions another team with \`@team\`,
287
+ Hermit will create and track the cross-team collaboration task automatically.
262
288
 
263
- Do NOT dispatch:
264
- - Task is within current team's project scope
265
- - Task can be completed with available tools
289
+ Do not call cross-team dispatch APIs yourself and do not invent dispatch IDs.
290
+ You may use the team list only to understand which teams exist and when a user is referring to one.
291
+ ${TEAM_INSTRUCTIONS_END}
266
292
  `;
267
293
 
268
294
  try {
@@ -273,11 +299,8 @@ Do NOT dispatch:
273
299
  // File doesn't exist yet
274
300
  }
275
301
 
276
- if (existing.includes('Cross-Team Task Dispatch (Hermit)')) {
277
- return;
278
- }
279
-
280
- await fs.promises.writeFile(mdPath, existing + section, 'utf8');
302
+ const cleaned = removeManagedTeamInstructions(existing);
303
+ await fs.promises.writeFile(mdPath, `${cleaned}${section}`, 'utf8');
281
304
  logger.info(`injected team instructions → ${mdPath}`);
282
305
  } catch (err) {
283
306
  logger.warn(
@@ -285,4 +308,20 @@ Do NOT dispatch:
285
308
  );
286
309
  }
287
310
  }
311
+
312
+ async removeTeamInstructions(workDir: string): Promise<void> {
313
+ const mdPath = path.join(workDir, 'CLAUDE.md');
314
+ try {
315
+ const existing = await fs.promises.readFile(mdPath, 'utf8');
316
+ const cleaned = removeManagedTeamInstructions(existing);
317
+ if (cleaned === existing.trimEnd()) return;
318
+ await fs.promises.writeFile(mdPath, cleaned ? `${cleaned}\n` : '', 'utf8');
319
+ logger.info(`removed team instructions → ${mdPath}`);
320
+ } catch (err) {
321
+ if ((err as NodeJS.ErrnoException).code === 'ENOENT') return;
322
+ logger.warn(
323
+ `Team instructions removal failed: ${err instanceof Error ? err.message : String(err)}`
324
+ );
325
+ }
326
+ }
288
327
  }
@@ -224,7 +224,23 @@ export class TeamWorkspaceService {
224
224
  async readTeamManifest(teamSlug: string): Promise<TeamManifest> {
225
225
  const root = teamRoot(teamSlug);
226
226
  const manifest = await readJson<TeamManifest | null>(path.join(root, 'team.json'), null);
227
- if (!manifest) throw new Error(`团队 "${teamSlug}" 不存在 (${root})`);
227
+ if (!manifest) {
228
+ if (!(await pathExists(root))) {
229
+ throw new Error(`团队 "${teamSlug}" 不存在 (${root})`);
230
+ }
231
+ const stat = await fs.promises.stat(root).catch(() => null);
232
+ return {
233
+ schemaVersion: 2,
234
+ slug: teamSlug,
235
+ displayName: teamSlug,
236
+ bindProject: teamSlug,
237
+ harness: 'claudecode',
238
+ workDir: '',
239
+ collaboration: true,
240
+ rootPath: root,
241
+ createdAt: (stat?.birthtime ?? stat?.mtime ?? new Date()).toISOString(),
242
+ };
243
+ }
228
244
  return manifest;
229
245
  }
230
246
 
@@ -359,7 +375,13 @@ export class TeamWorkspaceService {
359
375
 
360
376
  async createTask(
361
377
  teamSlug: string,
362
- payload: { title: string; description?: string; assignee?: string | null; status?: TaskStatus }
378
+ payload: {
379
+ title: string;
380
+ description?: string;
381
+ assignee?: string | null;
382
+ status?: TaskStatus;
383
+ dispatchMeta?: import('@shared/types/team').DispatchMeta;
384
+ }
363
385
  ): Promise<Task> {
364
386
  if (!payload?.title) throw new Error('title is required');
365
387
  const board = await this.readBoard(teamSlug);
@@ -378,6 +400,7 @@ export class TeamWorkspaceService {
378
400
  createdAt: now,
379
401
  updatedAt: now,
380
402
  order,
403
+ dispatchMeta: payload.dispatchMeta,
381
404
  };
382
405
  board.tasks = [...(board.tasks || []), task];
383
406
  await this.writeBoard(teamSlug, board);
@@ -4,6 +4,7 @@
4
4
  * 设计:一个 Team = 一个 cc-connect project,无 Member 子层级。
5
5
  * - TeamWorkspaceService: 本地目录管理(team.json / messages / tasks/board.json)
6
6
  * - TeamProvisioningService: 组合 cc-connect,含 Task Dispatcher
7
+ * - CollaborationBoardService: 全局协作看板数据管理
7
8
  */
8
9
 
9
10
  export {
@@ -24,3 +25,5 @@ export type {
24
25
  } from './TeamWorkspaceService';
25
26
 
26
27
  export { TeamProvisioningService } from './TeamProvisioningService';
28
+
29
+ export { CollaborationBoardService } from './CollaborationBoardService';
@@ -52,6 +52,8 @@ function buildPathForTab(activeTab: Tab | null): string {
52
52
  return '/extensions';
53
53
  case 'schedules':
54
54
  return '/schedules';
55
+ case 'tasks':
56
+ return '/tasks';
55
57
  case 'dashboard':
56
58
  return '/dashboard';
57
59
  case 'session': {
@@ -149,6 +151,9 @@ function useTabPathPersistence() {
149
151
  case 'schedules':
150
152
  state.openSchedulesTab();
151
153
  break;
154
+ case 'tasks':
155
+ state.openTasksTab();
156
+ break;
152
157
  case 'dashboard':
153
158
  state.openDashboard();
154
159
  break;
@@ -99,6 +99,7 @@ import type {
99
99
  TeamAgentRuntimeSnapshot,
100
100
  CcSession,
101
101
  CcSessionDetail,
102
+ CollabTask,
102
103
  } from '@shared/types';
103
104
 
104
105
  import type {
@@ -1632,6 +1633,72 @@ export class HttpAPIClient implements ElectronAPI {
1632
1633
  this.get<CrossTeamMessage[]>(`/api/cross-team/outbox/${encodeURIComponent(teamName)}`),
1633
1634
  };
1634
1635
 
1636
+ // Collaboration board API
1637
+ collab = {
1638
+ getBoard: () => this.get<{ tasks: CollabTask[] }>('/api/collab/board'),
1639
+ getTask: (dispatchId: string) =>
1640
+ this.get<{ task: CollabTask }>(`/api/collab/board/${encodeURIComponent(dispatchId)}`),
1641
+ getEvents: (dispatchId: string) =>
1642
+ this.get<{ events: import('@shared/types/team').CollabTaskEvent[] }>(
1643
+ `/api/collab/board/${encodeURIComponent(dispatchId)}/events`
1644
+ ),
1645
+ accept: (teamSlug: string, dispatchId: string) =>
1646
+ this.post<{ ok: boolean; taskId: string }>('/api/cross-team/accept', {
1647
+ team_slug: teamSlug,
1648
+ dispatch_id: dispatchId,
1649
+ }),
1650
+ reject: (teamSlug: string, dispatchId: string, reason?: string) =>
1651
+ this.post<{ ok: boolean }>('/api/cross-team/reject', {
1652
+ team_slug: teamSlug,
1653
+ dispatch_id: dispatchId,
1654
+ reason,
1655
+ }),
1656
+ deliver: (teamSlug: string, dispatchId: string, result: string) =>
1657
+ this.post<{ ok: boolean }>('/api/cross-team/deliver', {
1658
+ team_slug: teamSlug,
1659
+ dispatch_id: dispatchId,
1660
+ result,
1661
+ }),
1662
+ approve: (teamSlug: string, dispatchId: string) =>
1663
+ this.post<{ ok: boolean }>('/api/cross-team/approve', {
1664
+ team_slug: teamSlug,
1665
+ dispatch_id: dispatchId,
1666
+ }),
1667
+ revision: (teamSlug: string, dispatchId: string, feedback: string) =>
1668
+ this.post<{ ok: boolean }>('/api/cross-team/revision', {
1669
+ team_slug: teamSlug,
1670
+ dispatch_id: dispatchId,
1671
+ feedback,
1672
+ }),
1673
+ dispatch: (
1674
+ fromTeam: string,
1675
+ toTeam: string,
1676
+ subject: string,
1677
+ opts?: {
1678
+ description?: string;
1679
+ deadlineMinutes?: number;
1680
+ needsHumanReview?: boolean;
1681
+ messageId?: string;
1682
+ sessionKey?: string;
1683
+ }
1684
+ ) =>
1685
+ this.post<{
1686
+ ok: boolean;
1687
+ dispatchId: string;
1688
+ status: string;
1689
+ message: string;
1690
+ }>('/api/cross-team/send', {
1691
+ fromTeam,
1692
+ toTeam,
1693
+ subject,
1694
+ description: opts?.description,
1695
+ deadlineMinutes: opts?.deadlineMinutes,
1696
+ needsHumanReview: opts?.needsHumanReview,
1697
+ messageId: opts?.messageId,
1698
+ sessionKey: opts?.sessionKey,
1699
+ }),
1700
+ };
1701
+
1635
1702
  // Review API
1636
1703
  review = {
1637
1704
  getAgentChanges: async (teamName: string, memberName: string) => {
@@ -3,12 +3,11 @@
3
3
  * Keeps only screen composition and delegates recent-projects logic to the feature slice.
4
4
  */
5
5
 
6
- import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';
6
+ import React from 'react';
7
7
 
8
8
  import { RecentProjectsSection } from '@features/recent-projects/renderer';
9
9
  import { useStore } from '@renderer/store';
10
- import { formatShortcut } from '@renderer/utils/stringUtils';
11
- import { Command, PlugZap, Search, Sparkles, Users, Workflow } from 'lucide-react';
10
+ import { PlugZap, Sparkles, Users, Workflow } from 'lucide-react';
12
11
  import { useShallow } from 'zustand/react/shallow';
13
12
 
14
13
  const HIGHLIGHT_HARNESSES = [
@@ -30,92 +29,7 @@ const HIGHLIGHT_CHANNELS = [
30
29
  'Webhook / API',
31
30
  ];
32
31
 
33
- interface CommandSearchProps {
34
- value: string;
35
- onChange: (value: string) => void;
36
- }
37
-
38
- const CommandSearch = ({ value, onChange }: Readonly<CommandSearchProps>): React.JSX.Element => {
39
- const [isFocused, setIsFocused] = useState(false);
40
- const inputRef = useRef<HTMLInputElement>(null);
41
- const { openCommandPalette, selectedProjectId } = useStore(
42
- useShallow((state) => ({
43
- openCommandPalette: state.openCommandPalette,
44
- selectedProjectId: state.selectedProjectId,
45
- }))
46
- );
47
-
48
- useEffect(() => {
49
- const handleKeyDown = (event: KeyboardEvent): void => {
50
- if ((event.metaKey || event.ctrlKey) && event.code === 'KeyK') {
51
- event.preventDefault();
52
- openCommandPalette();
53
- }
54
- };
55
-
56
- window.addEventListener('keydown', handleKeyDown);
57
- return () => window.removeEventListener('keydown', handleKeyDown);
58
- }, [openCommandPalette]);
59
-
60
- useLayoutEffect(() => {
61
- const input = inputRef.current;
62
- if (!input) {
63
- return;
64
- }
65
-
66
- input.focus({ preventScroll: true });
67
- const timeoutId = window.setTimeout(() => {
68
- if (document.activeElement !== input) {
69
- input.focus({ preventScroll: true });
70
- }
71
- }, 50);
72
-
73
- return () => window.clearTimeout(timeoutId);
74
- }, []);
75
-
76
- return (
77
- <div className="relative w-full">
78
- <div
79
- className={`relative flex items-center gap-3 rounded-sm border bg-surface-raised px-4 py-3 transition-all duration-200 ${
80
- isFocused
81
- ? 'border-zinc-500 shadow-[0_0_20px_rgba(255,255,255,0.04)] ring-1 ring-zinc-600/30'
82
- : 'border-border hover:border-zinc-600'
83
- } `}
84
- >
85
- <Search className="size-4 shrink-0 text-text-muted" />
86
- <input
87
- ref={inputRef}
88
- type="text"
89
- value={value}
90
- onChange={(event) => onChange(event.target.value)}
91
- placeholder="搜索项目..."
92
- className="flex-1 bg-transparent text-sm text-text outline-none placeholder:text-text-muted"
93
- onFocus={() => setIsFocused(true)}
94
- onBlur={() => setIsFocused(false)}
95
- />
96
- <button
97
- onClick={() => openCommandPalette()}
98
- className="flex shrink-0 items-center gap-1 transition-opacity hover:opacity-80"
99
- title={
100
- selectedProjectId
101
- ? `搜索会话(${formatShortcut('K')})`
102
- : `搜索项目(${formatShortcut('K')})`
103
- }
104
- >
105
- <kbd className="flex h-5 items-center justify-center rounded border border-border bg-surface-overlay px-1.5 text-[10px] font-medium text-text-muted">
106
- <Command className="size-2.5" />
107
- </kbd>
108
- <kbd className="flex size-5 items-center justify-center rounded border border-border bg-surface-overlay text-[10px] font-medium text-text-muted">
109
- K
110
- </kbd>
111
- </button>
112
- </div>
113
- </div>
114
- );
115
- };
116
-
117
32
  export const DashboardView = (): React.JSX.Element => {
118
- const [searchQuery, setSearchQuery] = useState('');
119
33
  const { openTeamsTab, openSettingsTab, teams, teamsLoading } = useStore(
120
34
  useShallow((state) => ({
121
35
  openTeamsTab: state.openTeamsTab,
@@ -124,8 +38,7 @@ export const DashboardView = (): React.JSX.Element => {
124
38
  teamsLoading: state.teamsLoading,
125
39
  }))
126
40
  );
127
- const showQuickstartGuide =
128
- searchQuery.trim().length === 0 && !teamsLoading && teams.length === 0;
41
+ const showQuickstartGuide = !teamsLoading && teams.length === 0;
129
42
 
130
43
  return (
131
44
  <div className="relative flex-1 overflow-auto bg-surface">
@@ -201,7 +114,7 @@ export const DashboardView = (): React.JSX.Element => {
201
114
  </div>
202
115
  </section>
203
116
 
204
- <div className="mb-12 flex items-center justify-center gap-3">
117
+ <div className="mb-8 flex items-center justify-center">
205
118
  <button
206
119
  onClick={openTeamsTab}
207
120
  className="flex shrink-0 items-center gap-2 rounded-sm border border-border bg-surface-raised px-4 py-3 text-sm text-text-secondary transition-all duration-200 hover:border-zinc-500 hover:text-text"
@@ -209,10 +122,6 @@ export const DashboardView = (): React.JSX.Element => {
209
122
  <Users className="size-4" />
210
123
  选择团队
211
124
  </button>
212
- <span className="shrink-0 text-xs text-text-muted">或</span>
213
- <div className="flex-1">
214
- <CommandSearch value={searchQuery} onChange={setSearchQuery} />
215
- </div>
216
125
  </div>
217
126
 
218
127
  {showQuickstartGuide ? (
@@ -255,19 +164,11 @@ export const DashboardView = (): React.JSX.Element => {
255
164
  <>
256
165
  <div className="mb-4 flex items-center justify-between">
257
166
  <h2 className="text-xs font-medium uppercase tracking-wider text-text-muted">
258
- {searchQuery.trim() ? '搜索结果' : '最近项目'}
167
+ 最近项目
259
168
  </h2>
260
- {searchQuery.trim() && (
261
- <button
262
- onClick={() => setSearchQuery('')}
263
- className="text-xs text-text-muted transition-colors hover:text-text-secondary"
264
- >
265
- 清除搜索
266
- </button>
267
- )}
268
169
  </div>
269
170
 
270
- <RecentProjectsSection searchQuery={searchQuery} />
171
+ <RecentProjectsSection searchQuery="" />
271
172
  </>
272
173
  )}
273
174
  </div>
@@ -12,6 +12,7 @@ import { NotificationsView } from '../notifications/NotificationsView';
12
12
  import { SessionReportTab } from '../report/SessionReportTab';
13
13
  import { SchedulesView } from '../schedules/SchedulesView';
14
14
  import { SettingsView } from '../settings/SettingsView';
15
+ import { TasksView } from '../tasks/TasksView';
15
16
  import { TeamDetailView } from '../team/TeamDetailView';
16
17
  import { TeamListView } from '../team/TeamListView';
17
18
 
@@ -67,6 +68,7 @@ export const PaneContent = ({ pane, isPaneFocused }: PaneContentProps): React.JS
67
68
  </TabUIProvider>
68
69
  )}
69
70
  {tab.type === 'schedules' && <SchedulesView />}
71
+ {tab.type === 'tasks' && <TasksView />}
70
72
  {tab.type === 'graph' && (
71
73
  <TabUIProvider tabId={tab.id}>
72
74
  <TeamGraphTab
@@ -59,6 +59,7 @@ const TAB_ICONS = {
59
59
  report: Activity,
60
60
  extensions: Puzzle,
61
61
  schedules: Calendar,
62
+ tasks: Calendar,
62
63
  graph: Network,
63
64
  } as const;
64
65
 
@@ -9,7 +9,7 @@ import { useMemo, useState } from 'react';
9
9
  import { api } from '@renderer/api';
10
10
  import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip';
11
11
  import { useStore } from '@renderer/store';
12
- import { Calendar, PanelRight, Puzzle, Users } from 'lucide-react';
12
+ import { ListTodo, PanelRight, Puzzle, Users } from 'lucide-react';
13
13
  import { useShallow } from 'zustand/react/shallow';
14
14
 
15
15
  import { MoreMenu } from './MoreMenu';
@@ -18,7 +18,7 @@ export const TabBarActions = (): React.JSX.Element => {
18
18
  const {
19
19
  unreadCount,
20
20
  openExtensionsTab,
21
- openSchedulesTab,
21
+ openTasksTab,
22
22
  openTeamsTab,
23
23
  activeTabId,
24
24
  openTabs,
@@ -29,7 +29,7 @@ export const TabBarActions = (): React.JSX.Element => {
29
29
  useShallow((s) => ({
30
30
  unreadCount: s.unreadCount,
31
31
  openExtensionsTab: s.openExtensionsTab,
32
- openSchedulesTab: s.openSchedulesTab,
32
+ openTasksTab: s.openTasksTab,
33
33
  openTeamsTab: s.openTeamsTab,
34
34
  activeTabId: s.activeTabId,
35
35
  openTabs: s.openTabs,
@@ -42,7 +42,7 @@ export const TabBarActions = (): React.JSX.Element => {
42
42
  // Hover states for buttons
43
43
  const [teamsHover, setTeamsHover] = useState(false);
44
44
  const [extensionsHover, setExtensionsHover] = useState(false);
45
- const [schedulesHover, setSchedulesHover] = useState(false);
45
+ const [tasksHover, setTasksHover] = useState(false);
46
46
  const [githubHover, setGithubHover] = useState(false);
47
47
  const [expandHover, setExpandHover] = useState(false);
48
48
 
@@ -102,20 +102,20 @@ export const TabBarActions = (): React.JSX.Element => {
102
102
  <Tooltip>
103
103
  <TooltipTrigger asChild>
104
104
  <button
105
- onClick={openSchedulesTab}
106
- onMouseEnter={() => setSchedulesHover(true)}
107
- onMouseLeave={() => setSchedulesHover(false)}
105
+ onClick={openTasksTab}
106
+ onMouseEnter={() => setTasksHover(true)}
107
+ onMouseLeave={() => setTasksHover(false)}
108
108
  className="rounded-md p-2 transition-colors"
109
109
  style={{
110
- color: schedulesHover ? 'var(--color-text)' : 'var(--color-text-muted)',
111
- backgroundColor: schedulesHover ? 'var(--color-surface-raised)' : 'transparent',
110
+ color: tasksHover ? 'var(--color-text)' : 'var(--color-text-muted)',
111
+ backgroundColor: tasksHover ? 'var(--color-surface-raised)' : 'transparent',
112
112
  }}
113
- aria-label="定时任务"
113
+ aria-label="任务"
114
114
  >
115
- <Calendar className="size-4" />
115
+ <ListTodo className="size-4" />
116
116
  </button>
117
117
  </TooltipTrigger>
118
- <TooltipContent side="bottom">定时任务</TooltipContent>
118
+ <TooltipContent side="bottom">任务</TooltipContent>
119
119
  </Tooltip>
120
120
 
121
121
  {/* GitHub link */}