@wowyuarm/dsh-agent-team 0.1.0 → 0.1.1

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 (81) hide show
  1. package/README.md +14 -2
  2. package/README.zh.md +10 -2
  3. package/package.json +2 -2
  4. package/packages/agent-team/lib/attachments.js +157 -0
  5. package/packages/agent-team/lib/index.js +274 -5
  6. package/packages/agent-team/lib/ledger.js +65 -13
  7. package/packages/agent-team/lib/recovery.js +87 -0
  8. package/packages/agent-team/lib/spec.js +17 -1
  9. package/packages/agent-team/lib/typert.host.js +321 -21
  10. package/packages/agent-team/lib/typert.remote-client.d.ts +9 -1
  11. package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
  12. package/packages/agent-team/lib/typert.remote-client.js +249 -17
  13. package/packages/agent-team/lib/types/attachments.d.ts +69 -0
  14. package/packages/agent-team/lib/types/attachments.d.ts.map +1 -0
  15. package/packages/agent-team/lib/types/index.d.ts +66 -1
  16. package/packages/agent-team/lib/types/index.d.ts.map +1 -1
  17. package/packages/agent-team/lib/types/ledger.d.ts +17 -1
  18. package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
  19. package/packages/agent-team/lib/types/recovery.d.ts +42 -0
  20. package/packages/agent-team/lib/types/recovery.d.ts.map +1 -0
  21. package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
  22. package/packages/agent-team/lib/types/types.d.ts +85 -2
  23. package/packages/agent-team/lib/types/types.d.ts.map +1 -1
  24. package/packages/client-agent-team/README.md +2 -2
  25. package/packages/client-agent-team/README.zh.md +2 -2
  26. package/packages/client-agent-team/lib/client.js +1176 -281
  27. package/packages/client-agent-team/lib/client.js.map +1 -1
  28. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts +6 -2
  29. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
  30. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +104 -58
  31. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts +5 -1
  32. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
  33. package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +44 -19
  34. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -1
  35. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +23 -18
  36. package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts +4 -1
  37. package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -1
  38. package/packages/client-agent-team/lib/types/client/TeamComposer.js +40 -4
  39. package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts +1 -1
  40. package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts.map +1 -1
  41. package/packages/client-agent-team/lib/types/client/TeamConversation.js +3 -3
  42. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts +7 -2
  43. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
  44. package/packages/client-agent-team/lib/types/client/TeamMessage.js +34 -8
  45. package/packages/client-agent-team/lib/types/client/TeamRunDivider.d.ts +10 -0
  46. package/packages/client-agent-team/lib/types/client/TeamRunDivider.d.ts.map +1 -0
  47. package/packages/client-agent-team/lib/types/client/TeamRunDivider.js +12 -0
  48. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts +3 -0
  49. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
  50. package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +18 -11
  51. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts +1 -1
  52. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts.map +1 -1
  53. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +6 -5
  54. package/packages/client-agent-team/lib/types/client/attachment-preview.d.ts +22 -0
  55. package/packages/client-agent-team/lib/types/client/attachment-preview.d.ts.map +1 -0
  56. package/packages/client-agent-team/lib/types/client/attachment-preview.js +35 -0
  57. package/packages/client-agent-team/lib/types/client/drafts.d.ts +41 -0
  58. package/packages/client-agent-team/lib/types/client/drafts.d.ts.map +1 -0
  59. package/packages/client-agent-team/lib/types/client/drafts.js +144 -0
  60. package/packages/client-agent-team/lib/types/client/index.d.ts +2 -0
  61. package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
  62. package/packages/client-agent-team/lib/types/client/index.js +50 -10
  63. package/packages/client-agent-team/lib/types/client/locales.d.ts +28 -0
  64. package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
  65. package/packages/client-agent-team/lib/types/client/locales.js +28 -0
  66. package/packages/client-agent-team/lib/types/client/multi-menu-field.d.ts +33 -0
  67. package/packages/client-agent-team/lib/types/client/multi-menu-field.d.ts.map +1 -0
  68. package/packages/client-agent-team/lib/types/client/multi-menu-field.js +30 -0
  69. package/packages/client-agent-team/lib/types/client/navigation.d.ts +16 -1
  70. package/packages/client-agent-team/lib/types/client/navigation.d.ts.map +1 -1
  71. package/packages/client-agent-team/lib/types/client/navigation.js +45 -8
  72. package/packages/client-agent-team/lib/types/client/slots.d.ts +18 -2
  73. package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
  74. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +2 -0
  75. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts.map +1 -1
  76. package/packages/client-agent-team/lib/types/client/team-formatters.js +4 -0
  77. package/packages/client-agent-team/lib/types/client/team-separators.d.ts +8 -0
  78. package/packages/client-agent-team/lib/types/client/team-separators.d.ts.map +1 -1
  79. package/packages/client-agent-team/lib/types/client/team-separators.js +14 -0
  80. package/packages/tool-agent-team/lib/index.js +6 -2
  81. package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
@@ -1,4 +1,4 @@
1
- import type { AgentTeamAddMemberRequest } from '@wowyuarm/dsh-agent-team/types';
1
+ import type { AgentTeamAddMemberRequest, AgentTeamClientMemberStatus } from '@wowyuarm/dsh-agent-team/types';
2
2
  import type { WorkspaceId } from '@deepseek-ai/dsh-client-runtime/client';
3
3
  import type { TeamSidebarProps } from './slots.ts';
4
4
  interface TeamAgentsPanelProps {
@@ -8,13 +8,17 @@ interface TeamAgentsPanelProps {
8
8
  readonly loadChannels: TeamSidebarProps['loadChannels'];
9
9
  readonly addMember: TeamSidebarProps['addMember'];
10
10
  readonly updateMember: TeamSidebarProps['updateMember'];
11
+ readonly recoverMember: TeamSidebarProps['recoverMember'];
12
+ readonly restartMember: TeamSidebarProps['restartMember'];
11
13
  readonly joinChannel: TeamSidebarProps['joinChannel'];
12
14
  readonly removeChannelMember: TeamSidebarProps['removeChannelMember'];
13
15
  readonly loadModels: TeamSidebarProps['loadModels'];
16
+ /** The Member Session currently embedded in the conversation seat, if any. */
17
+ readonly memberSessionId?: AgentTeamClientMemberStatus['member']['sessionId'];
14
18
  readonly openMemberSession: TeamSidebarProps['openMemberSession'];
15
19
  readonly onCreatingChange: (request: AgentTeamAddMemberRequest, creating: boolean) => void;
16
20
  readonly t: TeamSidebarProps['t'];
17
21
  }
18
- export declare function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, loadChannels, addMember, updateMember, joinChannel, removeChannelMember, loadModels, openMemberSession, onCreatingChange, t }: TeamAgentsPanelProps): import("react").JSX.Element;
22
+ export declare function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, loadChannels, addMember, updateMember, recoverMember, restartMember, joinChannel, removeChannelMember, loadModels, memberSessionId, openMemberSession, onCreatingChange, t }: TeamAgentsPanelProps): import("react").JSX.Element;
19
23
  export {};
20
24
  //# sourceMappingURL=TeamAgentsPanel.d.ts.map
@@ -1 +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"}
1
+ {"version":3,"file":"TeamAgentsPanel.d.ts","sourceRoot":"","sources":["../../../src/client/TeamAgentsPanel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,yBAAyB,EAEzB,2BAA2B,EAM5B,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AAGzE,OAAO,KAAK,EAAiD,gBAAgB,EAAE,MAAM,YAAY,CAAA;AASjG,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,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,UAAU,EAAE,gBAAgB,CAAC,YAAY,CAAC,CAAA;IACnD,8EAA8E;IAC9E,QAAQ,CAAC,eAAe,CAAC,EAAE,2BAA2B,CAAC,QAAQ,CAAC,CAAC,WAAW,CAAC,CAAA;IAC7E,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,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,mBAAmB,EAAE,UAAU,EAAE,eAAe,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,CAAC,EAAE,EAAE,oBAAoB,+BAmK/Q"}
@@ -1,13 +1,14 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
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';
3
+ import { Button, IconChevronDownOutline14, IconEditOutline16, IconPlayOutline16, IconPlusOutline16, IconRefreshOutline14, Input, Menu, Modal, Tooltip } from '@deepseek-ai/dsh-client-ui-primitives';
4
4
  import { TeamMemberAvatar } from "./TeamMemberAvatar.js";
5
5
  import { TeamRowMenu } from "./TeamRowMenu.js";
6
6
  import { TeamSidebarSection } from "./TeamSidebarSection.js";
7
+ import { MultiMenuField } from "./multi-menu-field.js";
7
8
  import { useChannelMembership } from "./team-membership.js";
8
9
  import createCss from './create.module.css';
9
10
  import css from './sidebar.module.css';
10
- export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, loadChannels, addMember, updateMember, joinChannel, removeChannelMember, loadModels, openMemberSession, onCreatingChange, t }) {
11
+ export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, loadChannels, addMember, updateMember, recoverMember, restartMember, joinChannel, removeChannelMember, loadModels, memberSessionId, openMemberSession, onCreatingChange, t }) {
11
12
  const [members, setMembers] = useState([]);
12
13
  const [channels, setChannels] = useState([]);
13
14
  const [channelRefs, setChannelRefs] = useState([]);
@@ -16,6 +17,7 @@ export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, lo
16
17
  const [formOpen, setFormOpen] = useState(false);
17
18
  const [handle, setHandle] = useState('');
18
19
  const [description, setDescription] = useState('');
20
+ const [model, setModel] = useState(undefined);
19
21
  const [creating, setCreating] = useState(false);
20
22
  const [retryRequest, setRetryRequest] = useState();
21
23
  const triggerRef = useRef(null);
@@ -68,6 +70,7 @@ export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, lo
68
70
  });
69
71
  setHandle('');
70
72
  setDescription('');
73
+ setModel(undefined);
71
74
  setChannelRefs([]);
72
75
  setFormOpen(false);
73
76
  if (result.value.status.presence === 'unavailable') {
@@ -95,12 +98,11 @@ export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, lo
95
98
  event.preventDefault();
96
99
  const normalizedHandle = handle.trim();
97
100
  const normalizedDescription = description.trim();
98
- if (normalizedHandle.length === 0 || normalizedDescription.length === 0 || creating)
99
- return;
100
- if (channelRefs.length === 0)
101
+ if (normalizedHandle.length === 0 || creating)
101
102
  return;
102
103
  const sameRequest = retryRequest !== undefined && retryRequest.workspaceId === workspaceId
103
104
  && retryRequest.handle === normalizedHandle && retryRequest.description === normalizedDescription
105
+ && sameModel(retryRequest.model, model)
104
106
  && retryRequest.channelRefs.length === channelRefs.length && retryRequest.channelRefs.every(ref => channelRefs.includes(ref));
105
107
  void provision(sameRequest ? retryRequest : {
106
108
  requestId: crypto.randomUUID(),
@@ -109,47 +111,66 @@ export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, lo
109
111
  description: normalizedDescription,
110
112
  presetId: 'team-member',
111
113
  channelRefs,
114
+ ...(model === undefined ? {} : { model }),
112
115
  });
113
116
  };
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') })] }))] }));
117
+ 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, 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: [_jsxs("span", { children: [t('agentDescription'), t('optionalSuffix')] }), _jsx(Input, { className: createCss.input, value: description, placeholder: t('agentDescriptionPlaceholder'), onChange: event => { setDescription(event.target.value); setRetryRequest(undefined); }, disabled: creating })] }), _jsx(ModelPickerField, { model: model, onModelChange: choice => { setModel(choice); setRetryRequest(undefined); }, loadModels: loadModels, disabled: creating, t: t }), _jsx(MultiMenuField, { label: `${t('initialChannels')}${t('optionalSuffix')}`, disabled: creating, options: channels.map(channel => ({ id: channel.channelRef, label: channel.name })), selected: channelRefs, onToggle: ref => {
118
+ setChannelRefs(current => current.includes(ref) ? current.filter(item => item !== ref) : [...current, ref]);
119
+ setRetryRequest(undefined);
120
+ }, emptyText: t('noChannelsForAgent'), triggerEmptyLabel: t('channelsPickerEmpty'), formatCount: count => t('channelsPickerCount', { count }) }), 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, ...(memberSessionId === undefined ? {} : { current: status.member.sessionId === memberSessionId }), channels: channels, loadChannels: loadChannels, updateMember: updateMember, recoverMember: recoverMember, restartMember: restartMember, 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
121
  }
119
122
  /**
120
123
  * One sidebar Agent row: the select button opens the Member's own Session
121
124
  * conversation page, the avatar carries identity plus the presence badge, and
122
125
  * the row menu opens the editor.
123
126
  */
124
- function AgentRow({ status, channels, loadChannels, updateMember, joinChannel, removeChannelMember, loadModels, openMemberSession, onUpdated, t }) {
127
+ function AgentRow({ status, current, channels, loadChannels, updateMember, recoverMember, restartMember, joinChannel, removeChannelMember, loadModels, openMemberSession, onUpdated, t }) {
125
128
  const [menuOpen, setMenuOpen] = useState(false);
126
129
  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 }))] }));
130
+ const resume = async () => {
131
+ await recoverMember({
132
+ requestId: crypto.randomUUID(),
133
+ workspaceId: status.member.workspaceId,
134
+ memberId: status.member.memberId,
135
+ });
136
+ await onUpdated();
137
+ };
138
+ const restart = async () => {
139
+ await restartMember({
140
+ requestId: crypto.randomUUID(),
141
+ workspaceId: status.member.workspaceId,
142
+ memberId: status.member.memberId,
143
+ });
144
+ await onUpdated();
145
+ };
146
+ 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 }), "aria-current": current ? 'page' : undefined, 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: [
147
+ { id: 'edit', label: t('editAgent'), icon: _jsx(IconEditOutline16, {}) },
148
+ ...(status.presence === 'error' ? [{ id: 'resume', label: t('resumeAgent'), icon: _jsx(IconPlayOutline16, {}) }] : []),
149
+ { id: 'restart', label: t('restartAgent'), icon: _jsx(IconRefreshOutline14, {}) },
150
+ ], onSelect: (id) => {
151
+ if (id === 'resume')
152
+ void resume();
153
+ else if (id === 'restart')
154
+ void restart();
155
+ else
156
+ setEditing(true);
157
+ }, 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
158
  }
129
159
  /** Model option key inside one editor; opaque and resolved against the loaded groups. */
130
160
  function modelKey(provider, model) {
131
161
  return `${provider}\u0000${model}`;
132
162
  }
133
163
  /**
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.
164
+ * Shared provider/model dropdown for the create and edit forms. The option
165
+ * list rides the shared Menu primitive (one leading "follow Host default"
166
+ * row, then non-selectable provider headings) with a capped, internally
167
+ * scrolling card so growing model catalogs cannot stretch the dialog.
137
168
  */
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]);
169
+ function ModelPickerField({ model, onModelChange, loadModels, disabled, t }) {
143
170
  const [groups, setGroups] = useState();
144
171
  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();
172
+ const [open, setModelOpen] = useState(false);
173
+ const [effortOpen, setEffortOpen] = useState(false);
153
174
  useEffect(() => {
154
175
  let mounted = true;
155
176
  void loadModels().then(result => {
@@ -165,6 +186,56 @@ function AgentEditorDialog({ status, channels, loadChannels, updateMember, joinC
165
186
  });
166
187
  return () => { mounted = false; };
167
188
  }, [loadModels]);
189
+ const items = [{ id: '', label: t('modelFollowDefault') }];
190
+ const byKey = new Map();
191
+ for (const group of groups ?? []) {
192
+ items.push({ type: 'label', id: `model-group:${group.id}`, text: group.name });
193
+ for (const entry of group.models) {
194
+ const key = modelKey(group.id, entry.id);
195
+ byKey.set(key, { provider: group.id, id: entry.id, name: entry.name, efforts: entry.reasoning?.efforts ?? [] });
196
+ items.push({ id: key, label: entry.name });
197
+ }
198
+ }
199
+ const selectedModelKey = model === undefined ? '' : modelKey(model.provider, model.model);
200
+ const triggerLabel = model === undefined
201
+ ? t('modelFollowDefault')
202
+ : byKey.get(selectedModelKey)?.name ?? `${model.provider} / ${model.model}`;
203
+ // The effort sub-row only makes sense for a pinned model with adapter-exposed
204
+ // efforts; following the Host default inherits the operator's whole selection.
205
+ const efforts = model === undefined ? [] : byKey.get(selectedModelKey)?.efforts ?? [];
206
+ const effortItems = [{ id: '', label: t('effortFollowDefault') }, ...efforts.map(effort => ({ id: effort.id, label: effort.name }))];
207
+ const selectedEffort = model?.reasoningEffort ?? '';
208
+ const effortTriggerLabel = model === undefined || selectedEffort === ''
209
+ ? t('effortFollowDefault')
210
+ : efforts.find(effort => effort.id === selectedEffort)?.name ?? selectedEffort;
211
+ return _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: open, portal: true, className: createCss.menuCap, items: items, selectedId: selectedModelKey, onSelect: key => {
212
+ setModelOpen(false);
213
+ const choice = byKey.get(key);
214
+ onModelChange(choice === undefined ? undefined : { provider: choice.provider, model: choice.id });
215
+ }, onClose: () => { setModelOpen(false); }, anchor: _jsxs("button", { type: "button", className: createCss.selectTrigger, "aria-label": t('memberModel'), "aria-haspopup": "listbox", "aria-expanded": open, disabled: disabled, onClick: () => { setModelOpen(value => !value); }, children: [_jsx("span", { className: createCss.selectValue, children: triggerLabel }), _jsx("span", { className: `${createCss.chevron} ${open ? createCss.chevronOpen : ''}`, "aria-hidden": true, children: _jsx(IconChevronDownOutline14, {}) })] }) })), model !== undefined && efforts.length > 0 && (_jsx(Menu, { open: effortOpen, portal: true, className: createCss.menuCap, items: effortItems, selectedId: selectedEffort, onSelect: key => {
216
+ setEffortOpen(false);
217
+ onModelChange(key === ''
218
+ ? { provider: model.provider, model: model.model }
219
+ : { provider: model.provider, model: model.model, reasoningEffort: key });
220
+ }, onClose: () => { setEffortOpen(false); }, anchor: _jsxs("button", { type: "button", className: createCss.selectTrigger, "aria-label": t('reasoningEffort'), "aria-haspopup": "listbox", "aria-expanded": effortOpen, disabled: disabled, onClick: () => { setEffortOpen(value => !value); }, children: [_jsx("span", { className: createCss.selectValue, children: `${t('reasoningEffort')} · ${effortTriggerLabel}` }), _jsx("span", { className: `${createCss.chevron} ${effortOpen ? createCss.chevronOpen : ''}`, "aria-hidden": true, children: _jsx(IconChevronDownOutline14, {}) })] }) }))] });
221
+ }
222
+ /**
223
+ * Agent editor: handle, description, and per-Member model selection commit
224
+ * through one durable update; Channel membership below keeps its own
225
+ * immediate add/remove flow.
226
+ */
227
+ function AgentEditorDialog({ status, channels, loadChannels, updateMember, joinChannel, removeChannelMember, loadModels, onCommitted, onClose, t }) {
228
+ const memberId = status.member.memberId;
229
+ const [handle, setHandle] = useState(status.member.handle);
230
+ const [description, setDescription] = useState(status.member.description);
231
+ const [model, setModel] = useState(status.member.model);
232
+ const [saving, setSaving] = useState(false);
233
+ const [error, setError] = useState();
234
+ const pendingRequest = useRef();
235
+ // Membership comes from a fresh projection at open time so stale checkbox
236
+ // state never flips a committed fact backwards.
237
+ const [memberships, setMemberships] = useState();
238
+ const [loadError, setLoadError] = useState();
168
239
  useEffect(() => {
169
240
  let mounted = true;
170
241
  void loadChannels({ workspaceId: status.member.workspaceId, topLevelOnly: true, includeActivities: false, limit: 1 }).then(result => {
@@ -190,43 +261,23 @@ function AgentEditorDialog({ status, channels, loadChannels, updateMember, joinC
190
261
  });
191
262
  // A successful join appends the durable fact; removal filters it out above.
192
263
  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
264
  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]);
265
+ || !sameModel(model, status.member.model);
215
266
  const submit = async (event) => {
216
267
  event.preventDefault();
217
268
  const normalizedHandle = handle.trim();
218
269
  const normalizedDescription = description.trim();
219
- if (saving || !dirty || normalizedHandle.length === 0 || normalizedDescription.length === 0)
270
+ if (saving || !dirty || normalizedHandle.length === 0)
220
271
  return;
221
272
  const payload = {
222
273
  memberId,
223
274
  handle: normalizedHandle,
224
275
  description: normalizedDescription,
225
- ...(model === undefined ? {} : { model: { provider: model[0], model: model[1] } }),
276
+ ...(model === undefined ? {} : { model }),
226
277
  };
227
278
  const samePending = pendingRequest.current !== undefined && pendingRequest.current.memberId === payload.memberId
228
279
  && 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);
280
+ && sameModel(pendingRequest.current.model, model);
230
281
  const request = samePending ? pendingRequest.current : {
231
282
  requestId: crypto.randomUUID(),
232
283
  ...payload,
@@ -252,12 +303,7 @@ function AgentEditorDialog({ status, channels, loadChannels, updateMember, joinC
252
303
  setSaving(false);
253
304
  }
254
305
  };
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 => {
306
+ 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, 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: [_jsxs("span", { children: [t('agentDescription'), t('optionalSuffix')] }), _jsx(Input, { className: createCss.input, value: description, placeholder: t('agentDescriptionPlaceholder'), onChange: event => { setDescription(event.target.value); pendingRequest.current = undefined; }, disabled: saving })] }), _jsx(ModelPickerField, { model: model, onModelChange: choice => { pendingRequest.current = undefined; setModel(choice); }, loadModels: loadModels, disabled: saving, t: t }), _jsxs("fieldset", { className: createCss.memberPicker, disabled: saving, children: [_jsx("legend", { children: t('channelMembersSection') }), channels.map(channel => {
261
307
  const joined = joinedChannels.has(channel.channelRef);
262
308
  const rowPending = membership.pending.has(channel.channelRef);
263
309
  const disabled = rowPending || (!joined && status.presence === 'unavailable');
@@ -270,5 +316,5 @@ function sameModel(left, right) {
270
316
  return true;
271
317
  if (left === undefined || right === undefined)
272
318
  return false;
273
- return left[0] === right[0] && left[1] === right[1];
319
+ return left.provider === right.provider && left.model === right.model && left.reasoningEffort === right.reasoningEffort;
274
320
  }
@@ -1,12 +1,16 @@
1
1
  import type { AgentTeamChannelRef } from '@wowyuarm/dsh-agent-team/types';
2
2
  import type { WorkspaceId } from '@deepseek-ai/dsh-client-runtime/client';
3
3
  import type { TeamConversationProps } from './slots.ts';
4
+ import type { TeamDraftStore } from './drafts.ts';
4
5
  interface TeamChannelPageProps {
5
6
  readonly workspaceId: WorkspaceId;
6
7
  readonly channelRef: AgentTeamChannelRef;
7
8
  readonly loadChannels: TeamConversationProps['loadChannels'];
8
9
  readonly subscribeChanges: TeamConversationProps['subscribeChanges'];
9
10
  readonly loadMembers: TeamConversationProps['loadMembers'];
11
+ readonly drafts: TeamDraftStore;
12
+ readonly getAttachment: TeamConversationProps['getAttachment'];
13
+ readonly putAttachment: TeamConversationProps['putAttachment'];
10
14
  readonly sendMessage: TeamConversationProps['sendMessage'];
11
15
  readonly joinChannel: TeamConversationProps['joinChannel'];
12
16
  readonly removeChannelMember: TeamConversationProps['removeChannelMember'];
@@ -14,6 +18,6 @@ interface TeamChannelPageProps {
14
18
  readonly backToChannels: TeamConversationProps['backToChannels'];
15
19
  readonly t: TeamConversationProps['t'];
16
20
  }
17
- export declare function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscribeChanges, loadMembers, sendMessage, joinChannel, removeChannelMember, selectThread, backToChannels, t }: TeamChannelPageProps): import("react").JSX.Element;
21
+ export declare function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscribeChanges, loadMembers, drafts, putAttachment, getAttachment, sendMessage, joinChannel, removeChannelMember, selectThread, backToChannels, t }: TeamChannelPageProps): import("react").JSX.Element;
18
22
  export {};
19
23
  //# sourceMappingURL=TeamChannelPage.d.ts.map
@@ -1 +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"}
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;AAGvD,OAAO,KAAK,EAAgB,cAAc,EAAE,MAAM,aAAa,CAAA;AAa/D,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,MAAM,EAAE,cAAc,CAAA;IAC/B,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,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,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,WAAW,EAAE,mBAAmB,EAAE,YAAY,EAAE,cAAc,EAAE,CAAC,EAAE,EAAE,oBAAoB,+BA8RnP"}
@@ -1,13 +1,15 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useRef, useState } from 'react';
2
+ import { Fragment, useEffect, useRef, useState, useSyncExternalStore } from 'react';
3
3
  import { Button, IconChevronLeftOutline14, IconChevronRightOutline14, Modal, StateDot } from '@deepseek-ai/dsh-client-ui-primitives';
4
+ import { bytesToBase64 } from "./attachment-preview.js";
4
5
  import { TeamComposer } from "./TeamComposer.js";
5
6
  import { TeamPresenceDot } from "./TeamPresenceDot.js";
6
7
  import { TeamMessage } from "./TeamMessage.js";
8
+ import { TeamRunDivider } from "./TeamRunDivider.js";
7
9
  import { formatTaskStatus, taskStatusDot, mentionNamesOf } from "./team-formatters.js";
8
10
  import { useChannelMembership } from "./team-membership.js";
9
11
  import { useTimelineScroll } from "./timeline-scroll.js";
10
- import { chunkRunsWithDays } from "./team-separators.js";
12
+ import { chunkRunsWithDays, isRunGap } from "./team-separators.js";
11
13
  import channelCss from './channel.module.css';
12
14
  import css from './conversation.module.css';
13
15
  import threadCss from './thread.module.css';
@@ -27,17 +29,20 @@ function mergeChannelView(current, fresh) {
27
29
  hasMore: fresh.hasMore || current.cursor < fresh.cursor,
28
30
  };
29
31
  }
30
- export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscribeChanges, loadMembers, sendMessage, joinChannel, removeChannelMember, selectThread, backToChannels, t }) {
32
+ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscribeChanges, loadMembers, drafts, putAttachment, getAttachment, sendMessage, joinChannel, removeChannelMember, selectThread, backToChannels, t }) {
31
33
  const [view, setView] = useState();
32
34
  const [members, setMembers] = useState([]);
33
- const [draft, setDraft] = useState('');
34
35
  const [error, setError] = useState();
36
+ const [pendingFiles, setPendingFiles] = useState([]);
35
37
  const [statusMessage, setStatusMessage] = useState();
36
38
  const [loading, setLoading] = useState(true);
37
39
  const [pending, setPending] = useState(false);
38
40
  const [loadingOlder, setLoadingOlder] = useState(false);
39
- const [recipients, setRecipients] = useState(new Set());
40
41
  const [managingMembers, setManagingMembers] = useState(false);
42
+ // The composer draft lives in the keyed draft cache: view switches unmount
43
+ // this page, and a refresh must not cost the half-written message either.
44
+ const draftKey = `channel:${channelRef}`;
45
+ const { draft, recipients } = useSyncExternalStore(drafts.subscribe, () => drafts.getSnapshot(draftKey));
41
46
  const manageTriggerRef = useRef(null);
42
47
  const memberListRef = useRef(null);
43
48
  const mountedRef = useRef(false);
@@ -112,7 +117,6 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
112
117
  setView(undefined);
113
118
  setError(undefined);
114
119
  setLoading(true);
115
- setRecipients(new Set());
116
120
  setManagingMembers(false);
117
121
  void refresh();
118
122
  const disposers = [
@@ -179,19 +183,39 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
179
183
  // definitive outcomes (committed or rejected) start the next send fresh.
180
184
  const pendingSendId = useRef();
181
185
  const send = async () => {
186
+ console.log('DBG send entry pending=%s draft=%j files=%d', pending, draft, pendingFiles.length);
182
187
  if (pending || draft.trim() === '')
183
188
  return;
184
189
  const recipientIds = [...recipients].sort();
185
190
  const requestId = pendingSendId.current ?? crypto.randomUUID();
186
191
  pendingSendId.current = requestId;
187
- const request = {
188
- requestId, workspaceId,
189
- channelRef, body: draft.trim(), recipients: recipientIds,
190
- };
191
192
  setPending(true);
192
193
  setError(undefined);
193
194
  setStatusMessage(undefined);
194
195
  try {
196
+ // Upload chosen files first; any failure aborts the send with the
197
+ // existing error surface and keeps the chips for a retry.
198
+ const attachmentIds = [];
199
+ for (const file of pendingFiles) {
200
+ const uploaded = await putAttachment({
201
+ requestId: crypto.randomUUID(),
202
+ workspaceId,
203
+ name: file.name,
204
+ mediaType: file.type === '' ? undefined : file.type,
205
+ bytesBase64: bytesToBase64(new Uint8Array(await file.arrayBuffer())),
206
+ });
207
+ if (!uploaded.ok) {
208
+ pendingSendId.current = undefined;
209
+ setError(uploaded.error.message);
210
+ return;
211
+ }
212
+ attachmentIds.push(uploaded.value.attachmentId);
213
+ }
214
+ const request = {
215
+ requestId, workspaceId,
216
+ channelRef, body: draft.trim(), recipients: recipientIds,
217
+ ...(attachmentIds.length === 0 ? {} : { attachments: attachmentIds }),
218
+ };
195
219
  const result = await sendMessage(request);
196
220
  if (!result.ok) {
197
221
  pendingSendId.current = undefined;
@@ -200,8 +224,8 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
200
224
  else if (result.value.kind === 'committed') {
201
225
  pendingSendId.current = undefined;
202
226
  await refresh();
203
- setDraft('');
204
- setRecipients(new Set());
227
+ drafts.clear(draftKey);
228
+ setPendingFiles([]);
205
229
  setStatusMessage(undefined);
206
230
  }
207
231
  else if (result.value.kind === 'confirmation_required') {
@@ -233,11 +257,12 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
233
257
  const senderStatus = members.find(member => member.member.memberId === item.message.sender);
234
258
  const human = item.message.sender === view.humanMemberId;
235
259
  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", {})] });
260
+ return _jsxs(Fragment, { children: [isRunGap(index > 0 ? block.items[index - 1].message.occurredAt : undefined, item.message.occurredAt)
261
+ && _jsx(TeamRunDivider, { occurredAt: item.message.occurredAt }), _jsx(TeamMessage, { senderName: sender, memberId: item.message.sender, human: human, body: item.message.body, attachments: item.message.attachments, loadAttachment: getAttachment, t: t, 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: (() => {
262
+ const dot = taskStatusDot(item.task.status);
263
+ return dot === 'ongoing' || dot === 'warning' || dot === 'done'
264
+ ? _jsx(StateDot, { size: 8, state: dot })
265
+ : _jsx("span", { className: channelCss.taskDotQuiet, "data-variant": dot });
266
+ })() }), _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);
267
+ }) }, `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 => { drafts.writeDraft(draftKey, next); setStatusMessage(undefined); }, onRecipientsChange: next => { drafts.writeRecipients(draftKey, next); setStatusMessage(undefined); }, onSubmit: () => { void send(); }, pendingFiles: pendingFiles, onFilesChange: setPendingFiles, t: t }, draftKey) : _jsx("div", {})] });
243
268
  }
@@ -1 +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"}
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;AASlD,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,+BA8K9D"}
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
3
  import { Button, IconEditOutline16, IconPlusOutline16, Input, Modal, Tooltip } from '@deepseek-ai/dsh-client-ui-primitives';
4
4
  import { TeamPresenceDot } from "./TeamPresenceDot.js";
5
+ import { MultiMenuField } from "./multi-menu-field.js";
5
6
  import { TeamRowMenu } from "./TeamRowMenu.js";
6
7
  import { TeamSidebarSection } from "./TeamSidebarSection.js";
7
8
  import { useChannelMembership } from "./team-membership.js";
@@ -76,7 +77,7 @@ export function TeamChannelsPanel(props) {
76
77
  };
77
78
  const submit = async (event) => {
78
79
  event.preventDefault();
79
- if (mutating || name.trim() === '' || description.trim() === '')
80
+ if (mutating || name.trim() === '')
80
81
  return;
81
82
  const payload = { workspaceId, name: name.trim(), description: description.trim(), memberIds: [...selected] };
82
83
  const samePending = pendingCreate !== undefined && pendingCreate.workspaceId === payload.workspaceId
@@ -110,21 +111,25 @@ export function TeamChannelsPanel(props) {
110
111
  setMutating(false);
111
112
  }
112
113
  };
113
- return (_jsxs("div", { className: css.panel, children: [_jsx(Modal, { open: formOpen, onClose: closeForm, title: t('addChannel'), closeLabel: t('close'), contentClassName: createCss.dialogContent, footer: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", disabled: mutating, onClick: closeForm, children: t('cancel') }), _jsx(Button, { type: "submit", form: "team-channel-create-form", variant: "primary", disabled: mutating || name.trim() === '' || description.trim() === '', children: mutating ? t('creatingChannel') : t('createChannel') })] }), children: _jsxs("form", { id: "team-channel-create-form", className: createCss.form, onSubmit: event => { void submit(event); }, children: [_jsxs("label", { className: createCss.field, children: [_jsx("span", { children: t('channelName') }), _jsx(Input, { className: createCss.input, value: name, disabled: mutating, autoFocus: true, onChange: event => { setName(event.target.value); setPendingCreate(undefined); } })] }), _jsxs("label", { className: createCss.field, children: [_jsx("span", { children: t('channelDescription') }), _jsx(Input, { className: createCss.input, value: description, disabled: mutating, onChange: event => { setDescription(event.target.value); setPendingCreate(undefined); } })] }), _jsxs("fieldset", { className: createCss.memberPicker, disabled: mutating, children: [_jsx("legend", { children: t('initialMembers') }), creatingAgents.map(request => (_jsxs("label", { className: createCss.memberOption, children: [_jsx("input", { type: "checkbox", disabled: true, "aria-describedby": `creating-member-reason-${request.requestId}` }), _jsx("span", { className: createCss.unavailableDot, "aria-hidden": "true" }), _jsx("span", { children: request.handle }), _jsx("small", { id: `creating-member-reason-${request.requestId}`, children: t('memberCreatingReason') })] }, request.requestId))), members.map(status => {
114
- const disabled = status.presence === 'unavailable';
115
- const reasonId = `initial-member-reason-${status.member.memberId}`;
116
- return (_jsxs("label", { className: createCss.memberOption, children: [_jsx("input", { type: "checkbox", disabled: disabled, "aria-describedby": disabled ? reasonId : undefined, checked: selected.has(status.member.memberId), onChange: event => {
117
- setSelected(current => {
118
- const next = new Set(current);
119
- if (event.target.checked)
120
- next.add(status.member.memberId);
121
- else
122
- next.delete(status.member.memberId);
123
- return next;
124
- });
125
- setPendingCreate(undefined);
126
- } }), _jsx(TeamPresenceDot, { status: status, t: t }), _jsx("span", { children: status.member.handle }), disabled && _jsx("small", { id: reasonId, children: t('memberUnavailableReason') })] }, status.member.memberId));
127
- })] }), error !== undefined && _jsx("p", { className: createCss.error, role: "alert", children: error })] }) }), _jsxs(TeamSidebarSection, { title: t('channels'), actions: (_jsx(Tooltip, { label: t('addChannel'), delayMs: 500, children: _jsx("button", { ref: triggerRef, type: "button", className: css.iconButton, "aria-label": t('addChannel'), onClick: () => { setError(undefined); setFormOpen(true); }, children: _jsx(IconPlusOutline16, { size: 14 }) }) })), children: [loading && view === undefined && _jsx("p", { className: css.emptyState, children: t('loadingChannels') }), !loading && (view?.channels.length ?? 0) === 0 && _jsx("p", { className: css.emptyState, children: t('emptyChannels') }), _jsx("div", { className: css.channelList, children: view?.channels.map(channel => {
114
+ return (_jsxs("div", { className: css.panel, children: [_jsx(Modal, { open: formOpen, onClose: closeForm, title: t('addChannel'), closeLabel: t('close'), contentClassName: createCss.dialogContent, footer: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", disabled: mutating, onClick: closeForm, children: t('cancel') }), _jsx(Button, { type: "submit", form: "team-channel-create-form", variant: "primary", disabled: mutating || name.trim() === '', children: mutating ? t('creatingChannel') : t('createChannel') })] }), children: _jsxs("form", { id: "team-channel-create-form", className: createCss.form, onSubmit: event => { void submit(event); }, children: [_jsxs("label", { className: createCss.field, children: [_jsx("span", { children: t('channelName') }), _jsx(Input, { className: createCss.input, value: name, disabled: mutating, autoFocus: true, onChange: event => { setName(event.target.value); setPendingCreate(undefined); } })] }), _jsxs("label", { className: createCss.field, children: [_jsxs("span", { children: [t('channelDescription'), t('optionalSuffix')] }), _jsx(Input, { className: createCss.input, value: description, placeholder: t('agentDescriptionPlaceholder'), disabled: mutating, onChange: event => { setDescription(event.target.value); setPendingCreate(undefined); } })] }), _jsx(MultiMenuField, { label: t('initialMembers'), disabled: mutating, options: [
115
+ ...creatingAgents.map(request => ({ id: request.requestId, label: request.handle, disabled: true, hint: t('memberCreatingReason') })),
116
+ ...members.map(status => ({
117
+ id: status.member.memberId,
118
+ label: status.member.handle,
119
+ ...(status.presence === 'unavailable' ? { disabled: true, hint: t('memberUnavailableReason') } : {}),
120
+ icon: _jsx(TeamPresenceDot, { status: status, t: t }),
121
+ })),
122
+ ], selected: [...selected], onToggle: id => {
123
+ setSelected(current => {
124
+ const next = new Set(current);
125
+ if (next.has(id))
126
+ next.delete(id);
127
+ else
128
+ next.add(id);
129
+ return next;
130
+ });
131
+ setPendingCreate(undefined);
132
+ }, triggerEmptyLabel: t('membersPickerEmpty'), formatCount: count => t('membersPickerCount', { count }) }), error !== undefined && _jsx("p", { className: createCss.error, role: "alert", children: error })] }) }), _jsxs(TeamSidebarSection, { title: t('channels'), actions: (_jsx(Tooltip, { label: t('addChannel'), delayMs: 500, children: _jsx("button", { ref: triggerRef, type: "button", className: css.iconButton, "aria-label": t('addChannel'), onClick: () => { setError(undefined); setFormOpen(true); }, children: _jsx(IconPlusOutline16, { size: 14 }) }) })), children: [loading && view === undefined && _jsx("p", { className: css.emptyState, children: t('loadingChannels') }), !loading && (view?.channels.length ?? 0) === 0 && _jsx("p", { className: css.emptyState, children: t('emptyChannels') }), _jsx("div", { className: css.channelList, children: view?.channels.map(channel => {
128
133
  const joined = membership.get(channel.channelRef) ?? new Set();
129
134
  return (_jsx(ChannelRow, { channel: channel, members: members, joinedIds: joined, selected: selectedChannelRef === channel.channelRef, updateChannel: updateChannel, joinChannel: props.joinChannel, removeChannelMember: props.removeChannelMember, onCommitted: () => { void refresh(); }, selectChannel: selectChannel, t: t }, channel.channelRef));
130
135
  }) })] }), !formOpen && error !== undefined && _jsx("p", { className: css.error, role: "alert", children: error })] }));
@@ -154,7 +159,7 @@ function ChannelEditorDialog({ channel, members, joinedIds, updateChannel, joinC
154
159
  event.preventDefault();
155
160
  const normalizedName = name.trim();
156
161
  const normalizedDescription = description.trim();
157
- if (saving || !dirty || normalizedName === '' || normalizedDescription === '')
162
+ if (saving || !dirty || normalizedName === '')
158
163
  return;
159
164
  const samePending = pendingRequest.current !== undefined && pendingRequest.current.channelRef === channel.channelRef
160
165
  && pendingRequest.current.name === normalizedName && pendingRequest.current.description === normalizedDescription;
@@ -186,7 +191,7 @@ function ChannelEditorDialog({ channel, members, joinedIds, updateChannel, joinC
186
191
  setSaving(false);
187
192
  }
188
193
  };
189
- return (_jsx(Modal, { open: true, onClose: onClose, title: t('editChannel'), description: `# ${channel.name}`, 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-channel-edit-form", variant: "primary", disabled: saving || !dirty || name.trim() === '' || description.trim() === '', children: saving ? t('editSaving') : t('editSave') })] }), children: _jsxs("form", { id: "team-channel-edit-form", className: createCss.form, onSubmit: event => { void submit(event); }, children: [_jsxs("label", { className: createCss.field, children: [_jsx("span", { children: t('channelName') }), _jsx(Input, { className: createCss.input, value: name, onChange: event => { setName(event.target.value); pendingRequest.current = undefined; }, disabled: saving, autoFocus: true })] }), _jsxs("label", { className: createCss.field, children: [_jsx("span", { children: t('channelDescription') }), _jsx(Input, { className: createCss.input, value: description, onChange: event => { setDescription(event.target.value); pendingRequest.current = undefined; }, disabled: saving })] }), _jsxs("fieldset", { className: createCss.memberPicker, disabled: saving, children: [_jsx("legend", { children: t('channelMembersSection') }), members.map(status => {
194
+ return (_jsx(Modal, { open: true, onClose: onClose, title: t('editChannel'), description: `# ${channel.name}`, 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-channel-edit-form", variant: "primary", disabled: saving || !dirty || name.trim() === '', children: saving ? t('editSaving') : t('editSave') })] }), children: _jsxs("form", { id: "team-channel-edit-form", className: createCss.form, onSubmit: event => { void submit(event); }, children: [_jsxs("label", { className: createCss.field, children: [_jsx("span", { children: t('channelName') }), _jsx(Input, { className: createCss.input, value: name, onChange: event => { setName(event.target.value); pendingRequest.current = undefined; }, disabled: saving, autoFocus: true })] }), _jsxs("label", { className: createCss.field, children: [_jsxs("span", { children: [t('channelDescription'), t('optionalSuffix')] }), _jsx(Input, { className: createCss.input, value: description, placeholder: t('agentDescriptionPlaceholder'), onChange: event => { setDescription(event.target.value); pendingRequest.current = undefined; }, disabled: saving })] }), _jsxs("fieldset", { className: createCss.memberPicker, disabled: saving, children: [_jsx("legend", { children: t('channelMembersSection') }), members.map(status => {
190
195
  const joined = joinedIds.has(status.member.memberId);
191
196
  const rowPending = membership.pending.has(status.member.memberId);
192
197
  const disabled = rowPending || (!joined && status.presence === 'unavailable');