@wowyuarm/dsh-agent-team 0.1.4 → 0.1.5

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 (56) hide show
  1. package/README.zh.md +5 -2
  2. package/package.json +1 -1
  3. package/packages/agent-team/lib/auto-compaction.js +39 -1
  4. package/packages/agent-team/lib/index.js +85 -5
  5. package/packages/agent-team/lib/ledger.js +80 -2
  6. package/packages/agent-team/lib/spec.js +12 -0
  7. package/packages/agent-team/lib/typert.host.js +94 -22
  8. package/packages/agent-team/lib/typert.remote-client.d.ts +3 -1
  9. package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
  10. package/packages/agent-team/lib/typert.remote-client.js +79 -22
  11. package/packages/agent-team/lib/types/auto-compaction.d.ts +9 -0
  12. package/packages/agent-team/lib/types/auto-compaction.d.ts.map +1 -1
  13. package/packages/agent-team/lib/types/index.d.ts +13 -1
  14. package/packages/agent-team/lib/types/index.d.ts.map +1 -1
  15. package/packages/agent-team/lib/types/ledger.d.ts +20 -0
  16. package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
  17. package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
  18. package/packages/agent-team/lib/types/types.d.ts +41 -1
  19. package/packages/agent-team/lib/types/types.d.ts.map +1 -1
  20. package/packages/client-agent-team/lib/client.js +486 -90
  21. package/packages/client-agent-team/lib/client.js.map +1 -1
  22. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts +2 -1
  23. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
  24. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +41 -5
  25. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
  26. package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +20 -0
  27. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -1
  28. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +3 -1
  29. package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -1
  30. package/packages/client-agent-team/lib/types/client/TeamComposer.js +73 -17
  31. package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.d.ts.map +1 -1
  32. package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.js +16 -1
  33. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
  34. package/packages/client-agent-team/lib/types/client/TeamMessage.js +48 -18
  35. package/packages/client-agent-team/lib/types/client/TeamRowMenu.d.ts +3 -2
  36. package/packages/client-agent-team/lib/types/client/TeamRowMenu.d.ts.map +1 -1
  37. package/packages/client-agent-team/lib/types/client/TeamSidebarSection.d.ts +5 -2
  38. package/packages/client-agent-team/lib/types/client/TeamSidebarSection.d.ts.map +1 -1
  39. package/packages/client-agent-team/lib/types/client/TeamSidebarSection.js +2 -4
  40. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
  41. package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +21 -2
  42. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts +1 -1
  43. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts.map +1 -1
  44. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +4 -2
  45. package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
  46. package/packages/client-agent-team/lib/types/client/index.js +15 -13
  47. package/packages/client-agent-team/lib/types/client/locales.d.ts +16 -0
  48. package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
  49. package/packages/client-agent-team/lib/types/client/locales.js +16 -0
  50. package/packages/client-agent-team/lib/types/client/sidebar-sections.d.ts +16 -0
  51. package/packages/client-agent-team/lib/types/client/sidebar-sections.d.ts.map +1 -0
  52. package/packages/client-agent-team/lib/types/client/sidebar-sections.js +102 -0
  53. package/packages/client-agent-team/lib/types/client/slots.d.ts +2 -1
  54. package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
  55. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts.map +1 -1
  56. package/packages/client-agent-team/lib/types/client/team-formatters.js +9 -2
@@ -9,6 +9,7 @@ interface TeamAgentsPanelProps {
9
9
  readonly addMember: TeamSidebarProps['addMember'];
10
10
  readonly updateMember: TeamSidebarProps['updateMember'];
11
11
  readonly recoverMember: TeamSidebarProps['recoverMember'];
12
+ readonly clearMemberContext: TeamSidebarProps['clearMemberContext'];
12
13
  readonly joinChannel: TeamSidebarProps['joinChannel'];
13
14
  readonly removeChannelMember: TeamSidebarProps['removeChannelMember'];
14
15
  readonly loadModels: TeamSidebarProps['loadModels'];
@@ -18,6 +19,6 @@ interface TeamAgentsPanelProps {
18
19
  readonly onCreatingChange: (request: AgentTeamAddMemberRequest, creating: boolean) => void;
19
20
  readonly t: TeamSidebarProps['t'];
20
21
  }
21
- export declare function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, loadChannels, addMember, updateMember, recoverMember, joinChannel, removeChannelMember, loadModels, memberSessionId, openMemberSession, onCreatingChange, t }: TeamAgentsPanelProps): import("react").JSX.Element;
22
+ export declare function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, loadChannels, addMember, updateMember, recoverMember, clearMemberContext, joinChannel, removeChannelMember, loadModels, memberSessionId, openMemberSession, onCreatingChange, t }: TeamAgentsPanelProps): import("react").JSX.Element;
22
23
  export {};
23
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,EACzB,2BAA2B,EAG5B,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AAEzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAYlD,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,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,WAAW,EAAE,mBAAmB,EAAE,UAAU,EAAE,eAAe,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,CAAC,EAAE,EAAE,oBAAoB,+BAiLhQ"}
1
+ {"version":3,"file":"TeamAgentsPanel.d.ts","sourceRoot":"","sources":["../../../src/client/TeamAgentsPanel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,yBAAyB,EACzB,2BAA2B,EAG5B,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AAEzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAalD,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,kBAAkB,EAAE,gBAAgB,CAAC,oBAAoB,CAAC,CAAA;IACnE,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,kBAAkB,EAAE,WAAW,EAAE,mBAAmB,EAAE,UAAU,EAAE,eAAe,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,CAAC,EAAE,EAAE,oBAAoB,+BAoLpR"}
@@ -1,9 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
- import { Button, IconEditOutline16, IconPlayOutline16, IconPlusOutline16, IconRefreshOutline16, Input, Modal, Tooltip } from '@deepseek-ai/dsh-client-ui-primitives';
3
+ import { Button, IconEditOutline16, IconNewChatOutline16, IconPlayOutline16, IconPlusOutline16, IconRefreshOutline16, Input, Modal, Tooltip } from '@deepseek-ai/dsh-client-ui-primitives';
4
4
  import { TeamMemberAvatar } from "./TeamMemberAvatar.js";
5
5
  import { SortableRow, useSidebarRowDrag } from "./sidebar-drag.js";
6
6
  import { moveSidebarItem, useSidebarOrder } from "./sidebar-order.js";
7
+ import { useSidebarSectionOpen, setSidebarSectionOpen } from "./sidebar-sections.js";
7
8
  import { mintRequestId } from "./requests.js";
8
9
  import { TeamRowMenu } from "./TeamRowMenu.js";
9
10
  import { TeamSidebarSection } from "./TeamSidebarSection.js";
@@ -11,7 +12,7 @@ import { MultiMenuField } from "./multi-menu-field.js";
11
12
  import { AgentEditorDialog, ModelPickerField, sameModel } from "./TeamMemberEditor.js";
12
13
  import createCss from './create.module.css';
13
14
  import css from './sidebar.module.css';
14
- export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, loadChannels, addMember, updateMember, recoverMember, joinChannel, removeChannelMember, loadModels, memberSessionId, openMemberSession, onCreatingChange, t }) {
15
+ export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, loadChannels, addMember, updateMember, recoverMember, clearMemberContext, joinChannel, removeChannelMember, loadModels, memberSessionId, openMemberSession, onCreatingChange, t }) {
15
16
  const [members, setMembers] = useState([]);
16
17
  const [channels, setChannels] = useState([]);
17
18
  const [channelRefs, setChannelRefs] = useState([]);
@@ -36,6 +37,7 @@ export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, lo
36
37
  void moveSidebarItem(workspaceId, 'agents', orderedAgentRefs, movedRef, targetRef, marker);
37
38
  };
38
39
  const drag = useSidebarRowDrag({ refs: orderedAgentRefs, onCommit: applyMove });
40
+ const sectionOpen = useSidebarSectionOpen(workspaceId, 'agents');
39
41
  const refresh = useCallback(async () => {
40
42
  setLoading(true);
41
43
  const result = await loadMembers({ workspaceId });
@@ -132,16 +134,17 @@ export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, lo
132
134
  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 => {
133
135
  setChannelRefs(current => current.includes(ref) ? current.filter(item => item !== ref) : [...current, ref]);
134
136
  setRetryRequest(undefined);
135
- }, 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: orderedMembers.map(status => (_jsx(SortableRow, { drag: drag, orderKey: status.member.memberId, children: _jsx(AgentRow, { status: status, ...(memberSessionId === undefined ? {} : { current: status.member.sessionId === memberSessionId }), channels: channels, loadChannels: loadChannels, updateMember: updateMember, recoverMember: recoverMember, 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') })] }))] }));
137
+ }, 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'), open: sectionOpen, onToggle: open => { setSidebarSectionOpen(workspaceId, 'agents', open); }, 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: orderedMembers.map(status => (_jsx(SortableRow, { drag: drag, orderKey: status.member.memberId, children: _jsx(AgentRow, { status: status, ...(memberSessionId === undefined ? {} : { current: status.member.sessionId === memberSessionId }), channels: channels, loadChannels: loadChannels, updateMember: updateMember, recoverMember: recoverMember, clearMemberContext: clearMemberContext, 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') })] }))] }));
136
138
  }
137
139
  /**
138
140
  * One sidebar Agent row: the select button opens the Member's own Session
139
141
  * conversation page, the avatar carries identity plus the presence badge, and
140
142
  * the row menu opens the editor.
141
143
  */
142
- function AgentRow({ status, current, channels, loadChannels, updateMember, recoverMember, joinChannel, removeChannelMember, loadModels, openMemberSession, onUpdated, t }) {
144
+ function AgentRow({ status, current, channels, loadChannels, updateMember, recoverMember, clearMemberContext, joinChannel, removeChannelMember, loadModels, openMemberSession, onUpdated, t }) {
143
145
  const [menuOpen, setMenuOpen] = useState(false);
144
146
  const [editing, setEditing] = useState(false);
147
+ const [clearing, setClearing] = useState(false);
145
148
  const [rowAlert, setRowAlert] = useState();
146
149
  // Both row actions ride the same runtime remote: the Host steers a live
147
150
  // session, rebuilds an orphaned composition, or re-runs a failed activation.
@@ -171,14 +174,47 @@ function AgentRow({ status, current, channels, loadChannels, updateMember, recov
171
174
  setRowAlert(t('restartFailed', { message: cause instanceof Error ? cause.message : String(cause) }));
172
175
  }
173
176
  };
177
+ const clearContext = async () => {
178
+ try {
179
+ const result = await clearMemberContext({
180
+ requestId: mintRequestId(),
181
+ workspaceId: status.member.workspaceId,
182
+ memberId: status.member.memberId,
183
+ });
184
+ await onUpdated();
185
+ if (!result.ok) {
186
+ setRowAlert(t('clearContextFailed', { message: result.error.message }));
187
+ return;
188
+ }
189
+ setRowAlert(undefined);
190
+ // The Host moved the Member onto a fresh Session id (the previous log
191
+ // stays archived on disk). A new id has no resident client instance, so
192
+ // re-entering the member view lazily instantiates it from host truth:
193
+ // blank hero, live updates, and no disposed-generation gray-out.
194
+ if (current === true)
195
+ openMemberSession(result.value.status.member.sessionId);
196
+ }
197
+ catch (cause) {
198
+ setRowAlert(t('clearContextFailed', { message: cause instanceof Error ? cause.message : String(cause) }));
199
+ }
200
+ };
201
+ // Error Members keep a live idle handle, so starting from a new context
202
+ // is available to them too — it doubles as a recovery path (the broken
203
+ // handle's error markers are dropped with it). Only a running turn or a
204
+ // missing handle gates the entry.
205
+ const contextClearable = status.presence === 'available' || status.presence === 'error';
174
206
  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, disabled: status.availability !== 'active', 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: [
175
207
  { id: 'edit', label: t('editAgent'), icon: _jsx(IconEditOutline16, {}) },
176
208
  ...(status.presence === 'error' ? [{ id: 'resume', label: t('resumeAgent'), icon: _jsx(IconPlayOutline16, {}) }] : []),
177
209
  ...(status.availability === 'unavailable' ? [{ id: 'restart', label: t('restartAgent'), icon: _jsx(IconRefreshOutline16, {}) }] : []),
210
+ { id: 'clear-context', label: t('clearContextAgent'), icon: _jsx(IconNewChatOutline16, {}), danger: true, disabled: !contextClearable },
211
+ ...(!contextClearable ? [{ type: 'label', id: 'clear-context-reason', text: status.presence === 'working' ? t('clearContextWorkingReason') : t('clearContextUnavailableReason') }] : []),
178
212
  ], onSelect: (id) => {
179
213
  if (id === 'edit')
180
214
  setEditing(true);
215
+ else if (id === 'clear-context')
216
+ setClearing(true);
181
217
  else
182
218
  void recover();
183
- }, onOpenChange: setMenuOpen }) })] }), rowAlert !== undefined && _jsx("div", { className: css.rowAlert, role: "alert", children: rowAlert }), editing && (_jsx(AgentEditorDialog, { status: status, channels: channels, loadChannels: loadChannels, updateMember: updateMember, joinChannel: joinChannel, removeChannelMember: removeChannelMember, loadModels: loadModels, onCommitted: onUpdated, onClose: () => { setEditing(false); }, t: t }))] }));
219
+ }, onOpenChange: setMenuOpen }) })] }), rowAlert !== undefined && _jsx("div", { className: css.rowAlert, role: "alert", children: rowAlert }), clearing && (_jsx(Modal, { open: true, onClose: () => { setClearing(false); }, title: t('clearContextTitle', { name: status.member.handle }), closeLabel: t('close'), contentClassName: createCss.dialogContent, footer: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", onClick: () => { setClearing(false); }, children: t('cancel') }), _jsx(Button, { variant: "primary", onClick: () => { setClearing(false); void clearContext(); }, children: t('clearContextConfirm') })] }), children: _jsx("p", { className: createCss.error, children: t('clearContextNotice', { name: status.member.handle }) }) })), editing && (_jsx(AgentEditorDialog, { status: status, channels: channels, loadChannels: loadChannels, updateMember: updateMember, joinChannel: joinChannel, removeChannelMember: removeChannelMember, loadModels: loadModels, onCommitted: onUpdated, onClose: () => { setEditing(false); }, t: t }))] }));
184
220
  }
@@ -1 +1 @@
1
- {"version":3,"file":"TeamChannelPage.d.ts","sourceRoot":"","sources":["../../../src/client/TeamChannelPage.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAA+B,mBAAmB,EAE7D,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AAEzE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAIvD,OAAO,KAAK,EAAgB,cAAc,EAAE,MAAM,aAAa,CAAA;AAc/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,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,eAAe,EAAE,qBAAqB,CAAC,iBAAiB,CAAC,CAAA;IAClE,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,aAAa,EAAE,cAAc,EAAE,eAAe,EAAE,CAAC,EAAE,EAAE,oBAAoB,+BAiUnR"}
1
+ {"version":3,"file":"TeamChannelPage.d.ts","sourceRoot":"","sources":["../../../src/client/TeamChannelPage.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAA+B,mBAAmB,EAE7D,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AAEzE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAIvD,OAAO,KAAK,EAAgB,cAAc,EAAE,MAAM,aAAa,CAAA;AAc/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,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,eAAe,EAAE,qBAAqB,CAAC,iBAAiB,CAAC,CAAA;IAClE,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,aAAa,EAAE,cAAc,EAAE,eAAe,EAAE,CAAC,EAAE,EAAE,oBAAoB,+BAmVnR"}
@@ -60,6 +60,26 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
60
60
  selectChannel(ref);
61
61
  return;
62
62
  }
63
+ if (ref.startsWith('thread:')) {
64
+ const match = view?.items.find(item => item.thread.threadRef === ref);
65
+ if (match !== undefined) {
66
+ selectThread(match.thread.threadRef, channelRef, match.task?.taskRef, match.taskNumber);
67
+ return;
68
+ }
69
+ // A ref may point outside the currently loaded Channel window. Ask the
70
+ // Host for the bounded Thread view so it can provide the home Channel.
71
+ void loadChannels({ workspaceId, threadRef: ref, includeActivities: false, limit: 1 }).then(result => {
72
+ if (!result.ok)
73
+ return;
74
+ const target = result.value.items[0];
75
+ if (target !== undefined) {
76
+ const targetChannel = result.value.channels.find(channel => channel.channelRef === target.message.channelRef);
77
+ if (targetChannel !== undefined)
78
+ selectThread(target.thread.threadRef, targetChannel.channelRef, target.task?.taskRef, target.taskNumber);
79
+ }
80
+ });
81
+ return;
82
+ }
63
83
  if (ref.startsWith('task:')) {
64
84
  const match = view?.items.find(item => item.task?.taskRef === ref);
65
85
  if (match !== undefined) {
@@ -1 +1 @@
1
- {"version":3,"file":"TeamChannelsPanel.d.ts","sourceRoot":"","sources":["../../../src/client/TeamChannelsPanel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,yBAAyB,EAEzB,mBAAmB,EAMpB,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AAEzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAYlD,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,+BA2L9D"}
1
+ {"version":3,"file":"TeamChannelsPanel.d.ts","sourceRoot":"","sources":["../../../src/client/TeamChannelsPanel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,yBAAyB,EAEzB,mBAAmB,EAMpB,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AAEzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAalD,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,+BA8L9D"}
@@ -5,6 +5,7 @@ import { TeamPresenceDot } from "./TeamPresenceDot.js";
5
5
  import { MultiMenuField } from "./multi-menu-field.js";
6
6
  import { SortableRow, useSidebarRowDrag } from "./sidebar-drag.js";
7
7
  import { moveSidebarItem, useSidebarOrder } from "./sidebar-order.js";
8
+ import { useSidebarSectionOpen, setSidebarSectionOpen } from "./sidebar-sections.js";
8
9
  import { mintRequestId } from "./requests.js";
9
10
  import { TeamRowMenu } from "./TeamRowMenu.js";
10
11
  import { TeamSidebarSection } from "./TeamSidebarSection.js";
@@ -37,6 +38,7 @@ export function TeamChannelsPanel(props) {
37
38
  void moveSidebarItem(workspaceId, 'channels', orderedChannelRefs, movedRef, targetRef, marker);
38
39
  };
39
40
  const drag = useSidebarRowDrag({ refs: orderedChannelRefs, onCommit: applyMove });
41
+ const sectionOpen = useSidebarSectionOpen(workspaceId, 'channels');
40
42
  const refresh = useCallback(async () => {
41
43
  setLoading(true);
42
44
  const [channelResult, memberResult] = await Promise.all([
@@ -144,7 +146,7 @@ export function TeamChannelsPanel(props) {
144
146
  return next;
145
147
  });
146
148
  setPendingCreate(undefined);
147
- }, 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: orderedChannels.map(channel => {
149
+ }, triggerEmptyLabel: t('membersPickerEmpty'), formatCount: count => t('membersPickerCount', { count }) }), error !== undefined && _jsx("p", { className: createCss.error, role: "alert", children: error })] }) }), _jsxs(TeamSidebarSection, { title: t('channels'), open: sectionOpen, onToggle: open => { setSidebarSectionOpen(workspaceId, 'channels', open); }, 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: orderedChannels.map(channel => {
148
150
  const joined = membership.get(channel.channelRef) ?? new Set();
149
151
  return (_jsx(SortableRow, { drag: drag, orderKey: channel.channelRef, children: _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));
150
152
  }) })] }), !formOpen && error !== undefined && _jsx("p", { className: css.error, role: "alert", children: error })] }));
@@ -1 +1 @@
1
- {"version":3,"file":"TeamComposer.d.ts","sourceRoot":"","sources":["../../../src/client/TeamComposer.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,2BAA2B,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAEpG,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAkDvD,wBAAgB,YAAY,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC,EAAE,EAAE;IAC3M,QAAQ,CAAC,OAAO,EAAE,SAAS,2BAA2B,EAAE,CAAA;IACxD,QAAQ,CAAC,UAAU,EAAE,WAAW,CAAC,iBAAiB,CAAC,CAAA;IACnD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/C,QAAQ,CAAC,kBAAkB,EAAE,CAAC,UAAU,EAAE,WAAW,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAA;IACjF,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAA;IAC7B,8EAA8E;IAC9E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,gGAAgG;IAChG,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,IAAI,EAAE,CAAA;IACvC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,KAAK,IAAI,CAAA;IACzD,gFAAgF;IAChF,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAA;IACnD,QAAQ,CAAC,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC,CAAA;CACvC,+BA0OA"}
1
+ {"version":3,"file":"TeamComposer.d.ts","sourceRoot":"","sources":["../../../src/client/TeamComposer.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,2BAA2B,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAEpG,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AA8DvD,wBAAgB,YAAY,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC,EAAE,EAAE;IAC3M,QAAQ,CAAC,OAAO,EAAE,SAAS,2BAA2B,EAAE,CAAA;IACxD,QAAQ,CAAC,UAAU,EAAE,WAAW,CAAC,iBAAiB,CAAC,CAAA;IACnD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/C,QAAQ,CAAC,kBAAkB,EAAE,CAAC,UAAU,EAAE,WAAW,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAA;IACjF,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAA;IAC7B,8EAA8E;IAC9E,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,gGAAgG;IAChG,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,IAAI,EAAE,CAAA;IACvC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,KAAK,IAAI,CAAA;IACzD,gFAAgF;IAChF,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAA;IACnD,QAAQ,CAAC,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC,CAAA;CACvC,+BAqSA"}
@@ -30,6 +30,13 @@ function mentionCandidates(members, query) {
30
30
  && status.member.state !== 'inactive'
31
31
  && status.member.handle.toLocaleLowerCase().startsWith(normalized));
32
32
  }
33
+ /** Every member a manual handle pick could reach: the @all expansion snapshot. */
34
+ function allMentionMembers(members) {
35
+ return members.filter(status => status.presence !== 'unavailable' && status.member.state !== 'inactive');
36
+ }
37
+ function containsAllMention(draft) {
38
+ return /(?:^|[^\p{L}\p{N}_])@all(?=$|[^\p{L}\p{N}_])/u.test(draft);
39
+ }
33
40
  /** One object URL per draft file; revoked when the draft is removed. */
34
41
  const draftPreviewUrls = new WeakMap();
35
42
  function draftPreviewUrl(file) {
@@ -50,9 +57,15 @@ export function TeamComposer({ members, recipients, draft, pending, confirmation
50
57
  const composingRef = useRef(false);
51
58
  const [mention, setMention] = useState();
52
59
  const [highlight, setHighlight] = useState(0);
53
- const candidates = mention === undefined ? [] : mentionCandidates(members, mention.query);
54
- const menuOpen = mention !== undefined && candidates.length > 0;
55
- const activeCandidate = candidates[highlight];
60
+ // @all is a composer-layer expansion of the same eligibility filter a
61
+ // handle pick uses; the fixed row stays on top of the matching members.
62
+ const memberCandidates = mention === undefined ? [] : mentionCandidates(members, mention.query);
63
+ const options = mention !== undefined && 'all'.startsWith(mention.query.toLocaleLowerCase())
64
+ ? [{ kind: 'all' }, ...memberCandidates.map(status => ({ kind: 'member', status }))]
65
+ : memberCandidates.map(status => ({ kind: 'member', status }));
66
+ const menuOpen = mention !== undefined && options.length > 0;
67
+ const activeOption = options[highlight];
68
+ const allCount = allMentionMembers(members).length;
56
69
  const listId = 'team-mention-suggestions';
57
70
  const menuMaxHeight = useAnchoredMaxHeight(menuRef, 320, menuOpen ? draft : null);
58
71
  useDismissOnOutsidePointer(rootRef, menuOpen, open => { if (!open)
@@ -65,12 +78,12 @@ export function TeamComposer({ members, recipients, draft, pending, confirmation
65
78
  input.style.height = `${Math.min(input.scrollHeight, 180)}px`;
66
79
  }, [draft]);
67
80
  useEffect(() => {
68
- if (mention === undefined || candidates.length === 0) {
81
+ if (mention === undefined || options.length === 0) {
69
82
  setHighlight(0);
70
83
  return;
71
84
  }
72
- setHighlight(current => Math.min(current, candidates.length - 1));
73
- }, [mention, candidates.length]);
85
+ setHighlight(current => Math.min(current, options.length - 1));
86
+ }, [mention, options.length]);
74
87
  // Match the resident DSH composer without stealing a later user choice: Team
75
88
  // data can load after navigation, so a dialog or another control may already
76
89
  // own focus by the time this composer appears.
@@ -88,6 +101,10 @@ export function TeamComposer({ members, recipients, draft, pending, confirmation
88
101
  inputRef.current?.focus({ preventScroll: true });
89
102
  }, [confirmation, pending]);
90
103
  const pruneRecipients = (nextDraft) => {
104
+ // An @all marker stands for its expansion snapshot: the member handles it
105
+ // stands for are not in the text, so text-based pruning must stand down.
106
+ if (containsAllMention(nextDraft))
107
+ return;
91
108
  const knownMembers = new Map(members.map(status => [status.member.memberId, status.member]));
92
109
  const next = new Set([...recipients].filter(memberId => {
93
110
  const member = knownMembers.get(memberId);
@@ -117,9 +134,31 @@ export function TeamComposer({ members, recipients, draft, pending, confirmation
117
134
  pruneRecipients(nextDraft);
118
135
  updateMention(nextDraft, event.target.selectionStart ?? nextDraft.length);
119
136
  };
120
- const selectMention = (member) => {
137
+ const selectOption = (option) => {
121
138
  if (mention === undefined)
122
139
  return;
140
+ if (option.kind === 'all') {
141
+ // Expand at pick time: the recipients snapshot is every eligible member
142
+ // a handle pick could reach at this moment.
143
+ const nextDraft = `${draft.slice(0, mention.start)}@all ${draft.slice(mention.end)}`;
144
+ const nextCaret = mention.start + '@all '.length;
145
+ const nextRecipients = new Set(recipients);
146
+ for (const status of allMentionMembers(members))
147
+ nextRecipients.add(status.member.memberId);
148
+ onDraftChange(nextDraft);
149
+ onRecipientsChange(nextRecipients);
150
+ setMention(undefined);
151
+ setHighlight(0);
152
+ requestAnimationFrame(() => {
153
+ const input = inputRef.current;
154
+ if (input === null)
155
+ return;
156
+ input.focus({ preventScroll: true });
157
+ input.setSelectionRange(nextCaret, nextCaret);
158
+ });
159
+ return;
160
+ }
161
+ const member = option.status;
123
162
  const inserted = `@${member.member.handle} `;
124
163
  const nextDraft = `${draft.slice(0, mention.start)}${inserted}${draft.slice(mention.end)}`;
125
164
  const nextCaret = mention.start + inserted.length;
@@ -136,16 +175,31 @@ export function TeamComposer({ members, recipients, draft, pending, confirmation
136
175
  input.setSelectionRange(nextCaret, nextCaret);
137
176
  });
138
177
  };
178
+ // Pasted files (screenshots, copies) join the same chips the "+" picker
179
+ // fills; only a paste that carries files is intercepted, so plain text
180
+ // keeps the browser's native insertion.
181
+ const onPaste = (event) => {
182
+ if (pending || onFilesChange === undefined || pendingFiles === undefined)
183
+ return;
184
+ const files = Array.from(event.clipboardData.items)
185
+ .filter(item => item.kind === 'file')
186
+ .map(item => item.getAsFile())
187
+ .filter((file) => file !== null);
188
+ if (files.length === 0)
189
+ return;
190
+ event.preventDefault();
191
+ onFilesChange([...pendingFiles, ...files]);
192
+ };
139
193
  const onKeyDown = (event) => {
140
194
  const composing = composingRef.current || event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229;
141
195
  if (menuOpen && event.key === 'ArrowDown') {
142
196
  event.preventDefault();
143
- setHighlight(current => (current + 1) % candidates.length);
197
+ setHighlight(current => (current + 1) % options.length);
144
198
  return;
145
199
  }
146
200
  if (menuOpen && event.key === 'ArrowUp') {
147
201
  event.preventDefault();
148
- setHighlight(current => (current - 1 + candidates.length) % candidates.length);
202
+ setHighlight(current => (current - 1 + options.length) % options.length);
149
203
  return;
150
204
  }
151
205
  if (event.key === 'Escape' && menuOpen) {
@@ -156,15 +210,15 @@ export function TeamComposer({ members, recipients, draft, pending, confirmation
156
210
  // Tab accepts the highlighted candidate; Shift+Tab keeps default focus reversal.
157
211
  if (menuOpen && event.key === 'Tab' && !event.shiftKey) {
158
212
  event.preventDefault();
159
- if (activeCandidate !== undefined)
160
- selectMention(activeCandidate);
213
+ if (activeOption !== undefined)
214
+ selectOption(activeOption);
161
215
  return;
162
216
  }
163
217
  if (event.key !== 'Enter' || event.shiftKey || composing || event.repeat)
164
218
  return;
165
- if (menuOpen && activeCandidate !== undefined) {
219
+ if (menuOpen && activeOption !== undefined) {
166
220
  event.preventDefault();
167
- selectMention(activeCandidate);
221
+ selectOption(activeOption);
168
222
  return;
169
223
  }
170
224
  event.preventDefault();
@@ -175,11 +229,13 @@ export function TeamComposer({ members, recipients, draft, pending, confirmation
175
229
  event.preventDefault();
176
230
  if (!pending && draft.trim() !== '')
177
231
  onSubmit();
178
- }, children: [_jsxs("div", { className: css.card, "data-team-composer": true, children: [confirmation !== undefined && _jsx("p", { className: css.confirmation, role: "status", children: confirmation }), _jsxs("div", { className: css.inputArea, children: [menuOpen && _jsx("div", { id: listId, ref: menuRef, className: css.mentionMenu, role: "listbox", "aria-label": t('mentionSuggestions'), style: { maxHeight: menuMaxHeight }, children: candidates.map((status, index) => {
179
- const optionId = `${listId}-${status.member.memberId}`;
232
+ }, children: [_jsxs("div", { className: css.card, "data-team-composer": true, children: [confirmation !== undefined && _jsx("p", { className: css.confirmation, role: "status", children: confirmation }), _jsxs("div", { className: css.inputArea, children: [menuOpen && _jsx("div", { id: listId, ref: menuRef, className: css.mentionMenu, role: "listbox", "aria-label": t('mentionSuggestions'), style: { maxHeight: menuMaxHeight }, children: options.map((option, index) => {
233
+ const optionId = option.kind === 'all' ? `${listId}-all` : `${listId}-${option.status.member.memberId}`;
180
234
  const selected = index === highlight;
181
- return _jsxs("button", { id: optionId, type: "button", role: "option", "aria-selected": selected, className: css.mentionOption, onMouseDown: event => { event.preventDefault(); }, onClick: () => { selectMention(status); }, children: [_jsx(TeamPresenceDot, { status: status, t: t }), _jsxs("span", { className: css.mentionName, children: ["@", status.member.handle] }), _jsx("span", { className: css.mentionDescription, children: status.member.description })] }, status.member.memberId);
182
- }) }), _jsx("textarea", { ref: inputRef, "aria-label": t('messageDraft'), "aria-autocomplete": "list", "aria-controls": menuOpen ? listId : undefined, "aria-activedescendant": menuOpen && activeCandidate !== undefined ? `${listId}-${activeCandidate.member.memberId}` : undefined, "aria-expanded": menuOpen, value: draft, readOnly: pending, placeholder: placeholder ?? t('messagePlaceholder'), rows: 1, onChange: onChange, onKeyDown: onKeyDown, onSelect: event => { updateMention(event.currentTarget.value, event.currentTarget.selectionStart ?? event.currentTarget.value.length); }, onCompositionStart: () => { composingRef.current = true; }, onCompositionEnd: () => { setTimeout(() => { composingRef.current = false; }, 10); } })] }), recipients.size > 0 && _jsx("p", { className: css.notifyRow, "data-team-notify": true, children: t('composerNotify', { ids: [...recipients].sort().map(memberId => `@${members.find(candidate => candidate.member.memberId === memberId)?.member.handle ?? memberId}`).join(', ') }) }), onFilesChange !== undefined && pendingFiles !== undefined && pendingFiles.length > 0 && (_jsx("ul", { className: css.fileChips, "aria-label": t('attachFiles'), children: pendingFiles.map((file, index) => {
235
+ return option.kind === 'all'
236
+ ? _jsxs("button", { id: optionId, type: "button", role: "option", "aria-selected": selected, className: css.mentionOption, onMouseDown: event => { event.preventDefault(); }, onClick: () => { selectOption(option); }, children: [_jsx("span", { className: css.mentionAllDot, "aria-hidden": "true" }), _jsx("span", { className: css.mentionName, children: "@all" }), _jsx("span", { className: css.mentionDescription, children: t('mentionAll', { count: allCount }) })] }, "all")
237
+ : _jsxs("button", { id: optionId, type: "button", role: "option", "aria-selected": selected, className: css.mentionOption, onMouseDown: event => { event.preventDefault(); }, onClick: () => { selectOption(option); }, children: [_jsx(TeamPresenceDot, { status: option.status, t: t }), _jsxs("span", { className: css.mentionName, children: ["@", option.status.member.handle] }), _jsx("span", { className: css.mentionDescription, children: option.status.member.description })] }, option.status.member.memberId);
238
+ }) }), _jsx("textarea", { ref: inputRef, "aria-label": t('messageDraft'), "aria-autocomplete": "list", "aria-controls": menuOpen ? listId : undefined, "aria-activedescendant": menuOpen && activeOption !== undefined ? activeOption.kind === 'all' ? `${listId}-all` : `${listId}-${activeOption.status.member.memberId}` : undefined, "aria-expanded": menuOpen, value: draft, readOnly: pending, placeholder: placeholder ?? t('messagePlaceholder'), rows: 1, onChange: onChange, onPaste: onPaste, onKeyDown: onKeyDown, onSelect: event => { updateMention(event.currentTarget.value, event.currentTarget.selectionStart ?? event.currentTarget.value.length); }, onCompositionStart: () => { composingRef.current = true; }, onCompositionEnd: () => { setTimeout(() => { composingRef.current = false; }, 10); } })] }), recipients.size > 0 && _jsx("p", { className: css.notifyRow, "data-team-notify": true, children: t('composerNotify', { ids: [...recipients].sort().map(memberId => `@${members.find(candidate => candidate.member.memberId === memberId)?.member.handle ?? memberId}`).join(', ') }) }), onFilesChange !== undefined && pendingFiles !== undefined && pendingFiles.length > 0 && (_jsx("ul", { className: css.fileChips, "aria-label": t('attachFiles'), children: pendingFiles.map((file, index) => {
183
239
  const previewUrl = draftPreviewUrl(file);
184
240
  return _jsxs("li", { className: `${css.fileChip} ${previewUrl !== undefined ? css.imageChip : ''}`, children: [previewUrl !== undefined && _jsx("img", { src: previewUrl, alt: "", className: css.imageChipPreview }), _jsxs("span", { className: css.fileChipName, title: file.name, children: [file.name, _jsx("span", { className: css.fileChipSize, children: formatByteSize(file.size) })] }), _jsx("button", { type: "button", className: css.fileChipRemove, "aria-label": t('removeFile', { name: file.name }), disabled: pending, onClick: () => {
185
241
  const url = draftPreviewUrls.get(file);
@@ -1 +1 @@
1
- {"version":3,"file":"TeamMemberSessionComposer.d.ts","sourceRoot":"","sources":["../../../src/client/TeamMemberSessionComposer.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AACpE,OAAO,KAAK,EAAE,sBAAsB,EAAc,MAAM,iDAAiD,CAAA;AAIzG,+EAA+E;AAC/E,MAAM,MAAM,8BAA8B,GAAG,YAAY,CAAC,2BAA2B,CAAC,GAAG;IACvF,QAAQ,CAAC,UAAU,EAAE,sBAAsB,CAAA;IAC3C,6EAA6E;IAC7E,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAA;IACxC,wEAAwE;IACxE,QAAQ,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,KAAK,IAAI,CAAA;CACxD,CAAA;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,8BAA8B,+BA6GvL"}
1
+ {"version":3,"file":"TeamMemberSessionComposer.d.ts","sourceRoot":"","sources":["../../../src/client/TeamMemberSessionComposer.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AACpE,OAAO,KAAK,EAAE,sBAAsB,EAAc,MAAM,iDAAiD,CAAA;AAIzG,+EAA+E;AAC/E,MAAM,MAAM,8BAA8B,GAAG,YAAY,CAAC,2BAA2B,CAAC,GAAG;IACvF,QAAQ,CAAC,UAAU,EAAE,sBAAsB,CAAA;IAC3C,6EAA6E;IAC7E,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAA;IACxC,wEAAwE;IACxE,QAAQ,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,KAAK,IAAI,CAAA;CACxD,CAAA;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,8BAA8B,+BA4HvL"}
@@ -45,8 +45,13 @@ export function TeamMemberSessionComposer({ controller, inputActions, overlay, p
45
45
  setSubmissionNotice('Compact 未执行,请重试。');
46
46
  return;
47
47
  }
48
- if (input.phase === 'plain')
48
+ if (input.phase === 'plain') {
49
+ // Reaching plain after a compact flight means the InputMachine adopted
50
+ // the clear-on-success draft; a stale retry notice must not outlive it.
51
+ if (compactWasSubmitting.current)
52
+ setSubmissionNotice(undefined);
49
53
  compactWasSubmitting.current = false;
54
+ }
50
55
  }, [input?.claim?.token, input?.phase]);
51
56
  useEffect(() => {
52
57
  if (input === undefined)
@@ -86,6 +91,16 @@ export function TeamMemberSessionComposer({ controller, inputActions, overlay, p
86
91
  return;
87
92
  if (event.repeat || composing.current || event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229)
88
93
  return;
94
+ // Tab accepts the highlighted candidate, mirroring the team-mode composer
95
+ // and the Channel/Thread bars; Shift+Tab keeps native focus reversal.
96
+ if (event.key === 'Tab' && !event.shiftKey) {
97
+ const highlight = controller.menu.getSnapshot().highlight;
98
+ if (highlight !== null) {
99
+ event.preventDefault();
100
+ controller.pick(highlight.source, highlight.index);
101
+ }
102
+ return;
103
+ }
89
104
  const key = event.key === 'ArrowUp' ? 'up' : event.key === 'ArrowDown' ? 'down' : event.key === 'Escape' ? 'escape' : event.key === 'Enter' ? 'enter' : undefined;
90
105
  if (key !== undefined) {
91
106
  const outcome = controller.arbitrate(key, false);
@@ -1 +1 @@
1
- {"version":3,"file":"TeamMessage.d.ts","sourceRoot":"","sources":["../../../src/client/TeamMessage.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA2F,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAE/H,OAAO,KAAK,EAAE,iBAAiB,EAAE,0BAA0B,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAA;AACrH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAEvD,OAAO,EAA4E,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAI/H,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAA;IACpC,wEAAwE;IACxE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,uFAAuF;IACvF,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACzC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;IAC1B;2DACuD;IACvD,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAA;IAClC,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,0BAA0B,EAAE,GAAG,SAAS,CAAA;IACxE,6EAA6E;IAC7E,QAAQ,CAAC,cAAc,CAAC,EAAE,qBAAqB,CAAC,eAAe,CAAC,GAAG,SAAS,CAAA;IAC5E,QAAQ,CAAC,CAAC,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC,GAAG,SAAS,CAAA;IACnD,0FAA0F;IAC1F,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;IACxD,sFAAsF;IACtF,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE,SAAS,gBAAgB,EAAE,KAAK,OAAO,CAAC,SAAS,eAAe,EAAE,CAAC,CAAC,GAAG,SAAS,CAAA;IACzH,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;CAC9B;AAED,kFAAkF;AAClF,wBAAgB,WAAW,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC,EAAE,SAAS,EAAE,iBAAiB,EAAE,QAAQ,EAAE,EAAE,gBAAgB,+BA6G3N"}
1
+ {"version":3,"file":"TeamMessage.d.ts","sourceRoot":"","sources":["../../../src/client/TeamMessage.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA2F,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAE/H,OAAO,KAAK,EAAE,iBAAiB,EAAE,0BAA0B,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAA;AACrH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAEvD,OAAO,EAA4E,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAI/H,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAA;IACpC,wEAAwE;IACxE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,uFAAuF;IACvF,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACzC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;IAC1B;2DACuD;IACvD,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAA;IAClC,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,0BAA0B,EAAE,GAAG,SAAS,CAAA;IACxE,6EAA6E;IAC7E,QAAQ,CAAC,cAAc,CAAC,EAAE,qBAAqB,CAAC,eAAe,CAAC,GAAG,SAAS,CAAA;IAC5E,QAAQ,CAAC,CAAC,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC,GAAG,SAAS,CAAA;IACnD,0FAA0F;IAC1F,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;IACxD,sFAAsF;IACtF,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE,SAAS,gBAAgB,EAAE,KAAK,OAAO,CAAC,SAAS,eAAe,EAAE,CAAC,CAAC,GAAG,SAAS,CAAA;IACzH,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;CAC9B;AAED,kFAAkF;AAClF,wBAAgB,WAAW,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC,EAAE,SAAS,EAAE,iBAAiB,EAAE,QAAQ,EAAE,EAAE,gBAAgB,+BA8G3N"}
@@ -29,6 +29,7 @@ export function TeamMessage({ senderName, memberId, human, body, occurredAt, men
29
29
  void resolveUnknownTaskRefs(bodyTaskRefs, onResolveTaskRefs);
30
30
  }, [onResolveTaskRefs, bodyTaskRefKey, refVersion]);
31
31
  const taskLabel = useCallback((taskNumber) => t?.('taskLabel', { number: taskNumber }) ?? `Task #${taskNumber}`, [t]);
32
+ const refLabel = useCallback((ref) => ref.startsWith('thread:') ? (t?.('threadLabel') ?? 'Thread') : ref.startsWith('channel:') ? (t?.('channels') ?? 'Channels') : ref, [t]);
32
33
  // Long bodies start clamped behind the expand control. The default derives
33
34
  // from the body alone; the toggle itself is per-mount view state nothing
34
35
  // persists.
@@ -49,16 +50,16 @@ export function TeamMessage({ senderName, memberId, human, body, occurredAt, men
49
50
  if (onResolveTaskRefs !== undefined && taskRefs.length > 0)
50
51
  void resolveUnknownTaskRefs(taskRefs, onResolveTaskRefs);
51
52
  for (const code of styledRefCodes)
52
- renderResolvedMarkdownCodeRef(code, taskLabel);
53
+ renderResolvedMarkdownCodeRef(code, taskLabel, refLabel);
53
54
  for (const node of textNodes)
54
- renderResolvedMarkdownText(node, mentionNames ?? [], taskLabel);
55
+ renderResolvedMarkdownText(node, mentionNames ?? [], taskLabel, refLabel);
55
56
  for (const button of root.querySelectorAll('button[data-task-ref]')) {
56
57
  const taskRef = button.dataset.taskRef;
57
58
  const hit = taskRef === undefined ? undefined : cachedResolvedTaskRef(taskRef);
58
59
  if (taskRef !== undefined && hit !== undefined)
59
60
  button.textContent = taskLabel(hit.taskNumber);
60
61
  }
61
- }, [richAgentBody, onOpenRef, onResolveTaskRefs, refVersion, taskLabel, mentionNames]);
62
+ }, [richAgentBody, onOpenRef, onResolveTaskRefs, refVersion, taskLabel, refLabel, mentionNames]);
62
63
  useEffect(() => {
63
64
  const root = markdownRef.current;
64
65
  if (!richAgentBody || root === null || onOpenRef === undefined)
@@ -67,12 +68,12 @@ export function TeamMessage({ senderName, memberId, human, body, occurredAt, men
67
68
  const target = event.target;
68
69
  if (!(target instanceof Element))
69
70
  return;
70
- const button = target.closest('button[data-task-ref]');
71
+ const button = target.closest('button[data-ref], button[data-task-ref]');
71
72
  if (button === null || !root.contains(button))
72
73
  return;
73
- const taskRef = button instanceof HTMLElement ? button.dataset.taskRef : undefined;
74
- if (taskRef !== undefined)
75
- onOpenRef(taskRef);
74
+ const ref = button instanceof HTMLElement ? button.dataset.ref ?? button.dataset.taskRef : undefined;
75
+ if (ref !== undefined)
76
+ onOpenRef(ref);
76
77
  };
77
78
  root.addEventListener('click', openTask);
78
79
  return () => { root.removeEventListener('click', openTask); };
@@ -80,9 +81,9 @@ export function TeamMessage({ senderName, memberId, human, body, occurredAt, men
80
81
  const bodyNode = plan.render === 'inline' && plan.inline !== undefined
81
82
  ? _jsx("div", { className: css.messageText, children: plan.inline.segments.map((segment, index) => segment.mention
82
83
  ? _jsx("span", { className: css.mention, children: segment.text }, index)
83
- : _jsx(Fragment, { children: renderRefs(segment.text, onOpenRef, taskLabel) }, index)) })
84
+ : _jsx(Fragment, { children: renderRefs(segment.text, onOpenRef, taskLabel, refLabel) }, index)) })
84
85
  : plan.render === 'literal'
85
- ? _jsx("div", { className: css.messageText, children: onOpenRef === undefined ? _jsx(MessageText, { text: displayBody }) : renderRefs(displayBody, onOpenRef, taskLabel) })
86
+ ? _jsx("div", { className: css.messageText, children: onOpenRef === undefined ? _jsx(MessageText, { text: displayBody }) : renderRefs(displayBody, onOpenRef, taskLabel, refLabel) })
86
87
  : _jsx("div", { ref: markdownRef, className: css.messageMarkdown, children: _jsx(MarkdownText, { text: displayBody }, `${displayBody}:${onOpenRef === undefined ? 'literal' : 'refs'}`) });
87
88
  return (_jsxs("article", { className: css.messageRow, "data-human": human || undefined, "data-grouped": grouped || undefined, children: [_jsx("div", { className: css.messageIdentity, style: avatarStyle, "aria-hidden": "true", children: senderName.replace('@', '').slice(0, 1).toUpperCase() }), _jsxs("div", { className: css.messageBody, children: [(!grouped || showGroupedTime === true) && (_jsxs("div", { className: css.nameRow, children: [!grouped && _jsx("strong", { ...(senderTitle === undefined ? {} : { title: senderTitle }), children: senderName }), occurredAt !== undefined && _jsx("span", { className: css.messageTime, children: formatMessageTime(occurredAt) })] })), clampable ? _jsx("div", { className: clamped ? css.messageClamp : undefined, children: bodyNode }) : bodyNode, clampable && (_jsx("button", { type: "button", className: css.messageExpand, "data-message-expand": "true", "aria-expanded": expanded, onClick: () => { setExpanded(value => !value); }, children: expanded ? (t?.('collapseMessage') ?? 'Show less') : (t?.('expandMessage') ?? 'Show more') })), (plan.fallbackNames.length > 0 || plan.fallbackRefs.length > 0) && (_jsxs("div", { className: css.mentionsRow, children: [plan.fallbackNames.map(name => _jsxs("span", { className: css.mention, children: ["@", name] }, name)), plan.fallbackRefs.map(ref => {
88
89
  const resolved = cachedResolvedTaskRef(ref);
@@ -132,17 +133,31 @@ function resolvedTaskRefButton(taskRef, label) {
132
133
  return button;
133
134
  }
134
135
  /** Replace a whole-ref code span with its resolved Task chip once known. */
135
- function renderResolvedMarkdownCodeRef(code, taskLabel) {
136
+ function renderResolvedMarkdownCodeRef(code, taskLabel, refLabel) {
136
137
  const segments = splitBrandedRefs((code.textContent ?? '').trim());
137
138
  const segment = segments.length === 1 ? segments[0] : undefined;
138
- const taskRef = segment?.ref?.startsWith('task:') === true ? segment.ref : undefined;
139
+ const ref = segment?.ref;
140
+ if (ref === undefined)
141
+ return;
142
+ const taskRef = ref.startsWith('task:') === true ? ref : undefined;
139
143
  const resolved = taskRef === undefined ? undefined : cachedResolvedTaskRef(taskRef);
140
- if (taskRef === undefined || resolved === undefined)
144
+ if (taskRef !== undefined && resolved === undefined)
145
+ return;
146
+ if (taskRef !== undefined) {
147
+ code.replaceWith(resolvedTaskRefButton(taskRef, taskLabel(resolved.taskNumber)));
141
148
  return;
142
- code.replaceWith(resolvedTaskRefButton(taskRef, taskLabel(resolved.taskNumber)));
149
+ }
150
+ const button = document.createElement('button');
151
+ button.type = 'button';
152
+ if (css.refLink !== undefined)
153
+ button.className = css.refLink;
154
+ button.dataset.ref = ref;
155
+ button.title = ref;
156
+ button.textContent = refLabel(ref);
157
+ code.replaceWith(button);
143
158
  }
144
159
  /** Replace resolved Task refs and structured mention handles in one prose text node without changing Markdown structure. */
145
- function renderResolvedMarkdownText(node, mentionNames, taskLabel) {
160
+ function renderResolvedMarkdownText(node, mentionNames, taskLabel, refLabel) {
146
161
  let changed = false;
147
162
  const fragment = document.createDocumentFragment();
148
163
  for (const refSegment of splitBrandedRefs(node.data)) {
@@ -163,25 +178,40 @@ function renderResolvedMarkdownText(node, mentionNames, taskLabel) {
163
178
  }
164
179
  const taskRef = refSegment.ref.startsWith('task:') === true ? refSegment.ref : undefined;
165
180
  const resolved = taskRef === undefined ? undefined : cachedResolvedTaskRef(taskRef);
166
- if (resolved === undefined) {
181
+ // Task refs need Host resolution before becoming human-readable links;
182
+ // channel/thread refs already have their canonical display text and can be
183
+ // linked immediately at the authored position.
184
+ if (taskRef !== undefined && resolved === undefined) {
167
185
  fragment.append(refSegment.text);
168
186
  continue;
169
187
  }
170
188
  changed = true;
171
- fragment.append(resolvedTaskRefButton(taskRef, taskLabel(resolved.taskNumber)));
189
+ if (taskRef !== undefined) {
190
+ fragment.append(resolvedTaskRefButton(taskRef, taskLabel(resolved.taskNumber)));
191
+ }
192
+ else {
193
+ const button = document.createElement('button');
194
+ button.type = 'button';
195
+ if (css.refLink !== undefined)
196
+ button.className = css.refLink;
197
+ button.dataset.ref = refSegment.ref;
198
+ button.title = refSegment.ref;
199
+ button.textContent = refLabel(refSegment.ref);
200
+ fragment.append(button);
201
+ }
172
202
  }
173
203
  if (changed)
174
204
  node.replaceWith(fragment);
175
205
  }
176
206
  /** Render one literal text run, linkifying branded refs when navigation is available. */
177
- function renderRefs(text, onOpenRef, taskLabel) {
207
+ function renderRefs(text, onOpenRef, taskLabel, refLabel) {
178
208
  if (onOpenRef === undefined)
179
209
  return text;
180
210
  return splitBrandedRefs(text).map((segment, index) => {
181
211
  if (segment.ref === undefined)
182
212
  return _jsx(Fragment, { children: segment.text }, index);
183
213
  const resolved = cachedResolvedTaskRef(segment.ref);
184
- const label = resolved !== undefined && segment.ref.startsWith('task:') ? taskLabel(resolved.taskNumber) : segment.ref;
214
+ const label = resolved !== undefined && segment.ref.startsWith('task:') ? taskLabel(resolved.taskNumber) : refLabel(segment.ref);
185
215
  return _jsx("button", { type: "button", className: css.refLink, title: segment.ref, onClick: () => { onOpenRef(segment.ref); }, children: label }, index);
186
216
  });
187
217
  }
@@ -1,4 +1,4 @@
1
- import type { MenuItem } from '@deepseek-ai/dsh-client-ui-primitives';
1
+ import { type MenuEntry } from '@deepseek-ai/dsh-client-ui-primitives';
2
2
  /**
3
3
  * Row-level overflow menu shared by sidebar Channel and Agent rows, mirroring
4
4
  * the harness session-row pattern: a portal list anchored to a bare ellipsis
@@ -7,7 +7,8 @@ import type { MenuItem } from '@deepseek-ai/dsh-client-ui-primitives';
7
7
  export declare function TeamRowMenu({ label, items, onSelect, onOpenChange }: {
8
8
  /** Localized action label for the trigger, e.g. "{name} 的操作". */
9
9
  readonly label: string;
10
- readonly items: readonly MenuItem[];
10
+ /** Menu rows plus optional non-interactive labels and separators. */
11
+ readonly items: readonly MenuEntry[];
11
12
  readonly onSelect: (id: string) => void;
12
13
  /** Lets the row pin its hover styling while the portal list is up. */
13
14
  readonly onOpenChange?: (open: boolean) => void;