@yancyyu/openhermit 1.6.28 → 1.6.30
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/dist-renderer/assets/ProjectEditorOverlay-DsQt4FHy.js +52 -0
- package/dist-renderer/assets/{TeamGraphOverlay-Ba5njic5.js → TeamGraphOverlay-BjZC53xf.js} +1 -1
- package/dist-renderer/assets/{_basePickBy-BvnK-OC1.js → _basePickBy-CrWocIjq.js} +1 -1
- package/dist-renderer/assets/{_baseUniq-DmFYXx9G.js → _baseUniq-B6d8ysWi.js} +1 -1
- package/dist-renderer/assets/{arc-DX4ZQFY4.js → arc-DAIYCFP8.js} +1 -1
- package/dist-renderer/assets/{architectureDiagram-VXUJARFQ-DfYr3vEN.js → architectureDiagram-VXUJARFQ-B3UudXJh.js} +1 -1
- package/dist-renderer/assets/{blockDiagram-VD42YOAC-DuXdVeWn.js → blockDiagram-VD42YOAC-DbptKQ4W.js} +1 -1
- package/dist-renderer/assets/{c4Diagram-YG6GDRKO-Bw2nixXe.js → c4Diagram-YG6GDRKO-C4WQuZpV.js} +1 -1
- package/dist-renderer/assets/channel-DbjZvWii.js +1 -0
- package/dist-renderer/assets/{chunk-4BX2VUAB-DLiNGQoE.js → chunk-4BX2VUAB-Dp7fVpI_.js} +1 -1
- package/dist-renderer/assets/{chunk-55IACEB6-B1L_8VIF.js → chunk-55IACEB6-B8KGfbAy.js} +1 -1
- package/dist-renderer/assets/{chunk-B4BG7PRW-DaZMWKGk.js → chunk-B4BG7PRW-BG1oJrjA.js} +1 -1
- package/dist-renderer/assets/{chunk-DI55MBZ5-ku-dflJG.js → chunk-DI55MBZ5-DRmxNjht.js} +1 -1
- package/dist-renderer/assets/{chunk-FMBD7UC4-DV-mF1dP.js → chunk-FMBD7UC4-D6VLvy16.js} +1 -1
- package/dist-renderer/assets/{chunk-QN33PNHL-ByGcDFQ0.js → chunk-QN33PNHL-DZou1667.js} +1 -1
- package/dist-renderer/assets/{chunk-QZHKN3VN-7dv-Min8.js → chunk-QZHKN3VN-CghmasSh.js} +1 -1
- package/dist-renderer/assets/{chunk-TZMSLE5B-WdXL5fTu.js → chunk-TZMSLE5B-B7apcMPK.js} +1 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-D_FGxxsl.js +1 -0
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-D_FGxxsl.js +1 -0
- package/dist-renderer/assets/clone-CJ1kxO2J.js +1 -0
- package/dist-renderer/assets/{cose-bilkent-S5V4N54A-CNcsvqPl.js → cose-bilkent-S5V4N54A-05e5uQDp.js} +1 -1
- package/dist-renderer/assets/{dagre-6UL2VRFP-DBNx4qqx.js → dagre-6UL2VRFP-B06bRykF.js} +1 -1
- package/dist-renderer/assets/{diagram-PSM6KHXK-BfVlT6sT.js → diagram-PSM6KHXK-CY7VYQ7c.js} +1 -1
- package/dist-renderer/assets/{diagram-QEK2KX5R-HvVjs0K6.js → diagram-QEK2KX5R-BjKEH7dD.js} +1 -1
- package/dist-renderer/assets/{diagram-S2PKOQOG-DYb_KnWS.js → diagram-S2PKOQOG-Bf4ELS1_.js} +1 -1
- package/dist-renderer/assets/{erDiagram-Q2GNP2WA-Ba-IgI5G.js → erDiagram-Q2GNP2WA-DJ753_L9.js} +1 -1
- package/dist-renderer/assets/{flowDiagram-NV44I4VS-2iDN8Kpj.js → flowDiagram-NV44I4VS-B71S-lC-.js} +1 -1
- package/dist-renderer/assets/{ganttDiagram-JELNMOA3-Byjf8Fa3.js → ganttDiagram-JELNMOA3-C_U42mSZ.js} +1 -1
- package/dist-renderer/assets/{gitGraphDiagram-V2S2FVAM-DbKvfZ_j.js → gitGraphDiagram-V2S2FVAM-DKUJU4Ns.js} +1 -1
- package/dist-renderer/assets/{graph-Enirf-f8.js → graph-DY3qbzqj.js} +1 -1
- package/dist-renderer/assets/{index-DY1zqsb6.js → index-BlOrAXp3.js} +551 -537
- package/dist-renderer/assets/{index-AjxP_rE_.js → index-Bs27J5gB.js} +1 -1
- package/dist-renderer/assets/{index-CtlzGepK.js → index-C8B_nKOF.js} +1 -1
- package/dist-renderer/assets/index-CmZPUEhS.css +1 -0
- package/dist-renderer/assets/{index-COZPUWJW.js → index-DLKyDr4T.js} +1 -1
- package/dist-renderer/assets/{index-DdhqolqE.js → index-Dhsk3_DD.js} +1 -1
- package/dist-renderer/assets/{index-ChR1D6ZF.js → index-GpUvV2xs.js} +1 -1
- package/dist-renderer/assets/{infoDiagram-HS3SLOUP-D6uicwz1.js → infoDiagram-HS3SLOUP-BNs0y3IG.js} +1 -1
- package/dist-renderer/assets/{journeyDiagram-XKPGCS4Q-DqwZsXlQ.js → journeyDiagram-XKPGCS4Q-CqPnw4UV.js} +1 -1
- package/dist-renderer/assets/{kanban-definition-3W4ZIXB7-fCDVhVUm.js → kanban-definition-3W4ZIXB7-SLlzcUJ2.js} +1 -1
- package/dist-renderer/assets/{layout-CPFgj98r.js → layout-BZLlNmbr.js} +1 -1
- package/dist-renderer/assets/{linear-CYiQ7Y3M.js → linear-qz6v45xy.js} +1 -1
- package/dist-renderer/assets/{mindmap-definition-VGOIOE7T-D31dS2KE.js → mindmap-definition-VGOIOE7T-B1-kmEWV.js} +1 -1
- package/dist-renderer/assets/{pieDiagram-ADFJNKIX-BOsCJfds.js → pieDiagram-ADFJNKIX-B8a02iNx.js} +1 -1
- package/dist-renderer/assets/{quadrantDiagram-AYHSOK5B-CYTVQCfr.js → quadrantDiagram-AYHSOK5B-BKv1Xfou.js} +1 -1
- package/dist-renderer/assets/{requirementDiagram-UZGBJVZJ-CODCFpkt.js → requirementDiagram-UZGBJVZJ-B3DUpZi2.js} +1 -1
- package/dist-renderer/assets/{sankeyDiagram-TZEHDZUN-Z4ce9ZtZ.js → sankeyDiagram-TZEHDZUN-DmPzuTsy.js} +1 -1
- package/dist-renderer/assets/{sequenceDiagram-WL72ISMW-CmS9TxhW.js → sequenceDiagram-WL72ISMW-Bo7RelRb.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-FKZM4ZOC-o9k-ns3q.js → stateDiagram-FKZM4ZOC-1epX98gV.js} +1 -1
- package/dist-renderer/assets/{stateDiagram-v2-4FDKWEC3-CxHMyEt1.js → stateDiagram-v2-4FDKWEC3-03Ym9PTr.js} +1 -1
- package/dist-renderer/assets/{timeline-definition-IT6M3QCI-B6T3zrde.js → timeline-definition-IT6M3QCI-r6isC62H.js} +1 -1
- package/dist-renderer/assets/{treemap-GDKQZRPO-CVd5GNDw.js → treemap-GDKQZRPO-CGKpOUF2.js} +1 -1
- package/dist-renderer/assets/{xychartDiagram-PRI3JC2R-CleBrdqc.js → xychartDiagram-PRI3JC2R-t4-rwdAw.js} +1 -1
- package/dist-renderer/index.html +2 -2
- package/package.json +4 -1
- package/src/main/ipc/extensions.ts +353 -0
- package/src/main/server.ts +907 -184
- package/src/main/services/extensions/ExtensionFacadeService.ts +135 -0
- package/src/main/services/extensions/catalog/GlamaMcpEnrichmentService.ts +190 -0
- package/src/main/services/extensions/catalog/McpCatalogAggregator.ts +150 -0
- package/src/main/services/extensions/catalog/OfficialMcpRegistryService.ts +381 -0
- package/src/main/services/extensions/catalog/PluginCatalogService.ts +392 -0
- package/src/main/services/extensions/credentials/CredentialService.ts +343 -0
- package/src/main/services/extensions/install/McpInstallService.ts +407 -0
- package/src/main/services/extensions/install/PluginInstallService.ts +198 -0
- package/src/main/services/extensions/runtime/ClaudeCodeAdapter.ts +199 -0
- package/src/main/services/extensions/runtime/CodexAdapter.ts +100 -0
- package/src/main/services/extensions/runtime/CursorAdapter.ts +154 -0
- package/src/main/services/extensions/runtime/ExtensionsRuntimeAdapter.ts +172 -0
- package/src/main/services/extensions/runtime/GeminiAdapter.ts +91 -0
- package/src/main/services/extensions/runtime/HarnessInstallAdapter.ts +49 -0
- package/src/main/services/extensions/runtime/McpConfigStateReader.ts +209 -0
- package/src/main/services/extensions/runtime/OpenCodeAdapter.ts +91 -0
- package/src/main/services/extensions/runtime/adapterRegistry.ts +54 -0
- package/src/main/services/extensions/runtime/mcpDiagnosticsParser.ts +214 -0
- package/src/main/services/extensions/runtime/mcpRuntimeJson.ts +45 -0
- package/src/main/services/extensions/skills/SkillImportService.ts +155 -0
- package/src/main/services/extensions/skills/SkillMetadataParser.ts +323 -0
- package/src/main/services/extensions/skills/SkillPlanService.ts +411 -0
- package/src/main/services/extensions/skills/SkillReviewService.ts +73 -0
- package/src/main/services/extensions/skills/SkillRootsResolver.ts +49 -0
- package/src/main/services/extensions/skills/SkillScaffoldService.ts +89 -0
- package/src/main/services/extensions/skills/SkillScanner.ts +117 -0
- package/src/main/services/extensions/skills/SkillValidator.ts +69 -0
- package/src/main/services/extensions/skills/SkillsCatalogService.ts +92 -0
- package/src/main/services/extensions/skills/SkillsMutationService.ts +146 -0
- package/src/main/services/extensions/skills/SkillsWatcherService.ts +134 -0
- package/src/main/services/extensions/state/McpInstallationStateService.ts +42 -0
- package/src/main/services/extensions/state/PluginInstallationStateService.ts +281 -0
- package/src/main/services/identity/AgentTeamsIdentityStore.ts +218 -0
- package/src/main/services/runtime/providerAwareCliEnv.ts +60 -0
- package/src/main/services/session-intelligence/UsageTelemetryService.ts +33 -18
- package/src/main/services/team/ClaudeBinaryResolver.ts +469 -0
- package/src/main/services/team/ClaudeDoctorProbe.ts +0 -0
- package/src/main/services/team/cliFlavor.ts +54 -0
- package/src/main/services/teams-mvp/CollaborationBoardService.ts +310 -0
- package/src/main/services/teams-mvp/TaskDispatchService.ts +883 -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/main/utils/atomicWrite.ts +72 -0
- package/src/main/utils/childProcess.ts +554 -0
- package/src/main/utils/cliEnv.ts +54 -0
- package/src/main/utils/cliPathMerge.ts +97 -0
- package/src/main/utils/pathDecoder.ts +664 -0
- package/src/main/utils/pathValidation.ts +432 -0
- package/src/main/utils/shellEnv.ts +331 -0
- package/src/renderer/App.tsx +5 -0
- package/src/renderer/api/httpClient.ts +128 -0
- package/src/renderer/components/extensions/ExtensionStoreView.tsx +59 -34
- package/src/renderer/components/extensions/ExtensionsSubTabTrigger.tsx +1 -1
- package/src/renderer/components/extensions/common/ExtensionToast.tsx +141 -0
- package/src/renderer/components/extensions/common/HarnessSelector.tsx +71 -0
- package/src/renderer/components/extensions/env/EnvVarPanel.tsx +335 -0
- package/src/renderer/components/extensions/env/ProjectEnvPanel.tsx +239 -0
- package/src/renderer/components/extensions/mcp/CustomMcpServerDialog.tsx +14 -223
- package/src/renderer/components/extensions/mcp/McpServerDetailDialog.tsx +11 -0
- package/src/renderer/components/extensions/mcp/McpServersPanel.tsx +51 -1
- package/src/renderer/components/extensions/skills/SkillsPanel.tsx +1 -126
- 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/sections/AdvancedSection.tsx +1 -1
- package/src/renderer/components/settings/sections/HarnessSection.tsx +2 -6
- package/src/renderer/components/settings/sections/TaskBusSection.tsx +144 -84
- package/src/renderer/components/sidebar/SidebarSessions.tsx +23 -0
- package/src/renderer/components/sidebar/WorkspaceBrowser.tsx +1 -7
- package/src/renderer/components/tasks/TasksView.tsx +343 -0
- package/src/renderer/components/team/HarnessSelect.tsx +71 -0
- package/src/renderer/components/team/TeamDetailView.tsx +55 -98
- package/src/renderer/components/team/dialogs/CreateTeamDialog.tsx +21 -12
- package/src/renderer/components/team/dialogs/EditTeamDialog.tsx +8 -13
- 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 +31 -65
- 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 +100 -26
- package/src/renderer/components/team/messages/StatusBlock.tsx +2 -24
- package/src/renderer/components/team/schedule/ScheduleEmptyState.tsx +1 -1
- package/src/renderer/components/terminal/TerminalPanel.tsx +156 -0
- package/src/renderer/components/ui/MentionableTextarea.tsx +0 -1
- package/src/renderer/hooks/useExtensionsTabState.ts +2 -2
- package/src/renderer/store/slices/extensionsSlice.ts +42 -107
- package/src/renderer/store/slices/scheduleSlice.ts +21 -0
- package/src/renderer/store/slices/teamSlice.ts +67 -25
- package/src/renderer/types/tabs.ts +1 -0
- package/src/shared/types/api.ts +58 -0
- package/src/shared/types/extensions/index.ts +1 -0
- package/src/shared/types/extensions/mcp.ts +2 -0
- package/src/shared/types/extensions/plugin.ts +2 -1
- package/src/shared/types/extensions/skill.ts +7 -0
- package/src/shared/types/team.ts +104 -1
- package/src/shared/utils/providerExtensionCapabilities.ts +1 -1
- package/dist-renderer/assets/ProjectEditorOverlay-A4DZTvSy.js +0 -57
- package/dist-renderer/assets/channel-Pre42N5O.js +0 -1
- package/dist-renderer/assets/classDiagram-2ON5EDUG-CdJsTJsj.js +0 -1
- package/dist-renderer/assets/classDiagram-v2-WZHVMYZB-CdJsTJsj.js +0 -1
- package/dist-renderer/assets/clone-BjQBiNfj.js +0 -1
- package/dist-renderer/assets/index-BIOJremZ.css +0 -1
- package/src/features/recent-projects/main/adapters/input/http/registerRecentProjectsHttp.ts +0 -30
- package/src/features/recent-projects/main/adapters/output/presenters/DashboardRecentProjectsPresenter.ts +0 -27
- package/src/features/recent-projects/main/adapters/output/sources/ClaudeRecentProjectsSourceAdapter.ts +0 -91
- package/src/features/recent-projects/main/adapters/output/sources/CodexRecentProjectsSourceAdapter.ts +0 -326
- package/src/features/recent-projects/main/composition/createRecentProjectsFeature.ts +0 -43
- package/src/features/recent-projects/main/index.ts +0 -3
- package/src/features/recent-projects/main/infrastructure/cache/InMemoryRecentProjectsCache.ts +0 -34
- package/src/features/recent-projects/main/infrastructure/codex/CodexAppServerClient.ts +0 -116
- package/src/features/recent-projects/main/infrastructure/identity/RecentProjectIdentityResolver.ts +0 -20
- package/src/features/recent-projects/main/infrastructure/identity/normalizeIdentityPath.ts +0 -10
- package/src/renderer/components/extensions/apikeys/ApiKeyCard.tsx +0 -143
- package/src/renderer/components/extensions/apikeys/ApiKeyFormDialog.tsx +0 -282
- package/src/renderer/components/extensions/apikeys/ApiKeysPanel.tsx +0 -280
|
@@ -208,9 +208,11 @@ export const MessagesPanel = memo(function MessagesPanel({
|
|
|
208
208
|
teams,
|
|
209
209
|
openTeamTab,
|
|
210
210
|
messages,
|
|
211
|
-
|
|
211
|
+
hasMore,
|
|
212
|
+
loadingOlderMessages,
|
|
212
213
|
loadOlderTeamMessages,
|
|
213
214
|
refreshTeamMessagesHead,
|
|
215
|
+
addOptimisticTeamMessage,
|
|
214
216
|
} = useStore(
|
|
215
217
|
useShallow((s) => ({
|
|
216
218
|
sendTeamMessage: s.sendTeamMessage,
|
|
@@ -223,24 +225,30 @@ export const MessagesPanel = memo(function MessagesPanel({
|
|
|
223
225
|
teams: s.teams,
|
|
224
226
|
openTeamTab: s.openTeamTab,
|
|
225
227
|
messages: selectTeamMessages(s, teamName),
|
|
226
|
-
|
|
228
|
+
// Subscribe to only the primitive flags the panel renders. The full
|
|
229
|
+
// cache entry object is rebuilt on every (even no-op) head refresh —
|
|
230
|
+
// selecting it wholesale would re-render this heavy panel every poll.
|
|
231
|
+
hasMore: teamName ? (s.teamMessagesByName[teamName]?.hasMore ?? false) : false,
|
|
232
|
+
loadingOlderMessages: teamName
|
|
233
|
+
? (s.teamMessagesByName[teamName]?.loadingOlder ?? false)
|
|
234
|
+
: false,
|
|
227
235
|
loadOlderTeamMessages: s.loadOlderTeamMessages,
|
|
228
236
|
refreshTeamMessagesHead: s.refreshTeamMessagesHead,
|
|
237
|
+
addOptimisticTeamMessage: s.addOptimisticTeamMessage,
|
|
229
238
|
}))
|
|
230
239
|
);
|
|
231
240
|
const bootstrapHeadRefreshAttemptedForTeamRef = useRef<string | null>(null);
|
|
232
241
|
|
|
233
242
|
const loadOlderMessages = useCallback(async () => {
|
|
234
|
-
|
|
243
|
+
// Read the live cache entry instead of subscribing to it — loadingHead
|
|
244
|
+
// toggles on every background head refresh and must not re-render us.
|
|
245
|
+
const entry = useStore.getState().teamMessagesByName[teamName];
|
|
246
|
+
if (!entry?.hasMore || entry.loadingHead || entry.loadingOlder) {
|
|
235
247
|
return;
|
|
236
248
|
}
|
|
237
249
|
await loadOlderTeamMessages(teamName);
|
|
238
|
-
}, [loadOlderTeamMessages,
|
|
250
|
+
}, [loadOlderTeamMessages, teamName]);
|
|
239
251
|
|
|
240
|
-
const messagesLoading =
|
|
241
|
-
(messagesState?.loadingHead ?? false) || (messagesState?.loadingOlder ?? false);
|
|
242
|
-
const loadingOlderMessages = messagesState?.loadingOlder ?? false;
|
|
243
|
-
const hasMore = messagesState?.hasMore ?? false;
|
|
244
252
|
const effectiveMessages = messages;
|
|
245
253
|
const loadedMessageCount = effectiveMessages.length;
|
|
246
254
|
const autoLoadOlderLockRef = useRef(false);
|
|
@@ -269,12 +277,11 @@ export const MessagesPanel = memo(function MessagesPanel({
|
|
|
269
277
|
|
|
270
278
|
const maybeAutoLoadOlderMessages = useCallback(
|
|
271
279
|
(scrollTop: number) => {
|
|
272
|
-
if (
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
) {
|
|
280
|
+
if (scrollTop > AUTO_LOAD_OLDER_SCROLL_TOP_PX || !hasMore || loadingOlderMessages) {
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
// loadingHead is read live (not subscribed) to avoid per-poll re-renders.
|
|
284
|
+
if (useStore.getState().teamMessagesByName[teamName]?.loadingHead) {
|
|
278
285
|
return;
|
|
279
286
|
}
|
|
280
287
|
if (autoLoadOlderLockRef.current) {
|
|
@@ -283,7 +290,7 @@ export const MessagesPanel = memo(function MessagesPanel({
|
|
|
283
290
|
autoLoadOlderLockRef.current = true;
|
|
284
291
|
void loadOlderMessages();
|
|
285
292
|
},
|
|
286
|
-
[hasMore, loadOlderMessages, loadingOlderMessages,
|
|
293
|
+
[hasMore, loadOlderMessages, loadingOlderMessages, teamName]
|
|
287
294
|
);
|
|
288
295
|
|
|
289
296
|
useEffect(() => {
|
|
@@ -377,7 +384,9 @@ export const MessagesPanel = memo(function MessagesPanel({
|
|
|
377
384
|
return () => {
|
|
378
385
|
cancelled = true;
|
|
379
386
|
};
|
|
380
|
-
|
|
387
|
+
// Refetch when the lead session id changes (e.g. a new session is spawned)
|
|
388
|
+
// so the session list/selector reflects the updated id without a remount.
|
|
389
|
+
}, [teamName, currentLeadSessionId]);
|
|
381
390
|
|
|
382
391
|
const selectedSession = useMemo(
|
|
383
392
|
() => teamSessions.find((session) => session.sessionKey === selectedSessionKey) ?? null,
|
|
@@ -453,7 +462,10 @@ export const MessagesPanel = memo(function MessagesPanel({
|
|
|
453
462
|
bootstrapHeadRefreshAttemptedForTeamRef.current = null;
|
|
454
463
|
return;
|
|
455
464
|
}
|
|
456
|
-
|
|
465
|
+
// Read loading flags live rather than subscribing — they toggle on every
|
|
466
|
+
// background head refresh and must not drive this bootstrap effect.
|
|
467
|
+
const entry = useStore.getState().teamMessagesByName[teamName];
|
|
468
|
+
if (entry?.loadingHead || entry?.loadingOlder) {
|
|
457
469
|
return;
|
|
458
470
|
}
|
|
459
471
|
if (bootstrapHeadRefreshAttemptedForTeamRef.current === teamName) {
|
|
@@ -461,13 +473,7 @@ export const MessagesPanel = memo(function MessagesPanel({
|
|
|
461
473
|
}
|
|
462
474
|
bootstrapHeadRefreshAttemptedForTeamRef.current = teamName;
|
|
463
475
|
void refreshTeamMessagesHead(teamName).catch(() => undefined);
|
|
464
|
-
}, [
|
|
465
|
-
effectiveMessages.length,
|
|
466
|
-
messagesState?.loadingHead,
|
|
467
|
-
messagesState?.loadingOlder,
|
|
468
|
-
refreshTeamMessagesHead,
|
|
469
|
-
teamName,
|
|
470
|
-
]);
|
|
476
|
+
}, [effectiveMessages.length, refreshTeamMessagesHead, teamName]);
|
|
471
477
|
|
|
472
478
|
useLayoutEffect(() => {
|
|
473
479
|
if (position !== 'sidebar') return;
|
|
@@ -534,7 +540,7 @@ export const MessagesPanel = memo(function MessagesPanel({
|
|
|
534
540
|
const sessionScopedMessages = useMemo(() => {
|
|
535
541
|
const newestFirst = (items: InboxMessage[]) =>
|
|
536
542
|
[...items].sort((a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp));
|
|
537
|
-
if (!selectedSessionKey) return
|
|
543
|
+
if (!selectedSessionKey) return newestFirst(effectiveMessages);
|
|
538
544
|
if (selectedSession && !selectedIsHermitLocalSession) {
|
|
539
545
|
if (!selectedSessionDetail) {
|
|
540
546
|
return [];
|
|
@@ -731,12 +737,77 @@ export const MessagesPanel = memo(function MessagesPanel({
|
|
|
731
737
|
fromMember: 'user',
|
|
732
738
|
toTeam,
|
|
733
739
|
text,
|
|
740
|
+
sessionKey:
|
|
741
|
+
selectedSessionKey && selectedSessionKey !== '__unassigned__'
|
|
742
|
+
? selectedSessionKey
|
|
743
|
+
: undefined,
|
|
734
744
|
taskRefs,
|
|
735
745
|
actionMode,
|
|
736
746
|
summary,
|
|
737
747
|
});
|
|
738
748
|
},
|
|
739
|
-
[teamName, sendCrossTeamMessage]
|
|
749
|
+
[teamName, selectedSessionKey, sendCrossTeamMessage]
|
|
750
|
+
);
|
|
751
|
+
|
|
752
|
+
const handleDispatchTaskToTeam = useCallback(
|
|
753
|
+
async (toTeam: string, subject: string, description: string) => {
|
|
754
|
+
const now = Date.now();
|
|
755
|
+
const optimisticMessageId = `optimistic-cross-team-${now}`;
|
|
756
|
+
addOptimisticTeamMessage(teamName, {
|
|
757
|
+
from: 'user',
|
|
758
|
+
to: toTeam,
|
|
759
|
+
text: `@${toTeam} ${subject}`,
|
|
760
|
+
timestamp: new Date(now).toISOString(),
|
|
761
|
+
read: true,
|
|
762
|
+
messageId: optimisticMessageId,
|
|
763
|
+
source: 'cross_team_sent',
|
|
764
|
+
session:
|
|
765
|
+
selectedSessionKey && selectedSessionKey !== '__unassigned__'
|
|
766
|
+
? { key: selectedSessionKey }
|
|
767
|
+
: undefined,
|
|
768
|
+
});
|
|
769
|
+
try {
|
|
770
|
+
await sendCrossTeamMessage({
|
|
771
|
+
fromTeam: teamName,
|
|
772
|
+
fromMember: 'user',
|
|
773
|
+
toTeam,
|
|
774
|
+
text: description,
|
|
775
|
+
messageId: optimisticMessageId,
|
|
776
|
+
});
|
|
777
|
+
} catch (error) {
|
|
778
|
+
const rawMessage = error instanceof Error ? error.message : '跨团队任务派发失败';
|
|
779
|
+
const readableMessage = rawMessage.includes('Redis not configured')
|
|
780
|
+
? '无法派发给其他团队:Redis 未配置或未连接。请先在设置里开启团队总线并配置 Redis。'
|
|
781
|
+
: rawMessage.includes('Distributed collaboration is not enabled')
|
|
782
|
+
? '无法派发给其他团队:团队总线/分布式团队协作未开启。请先在设置里开启。'
|
|
783
|
+
: `无法派发给 ${toTeam}:${rawMessage}`;
|
|
784
|
+
addOptimisticTeamMessage(teamName, {
|
|
785
|
+
from: 'system',
|
|
786
|
+
to: 'user',
|
|
787
|
+
text: readableMessage,
|
|
788
|
+
timestamp: new Date(Date.now()).toISOString(),
|
|
789
|
+
read: true,
|
|
790
|
+
messageId: `optimistic-cross-team-error-${Date.now()}`,
|
|
791
|
+
source: 'system_notification',
|
|
792
|
+
});
|
|
793
|
+
window.dispatchEvent(new CustomEvent('collab:refresh'));
|
|
794
|
+
await refreshTeamMessagesHead(teamName);
|
|
795
|
+
return false;
|
|
796
|
+
}
|
|
797
|
+
window.dispatchEvent(new CustomEvent('collab:refresh'));
|
|
798
|
+
await refreshTeamMessagesHead(teamName);
|
|
799
|
+
window.setTimeout(() => {
|
|
800
|
+
void refreshTeamMessagesHead(teamName);
|
|
801
|
+
}, 300);
|
|
802
|
+
return true;
|
|
803
|
+
},
|
|
804
|
+
[
|
|
805
|
+
addOptimisticTeamMessage,
|
|
806
|
+
teamName,
|
|
807
|
+
refreshTeamMessagesHead,
|
|
808
|
+
selectedSessionKey,
|
|
809
|
+
sendCrossTeamMessage,
|
|
810
|
+
]
|
|
740
811
|
);
|
|
741
812
|
|
|
742
813
|
const moveToInline = useCallback(() => {
|
|
@@ -869,6 +940,7 @@ export const MessagesPanel = memo(function MessagesPanel({
|
|
|
869
940
|
onSessionChange={setSelectedSessionKey}
|
|
870
941
|
textareaRef={composerTextareaRef}
|
|
871
942
|
onSend={handleSend}
|
|
943
|
+
onDispatchTask={handleDispatchTaskToTeam}
|
|
872
944
|
/>
|
|
873
945
|
<StatusBlock
|
|
874
946
|
members={members}
|
|
@@ -1065,6 +1137,7 @@ export const MessagesPanel = memo(function MessagesPanel({
|
|
|
1065
1137
|
onSessionChange={setSelectedSessionKey}
|
|
1066
1138
|
textareaRef={composerTextareaRef}
|
|
1067
1139
|
onSend={handleSend}
|
|
1140
|
+
onDispatchTask={handleDispatchTaskToTeam}
|
|
1068
1141
|
/>
|
|
1069
1142
|
<StatusBlock
|
|
1070
1143
|
members={members}
|
|
@@ -1352,6 +1425,7 @@ export const MessagesPanel = memo(function MessagesPanel({
|
|
|
1352
1425
|
onSessionChange={setSelectedSessionKey}
|
|
1353
1426
|
textareaRef={composerTextareaRef}
|
|
1354
1427
|
onSend={handleSend}
|
|
1428
|
+
onDispatchTask={handleDispatchTaskToTeam}
|
|
1355
1429
|
/>
|
|
1356
1430
|
</div>
|
|
1357
1431
|
</div>
|
|
@@ -3,7 +3,6 @@ import { useEffect, useMemo, useState } from 'react';
|
|
|
3
3
|
import { computePendingCrossTeamReplies } from '@renderer/utils/crossTeamPendingReplies';
|
|
4
4
|
import { ChevronRight } from 'lucide-react';
|
|
5
5
|
|
|
6
|
-
import { ActiveTasksBlock } from '../activity/ActiveTasksBlock';
|
|
7
6
|
import { PendingRepliesBlock } from '../activity/PendingRepliesBlock';
|
|
8
7
|
|
|
9
8
|
import type { InboxMessage, ResolvedTeamMember, TeamTaskWithKanban } from '@shared/types';
|
|
@@ -29,13 +28,10 @@ interface StatusBlockProps {
|
|
|
29
28
|
*/
|
|
30
29
|
export const StatusBlock = ({
|
|
31
30
|
members,
|
|
32
|
-
tasks,
|
|
33
31
|
messages,
|
|
34
32
|
pendingRepliesByMember,
|
|
35
|
-
position,
|
|
36
33
|
layout = 'overlay',
|
|
37
34
|
onMemberClick,
|
|
38
|
-
onTaskClick,
|
|
39
35
|
}: StatusBlockProps): React.JSX.Element | null => {
|
|
40
36
|
const [collapsed, setCollapsed] = useState(false);
|
|
41
37
|
const [nowMs, setNowMs] = useState(() => Date.now());
|
|
@@ -50,21 +46,11 @@ export const StatusBlock = ({
|
|
|
50
46
|
);
|
|
51
47
|
return hasMemberPendingReplies || pendingCrossTeamReplies.length > 0;
|
|
52
48
|
}, [members, pendingRepliesByMember, pendingCrossTeamReplies.length]);
|
|
53
|
-
const hasActiveTasks = useMemo(() => {
|
|
54
|
-
const tMap = new Map(tasks.map((t) => [t.id, t]));
|
|
55
|
-
return members.some((m) => {
|
|
56
|
-
if (!m.currentTaskId) return false;
|
|
57
|
-
const task = tMap.get(m.currentTaskId);
|
|
58
|
-
if (task && (task.reviewState === 'approved' || task.status === 'completed')) return false;
|
|
59
|
-
return true;
|
|
60
|
-
});
|
|
61
|
-
}, [members, tasks]);
|
|
62
49
|
|
|
63
50
|
/** Whether the Status block has any visible items. */
|
|
64
51
|
const hasItems = useMemo(() => {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}, [hasActiveTasks, hasPendingReplies]);
|
|
52
|
+
return hasPendingReplies;
|
|
53
|
+
}, [hasPendingReplies]);
|
|
68
54
|
|
|
69
55
|
// Only run the 1-second timer when the block actually has content to show.
|
|
70
56
|
useEffect(() => {
|
|
@@ -111,14 +97,6 @@ export const StatusBlock = ({
|
|
|
111
97
|
onMemberClick={onMemberClick}
|
|
112
98
|
/>
|
|
113
99
|
) : null}
|
|
114
|
-
<ActiveTasksBlock
|
|
115
|
-
members={members}
|
|
116
|
-
tasks={tasks}
|
|
117
|
-
defaultCollapsed={position === 'sidebar'}
|
|
118
|
-
headerRight={!hasPendingReplies ? flowInlineToggle : undefined}
|
|
119
|
-
onMemberClick={onMemberClick}
|
|
120
|
-
onTaskClick={onTaskClick}
|
|
121
|
-
/>
|
|
122
100
|
</div>
|
|
123
101
|
)}
|
|
124
102
|
</>
|
|
@@ -8,7 +8,7 @@ export const ScheduleEmptyState = (): React.JSX.Element => (
|
|
|
8
8
|
<div className="space-y-1">
|
|
9
9
|
<p className="text-xs font-medium text-[var(--color-text-secondary)]">暂无定时计划</p>
|
|
10
10
|
<p className="text-[11px] text-[var(--color-text-muted)]">
|
|
11
|
-
创建计划后,可按 cron
|
|
11
|
+
创建计划后,可按 cron 时间自动运行团队。
|
|
12
12
|
</p>
|
|
13
13
|
</div>
|
|
14
14
|
</div>
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TerminalPanel — a good-looking, read-only terminal-style panel for rendering
|
|
3
|
+
* command / CLI output faithfully.
|
|
4
|
+
*
|
|
5
|
+
* Unlike the structured markdown renderers, this preserves the raw terminal feel:
|
|
6
|
+
* - full ANSI color / decoration fidelity (via `anser`)
|
|
7
|
+
* - monospace, whitespace-exact output
|
|
8
|
+
* - carriage-return (\r) overwrite handling so progress bars settle to their
|
|
9
|
+
* final frame instead of dumping every intermediate line
|
|
10
|
+
* - optional `$ command` prompt line so a Bash call reads like a real terminal
|
|
11
|
+
*
|
|
12
|
+
* It is intentionally lightweight (no xterm.js / node-pty): the session view is a
|
|
13
|
+
* read-only viewer of recorded output, so we only need faithful rendering, not a
|
|
14
|
+
* live PTY.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { useMemo, useState } from 'react';
|
|
18
|
+
|
|
19
|
+
import Anser, { type AnserJsonEntry } from 'anser';
|
|
20
|
+
import { Check, Copy } from 'lucide-react';
|
|
21
|
+
|
|
22
|
+
interface TerminalPanelProps {
|
|
23
|
+
/** Raw output text, may contain ANSI escape sequences. */
|
|
24
|
+
text: string;
|
|
25
|
+
/** Optional command to render as a `$ command` prompt line above the output. */
|
|
26
|
+
command?: string;
|
|
27
|
+
/** Optional label shown in the header bar (e.g. a short description). */
|
|
28
|
+
title?: string;
|
|
29
|
+
/** Max body height in px before scrolling. Defaults to 384. */
|
|
30
|
+
maxHeight?: number;
|
|
31
|
+
className?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Collapse carriage-return overwrites within each line and strip non-color
|
|
36
|
+
* escape sequences (cursor moves, screen clears, OSC) that would otherwise
|
|
37
|
+
* render as garbage. SGR color codes are left intact for `anser`.
|
|
38
|
+
*/
|
|
39
|
+
function normalizeTerminalText(raw: string): string {
|
|
40
|
+
// Strip OSC sequences: ESC ] ... BEL or ESC ] ... ESC \
|
|
41
|
+
let out = raw.replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, '');
|
|
42
|
+
// Strip CSI sequences that are NOT SGR ("m"): cursor movement, erase, etc.
|
|
43
|
+
out = out.replace(/\x1b\[[0-9;?]*[A-Za-ln-z]/g, '');
|
|
44
|
+
// Collapse \r overwrites per line: later segments overwrite earlier from col 0.
|
|
45
|
+
out = out
|
|
46
|
+
.split('\n')
|
|
47
|
+
.map((line) => {
|
|
48
|
+
if (!line.includes('\r')) return line;
|
|
49
|
+
let acc = '';
|
|
50
|
+
for (const seg of line.split('\r')) {
|
|
51
|
+
acc = seg.length >= acc.length ? seg : seg + acc.slice(seg.length);
|
|
52
|
+
}
|
|
53
|
+
return acc;
|
|
54
|
+
})
|
|
55
|
+
.join('\n');
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function styleForSegment(seg: AnserJsonEntry): React.CSSProperties {
|
|
60
|
+
const style: React.CSSProperties = {};
|
|
61
|
+
if (seg.fg) style.color = `rgb(${seg.fg})`;
|
|
62
|
+
if (seg.bg) style.backgroundColor = `rgb(${seg.bg})`;
|
|
63
|
+
const decorations = seg.decorations ?? [];
|
|
64
|
+
if (decorations.includes('bold')) style.fontWeight = 600;
|
|
65
|
+
if (decorations.includes('italic')) style.fontStyle = 'italic';
|
|
66
|
+
if (decorations.includes('underline')) style.textDecoration = 'underline';
|
|
67
|
+
if (decorations.includes('dim')) style.opacity = 0.6;
|
|
68
|
+
return style;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const MONO_FONT =
|
|
72
|
+
'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace';
|
|
73
|
+
|
|
74
|
+
export const TerminalPanel = ({
|
|
75
|
+
text,
|
|
76
|
+
command,
|
|
77
|
+
title,
|
|
78
|
+
maxHeight = 384,
|
|
79
|
+
className,
|
|
80
|
+
}: TerminalPanelProps): React.JSX.Element => {
|
|
81
|
+
const [copied, setCopied] = useState(false);
|
|
82
|
+
|
|
83
|
+
const segments = useMemo<AnserJsonEntry[]>(
|
|
84
|
+
() =>
|
|
85
|
+
Anser.ansiToJson(normalizeTerminalText(text ?? ''), {
|
|
86
|
+
json: true,
|
|
87
|
+
use_classes: false,
|
|
88
|
+
remove_empty: false,
|
|
89
|
+
}),
|
|
90
|
+
[text]
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const handleCopy = (): void => {
|
|
94
|
+
const payload = command ? `$ ${command}\n${text ?? ''}` : (text ?? '');
|
|
95
|
+
void navigator.clipboard?.writeText(payload).then(() => {
|
|
96
|
+
setCopied(true);
|
|
97
|
+
setTimeout(() => setCopied(false), 1500);
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<div
|
|
103
|
+
className={`overflow-hidden rounded-lg border ${className ?? ''}`}
|
|
104
|
+
style={{ borderColor: 'rgba(255,255,255,0.08)', backgroundColor: '#0c0c0f' }}
|
|
105
|
+
>
|
|
106
|
+
{/* Header / window chrome */}
|
|
107
|
+
<div
|
|
108
|
+
className="flex items-center gap-2 px-3 py-1.5"
|
|
109
|
+
style={{
|
|
110
|
+
backgroundColor: '#16161b',
|
|
111
|
+
borderBottom: '1px solid rgba(255,255,255,0.06)',
|
|
112
|
+
}}
|
|
113
|
+
>
|
|
114
|
+
<span className="flex items-center gap-1.5">
|
|
115
|
+
<span className="size-2.5 rounded-full" style={{ backgroundColor: '#ff5f57' }} />
|
|
116
|
+
<span className="size-2.5 rounded-full" style={{ backgroundColor: '#febc2e' }} />
|
|
117
|
+
<span className="size-2.5 rounded-full" style={{ backgroundColor: '#28c840' }} />
|
|
118
|
+
</span>
|
|
119
|
+
<span
|
|
120
|
+
className="ml-1 flex-1 truncate text-[11px]"
|
|
121
|
+
style={{ color: 'rgba(255,255,255,0.45)', fontFamily: MONO_FONT }}
|
|
122
|
+
>
|
|
123
|
+
{title ?? (command ? command : 'terminal')}
|
|
124
|
+
</span>
|
|
125
|
+
<button
|
|
126
|
+
type="button"
|
|
127
|
+
onClick={handleCopy}
|
|
128
|
+
className="flex items-center gap-1 rounded px-1.5 py-0.5 text-[10px] transition-colors"
|
|
129
|
+
style={{ color: 'rgba(255,255,255,0.45)' }}
|
|
130
|
+
title="复制"
|
|
131
|
+
>
|
|
132
|
+
{copied ? <Check className="size-3" /> : <Copy className="size-3" />}
|
|
133
|
+
{copied ? '已复制' : '复制'}
|
|
134
|
+
</button>
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
{/* Body */}
|
|
138
|
+
<pre
|
|
139
|
+
className="overflow-auto whitespace-pre-wrap break-all px-3 py-2.5 text-xs leading-relaxed"
|
|
140
|
+
style={{ maxHeight, fontFamily: MONO_FONT, color: '#d4d4d4', margin: 0 }}
|
|
141
|
+
>
|
|
142
|
+
{command && (
|
|
143
|
+
<div className="mb-1">
|
|
144
|
+
<span style={{ color: '#28c840' }}>$ </span>
|
|
145
|
+
<span style={{ color: '#e8e8e8' }}>{command}</span>
|
|
146
|
+
</div>
|
|
147
|
+
)}
|
|
148
|
+
{segments.map((seg, i) => (
|
|
149
|
+
<span key={i} style={styleForSegment(seg)}>
|
|
150
|
+
{seg.content}
|
|
151
|
+
</span>
|
|
152
|
+
))}
|
|
153
|
+
</pre>
|
|
154
|
+
</div>
|
|
155
|
+
);
|
|
156
|
+
};
|
|
@@ -1069,7 +1069,6 @@ export const MentionableTextarea = React.forwardRef<HTMLTextAreaElement, Mention
|
|
|
1069
1069
|
const rotatingTips = React.useMemo(
|
|
1070
1070
|
() => [
|
|
1071
1071
|
'Tips:输入 @ 可提及成员、团队或文件,输入 # 可引用任务。',
|
|
1072
|
-
'Tips:输入“创建任务”可以把事项加入任务看板。',
|
|
1073
1072
|
'Tips:不要把所有工作都堆给团队负责人,可以让负责人分配给合适的成员。',
|
|
1074
1073
|
...extraTips,
|
|
1075
1074
|
],
|
|
@@ -16,7 +16,7 @@ import type {
|
|
|
16
16
|
PluginSortField,
|
|
17
17
|
} from '@shared/types/extensions';
|
|
18
18
|
|
|
19
|
-
export type ExtensionsSubTab = 'plugins' | 'mcp-servers' | 'skills' | '
|
|
19
|
+
export type ExtensionsSubTab = 'plugins' | 'mcp-servers' | 'skills' | 'env-vars';
|
|
20
20
|
export type SkillsSortState = 'name-asc' | 'recent-desc';
|
|
21
21
|
|
|
22
22
|
interface PluginSortState {
|
|
@@ -33,7 +33,7 @@ const DEFAULT_FILTERS: PluginFilters = {
|
|
|
33
33
|
|
|
34
34
|
export function useExtensionsTabState() {
|
|
35
35
|
// ── Sub-tab navigation ──
|
|
36
|
-
const [activeSubTab, setActiveSubTab] = useState<ExtensionsSubTab>('
|
|
36
|
+
const [activeSubTab, setActiveSubTab] = useState<ExtensionsSubTab>('plugins');
|
|
37
37
|
|
|
38
38
|
// ── Plugin filters & sort ──
|
|
39
39
|
const [pluginFilters, setPluginFilters] = useState<PluginFilters>(DEFAULT_FILTERS);
|