@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.
- package/README.md +7 -1
- package/bin/hermit.mjs +2 -2
- package/dist-renderer/assets/ProjectEditorOverlay-CQm6jUR1.js +52 -0
- package/dist-renderer/assets/{TeamGraphOverlay-DVq8rt6_.js → TeamGraphOverlay-h0WDfifv.js} +1 -1
- package/dist-renderer/assets/{_basePickBy-ZbF0pKvS.js → _basePickBy-CgG_tjgX.js} +1 -1
- package/dist-renderer/assets/{_baseUniq-BBLBOeXc.js → _baseUniq-DwPTU9lP.js} +1 -1
- package/dist-renderer/assets/{arc-wGaEgkCf.js → arc-7nIrGRzY.js} +1 -1
- package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-BpMkdC35.js → architectureDiagram-VXUJARFQ-BYhA6Ev2.js} +1 -1
- package/dist-renderer/assets/{blockDiagram-VD42YOAC-C8Z1xhG4.js → blockDiagram-VD42YOAC-BVpZUGDg.js} +1 -1
- package/dist-renderer/assets/{c4Diagram-YG6GDRKO-CJmlw9LA.js → c4Diagram-YG6GDRKO-DsdreMQ9.js} +1 -1
- package/dist-renderer/assets/channel-C0SqeFU7.js +1 -0
- package/dist-renderer/assets/{chunk-4BX2VUAB-CHPHiRPP.js → chunk-4BX2VUAB-CcoAs7Jd.js} +1 -1
- package/dist-renderer/assets/{chunk-55IACEB6-DyVohOQb.js → chunk-55IACEB6-CGGAOoXd.js} +1 -1
- package/dist-renderer/assets/{chunk-B4BG7PRW-p5bffh_R.js → chunk-B4BG7PRW-FhpTEPvD.js} +1 -1
- package/dist-renderer/assets/{chunk-DI55MBZ5-BnfGPSUu.js → chunk-DI55MBZ5-DoYySbm1.js} +1 -1
- package/dist-renderer/assets/{chunk-FMBD7UC4-B6SCKseX.js → chunk-FMBD7UC4-e9l2tGHG.js} +1 -1
- package/dist-renderer/assets/{chunk-QN33PNHL-L12RvLBR.js → chunk-QN33PNHL-DeiXVTCy.js} +1 -1
- package/dist-renderer/assets/{chunk-QZHKN3VN-DeH1Kxge.js → chunk-QZHKN3VN-DC2UJLJM.js} +1 -1
- package/dist-renderer/assets/{chunk-TZMSLE5B-BWnjzSlI.js → chunk-TZMSLE5B-BHFD9eZI.js} +1 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-DWew1HpM.js +1 -0
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-DWew1HpM.js +1 -0
- package/dist-renderer/assets/clone-Dm-k63Yr.js +1 -0
- package/dist-renderer/assets/{cose-bilkent-S5V4N54A-BtzoT5fu.js → cose-bilkent-S5V4N54A-BdybQraU.js} +1 -1
- package/dist-renderer/assets/{dagre-6UL2VRFP-CBBvuoUD.js → dagre-6UL2VRFP-DdF3pwM3.js} +1 -1
- package/dist-renderer/assets/{diagram-PSM6KHXK-Be9BAKws.js → diagram-PSM6KHXK-B9Ldd3nh.js} +1 -1
- package/dist-renderer/assets/{diagram-QEK2KX5R-BDS4PI_i.js → diagram-QEK2KX5R-XEqkrbpu.js} +1 -1
- package/dist-renderer/assets/{diagram-S2PKOQOG-2Rameaq7.js → diagram-S2PKOQOG-CipwtY59.js} +1 -1
- package/dist-renderer/assets/{erDiagram-Q2GNP2WA-CSIzCEZD.js → erDiagram-Q2GNP2WA-BB-2ISGo.js} +1 -1
- package/dist-renderer/assets/{flowDiagram-NV44I4VS-ForEIVM5.js → flowDiagram-NV44I4VS-B8XmJ0u2.js} +1 -1
- package/dist-renderer/assets/{ganttDiagram-JELNMOA3-BJrli_xr.js → ganttDiagram-JELNMOA3-D-8XglBb.js} +1 -1
- package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-C_4GuLno.js → gitGraphDiagram-V2S2FVAM-DL4ChakD.js} +1 -1
- package/dist-renderer/assets/{graph-B1EAT_gw.js → graph-BiFNoBjP.js} +1 -1
- package/dist-renderer/assets/{index-eKRmS5kI.js → index-6m1ZAymG.js} +1 -1
- package/dist-renderer/assets/index-BhellmRb.css +1 -0
- package/dist-renderer/assets/{index-DYdseEwc.js → index-BowUl0Jb.js} +518 -514
- package/dist-renderer/assets/{index-DR602dwJ.js → index-Dp3kJTEe.js} +1 -1
- package/dist-renderer/assets/{index-Dwr5wu5x.js → index-TOpt_T7A.js} +1 -1
- package/dist-renderer/assets/{index-DOA_jbYb.js → index-qNBNjW4K.js} +1 -1
- package/dist-renderer/assets/{index-k4tnOFC5.js → index-vAykq1H1.js} +1 -1
- package/dist-renderer/assets/{infoDiagram-HS3SLOUP-DjI0uaMz.js → infoDiagram-HS3SLOUP-DRIBfHDi.js} +1 -1
- package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-jQ6Thae-.js → journeyDiagram-XKPGCS4Q-BOMiigU4.js} +1 -1
- package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-CKw6InbL.js → kanban-definition-3W4ZIXB7-DDxeyjod.js} +1 -1
- package/dist-renderer/assets/{layout-Dad20y3V.js → layout-DNANbrI4.js} +1 -1
- package/dist-renderer/assets/{linear-vMgo_2Cv.js → linear-DxEJi1yT.js} +1 -1
- package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-DYp6YoHL.js → mindmap-definition-VGOIOE7T-nBfGriW8.js} +1 -1
- package/dist-renderer/assets/{pieDiagram-ADFJNKIX-BytBecG9.js → pieDiagram-ADFJNKIX-Din5j6sV.js} +1 -1
- package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-RUaspLsc.js → quadrantDiagram-AYHSOK5B-DMVK2BEQ.js} +1 -1
- package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-rR2B1Use.js → requirementDiagram-UZGBJVZJ-6SC94Gg_.js} +1 -1
- package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-BJi5qYhq.js → sankeyDiagram-TZEHDZUN-CD2gghhu.js} +1 -1
- package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-BM-wggUb.js → sequenceDiagram-WL72ISMW-BnhkN7nZ.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-BqmcVjnj.js → stateDiagram-FKZM4ZOC-Bn8XdYX-.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-v2-4FDKWEC3-By3JDVbB.js → stateDiagram-v2-4FDKWEC3-1b6sI1_g.js} +1 -1
- package/dist-renderer/assets/{timeline-definition-IT6M3QCI-szH0GUyk.js → timeline-definition-IT6M3QCI-CNs3RPoa.js} +1 -1
- package/dist-renderer/assets/treemap-GDKQZRPO-DU_yr827.js +162 -0
- package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-dwDpvw0w.js → xychartDiagram-PRI3JC2R-B8o5J2f3.js} +1 -1
- package/dist-renderer/index.html +2 -2
- package/package.json +1 -1
- package/src/main/server.ts +800 -163
- package/src/main/services/session-intelligence/SessionUsageParser.ts +446 -0
- package/src/main/services/session-intelligence/UsageTelemetryService.ts +252 -0
- package/src/main/services/teams-mvp/CollaborationBoardService.ts +310 -0
- package/src/main/services/teams-mvp/TaskDispatchService.ts +880 -95
- package/src/main/services/teams-mvp/TeamProvisioningService.ts +58 -19
- package/src/main/services/teams-mvp/TeamWorkspaceService.ts +25 -2
- package/src/main/services/teams-mvp/index.ts +3 -0
- package/src/renderer/App.tsx +5 -0
- package/src/renderer/api/httpClient.ts +67 -0
- package/src/renderer/components/dashboard/DashboardView.tsx +6 -105
- package/src/renderer/components/layout/PaneContent.tsx +2 -0
- package/src/renderer/components/layout/SortableTab.tsx +1 -0
- package/src/renderer/components/layout/TabBarActions.tsx +12 -12
- package/src/renderer/components/schedules/SchedulesView.tsx +54 -22
- package/src/renderer/components/settings/SettingsTabs.tsx +2 -2
- package/src/renderer/components/settings/sections/AdvancedSection.tsx +1 -1
- package/src/renderer/components/settings/sections/TaskBusSection.tsx +511 -81
- package/src/renderer/components/tasks/TasksView.tsx +343 -0
- package/src/renderer/components/team/TeamDetailView.tsx +20 -98
- package/src/renderer/components/team/dialogs/LaunchTeamDialog.tsx +1 -1
- package/src/renderer/components/team/editor/EditorContextMenu.tsx +8 -23
- package/src/renderer/components/team/editor/EditorFileTree.tsx +0 -4
- package/src/renderer/components/team/editor/EditorSelectionMenu.tsx +1 -8
- package/src/renderer/components/team/editor/ProjectEditorOverlay.tsx +0 -10
- package/src/renderer/components/team/kanban/KanbanBoard.tsx +5 -1
- package/src/renderer/components/team/members/MemberDetailDialog.tsx +8 -33
- package/src/renderer/components/team/messages/MessageComposer.tsx +39 -3
- package/src/renderer/components/team/messages/MessagesPanel.tsx +72 -2
- package/src/renderer/components/team/messages/StatusBlock.tsx +2 -24
- package/src/renderer/components/team/schedule/ScheduleEmptyState.tsx +1 -1
- package/src/renderer/components/ui/MentionableTextarea.tsx +0 -1
- package/src/renderer/store/slices/scheduleSlice.ts +21 -0
- package/src/renderer/store/slices/teamSlice.ts +59 -23
- package/src/renderer/types/tabs.ts +1 -0
- package/src/shared/types/api.ts +29 -0
- package/src/shared/types/team.ts +109 -1
- package/dist-renderer/assets/ProjectEditorOverlay-BBwYdXPv.js +0 -57
- package/dist-renderer/assets/channel-DJUrwVrK.js +0 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-blc3DrH7.js +0 -1
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-blc3DrH7.js +0 -1
- package/dist-renderer/assets/clone-BftjWakJ.js +0 -1
- package/dist-renderer/assets/index-CWpFqEvz.css +0 -1
- 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
|
-
|
|
277
|
+
${TEAM_INSTRUCTIONS_BEGIN}
|
|
250
278
|
|
|
251
|
-
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
-
|
|
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
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
|
|
277
|
-
|
|
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)
|
|
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: {
|
|
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';
|
package/src/renderer/App.tsx
CHANGED
|
@@ -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
|
|
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 {
|
|
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-
|
|
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
|
-
|
|
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=
|
|
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
|
|
@@ -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 {
|
|
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
|
-
|
|
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
|
-
|
|
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 [
|
|
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={
|
|
106
|
-
onMouseEnter={() =>
|
|
107
|
-
onMouseLeave={() =>
|
|
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:
|
|
111
|
-
backgroundColor:
|
|
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
|
-
<
|
|
115
|
+
<ListTodo className="size-4" />
|
|
116
116
|
</button>
|
|
117
117
|
</TooltipTrigger>
|
|
118
|
-
<TooltipContent side="bottom"
|
|
118
|
+
<TooltipContent side="bottom">任务</TooltipContent>
|
|
119
119
|
</Tooltip>
|
|
120
120
|
|
|
121
121
|
{/* GitHub link */}
|