@wowyuarm/dsh-agent-team 0.1.0

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 (125) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +103 -0
  3. package/README.zh.md +103 -0
  4. package/cordis.patch.yml +36 -0
  5. package/package.json +152 -0
  6. package/packages/agent-team/README.md +58 -0
  7. package/packages/agent-team/README.zh.md +57 -0
  8. package/packages/agent-team/lib/index.js +841 -0
  9. package/packages/agent-team/lib/invariant.js +24 -0
  10. package/packages/agent-team/lib/ledger.js +2161 -0
  11. package/packages/agent-team/lib/member-context.js +69 -0
  12. package/packages/agent-team/lib/preset-roster.js +14 -0
  13. package/packages/agent-team/lib/spec.js +347 -0
  14. package/packages/agent-team/lib/typert.host.d.ts +3 -0
  15. package/packages/agent-team/lib/typert.host.js +2084 -0
  16. package/packages/agent-team/lib/typert.remote-client.d.ts +54 -0
  17. package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -0
  18. package/packages/agent-team/lib/typert.remote-client.js +1218 -0
  19. package/packages/agent-team/lib/types/index.d.ts +156 -0
  20. package/packages/agent-team/lib/types/index.d.ts.map +1 -0
  21. package/packages/agent-team/lib/types/invariant.d.ts +13 -0
  22. package/packages/agent-team/lib/types/invariant.d.ts.map +1 -0
  23. package/packages/agent-team/lib/types/ledger.d.ts +271 -0
  24. package/packages/agent-team/lib/types/ledger.d.ts.map +1 -0
  25. package/packages/agent-team/lib/types/member-context.d.ts +7 -0
  26. package/packages/agent-team/lib/types/member-context.d.ts.map +1 -0
  27. package/packages/agent-team/lib/types/preset-roster.d.ts +5 -0
  28. package/packages/agent-team/lib/types/preset-roster.d.ts.map +1 -0
  29. package/packages/agent-team/lib/types/spec.d.ts +13 -0
  30. package/packages/agent-team/lib/types/spec.d.ts.map +1 -0
  31. package/packages/agent-team/lib/types/types.d.ts +818 -0
  32. package/packages/agent-team/lib/types/types.d.ts.map +1 -0
  33. package/packages/agent-team/lib/types.js +1 -0
  34. package/packages/agent-team/preset/team-member/agent.cordis.yml +89 -0
  35. package/packages/client-agent-team/README.md +39 -0
  36. package/packages/client-agent-team/README.zh.md +39 -0
  37. package/packages/client-agent-team/lib/client.js +9633 -0
  38. package/packages/client-agent-team/lib/client.js.map +1 -0
  39. package/packages/client-agent-team/lib/index.js +5 -0
  40. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts +20 -0
  41. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -0
  42. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +274 -0
  43. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts +19 -0
  44. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -0
  45. package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +243 -0
  46. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts +21 -0
  47. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -0
  48. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +196 -0
  49. package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts +15 -0
  50. package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -0
  51. package/packages/client-agent-team/lib/types/client/TeamComposer.js +144 -0
  52. package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts +3 -0
  53. package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts.map +1 -0
  54. package/packages/client-agent-team/lib/types/client/TeamConversation.js +20 -0
  55. package/packages/client-agent-team/lib/types/client/TeamFooterAction.d.ts +3 -0
  56. package/packages/client-agent-team/lib/types/client/TeamFooterAction.d.ts.map +1 -0
  57. package/packages/client-agent-team/lib/types/client/TeamFooterAction.js +19 -0
  58. package/packages/client-agent-team/lib/types/client/TeamInboxPanel.d.ts +13 -0
  59. package/packages/client-agent-team/lib/types/client/TeamInboxPanel.d.ts.map +1 -0
  60. package/packages/client-agent-team/lib/types/client/TeamInboxPanel.js +90 -0
  61. package/packages/client-agent-team/lib/types/client/TeamMemberAvatar.d.ts +12 -0
  62. package/packages/client-agent-team/lib/types/client/TeamMemberAvatar.d.ts.map +1 -0
  63. package/packages/client-agent-team/lib/types/client/TeamMemberAvatar.js +15 -0
  64. package/packages/client-agent-team/lib/types/client/TeamMembersAction.d.ts +5 -0
  65. package/packages/client-agent-team/lib/types/client/TeamMembersAction.d.ts.map +1 -0
  66. package/packages/client-agent-team/lib/types/client/TeamMembersAction.js +32 -0
  67. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts +19 -0
  68. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -0
  69. package/packages/client-agent-team/lib/types/client/TeamMessage.js +24 -0
  70. package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts +11 -0
  71. package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts.map +1 -0
  72. package/packages/client-agent-team/lib/types/client/TeamPresenceDot.js +20 -0
  73. package/packages/client-agent-team/lib/types/client/TeamRowMenu.d.ts +15 -0
  74. package/packages/client-agent-team/lib/types/client/TeamRowMenu.d.ts.map +1 -0
  75. package/packages/client-agent-team/lib/types/client/TeamRowMenu.js +23 -0
  76. package/packages/client-agent-team/lib/types/client/TeamSettings.d.ts +3 -0
  77. package/packages/client-agent-team/lib/types/client/TeamSettings.d.ts.map +1 -0
  78. package/packages/client-agent-team/lib/types/client/TeamSettings.js +5 -0
  79. package/packages/client-agent-team/lib/types/client/TeamSidebarSection.d.ts +9 -0
  80. package/packages/client-agent-team/lib/types/client/TeamSidebarSection.d.ts.map +1 -0
  81. package/packages/client-agent-team/lib/types/client/TeamSidebarSection.js +9 -0
  82. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts +22 -0
  83. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -0
  84. package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +464 -0
  85. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts +3 -0
  86. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts.map +1 -0
  87. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +39 -0
  88. package/packages/client-agent-team/lib/types/client/TeamWorkspaceRow.d.ts +11 -0
  89. package/packages/client-agent-team/lib/types/client/TeamWorkspaceRow.d.ts.map +1 -0
  90. package/packages/client-agent-team/lib/types/client/TeamWorkspaceRow.js +6 -0
  91. package/packages/client-agent-team/lib/types/client/index.d.ts +19 -0
  92. package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -0
  93. package/packages/client-agent-team/lib/types/client/index.js +113 -0
  94. package/packages/client-agent-team/lib/types/client/locales.d.ts +224 -0
  95. package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -0
  96. package/packages/client-agent-team/lib/types/client/locales.js +222 -0
  97. package/packages/client-agent-team/lib/types/client/navigation.d.ts +39 -0
  98. package/packages/client-agent-team/lib/types/client/navigation.d.ts.map +1 -0
  99. package/packages/client-agent-team/lib/types/client/navigation.js +96 -0
  100. package/packages/client-agent-team/lib/types/client/slots.d.ts +74 -0
  101. package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -0
  102. package/packages/client-agent-team/lib/types/client/slots.js +1 -0
  103. package/packages/client-agent-team/lib/types/client/team-changes.d.ts +28 -0
  104. package/packages/client-agent-team/lib/types/client/team-changes.d.ts.map +1 -0
  105. package/packages/client-agent-team/lib/types/client/team-changes.js +70 -0
  106. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +53 -0
  107. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts.map +1 -0
  108. package/packages/client-agent-team/lib/types/client/team-formatters.js +128 -0
  109. package/packages/client-agent-team/lib/types/client/team-membership.d.ts +25 -0
  110. package/packages/client-agent-team/lib/types/client/team-membership.d.ts.map +1 -0
  111. package/packages/client-agent-team/lib/types/client/team-membership.js +39 -0
  112. package/packages/client-agent-team/lib/types/client/team-separators.d.ts +23 -0
  113. package/packages/client-agent-team/lib/types/client/team-separators.d.ts.map +1 -0
  114. package/packages/client-agent-team/lib/types/client/team-separators.js +52 -0
  115. package/packages/client-agent-team/lib/types/client/timeline-scroll.d.ts +18 -0
  116. package/packages/client-agent-team/lib/types/client/timeline-scroll.d.ts.map +1 -0
  117. package/packages/client-agent-team/lib/types/client/timeline-scroll.js +54 -0
  118. package/packages/client-agent-team/lib/types/index.d.ts +3 -0
  119. package/packages/client-agent-team/lib/types/index.d.ts.map +1 -0
  120. package/packages/client-agent-team/lib/types/index.js +2 -0
  121. package/packages/tool-agent-team/README.md +23 -0
  122. package/packages/tool-agent-team/README.zh.md +23 -0
  123. package/packages/tool-agent-team/lib/index.js +294 -0
  124. package/packages/tool-agent-team/lib/types/index.d.ts +5 -0
  125. package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -0
@@ -0,0 +1,5 @@
1
+ //#region lib/types/index.js
2
+ /** Host half: the Team UI has no Node-side behavior. */
3
+ function apply() {}
4
+ //#endregion
5
+ export { apply };
@@ -0,0 +1,20 @@
1
+ import type { AgentTeamAddMemberRequest } from '@wowyuarm/dsh-agent-team/types';
2
+ import type { WorkspaceId } from '@deepseek-ai/dsh-client-runtime/client';
3
+ import type { TeamSidebarProps } from './slots.ts';
4
+ interface TeamAgentsPanelProps {
5
+ readonly workspaceId: WorkspaceId;
6
+ readonly loadMembers: TeamSidebarProps['loadMembers'];
7
+ readonly subscribeChanges: TeamSidebarProps['subscribeChanges'];
8
+ readonly loadChannels: TeamSidebarProps['loadChannels'];
9
+ readonly addMember: TeamSidebarProps['addMember'];
10
+ readonly updateMember: TeamSidebarProps['updateMember'];
11
+ readonly joinChannel: TeamSidebarProps['joinChannel'];
12
+ readonly removeChannelMember: TeamSidebarProps['removeChannelMember'];
13
+ readonly loadModels: TeamSidebarProps['loadModels'];
14
+ readonly openMemberSession: TeamSidebarProps['openMemberSession'];
15
+ readonly onCreatingChange: (request: AgentTeamAddMemberRequest, creating: boolean) => void;
16
+ readonly t: TeamSidebarProps['t'];
17
+ }
18
+ export declare function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, loadChannels, addMember, updateMember, joinChannel, removeChannelMember, loadModels, openMemberSession, onCreatingChange, t }: TeamAgentsPanelProps): import("react").JSX.Element;
19
+ export {};
20
+ //# sourceMappingURL=TeamAgentsPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TeamAgentsPanel.d.ts","sourceRoot":"","sources":["../../../src/client/TeamAgentsPanel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,yBAAyB,EAM1B,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AAGzE,OAAO,KAAK,EAA0B,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAQ1E,UAAU,oBAAoB;IAC5B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAA;IACrD,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,kBAAkB,CAAC,CAAA;IAC/D,QAAQ,CAAC,YAAY,EAAE,gBAAgB,CAAC,cAAc,CAAC,CAAA;IACvD,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAC,WAAW,CAAC,CAAA;IACjD,QAAQ,CAAC,YAAY,EAAE,gBAAgB,CAAC,cAAc,CAAC,CAAA;IACvD,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAA;IACrD,QAAQ,CAAC,mBAAmB,EAAE,gBAAgB,CAAC,qBAAqB,CAAC,CAAA;IACrE,QAAQ,CAAC,UAAU,EAAE,gBAAgB,CAAC,YAAY,CAAC,CAAA;IACnD,QAAQ,CAAC,iBAAiB,EAAE,gBAAgB,CAAC,mBAAmB,CAAC,CAAA;IACjE,QAAQ,CAAC,gBAAgB,EAAE,CAAC,OAAO,EAAE,yBAAyB,EAAE,QAAQ,EAAE,OAAO,KAAK,IAAI,CAAA;IAC1F,QAAQ,CAAC,CAAC,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAA;CAClC;AAED,wBAAgB,eAAe,CAAC,EAAE,WAAW,EAAE,WAAW,EAAE,gBAAgB,EAAE,YAAY,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,mBAAmB,EAAE,UAAU,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,CAAC,EAAE,EAAE,oBAAoB,+BA8JhO"}
@@ -0,0 +1,274 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useRef, useState } from 'react';
3
+ import { Button, IconChevronDownOutline14, IconEditOutline16, IconPlusOutline16, Input, Menu, Modal, Tooltip } from '@deepseek-ai/dsh-client-ui-primitives';
4
+ import { TeamMemberAvatar } from "./TeamMemberAvatar.js";
5
+ import { TeamRowMenu } from "./TeamRowMenu.js";
6
+ import { TeamSidebarSection } from "./TeamSidebarSection.js";
7
+ import { useChannelMembership } from "./team-membership.js";
8
+ import createCss from './create.module.css';
9
+ import css from './sidebar.module.css';
10
+ export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, loadChannels, addMember, updateMember, joinChannel, removeChannelMember, loadModels, openMemberSession, onCreatingChange, t }) {
11
+ const [members, setMembers] = useState([]);
12
+ const [channels, setChannels] = useState([]);
13
+ const [channelRefs, setChannelRefs] = useState([]);
14
+ const [loading, setLoading] = useState(true);
15
+ const [error, setError] = useState();
16
+ const [formOpen, setFormOpen] = useState(false);
17
+ const [handle, setHandle] = useState('');
18
+ const [description, setDescription] = useState('');
19
+ const [creating, setCreating] = useState(false);
20
+ const [retryRequest, setRetryRequest] = useState();
21
+ const triggerRef = useRef(null);
22
+ const refresh = useCallback(async () => {
23
+ setLoading(true);
24
+ const result = await loadMembers({ workspaceId });
25
+ if (result.ok) {
26
+ setMembers(result.value);
27
+ setError(undefined);
28
+ }
29
+ else {
30
+ setError(result.error.message);
31
+ }
32
+ setLoading(false);
33
+ }, [loadMembers, workspaceId]);
34
+ useEffect(() => { void refresh(); }, [refresh]);
35
+ const loadWorkspaceChannels = useCallback(async () => {
36
+ const result = await loadChannels({ workspaceId, topLevelOnly: true, includeActivities: false, limit: 1 });
37
+ if (result.ok)
38
+ setChannels(result.value.channels);
39
+ }, [loadChannels, workspaceId]);
40
+ useEffect(() => { void loadWorkspaceChannels(); }, [loadWorkspaceChannels]);
41
+ useEffect(() => subscribeChanges({ kind: 'workspace', workspaceId }, update => {
42
+ if (update.type === 'failed') {
43
+ setError(update.message);
44
+ return;
45
+ }
46
+ // The section stays mounted across Channel creation, so both the Member
47
+ // roster and the Channel list ride every workspace invalidation.
48
+ void refresh();
49
+ void loadWorkspaceChannels();
50
+ }), [subscribeChanges, refresh, loadWorkspaceChannels, workspaceId]);
51
+ const closeForm = () => {
52
+ if (creating)
53
+ return;
54
+ setFormOpen(false);
55
+ queueMicrotask(() => { triggerRef.current?.focus(); });
56
+ };
57
+ const provision = async (request) => {
58
+ setCreating(true);
59
+ onCreatingChange(request, true);
60
+ setError(undefined);
61
+ setRetryRequest(request);
62
+ try {
63
+ const result = await addMember(request);
64
+ if (result.ok) {
65
+ setMembers(current => {
66
+ const retained = current.filter(status => status.member.memberId !== result.value.status.member.memberId);
67
+ return [...retained, result.value.status];
68
+ });
69
+ setHandle('');
70
+ setDescription('');
71
+ setChannelRefs([]);
72
+ setFormOpen(false);
73
+ if (result.value.status.presence === 'unavailable') {
74
+ setError(result.value.status.diagnostic ?? t('statusUnavailable'));
75
+ }
76
+ else {
77
+ setRetryRequest(undefined);
78
+ }
79
+ queueMicrotask(() => { triggerRef.current?.focus(); });
80
+ }
81
+ else {
82
+ await refresh();
83
+ setError(result.error.message);
84
+ }
85
+ }
86
+ catch (cause) {
87
+ setError(cause instanceof Error ? cause.message : String(cause));
88
+ }
89
+ finally {
90
+ setCreating(false);
91
+ onCreatingChange(request, false);
92
+ }
93
+ };
94
+ const submit = (event) => {
95
+ event.preventDefault();
96
+ const normalizedHandle = handle.trim();
97
+ const normalizedDescription = description.trim();
98
+ if (normalizedHandle.length === 0 || normalizedDescription.length === 0 || creating)
99
+ return;
100
+ if (channelRefs.length === 0)
101
+ return;
102
+ const sameRequest = retryRequest !== undefined && retryRequest.workspaceId === workspaceId
103
+ && retryRequest.handle === normalizedHandle && retryRequest.description === normalizedDescription
104
+ && retryRequest.channelRefs.length === channelRefs.length && retryRequest.channelRefs.every(ref => channelRefs.includes(ref));
105
+ void provision(sameRequest ? retryRequest : {
106
+ requestId: crypto.randomUUID(),
107
+ workspaceId,
108
+ handle: normalizedHandle,
109
+ description: normalizedDescription,
110
+ presetId: 'team-member',
111
+ channelRefs,
112
+ });
113
+ };
114
+ return (_jsxs("div", { className: css.panel, children: [_jsx(Modal, { open: formOpen, onClose: closeForm, title: t('addAgent'), closeLabel: t('close'), contentClassName: createCss.dialogContent, footer: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", disabled: creating, onClick: closeForm, children: t('cancel') }), _jsx(Button, { type: "submit", form: "team-agent-create-form", variant: "primary", disabled: creating || handle.trim().length === 0 || description.trim().length === 0 || channelRefs.length === 0, children: creating ? t('creatingAgent') : t('createAgent') })] }), children: _jsxs("form", { id: "team-agent-create-form", className: createCss.form, onSubmit: submit, children: [_jsxs("label", { className: createCss.field, children: [_jsx("span", { children: t('agentName') }), _jsx(Input, { className: createCss.input, value: handle, onChange: event => { setHandle(event.target.value); setRetryRequest(undefined); }, disabled: creating, autoFocus: true })] }), _jsxs("label", { className: createCss.field, children: [_jsx("span", { children: t('agentDescription') }), _jsx(Input, { className: createCss.input, value: description, onChange: event => { setDescription(event.target.value); setRetryRequest(undefined); }, disabled: creating })] }), _jsxs("fieldset", { className: createCss.field, children: [_jsx("legend", { children: t('initialChannels') }), channels.length === 0 ? _jsx("span", { children: t('noChannelsForAgent') }) : channels.map(channel => _jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: channelRefs.includes(channel.channelRef), disabled: creating, onChange: event => {
115
+ setChannelRefs(current => event.target.checked ? [...current, channel.channelRef] : current.filter(ref => ref !== channel.channelRef));
116
+ setRetryRequest(undefined);
117
+ } }), " ", channel.name] }, channel.channelRef))] }), formOpen && error !== undefined && _jsx("p", { className: createCss.error, role: "alert", children: error })] }) }), _jsxs(TeamSidebarSection, { title: t('agents'), actions: (_jsx(Tooltip, { label: t('addAgent'), delayMs: 500, children: _jsx("button", { ref: triggerRef, type: "button", className: css.iconButton, "aria-label": t('addAgent'), onClick: () => { setError(undefined); setFormOpen(true); }, children: _jsx(IconPlusOutline16, { size: 14 }) }) })), children: [loading && members.length === 0 && _jsx("p", { className: css.emptyState, children: t('loadingAgents') }), !loading && members.length === 0 && _jsx("p", { className: css.emptyState, children: t('emptyAgents') }), _jsx("div", { className: css.agentList, children: members.map(status => (_jsx(AgentRow, { status: status, channels: channels, loadChannels: loadChannels, updateMember: updateMember, joinChannel: joinChannel, removeChannelMember: removeChannelMember, loadModels: loadModels, openMemberSession: openMemberSession, onUpdated: () => { void refresh(); }, t: t }, status.member.memberId))) })] }), !formOpen && error !== undefined && (_jsxs("div", { className: css.retryError, role: "alert", children: [_jsx("span", { children: error }), retryRequest !== undefined && _jsx("button", { type: "button", className: css.textButton, disabled: creating, onClick: () => { setFormOpen(true); }, children: t('retry') })] }))] }));
118
+ }
119
+ /**
120
+ * One sidebar Agent row: the select button opens the Member's own Session
121
+ * conversation page, the avatar carries identity plus the presence badge, and
122
+ * the row menu opens the editor.
123
+ */
124
+ function AgentRow({ status, channels, loadChannels, updateMember, joinChannel, removeChannelMember, loadModels, openMemberSession, onUpdated, t }) {
125
+ const [menuOpen, setMenuOpen] = useState(false);
126
+ const [editing, setEditing] = useState(false);
127
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: css.agentRow, "data-menu-open": menuOpen || undefined, children: [_jsxs("button", { type: "button", className: css.agentSelect, "aria-label": t('openAgentSession', { name: status.member.handle }), onClick: () => { openMemberSession(status.member.sessionId); }, children: [_jsx(TeamMemberAvatar, { status: status, t: t }), _jsxs("span", { className: css.agentCopy, children: [_jsx("strong", { children: status.member.handle }), _jsx("small", { children: status.member.description })] })] }), _jsx("span", { className: css.rowMenu, children: _jsx(TeamRowMenu, { label: t('actionsAgent', { name: status.member.handle }), items: [{ id: 'edit', label: t('editAgent'), icon: _jsx(IconEditOutline16, {}) }], onSelect: () => { setEditing(true); }, onOpenChange: setMenuOpen }) })] }), editing && (_jsx(AgentEditorDialog, { status: status, channels: channels, loadChannels: loadChannels, updateMember: updateMember, joinChannel: joinChannel, removeChannelMember: removeChannelMember, loadModels: loadModels, onCommitted: onUpdated, onClose: () => { setEditing(false); }, t: t }))] }));
128
+ }
129
+ /** Model option key inside one editor; opaque and resolved against the loaded groups. */
130
+ function modelKey(provider, model) {
131
+ return `${provider}\u0000${model}`;
132
+ }
133
+ /**
134
+ * Agent editor: handle, description, and per-Member model selection commit
135
+ * through one durable update; Channel membership below keeps its own
136
+ * immediate add/remove flow.
137
+ */
138
+ function AgentEditorDialog({ status, channels, loadChannels, updateMember, joinChannel, removeChannelMember, loadModels, onCommitted, onClose, t }) {
139
+ const memberId = status.member.memberId;
140
+ const [handle, setHandle] = useState(status.member.handle);
141
+ const [description, setDescription] = useState(status.member.description);
142
+ const [model, setModel] = useState(status.member.model === undefined ? undefined : [status.member.model.provider, status.member.model.model]);
143
+ const [groups, setGroups] = useState();
144
+ const [modelsError, setModelsError] = useState();
145
+ const [modelOpen, setModelOpen] = useState(false);
146
+ const [saving, setSaving] = useState(false);
147
+ const [error, setError] = useState();
148
+ const pendingRequest = useRef();
149
+ // Membership comes from a fresh projection at open time so stale checkbox
150
+ // state never flips a committed fact backwards.
151
+ const [memberships, setMemberships] = useState();
152
+ const [loadError, setLoadError] = useState();
153
+ useEffect(() => {
154
+ let mounted = true;
155
+ void loadModels().then(result => {
156
+ if (!mounted)
157
+ return;
158
+ if (result.ok) {
159
+ setGroups(result.value.groups);
160
+ setModelsError(undefined);
161
+ }
162
+ else {
163
+ setModelsError(result.error.message);
164
+ }
165
+ });
166
+ return () => { mounted = false; };
167
+ }, [loadModels]);
168
+ useEffect(() => {
169
+ let mounted = true;
170
+ void loadChannels({ workspaceId: status.member.workspaceId, topLevelOnly: true, includeActivities: false, limit: 1 }).then(result => {
171
+ if (!mounted)
172
+ return;
173
+ if (result.ok) {
174
+ setMemberships(result.value.members);
175
+ setLoadError(undefined);
176
+ }
177
+ else {
178
+ setLoadError(result.error.message);
179
+ }
180
+ });
181
+ return () => { mounted = false; };
182
+ }, [loadChannels, status.member.workspaceId]);
183
+ const membership = useChannelMembership({ joinChannel, removeChannelMember }, change => change.channelRef, change => {
184
+ setMemberships(current => {
185
+ const base = current ?? [];
186
+ return change.joined
187
+ ? base.filter(item => !(item.channelRef === change.channelRef && item.memberId === change.memberId))
188
+ : [...base, { channelRef: change.channelRef, memberId: change.memberId }];
189
+ });
190
+ });
191
+ // A successful join appends the durable fact; removal filters it out above.
192
+ const joinedChannels = new Set((memberships ?? []).filter(item => item.memberId === memberId).map(item => item.channelRef));
193
+ // The provider-grouped option list rides the shared Menu primitive: one
194
+ // leading "follow Host default" row, then a non-selectable heading per
195
+ // provider group. Keys resolve back through the map, never by parsing.
196
+ const modelChoices = (() => {
197
+ const items = [{ id: '', label: t('modelFollowDefault') }];
198
+ const byKey = new Map();
199
+ for (const group of groups ?? []) {
200
+ items.push({ type: 'label', id: `model-group:${group.id}`, text: group.name });
201
+ for (const entry of group.models) {
202
+ const key = modelKey(group.id, entry.id);
203
+ byKey.set(key, { provider: group.id, id: entry.id, name: entry.name });
204
+ items.push({ id: key, label: entry.name });
205
+ }
206
+ }
207
+ return { items, byKey };
208
+ })();
209
+ const selectedModelKey = model === undefined ? '' : modelKey(model[0], model[1]);
210
+ const modelTriggerLabel = model === undefined
211
+ ? t('modelFollowDefault')
212
+ : modelChoices.byKey.get(selectedModelKey)?.name ?? `${model[0]} / ${model[1]}`;
213
+ const dirty = handle.trim() !== status.member.handle || description.trim() !== status.member.description
214
+ || !sameModel(model, status.member.model === undefined ? undefined : [status.member.model.provider, status.member.model.model]);
215
+ const submit = async (event) => {
216
+ event.preventDefault();
217
+ const normalizedHandle = handle.trim();
218
+ const normalizedDescription = description.trim();
219
+ if (saving || !dirty || normalizedHandle.length === 0 || normalizedDescription.length === 0)
220
+ return;
221
+ const payload = {
222
+ memberId,
223
+ handle: normalizedHandle,
224
+ description: normalizedDescription,
225
+ ...(model === undefined ? {} : { model: { provider: model[0], model: model[1] } }),
226
+ };
227
+ const samePending = pendingRequest.current !== undefined && pendingRequest.current.memberId === payload.memberId
228
+ && pendingRequest.current.handle === payload.handle && pendingRequest.current.description === payload.description
229
+ && sameModel(pendingRequest.current.model === undefined ? undefined : [pendingRequest.current.model.provider, pendingRequest.current.model.model], model);
230
+ const request = samePending ? pendingRequest.current : {
231
+ requestId: crypto.randomUUID(),
232
+ ...payload,
233
+ };
234
+ pendingRequest.current = request;
235
+ setSaving(true);
236
+ setError(undefined);
237
+ try {
238
+ const result = await updateMember(request);
239
+ if (result.ok) {
240
+ pendingRequest.current = undefined;
241
+ await onCommitted();
242
+ onClose();
243
+ }
244
+ else {
245
+ setError(result.error.message);
246
+ }
247
+ }
248
+ catch (cause) {
249
+ setError(cause instanceof Error ? cause.message : String(cause));
250
+ }
251
+ finally {
252
+ setSaving(false);
253
+ }
254
+ };
255
+ return (_jsx(Modal, { open: true, onClose: onClose, title: t('editAgent'), description: `@${status.member.handle}`, closeLabel: t('close'), contentClassName: createCss.dialogContent, footer: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", disabled: saving, onClick: onClose, children: t('cancel') }), _jsx(Button, { type: "submit", form: "team-agent-edit-form", variant: "primary", disabled: saving || !dirty || handle.trim().length === 0 || description.trim().length === 0, children: saving ? t('editSaving') : t('editSave') })] }), children: _jsxs("form", { id: "team-agent-edit-form", className: createCss.form, onSubmit: event => { void submit(event); }, children: [_jsxs("label", { className: createCss.field, children: [_jsx("span", { children: t('agentName') }), _jsx(Input, { className: createCss.input, value: handle, onChange: event => { setHandle(event.target.value); pendingRequest.current = undefined; }, disabled: saving, autoFocus: true })] }), _jsxs("label", { className: createCss.field, children: [_jsx("span", { children: t('agentDescription') }), _jsx(Input, { className: createCss.input, value: description, onChange: event => { setDescription(event.target.value); pendingRequest.current = undefined; }, disabled: saving })] }), _jsxs("div", { className: createCss.field, children: [_jsx("span", { children: t('memberModel') }), groups === undefined && modelsError === undefined && _jsx("small", { className: css.editHint, children: t('modelsLoading') }), modelsError !== undefined && _jsx("small", { className: css.editHint, children: t('modelsLoadFailed', { message: modelsError }) }), groups !== undefined && (_jsx(Menu, { open: modelOpen, portal: true, items: modelChoices.items, selectedId: selectedModelKey, onSelect: (key) => {
256
+ setModelOpen(false);
257
+ pendingRequest.current = undefined;
258
+ const choice = modelChoices.byKey.get(key);
259
+ setModel(choice === undefined ? undefined : [choice.provider, choice.id]);
260
+ }, onClose: () => { setModelOpen(false); }, anchor: _jsxs("button", { type: "button", className: createCss.selectTrigger, "aria-label": t('memberModel'), "aria-haspopup": "listbox", "aria-expanded": modelOpen, disabled: saving, onClick: () => { setModelOpen(value => !value); }, children: [_jsx("span", { className: createCss.selectValue, children: modelTriggerLabel }), _jsx("span", { className: `${createCss.chevron} ${modelOpen ? createCss.chevronOpen : ''}`, "aria-hidden": true, children: _jsx(IconChevronDownOutline14, {}) })] }) }))] }), _jsxs("fieldset", { className: createCss.memberPicker, disabled: saving, children: [_jsx("legend", { children: t('channelMembersSection') }), channels.map(channel => {
261
+ const joined = joinedChannels.has(channel.channelRef);
262
+ const rowPending = membership.pending.has(channel.channelRef);
263
+ const disabled = rowPending || (!joined && status.presence === 'unavailable');
264
+ const rowError = membership.errors.get(channel.channelRef);
265
+ return _jsxs("div", { className: `${css.editMemberRow} ${css.editMemberRowChannels}`, children: [_jsxs("strong", { className: css.editChannelName, children: ["# ", channel.name] }), _jsx(Button, { size: "sm", variant: "outline", disabled: disabled, onClick: () => { void membership.change({ workspaceId: status.member.workspaceId, channelRef: channel.channelRef, memberId, joined }); }, children: rowPending ? t('membershipUpdating') : joined ? t('removeFromChannel') : t('addToChannel') }), rowError !== undefined && _jsx("p", { className: css.rowError, role: "alert", children: rowError })] }, channel.channelRef);
266
+ }), channels.length === 0 && _jsx("small", { children: t('noChannelsForAgent') }), loadError !== undefined && _jsx("p", { className: css.rowError, role: "alert", children: loadError })] }), error !== undefined && _jsx("p", { className: createCss.error, role: "alert", children: error })] }) }));
267
+ }
268
+ function sameModel(left, right) {
269
+ if (left === undefined && right === undefined)
270
+ return true;
271
+ if (left === undefined || right === undefined)
272
+ return false;
273
+ return left[0] === right[0] && left[1] === right[1];
274
+ }
@@ -0,0 +1,19 @@
1
+ import type { AgentTeamChannelRef } from '@wowyuarm/dsh-agent-team/types';
2
+ import type { WorkspaceId } from '@deepseek-ai/dsh-client-runtime/client';
3
+ import type { TeamConversationProps } from './slots.ts';
4
+ interface TeamChannelPageProps {
5
+ readonly workspaceId: WorkspaceId;
6
+ readonly channelRef: AgentTeamChannelRef;
7
+ readonly loadChannels: TeamConversationProps['loadChannels'];
8
+ readonly subscribeChanges: TeamConversationProps['subscribeChanges'];
9
+ readonly loadMembers: TeamConversationProps['loadMembers'];
10
+ readonly sendMessage: TeamConversationProps['sendMessage'];
11
+ readonly joinChannel: TeamConversationProps['joinChannel'];
12
+ readonly removeChannelMember: TeamConversationProps['removeChannelMember'];
13
+ readonly selectThread: TeamConversationProps['selectThread'];
14
+ readonly backToChannels: TeamConversationProps['backToChannels'];
15
+ readonly t: TeamConversationProps['t'];
16
+ }
17
+ export declare function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscribeChanges, loadMembers, sendMessage, joinChannel, removeChannelMember, selectThread, backToChannels, t }: TeamChannelPageProps): import("react").JSX.Element;
18
+ export {};
19
+ //# sourceMappingURL=TeamChannelPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TeamChannelPage.d.ts","sourceRoot":"","sources":["../../../src/client/TeamChannelPage.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAA+B,mBAAmB,EAAoF,MAAM,gCAAgC,CAAA;AACxL,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AAEzE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAYvD,UAAU,oBAAoB;IAC5B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,QAAQ,CAAC,UAAU,EAAE,mBAAmB,CAAA;IACxC,QAAQ,CAAC,YAAY,EAAE,qBAAqB,CAAC,cAAc,CAAC,CAAA;IAC5D,QAAQ,CAAC,gBAAgB,EAAE,qBAAqB,CAAC,kBAAkB,CAAC,CAAA;IACpE,QAAQ,CAAC,WAAW,EAAE,qBAAqB,CAAC,aAAa,CAAC,CAAA;IAC1D,QAAQ,CAAC,WAAW,EAAE,qBAAqB,CAAC,aAAa,CAAC,CAAA;IAC1D,QAAQ,CAAC,WAAW,EAAE,qBAAqB,CAAC,aAAa,CAAC,CAAA;IAC1D,QAAQ,CAAC,mBAAmB,EAAE,qBAAqB,CAAC,qBAAqB,CAAC,CAAA;IAC1E,QAAQ,CAAC,YAAY,EAAE,qBAAqB,CAAC,cAAc,CAAC,CAAA;IAC5D,QAAQ,CAAC,cAAc,EAAE,qBAAqB,CAAC,gBAAgB,CAAC,CAAA;IAChE,QAAQ,CAAC,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC,CAAA;CACvC;AAmBD,wBAAgB,eAAe,CAAC,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,gBAAgB,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,mBAAmB,EAAE,YAAY,EAAE,cAAc,EAAE,CAAC,EAAE,EAAE,oBAAoB,+BA+P7M"}
@@ -0,0 +1,243 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from 'react';
3
+ import { Button, IconChevronLeftOutline14, IconChevronRightOutline14, Modal, StateDot } from '@deepseek-ai/dsh-client-ui-primitives';
4
+ import { TeamComposer } from "./TeamComposer.js";
5
+ import { TeamPresenceDot } from "./TeamPresenceDot.js";
6
+ import { TeamMessage } from "./TeamMessage.js";
7
+ import { formatTaskStatus, taskStatusDot, mentionNamesOf } from "./team-formatters.js";
8
+ import { useChannelMembership } from "./team-membership.js";
9
+ import { useTimelineScroll } from "./timeline-scroll.js";
10
+ import { chunkRunsWithDays } from "./team-separators.js";
11
+ import channelCss from './channel.module.css';
12
+ import css from './conversation.module.css';
13
+ import threadCss from './thread.module.css';
14
+ /**
15
+ * Merge the freshest top-level window over what the reader already has:
16
+ * a change-driven refresh must not discard older messages loaded earlier.
17
+ */
18
+ function mergeChannelView(current, fresh) {
19
+ const known = new Set(current.items.map(item => item.message.messageRef));
20
+ const items = [...fresh.items.filter(item => !known.has(item.message.messageRef)), ...current.items]
21
+ .sort((left, right) => left.message.sequence - right.message.sequence);
22
+ return {
23
+ ...fresh,
24
+ items,
25
+ cursor: Math.min(fresh.cursor, current.cursor),
26
+ // Older retained items may precede even a saturated fresh window.
27
+ hasMore: fresh.hasMore || current.cursor < fresh.cursor,
28
+ };
29
+ }
30
+ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscribeChanges, loadMembers, sendMessage, joinChannel, removeChannelMember, selectThread, backToChannels, t }) {
31
+ const [view, setView] = useState();
32
+ const [members, setMembers] = useState([]);
33
+ const [draft, setDraft] = useState('');
34
+ const [error, setError] = useState();
35
+ const [statusMessage, setStatusMessage] = useState();
36
+ const [loading, setLoading] = useState(true);
37
+ const [pending, setPending] = useState(false);
38
+ const [loadingOlder, setLoadingOlder] = useState(false);
39
+ const [recipients, setRecipients] = useState(new Set());
40
+ const [managingMembers, setManagingMembers] = useState(false);
41
+ const manageTriggerRef = useRef(null);
42
+ const memberListRef = useRef(null);
43
+ const mountedRef = useRef(false);
44
+ const refreshSequenceRef = useRef(0);
45
+ const channelLastItem = view?.items[view.items.length - 1];
46
+ const timeline = useTimelineScroll(`${view?.items.length ?? 0}:${channelLastItem?.message.messageRef ?? ''}`);
47
+ const channel = view?.channels.find(item => item.channelRef === channelRef);
48
+ const channelMemberIds = new Set(view?.members.filter(item => item.channelRef === channelRef).map(item => item.memberId) ?? []);
49
+ const channelMembers = members.filter(status => channelMemberIds.has(status.member.memberId) && status.member.state !== 'inactive');
50
+ // Presence counts ride the header meta line; error and unavailable do not count as online.
51
+ const onlineCount = channelMembers.filter(status => status.presence === 'available' || status.presence === 'working').length;
52
+ const messageSender = (item) => item.message.sender;
53
+ const mentionHandlesMap = new Map(members.map(status => [status.member.memberId, status.member.handle.replace(/^@/, '')]));
54
+ const refresh = async (clearError = false) => {
55
+ if (!mountedRef.current)
56
+ return false;
57
+ const sequence = refreshSequenceRef.current + 1;
58
+ refreshSequenceRef.current = sequence;
59
+ setLoading(true);
60
+ if (clearError) {
61
+ setError(undefined);
62
+ setStatusMessage(undefined);
63
+ }
64
+ try {
65
+ const [loaded, loadedMembers] = await Promise.all([
66
+ loadChannels({ workspaceId, channelRef, direction: 'before', topLevelOnly: true, includeActivities: false, limit: 20 }),
67
+ loadMembers({ workspaceId }),
68
+ ]);
69
+ if (!mountedRef.current || sequence !== refreshSequenceRef.current)
70
+ return false;
71
+ if (loaded.ok)
72
+ setView(current => current === undefined ? loaded.value : mergeChannelView(current, loaded.value));
73
+ else
74
+ setError(loaded.error.message);
75
+ if (loadedMembers.ok)
76
+ setMembers(loadedMembers.value);
77
+ else
78
+ setError(loadedMembers.error.message);
79
+ return loaded.ok && loadedMembers.ok;
80
+ }
81
+ catch (cause) {
82
+ if (mountedRef.current && sequence === refreshSequenceRef.current)
83
+ setError(cause instanceof Error ? cause.message : String(cause));
84
+ return false;
85
+ }
86
+ finally {
87
+ if (mountedRef.current && sequence === refreshSequenceRef.current)
88
+ setLoading(false);
89
+ }
90
+ };
91
+ // Presence and membership live in the workspace projection; they never need
92
+ // the Channel timeline refetch that a full refresh performs.
93
+ const refreshMembers = async () => {
94
+ if (!mountedRef.current)
95
+ return;
96
+ try {
97
+ const loaded = await loadMembers({ workspaceId });
98
+ if (!mountedRef.current)
99
+ return;
100
+ if (loaded.ok)
101
+ setMembers(loaded.value);
102
+ else
103
+ setError(loaded.error.message);
104
+ }
105
+ catch (cause) {
106
+ if (mountedRef.current)
107
+ setError(cause instanceof Error ? cause.message : String(cause));
108
+ }
109
+ };
110
+ useEffect(() => {
111
+ mountedRef.current = true;
112
+ setView(undefined);
113
+ setError(undefined);
114
+ setLoading(true);
115
+ setRecipients(new Set());
116
+ setManagingMembers(false);
117
+ void refresh();
118
+ const disposers = [
119
+ subscribeChanges({ kind: 'channel', channelRef }, update => {
120
+ if (!mountedRef.current)
121
+ return;
122
+ if (update.type === 'failed') {
123
+ setError(update.message);
124
+ return;
125
+ }
126
+ void refresh();
127
+ }),
128
+ subscribeChanges({ kind: 'workspace', workspaceId }, update => {
129
+ if (!mountedRef.current)
130
+ return;
131
+ if (update.type === 'failed') {
132
+ setError(update.message);
133
+ return;
134
+ }
135
+ void refreshMembers();
136
+ }),
137
+ ];
138
+ return () => {
139
+ mountedRef.current = false;
140
+ refreshSequenceRef.current += 1;
141
+ for (const dispose of disposers)
142
+ dispose();
143
+ };
144
+ }, [workspaceId, channelRef]);
145
+ useEffect(() => {
146
+ if (!managingMembers)
147
+ return;
148
+ queueMicrotask(() => { memberListRef.current?.querySelector('button')?.focus(); });
149
+ }, [managingMembers]);
150
+ const loadOlder = async () => {
151
+ if (view === undefined || !view.hasMore || loadingOlder)
152
+ return;
153
+ setLoadingOlder(true);
154
+ try {
155
+ const result = await loadChannels({ workspaceId, channelRef, direction: 'before', topLevelOnly: true, includeActivities: false, cursor: view.cursor, limit: 20 });
156
+ if (!mountedRef.current)
157
+ return;
158
+ if (!result.ok) {
159
+ setError(result.error.message);
160
+ return;
161
+ }
162
+ setView(current => current === undefined ? result.value : mergeChannelView(current, result.value));
163
+ }
164
+ catch (cause) {
165
+ if (mountedRef.current)
166
+ setError(cause instanceof Error ? cause.message : String(cause));
167
+ }
168
+ finally {
169
+ if (mountedRef.current)
170
+ setLoadingOlder(false);
171
+ }
172
+ };
173
+ const closeMembers = () => {
174
+ setManagingMembers(false);
175
+ queueMicrotask(() => { manageTriggerRef.current?.querySelector('button')?.focus(); });
176
+ };
177
+ const membership = useChannelMembership({ joinChannel, removeChannelMember }, change => change.memberId, async () => { await refresh(); });
178
+ // Retained across transport failures so a replayed send dedupes on the Host;
179
+ // definitive outcomes (committed or rejected) start the next send fresh.
180
+ const pendingSendId = useRef();
181
+ const send = async () => {
182
+ if (pending || draft.trim() === '')
183
+ return;
184
+ const recipientIds = [...recipients].sort();
185
+ const requestId = pendingSendId.current ?? crypto.randomUUID();
186
+ pendingSendId.current = requestId;
187
+ const request = {
188
+ requestId, workspaceId,
189
+ channelRef, body: draft.trim(), recipients: recipientIds,
190
+ };
191
+ setPending(true);
192
+ setError(undefined);
193
+ setStatusMessage(undefined);
194
+ try {
195
+ const result = await sendMessage(request);
196
+ if (!result.ok) {
197
+ pendingSendId.current = undefined;
198
+ setError(result.error.message);
199
+ }
200
+ else if (result.value.kind === 'committed') {
201
+ pendingSendId.current = undefined;
202
+ await refresh();
203
+ setDraft('');
204
+ setRecipients(new Set());
205
+ setStatusMessage(undefined);
206
+ }
207
+ else if (result.value.kind === 'confirmation_required') {
208
+ // Same-requestId resend continues the pending operation.
209
+ setStatusMessage(t('mentionConfirmation'));
210
+ }
211
+ else {
212
+ // Rejections are final for this draft; editing it must mint a new operation.
213
+ pendingSendId.current = undefined;
214
+ setError(t('memberNotFollowing', { ids: result.value.memberIds.map(memberId => `@${members.find(candidate => candidate.member.memberId === memberId)?.member.handle ?? memberId}`).join(', ') }));
215
+ }
216
+ }
217
+ catch (cause) {
218
+ setError(cause instanceof Error ? cause.message : String(cause));
219
+ }
220
+ finally {
221
+ setPending(false);
222
+ }
223
+ };
224
+ return _jsxs("main", { className: css.surface, "data-team-channel": channelRef, children: [_jsxs("div", { className: css.surfaceHeader, children: [_jsx("div", { className: css.backRow, children: _jsx(Button, { size: "sm", icon: _jsx(IconChevronLeftOutline14, {}), onClick: backToChannels, children: t('backToChannels') }) }), _jsxs("header", { className: css.headerRow, children: [_jsxs("div", { className: css.headerCopy, children: [_jsx("h1", { children: channel === undefined ? '# …' : `# ${channel.name}` }), channel !== undefined && _jsx("p", { children: channel.description }), channel !== undefined && _jsxs("div", { className: channelCss.headerMeta, children: [_jsx("span", { children: t('memberCount', { count: channelMembers.length }) }), _jsx("span", { children: t('onlineCount', { count: onlineCount }) })] })] }), channel !== undefined && _jsx("span", { ref: manageTriggerRef, children: _jsx(Button, { size: "sm", variant: "outline", "aria-haspopup": "dialog", onClick: () => { setManagingMembers(true); }, children: t('manageMembers') }) })] })] }), _jsx(Modal, { open: managingMembers, onClose: closeMembers, title: t('channelMembers'), ...(channel === undefined ? {} : { description: `# ${channel.name} · ${t('memberCount', { count: channelMembers.length })}` }), closeLabel: t('close'), contentClassName: channelCss.modalBody, children: _jsx("div", { ref: memberListRef, className: channelCss.memberList, children: members.filter(status => status.member.state !== 'inactive').map(status => {
225
+ const joined = channelMemberIds.has(status.member.memberId);
226
+ const rowPending = membership.pending.has(status.member.memberId);
227
+ const disabled = rowPending || (!joined && status.presence === 'unavailable');
228
+ const rowError = membership.errors.get(status.member.memberId);
229
+ return _jsxs("div", { className: channelCss.memberRow, children: [_jsx(TeamPresenceDot, { status: status, t: t }), _jsxs("span", { className: channelCss.memberCopy, children: [_jsxs("strong", { children: ["@", status.member.handle] }), _jsx("small", { children: status.member.description })] }), _jsx(Button, { className: channelCss.memberAction, size: "sm", disabled: disabled, onClick: () => { void membership.change({ workspaceId, channelRef, memberId: status.member.memberId, joined }); }, children: rowPending ? t('membershipUpdating') : joined ? t('removeFromChannel') : t('addToChannel') }), rowError !== undefined && _jsx("p", { className: channelCss.memberError, role: "alert", children: rowError })] }, status.member.memberId);
230
+ }) }) }), _jsx("section", { ref: timeline.ref, onScroll: timeline.onScroll, className: css.timeline, "aria-label": t('timelineLabel'), children: _jsxs("div", { className: css.timelineContent, children: [loading && channel === undefined && error === undefined && _jsx("div", { className: css.emptySurface, children: _jsxs("p", { className: css.loadingState, children: [_jsx("span", { className: css.loadingMark, "aria-hidden": "true" }), t('loadingChannels')] }) }), !loading && channel === undefined && error === undefined && _jsx("div", { className: css.emptySurface, children: _jsx("p", { className: css.emptyState, children: t('emptyChannels') }) }), !loading && channel === undefined && error !== undefined && _jsxs("div", { className: css.errorState, role: "alert", children: [_jsx("span", { children: error }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => { void refresh(true); }, children: t('retry') })] }), view?.hasMore && _jsx("div", { className: css.timelineAction, children: _jsx(Button, { size: "sm", disabled: loadingOlder, onClick: () => { void loadOlder(); }, children: t('loadOlder') }) }), channel !== undefined && view?.items.length === 0 && _jsx("div", { className: css.emptySurface, children: _jsxs("div", { className: css.emptyState, children: [_jsx("strong", { children: t('emptyMessages') }), _jsx("span", { children: t('emptyMessagesHint') })] }) }), (view?.items.length ?? 0) > 0 && chunkRunsWithDays(view.items, messageSender, item => item.message.occurredAt).map((block, blockIndex) => block.kind === 'day'
231
+ ? _jsx("p", { className: threadCss.daySeparator, children: _jsx("span", { children: block.label }) }, `day-${blockIndex}-${block.label}`)
232
+ : _jsx("div", { className: css.messageRun, children: block.items.map((item, index) => {
233
+ const senderStatus = members.find(member => member.member.memberId === item.message.sender);
234
+ const human = item.message.sender === view.humanMemberId;
235
+ const sender = human ? t('human') : senderStatus?.member.handle ?? item.message.sender;
236
+ return _jsx(TeamMessage, { senderName: sender, memberId: item.message.sender, human: human, body: item.message.body, occurredAt: item.message.occurredAt, mentionNames: mentionNamesOf(item.mentions, mentionHandlesMap), grouped: index > 0, ...(senderStatus === undefined ? {} : { senderTitle: senderStatus.member.description }), children: item.message.topLevel && _jsxs("button", { type: "button", className: channelCss.taskFooter, "aria-label": t('openTask', { number: item.taskNumber }), onClick: () => { selectThread(item.task.taskRef, item.thread.threadRef, channelRef, item.taskNumber); }, children: [_jsx("span", { className: channelCss.taskDot, children: (() => {
237
+ const dot = taskStatusDot(item.task.status);
238
+ return dot === 'ongoing' || dot === 'warning' || dot === 'done'
239
+ ? _jsx(StateDot, { size: 8, state: dot })
240
+ : _jsx("span", { className: channelCss.taskDotQuiet, "data-variant": dot });
241
+ })() }), _jsx("span", { className: channelCss.taskNumber, children: t('taskLabel', { number: item.taskNumber }) }), _jsx("span", { className: channelCss.taskStatus, children: formatTaskStatus(item.task.status, t) }), _jsx("span", { className: channelCss.taskCount, children: t('taskMessageCount', { count: item.messageCount }) }), _jsx("span", { className: channelCss.taskArrow, "aria-hidden": "true", children: _jsx(IconChevronRightOutline14, { size: 12 }) })] }) }, item.message.messageRef);
242
+ }) }, `run-${block.items[0].message.messageRef}`))] }) }), channel !== undefined ? _jsx(TeamComposer, { members: channelMembers, recipients: recipients, draft: draft, pending: pending, ...(statusMessage === undefined ? {} : { confirmation: statusMessage }), ...(error === undefined ? {} : { error }), onDraftChange: next => { setDraft(next); setStatusMessage(undefined); }, onRecipientsChange: next => { setRecipients(next); setStatusMessage(undefined); }, onSubmit: () => { void send(); }, t: t }) : _jsx("div", {})] });
243
+ }
@@ -0,0 +1,21 @@
1
+ import type { AgentTeamAddMemberRequest, AgentTeamChannelRef } from '@wowyuarm/dsh-agent-team/types';
2
+ import type { WorkspaceId } from '@deepseek-ai/dsh-client-runtime/client';
3
+ import type { TeamSidebarProps } from './slots.ts';
4
+ interface TeamChannelsPanelProps {
5
+ readonly workspaceId: WorkspaceId;
6
+ readonly loadMembers: TeamSidebarProps['loadMembers'];
7
+ readonly loadChannels: TeamSidebarProps['loadChannels'];
8
+ /** Membership and channel facts change outside this panel; the workspace scope keeps the list fresh. */
9
+ readonly subscribeChanges: TeamSidebarProps['subscribeChanges'];
10
+ readonly createChannel: TeamSidebarProps['createChannel'];
11
+ readonly updateChannel: TeamSidebarProps['updateChannel'];
12
+ readonly joinChannel: TeamSidebarProps['joinChannel'];
13
+ readonly removeChannelMember: TeamSidebarProps['removeChannelMember'];
14
+ readonly creatingAgents: readonly AgentTeamAddMemberRequest[];
15
+ readonly selectedChannelRef?: AgentTeamChannelRef;
16
+ readonly selectChannel: TeamSidebarProps['selectChannel'];
17
+ readonly t: TeamSidebarProps['t'];
18
+ }
19
+ export declare function TeamChannelsPanel(props: TeamChannelsPanelProps): import("react").JSX.Element;
20
+ export {};
21
+ //# sourceMappingURL=TeamChannelsPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TeamChannelsPanel.d.ts","sourceRoot":"","sources":["../../../src/client/TeamChannelsPanel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,yBAAyB,EAEzB,mBAAmB,EAOpB,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AAEzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAQlD,UAAU,sBAAsB;IAC9B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAA;IACrD,QAAQ,CAAC,YAAY,EAAE,gBAAgB,CAAC,cAAc,CAAC,CAAA;IACvD,wGAAwG;IACxG,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,kBAAkB,CAAC,CAAA;IAC/D,QAAQ,CAAC,aAAa,EAAE,gBAAgB,CAAC,eAAe,CAAC,CAAA;IACzD,QAAQ,CAAC,aAAa,EAAE,gBAAgB,CAAC,eAAe,CAAC,CAAA;IACzD,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAA;IACrD,QAAQ,CAAC,mBAAmB,EAAE,gBAAgB,CAAC,qBAAqB,CAAC,CAAA;IACrE,QAAQ,CAAC,cAAc,EAAE,SAAS,yBAAyB,EAAE,CAAA;IAC7D,QAAQ,CAAC,kBAAkB,CAAC,EAAE,mBAAmB,CAAA;IACjD,QAAQ,CAAC,aAAa,EAAE,gBAAgB,CAAC,eAAe,CAAC,CAAA;IACzD,QAAQ,CAAC,CAAC,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAA;CAClC;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,sBAAsB,+BAuL9D"}