@wowyuarm/dsh-agent-team 0.1.11 → 0.1.13

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 (114) hide show
  1. package/README.md +6 -4
  2. package/README.zh.md +6 -4
  3. package/cordis.patch.yml +7 -3
  4. package/package.json +16 -3
  5. package/packages/agent-team/README.md +4 -4
  6. package/packages/agent-team/README.zh.md +4 -4
  7. package/packages/agent-team/lib/context-projection.js +5 -1
  8. package/packages/agent-team/lib/index.js +162 -113
  9. package/packages/agent-team/lib/ledger.js +496 -169
  10. package/packages/agent-team/lib/member-context.js +18 -1
  11. package/packages/agent-team/lib/member-runtime.js +48 -9
  12. package/packages/agent-team/lib/mentions.js +80 -0
  13. package/packages/agent-team/lib/spec.js +20 -0
  14. package/packages/agent-team/lib/typert.host.js +241 -46
  15. package/packages/agent-team/lib/typert.remote-client.d.ts +7 -3
  16. package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
  17. package/packages/agent-team/lib/typert.remote-client.js +178 -28
  18. package/packages/agent-team/lib/types/context-projection.d.ts.map +1 -1
  19. package/packages/agent-team/lib/types/index.d.ts +33 -14
  20. package/packages/agent-team/lib/types/index.d.ts.map +1 -1
  21. package/packages/agent-team/lib/types/ledger.d.ts +120 -18
  22. package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
  23. package/packages/agent-team/lib/types/member-context.d.ts +6 -0
  24. package/packages/agent-team/lib/types/member-context.d.ts.map +1 -1
  25. package/packages/agent-team/lib/types/member-runtime.d.ts +2 -1
  26. package/packages/agent-team/lib/types/member-runtime.d.ts.map +1 -1
  27. package/packages/agent-team/lib/types/mentions.d.ts +47 -0
  28. package/packages/agent-team/lib/types/mentions.d.ts.map +1 -0
  29. package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
  30. package/packages/agent-team/lib/types/types/entities.d.ts +3 -20
  31. package/packages/agent-team/lib/types/types/entities.d.ts.map +1 -1
  32. package/packages/agent-team/lib/types/types/operations.d.ts +37 -1
  33. package/packages/agent-team/lib/types/types/operations.d.ts.map +1 -1
  34. package/packages/agent-team/lib/types/types/requests-results.d.ts +107 -25
  35. package/packages/agent-team/lib/types/types/requests-results.d.ts.map +1 -1
  36. package/packages/agent-team/lib/types/vendor/storage-sqlite/index.d.ts +87 -0
  37. package/packages/agent-team/lib/types/vendor/storage-sqlite/index.d.ts.map +1 -0
  38. package/packages/agent-team/lib/types/vendor/storage-sqlite/schema.d.ts +59 -0
  39. package/packages/agent-team/lib/types/vendor/storage-sqlite/schema.d.ts.map +1 -0
  40. package/packages/agent-team/lib/types/vendor/storage-sqlite/unit.d.ts +57 -0
  41. package/packages/agent-team/lib/types/vendor/storage-sqlite/unit.d.ts.map +1 -0
  42. package/packages/agent-team/lib/vendor/storage-sqlite/index.js +139 -0
  43. package/packages/agent-team/lib/vendor/storage-sqlite/schema.js +120 -0
  44. package/packages/agent-team/lib/vendor/storage-sqlite/unit.js +132 -0
  45. package/packages/agent-team/preset/team-member/agent.cordis.yml +48 -8
  46. package/packages/client-agent-team/README.md +1 -1
  47. package/packages/client-agent-team/README.zh.md +1 -1
  48. package/packages/client-agent-team/lib/client.js +2123 -1406
  49. package/packages/client-agent-team/lib/client.js.map +1 -1
  50. package/packages/client-agent-team/lib/types/client/TeamAgentImport.d.ts +12 -0
  51. package/packages/client-agent-team/lib/types/client/TeamAgentImport.d.ts.map +1 -0
  52. package/packages/client-agent-team/lib/types/client/TeamAgentImport.js +69 -0
  53. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts +3 -1
  54. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
  55. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +31 -19
  56. package/packages/client-agent-team/lib/types/client/TeamAvatarStack.d.ts +18 -0
  57. package/packages/client-agent-team/lib/types/client/TeamAvatarStack.d.ts.map +1 -0
  58. package/packages/client-agent-team/lib/types/client/TeamAvatarStack.js +22 -0
  59. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts +3 -1
  60. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
  61. package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +111 -24
  62. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -1
  63. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +10 -3
  64. package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -1
  65. package/packages/client-agent-team/lib/types/client/TeamComposer.js +11 -17
  66. package/packages/client-agent-team/lib/types/client/TeamConversation.js +1 -1
  67. package/packages/client-agent-team/lib/types/client/TeamCountBadge.d.ts +20 -0
  68. package/packages/client-agent-team/lib/types/client/TeamCountBadge.d.ts.map +1 -0
  69. package/packages/client-agent-team/lib/types/client/TeamCountBadge.js +25 -0
  70. package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts +9 -5
  71. package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts.map +1 -1
  72. package/packages/client-agent-team/lib/types/client/TeamInboxPage.js +92 -23
  73. package/packages/client-agent-team/lib/types/client/TeamMemberRow.d.ts +43 -0
  74. package/packages/client-agent-team/lib/types/client/TeamMemberRow.d.ts.map +1 -0
  75. package/packages/client-agent-team/lib/types/client/TeamMemberRow.js +27 -0
  76. package/packages/client-agent-team/lib/types/client/TeamMembersAction.d.ts +2 -2
  77. package/packages/client-agent-team/lib/types/client/TeamMembersAction.d.ts.map +1 -1
  78. package/packages/client-agent-team/lib/types/client/TeamMembersAction.js +2 -2
  79. package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts +1 -1
  80. package/packages/client-agent-team/lib/types/client/TeamPresenceDot.d.ts.map +1 -1
  81. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
  82. package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +21 -31
  83. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts +1 -1
  84. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts.map +1 -1
  85. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +29 -11
  86. package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
  87. package/packages/client-agent-team/lib/types/client/index.js +11 -5
  88. package/packages/client-agent-team/lib/types/client/locales.d.ts +54 -16
  89. package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
  90. package/packages/client-agent-team/lib/types/client/locales.js +54 -16
  91. package/packages/client-agent-team/lib/types/client/slots.d.ts +3 -2
  92. package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
  93. package/packages/client-agent-team/lib/types/client/team-changes.d.ts +15 -25
  94. package/packages/client-agent-team/lib/types/client/team-changes.d.ts.map +1 -1
  95. package/packages/client-agent-team/lib/types/client/team-changes.js +82 -80
  96. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +45 -10
  97. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts.map +1 -1
  98. package/packages/client-agent-team/lib/types/client/team-formatters.js +71 -30
  99. package/packages/tool-agent-team/README.md +3 -3
  100. package/packages/tool-agent-team/README.zh.md +3 -3
  101. package/packages/tool-agent-team/lib/host-access.js +19 -0
  102. package/packages/tool-agent-team/lib/index.js +71 -49
  103. package/packages/tool-agent-team/lib/types/host-access.d.ts +10 -0
  104. package/packages/tool-agent-team/lib/types/host-access.d.ts.map +1 -1
  105. package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
  106. package/packages/agent-team/lib/progress-nudge.js +0 -328
  107. package/packages/agent-team/lib/types/progress-nudge.d.ts +0 -136
  108. package/packages/agent-team/lib/types/progress-nudge.d.ts.map +0 -1
  109. package/packages/client-agent-team/lib/types/client/TeamSettings.d.ts +0 -3
  110. package/packages/client-agent-team/lib/types/client/TeamSettings.d.ts.map +0 -1
  111. package/packages/client-agent-team/lib/types/client/TeamSettings.js +0 -5
  112. package/packages/client-agent-team/lib/types/client/scope-coverage.d.ts +0 -41
  113. package/packages/client-agent-team/lib/types/client/scope-coverage.d.ts.map +0 -1
  114. package/packages/client-agent-team/lib/types/client/scope-coverage.js +0 -64
@@ -0,0 +1,12 @@
1
+ import type { WorkspaceId } from '@deepseek-ai/dsh-api-workspace-controller/client';
2
+ import type { TeamSidebarProps } from './slots.ts';
3
+ /** Selection is a Host join, not a new Agent or a copied Session. */
4
+ export declare function TeamAgentImport({ workspaceId, loadMembers, joinWorkspace, onJoined, onPending, t }: {
5
+ readonly workspaceId: WorkspaceId;
6
+ readonly loadMembers: TeamSidebarProps['loadMembers'];
7
+ readonly joinWorkspace: TeamSidebarProps['joinWorkspace'];
8
+ readonly onJoined: () => Promise<void>;
9
+ readonly onPending: (pending: boolean) => void;
10
+ readonly t: TeamSidebarProps['t'];
11
+ }): import("react").JSX.Element;
12
+ //# sourceMappingURL=TeamAgentImport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TeamAgentImport.d.ts","sourceRoot":"","sources":["../../../src/client/TeamAgentImport.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kDAAkD,CAAA;AAEnF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAKlD,qEAAqE;AACrE,wBAAgB,eAAe,CAAC,EAAE,WAAW,EAAE,WAAW,EAAE,aAAa,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,EAAE,EAAE;IACnG,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAA;IACrD,QAAQ,CAAC,aAAa,EAAE,gBAAgB,CAAC,eAAe,CAAC,CAAA;IACzD,QAAQ,CAAC,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACtC,QAAQ,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAA;IAC9C,QAAQ,CAAC,CAAC,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAA;CAClC,+BA2DA"}
@@ -0,0 +1,69 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useRef, useState } from 'react';
3
+ import { Button } from '@deepseek-ai/dsh-client-ui-primitives';
4
+ import { TeamMemberRow } from "./TeamMemberRow.js";
5
+ import { mintRequestId } from "./requests.js";
6
+ import css from './create.module.css';
7
+ /** Selection is a Host join, not a new Agent or a copied Session. */
8
+ export function TeamAgentImport({ workspaceId, loadMembers, joinWorkspace, onJoined, onPending, t }) {
9
+ const [members, setMembers] = useState([]);
10
+ const [loading, setLoading] = useState(true);
11
+ const [error, setError] = useState();
12
+ const [pending, setPending] = useState();
13
+ const retry = useRef();
14
+ const busy = useRef(false);
15
+ const generation = useRef(0);
16
+ const load = useCallback(async () => {
17
+ const current = ++generation.current;
18
+ setLoading(true);
19
+ setError(undefined);
20
+ try {
21
+ const result = await loadMembers({});
22
+ if (current !== generation.current)
23
+ return;
24
+ if (!result.ok)
25
+ throw new Error(result.error.message);
26
+ setMembers(result.value.filter(status => (status.member.state === 'enabled' || status.member.state === 'suspended') && !status.workspaceIds.includes(workspaceId)));
27
+ }
28
+ catch (cause) {
29
+ if (current === generation.current)
30
+ setError(cause instanceof Error ? cause.message : String(cause));
31
+ }
32
+ finally {
33
+ if (current === generation.current)
34
+ setLoading(false);
35
+ }
36
+ }, [loadMembers, workspaceId]);
37
+ useEffect(() => { void load(); return () => { generation.current++; }; }, [load]);
38
+ const join = async (status) => {
39
+ if (busy.current)
40
+ return;
41
+ busy.current = true;
42
+ setPending(status.member.memberId);
43
+ onPending(true);
44
+ setError(undefined);
45
+ const request = retry.current?.memberId === status.member.memberId ? retry.current : {
46
+ requestId: mintRequestId(), workspaceId, memberId: status.member.memberId,
47
+ };
48
+ retry.current = request;
49
+ try {
50
+ const result = await joinWorkspace(request);
51
+ if (!result.ok)
52
+ throw new Error(result.error.message);
53
+ await onJoined();
54
+ }
55
+ catch (cause) {
56
+ setError(cause instanceof Error ? cause.message : String(cause));
57
+ }
58
+ finally {
59
+ busy.current = false;
60
+ setPending(undefined);
61
+ onPending(false);
62
+ }
63
+ };
64
+ return _jsxs("div", { className: css.form, children: [_jsx("p", { className: css.notice, children: t('importAgentNotice') }), loading && _jsx("p", { className: css.state, role: "status", children: t('loadingAgents') }), !loading && error === undefined && members.length === 0 && _jsx("p", { className: css.state, children: t('emptyImportAgents') }), !loading && members.length > 0 && _jsx("div", { className: css.roster, children: members.map(status => _jsx(TeamMemberRow, { status: status, t: t, action: {
65
+ label: pending === status.member.memberId ? t('importingAgent') : t('importAgent'),
66
+ disabled: pending !== undefined,
67
+ onSelect: () => { void join(status); },
68
+ } }, status.member.memberId)) }), error !== undefined && _jsxs("div", { className: css.failure, role: "alert", children: [_jsx("p", { className: css.error, children: error }), _jsx(Button, { disabled: pending !== undefined, variant: "outline", onClick: () => { void load(); }, children: t('retry') })] })] });
69
+ }
@@ -9,6 +9,8 @@ interface TeamAgentsPanelProps {
9
9
  readonly updateMember: TeamSidebarProps['updateMember'];
10
10
  readonly recoverMember: TeamSidebarProps['recoverMember'];
11
11
  readonly archiveMember: TeamSidebarProps['archiveMember'];
12
+ readonly joinWorkspace: TeamSidebarProps['joinWorkspace'];
13
+ readonly leaveWorkspace: TeamSidebarProps['leaveWorkspace'];
12
14
  readonly loadModels: TeamSidebarProps['loadModels'];
13
15
  /** The Member Session currently embedded in the conversation seat, if any. */
14
16
  readonly memberSessionId?: AgentTeamClientMemberStatus['member']['sessionId'];
@@ -16,6 +18,6 @@ interface TeamAgentsPanelProps {
16
18
  readonly onCreatingChange: (request: AgentTeamAddMemberRequest, creating: boolean) => void;
17
19
  readonly t: TeamSidebarProps['t'];
18
20
  }
19
- export declare function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, addMember, updateMember, recoverMember, archiveMember, loadModels, memberSessionId, openMemberSession, onCreatingChange, t }: TeamAgentsPanelProps): import("react").JSX.Element;
21
+ export declare function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, addMember, updateMember, recoverMember, archiveMember, joinWorkspace, leaveWorkspace, loadModels, memberSessionId, openMemberSession, onCreatingChange, t }: TeamAgentsPanelProps): import("react").JSX.Element;
20
22
  export {};
21
23
  //# 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,EAE5B,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kDAAkD,CAAA;AAEnF,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,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,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,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,CAAC,EAAE,EAAE,oBAAoB,+BAwO/N"}
1
+ {"version":3,"file":"TeamAgentsPanel.d.ts","sourceRoot":"","sources":["../../../src/client/TeamAgentsPanel.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,yBAAyB,EACzB,2BAA2B,EAE5B,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kDAAkD,CAAA;AAEnF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAclD,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,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,aAAa,EAAE,gBAAgB,CAAC,eAAe,CAAC,CAAA;IACzD,QAAQ,CAAC,cAAc,EAAE,gBAAgB,CAAC,gBAAgB,CAAC,CAAA;IAC3D,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,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,UAAU,EAAE,eAAe,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,CAAC,EAAE,EAAE,oBAAoB,+BA2O9P"}
@@ -1,7 +1,7 @@
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
3
  import { Button, IconArchiveOutline20, IconEditOutline16, IconPlayOutline16, IconPlusOutline16, IconRefreshOutline16, Input, Modal, Tooltip } from '@deepseek-ai/dsh-client-ui-primitives';
4
- import { TeamMemberAvatar } from "./TeamMemberAvatar.js";
4
+ import { TeamMemberIdentity } from "./TeamMemberRow.js";
5
5
  import { SortableRow, useSidebarRowDrag } from "./sidebar-drag.js";
6
6
  import { moveSidebarItem, useSidebarOrder } from "./sidebar-order.js";
7
7
  import { useSidebarSectionOpen, setSidebarSectionOpen } from "./sidebar-sections.js";
@@ -10,13 +10,15 @@ import { diagnosticText, restartOffered } from "./TeamPresenceDot.js";
10
10
  import { TeamRowMenu } from "./TeamRowMenu.js";
11
11
  import { TeamSidebarSection } from "./TeamSidebarSection.js";
12
12
  import { AgentEditorDialog, ModelPickerField, sameModel } from "./TeamMemberEditor.js";
13
+ import { TeamAgentImport } from "./TeamAgentImport.js";
13
14
  import createCss from './create.module.css';
14
15
  import css from './sidebar.module.css';
15
- export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, addMember, updateMember, recoverMember, archiveMember, loadModels, memberSessionId, openMemberSession, onCreatingChange, t }) {
16
+ export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, addMember, updateMember, recoverMember, archiveMember, joinWorkspace, leaveWorkspace, loadModels, memberSessionId, openMemberSession, onCreatingChange, t }) {
16
17
  const [members, setMembers] = useState([]);
17
18
  const [loading, setLoading] = useState(true);
18
19
  const [error, setError] = useState();
19
20
  const [formOpen, setFormOpen] = useState(false);
21
+ const [importing, setImporting] = useState(false);
20
22
  const [handle, setHandle] = useState('');
21
23
  const [description, setDescription] = useState('');
22
24
  const [model, setModel] = useState(undefined);
@@ -142,11 +144,10 @@ export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, ad
142
144
  try {
143
145
  const result = await addMember(request);
144
146
  if (result.ok) {
147
+ const committed = { ...result.value.status, workspaceIds: [result.value.status.member.workspaceId] };
145
148
  setMembers(current => {
146
- const retained = current.filter(status => status.member.memberId !== result.value.status.member.memberId);
147
- return result.value.status.member.state === 'inactive' || result.value.status.member.state === 'archived'
148
- ? retained
149
- : [...retained, result.value.status];
149
+ const retained = current.filter(status => status.member.memberId !== committed.member.memberId);
150
+ return committed.member.state === 'inactive' || committed.member.state === 'archived' ? retained : [...retained, committed];
150
151
  });
151
152
  setHandle('');
152
153
  setDescription('');
@@ -195,18 +196,21 @@ export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, ad
195
196
  ...(model === undefined ? {} : { model }),
196
197
  });
197
198
  };
198
- 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 }), 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 }), updateMember: updateMember, recoverMember: recoverMember, archiveMember: archiveMember, 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') })] }))] }));
199
+ return (_jsxs("div", { className: css.panel, children: [_jsxs(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') }), !importing && _jsx(Button, { type: "submit", form: "team-agent-create-form", variant: "primary", disabled: creating || handle.trim().length === 0, children: creating ? t('creatingAgent') : t('createAgent') })] }), children: [_jsx(Button, { className: createCss.modeSwitch, variant: "outline", disabled: creating, "aria-expanded": importing, onClick: () => { setImporting(value => !value); setError(undefined); }, children: importing ? t('createAgent') : t('importAgentTitle') }), formOpen && importing ? _jsx(TeamAgentImport, { workspaceId: workspaceId, loadMembers: loadMembers, joinWorkspace: joinWorkspace, onPending: setCreating, onJoined: async () => { await refresh(); setFormOpen(false); queueMicrotask(() => { triggerRef.current?.focus(); }); }, t: t }) : _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 }), 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); setImporting(false); setFormOpen(true); }, children: _jsx(IconPlusOutline16, { size: 14 }) }) })), children: [loading && members.length === 0 && _jsx("p", { className: css.emptyState, children: t('loadingAgents') }), !loading && error === undefined && 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, { workspaceId: workspaceId, leaveWorkspace: leaveWorkspace, status: status, ...(memberSessionId === undefined ? {} : { current: status.member.sessionId === memberSessionId }), updateMember: updateMember, recoverMember: recoverMember, archiveMember: archiveMember, loadModels: loadModels, openMemberSession: openMemberSession, onUpdated: 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') })] }))] }));
199
200
  }
200
201
  /**
201
202
  * One sidebar Agent row: the select button opens the Member's own Session
202
203
  * conversation page, the avatar carries identity plus the presence badge, and
203
204
  * the row menu opens the editor.
204
205
  */
205
- function AgentRow({ status, current, updateMember, recoverMember, archiveMember, loadModels, openMemberSession, onUpdated, t }) {
206
+ function AgentRow({ workspaceId, leaveWorkspace, status, current, updateMember, recoverMember, archiveMember, loadModels, openMemberSession, onUpdated, t }) {
206
207
  const [menuOpen, setMenuOpen] = useState(false);
207
208
  const [editing, setEditing] = useState(false);
208
209
  const [archiving, setArchiving] = useState(false);
209
210
  const [rowAlert, setRowAlert] = useState();
211
+ const [archivePending, setArchivePending] = useState(false);
212
+ const archiveRequest = useRef();
213
+ const withdrawing = workspaceId !== status.member.workspaceId;
210
214
  // Both row actions ride the same runtime remote: the Host steers a live
211
215
  // session, rebuilds an orphaned composition, or re-runs a failed activation.
212
216
  const recover = async () => {
@@ -241,25 +245,32 @@ function AgentRow({ status, current, updateMember, recoverMember, archiveMember,
241
245
  }
242
246
  };
243
247
  const archive = async () => {
248
+ if (archivePending)
249
+ return;
250
+ setArchivePending(true);
251
+ setRowAlert(undefined);
252
+ archiveRequest.current ??= mintRequestId();
253
+ const request = { requestId: archiveRequest.current, memberId: status.member.memberId };
244
254
  try {
245
- const result = await archiveMember({
246
- requestId: mintRequestId(),
247
- memberId: status.member.memberId,
248
- });
255
+ const result = withdrawing ? await leaveWorkspace({ ...request, workspaceId }) : await archiveMember(request);
256
+ if (!result.ok)
257
+ throw new Error(result.error.message);
258
+ setArchiving(false);
259
+ archiveRequest.current = undefined;
249
260
  await onUpdated();
250
- if (!result.ok) {
251
- setRowAlert(t('archiveAgentFailed', { message: result.error.message }));
252
- }
253
261
  }
254
262
  catch (cause) {
255
- setRowAlert(t('archiveAgentFailed', { message: cause instanceof Error ? cause.message : String(cause) }));
263
+ setRowAlert(cause instanceof Error ? cause.message : String(cause));
264
+ }
265
+ finally {
266
+ setArchivePending(false);
256
267
  }
257
268
  };
258
- 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: [
269
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: css.agentRow, "data-agent-row": true, "data-menu-open": menuOpen || undefined, children: [_jsx("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(TeamMemberIdentity, { status: status, name: status.member.handle.replace(/^@/, ''), className: css.agentCopy, t: t }) }), _jsx("span", { className: css.rowMenu, children: _jsx(TeamRowMenu, { label: t('actionsAgent', { name: status.member.handle }), items: [
259
270
  { id: 'edit', label: t('editAgent'), icon: _jsx(IconEditOutline16, {}) },
260
271
  ...(status.presence === 'error' ? [{ id: 'resume', label: t('resumeAgent'), icon: _jsx(IconPlayOutline16, {}) }] : []),
261
272
  ...(status.availability === 'unavailable' && restartOffered(status) ? [{ id: 'restart', label: t('restartAgent'), icon: _jsx(IconRefreshOutline16, {}) }] : []),
262
- { id: 'archive', label: t('archiveAgent'), icon: _jsx(IconArchiveOutline20, { size: 16 }), danger: true },
273
+ { id: 'archive', label: t(withdrawing ? 'withdrawAgent' : 'archiveAgent'), icon: _jsx(IconArchiveOutline20, { size: 16 }), danger: true },
263
274
  ], onSelect: (id) => {
264
275
  if (id === 'edit')
265
276
  setEditing(true);
@@ -267,5 +278,6 @@ function AgentRow({ status, current, updateMember, recoverMember, archiveMember,
267
278
  setArchiving(true);
268
279
  else
269
280
  void recover();
270
- }, onOpenChange: setMenuOpen }) })] }), rowAlert !== undefined && _jsx("div", { className: css.rowAlert, role: "alert", children: rowAlert }), archiving && (_jsx(Modal, { open: true, onClose: () => { setArchiving(false); }, title: t('archiveAgentTitle', { name: status.member.handle }), closeLabel: t('close'), contentClassName: createCss.dialogContent, footer: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", onClick: () => { setArchiving(false); }, children: t('cancel') }), _jsx(Button, { variant: "primary", onClick: () => { setArchiving(false); void archive(); }, children: t('archiveAgentConfirm') })] }), children: _jsx("p", { className: createCss.error, children: t('archiveAgentNotice', { name: status.member.handle }) }) })), editing && (_jsx(AgentEditorDialog, { status: status, updateMember: updateMember, loadModels: loadModels, onCommitted: onUpdated, onClose: () => { setEditing(false); }, t: t }))] }));
281
+ }, onOpenChange: setMenuOpen }) })] }), !archiving && rowAlert !== undefined && _jsx("div", { className: css.rowAlert, role: "alert", children: rowAlert }), archiving && (_jsxs(Modal, { open: true, onClose: () => { if (!archivePending)
282
+ setArchiving(false); }, title: t(withdrawing ? 'withdrawAgentTitle' : 'archiveAgentTitle', { name: status.member.handle }), closeLabel: t('close'), contentClassName: createCss.dialogContent, footer: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", disabled: archivePending, onClick: () => { setArchiving(false); }, children: t('cancel') }), _jsx(Button, { variant: "primary", disabled: archivePending, onClick: () => { void archive(); }, children: t(withdrawing ? 'withdrawAgent' : 'archiveAgentConfirm') })] }), children: [_jsx("p", { className: createCss.error, children: t(withdrawing ? 'withdrawAgentNotice' : 'archiveAgentNotice', { name: status.member.handle }) }), !withdrawing && status.workspaceIds.length > 1 && _jsx("p", { children: t('archiveOtherWorkspaces', { count: status.workspaceIds.length - 1 }) }), rowAlert !== undefined && _jsx("p", { className: createCss.error, role: "alert", children: rowAlert })] })), editing && (_jsx(AgentEditorDialog, { status: status, updateMember: updateMember, loadModels: loadModels, onCommitted: onUpdated, onClose: () => { setEditing(false); }, t: t }))] }));
271
283
  }
@@ -0,0 +1,18 @@
1
+ import type { AgentTeamMemberId } from '@wowyuarm/dsh-agent-team/types';
2
+ /** One owner as the stack draws it: the id carries the hue, the name the initial. */
3
+ export interface TeamAvatarOwner {
4
+ readonly memberId: AgentTeamMemberId;
5
+ /** Public handle, or the raw Member id when the roster no longer names them. */
6
+ readonly name: string;
7
+ }
8
+ /**
9
+ * The compact "who is on this work" stack: overlapping 18px Member circles in
10
+ * the shared identity language, capped at three plus a `+N` chip. The circles
11
+ * are presentational, so the stack is one `role="img"` whose label carries the
12
+ * whole roster — three anonymous initials would read as noise.
13
+ */
14
+ export declare function TeamAvatarStack({ owners, label }: {
15
+ readonly owners: readonly TeamAvatarOwner[];
16
+ readonly label: string;
17
+ }): import("react").JSX.Element | null;
18
+ //# sourceMappingURL=TeamAvatarStack.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TeamAvatarStack.d.ts","sourceRoot":"","sources":["../../../src/client/TeamAvatarStack.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAOvE,qFAAqF;AACrF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAA;IACpC,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE;IACjD,QAAQ,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE,CAAA;IAC3C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB,sCAQA"}
@@ -0,0 +1,22 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { memberHue } from "./team-formatters.js";
3
+ import css from './avatar-stack.module.css';
4
+ /** Distinct owners past this count collapse into one `+N` chip. */
5
+ const MAX_VISIBLE = 3;
6
+ /**
7
+ * The compact "who is on this work" stack: overlapping 18px Member circles in
8
+ * the shared identity language, capped at three plus a `+N` chip. The circles
9
+ * are presentational, so the stack is one `role="img"` whose label carries the
10
+ * whole roster — three anonymous initials would read as noise.
11
+ */
12
+ export function TeamAvatarStack({ owners, label }) {
13
+ if (owners.length === 0)
14
+ return null;
15
+ const shown = owners.slice(0, MAX_VISIBLE);
16
+ const overflow = owners.length - shown.length;
17
+ return _jsxs("span", { className: css.stack, role: "img", "aria-label": label, children: [shown.map(owner => _jsx("span", { className: css.avatar, style: { '--team-avatar-hue': memberHue(owner.memberId) }, children: initial(owner.name) }, owner.memberId)), overflow > 0 && _jsx("span", { className: css.overflow, children: `+${overflow}` })] });
18
+ }
19
+ /** First visible character of a handle — and of a raw Member id when that is all there is. */
20
+ function initial(name) {
21
+ return name.replace(/^@/, '').replace(/^member:/, '').slice(0, 1).toUpperCase();
22
+ }
@@ -8,6 +8,8 @@ interface TeamChannelPageProps {
8
8
  readonly loadChannels: TeamConversationProps['loadChannels'];
9
9
  readonly subscribeChanges: TeamConversationProps['subscribeChanges'];
10
10
  readonly loadMembers: TeamConversationProps['loadMembers'];
11
+ /** The Human's own unread per Thread: the Host's three-class judgement, never a Client guess. */
12
+ readonly loadInbox: TeamConversationProps['loadInbox'];
11
13
  readonly drafts: TeamDraftStore;
12
14
  readonly getAttachment: TeamConversationProps['getAttachment'];
13
15
  readonly putAttachment: TeamConversationProps['putAttachment'];
@@ -20,6 +22,6 @@ interface TeamChannelPageProps {
20
22
  readonly backToChannels: TeamConversationProps['backToChannels'];
21
23
  readonly t: TeamConversationProps['t'];
22
24
  }
23
- export declare function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscribeChanges, loadMembers, drafts, putAttachment, getAttachment, sendMessage, joinChannel, removeChannelMember, selectThread, selectChannel, backToChannels, resolveTaskRefs, t }: TeamChannelPageProps): import("react").JSX.Element;
25
+ export declare function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscribeChanges, loadMembers, loadInbox, drafts, putAttachment, getAttachment, sendMessage, joinChannel, removeChannelMember, selectThread, selectChannel, backToChannels, resolveTaskRefs, t }: TeamChannelPageProps): import("react").JSX.Element;
24
26
  export {};
25
27
  //# 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,EAE7D,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kDAAkD,CAAA;AAEnF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAEvD,OAAO,KAAK,EAAgB,cAAc,EAAE,MAAM,aAAa,CAAA;AAe/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,+BA4VnR"}
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,kDAAkD,CAAA;AAEnF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAEvD,OAAO,KAAK,EAAgB,cAAc,EAAE,MAAM,aAAa,CAAA;AAiB/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,iGAAiG;IACjG,QAAQ,CAAC,SAAS,EAAE,qBAAqB,CAAC,WAAW,CAAC,CAAA;IACtD,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;AAoCD,wBAAgB,eAAe,CAAC,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,gBAAgB,EAAE,WAAW,EAAE,SAAS,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,+BAgX9R"}
@@ -3,10 +3,12 @@ import { Fragment, useCallback, useEffect, useRef, useState } from 'react';
3
3
  import { Button, IconChevronLeftOutline14, IconChevronRightOutline14, Modal } from '@deepseek-ai/dsh-client-ui-primitives';
4
4
  import { mintRequestId, uploadComposerFiles } from "./requests.js";
5
5
  import { TeamComposer } from "./TeamComposer.js";
6
- import { TeamPresenceDot } from "./TeamPresenceDot.js";
6
+ import { TeamMemberRow } from "./TeamMemberRow.js";
7
7
  import { TeamMessage } from "./TeamMessage.js";
8
+ import { TeamAvatarStack } from "./TeamAvatarStack.js";
9
+ import { TeamCountBadge } from "./TeamCountBadge.js";
8
10
  import { TeamRunDivider } from "./TeamRunDivider.js";
9
- import { formatTaskStatus, taskStatusDot, mentionNamesOf } from "./team-formatters.js";
11
+ import { claimersLabel, formatAbsoluteTime, formatInboxTime, formatTaskStatus, taskStatusDot, mentionNamesOf } from "./team-formatters.js";
10
12
  import { TeamStateDot } from "./TeamStateDot.js";
11
13
  import { useChannelMembership } from "./team-membership.js";
12
14
  import { useTimelineScroll } from "./timeline-scroll.js";
@@ -16,12 +18,14 @@ import channelCss from './channel.module.css';
16
18
  import css from './conversation.module.css';
17
19
  import threadCss from './thread.module.css';
18
20
  /**
19
- * Merge the freshest top-level window over what the reader already has:
20
- * a change-driven refresh must not discard older messages loaded earlier.
21
+ * Merge the freshest top-level window over what the reader already has. The
22
+ * fresh window is authoritative for every Message it covers — a change wake
23
+ * must move that row's live Task state, newest instant, and unread with it —
24
+ * while older Messages loaded earlier are retained instead of discarded.
21
25
  */
22
26
  function mergeChannelView(current, fresh) {
23
- const known = new Set(current.items.map(item => item.message.messageRef));
24
- const items = [...fresh.items.filter(item => !known.has(item.message.messageRef)), ...current.items]
27
+ const freshRefs = new Set(fresh.items.map(item => item.message.messageRef));
28
+ const items = [...current.items.filter(item => !freshRefs.has(item.message.messageRef)), ...fresh.items]
25
29
  .sort((left, right) => left.message.sequence - right.message.sequence);
26
30
  return {
27
31
  ...fresh,
@@ -31,10 +35,26 @@ function mergeChannelView(current, fresh) {
31
35
  hasMore: fresh.hasMore || current.cursor < fresh.cursor,
32
36
  };
33
37
  }
34
- export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscribeChanges, loadMembers, drafts, putAttachment, getAttachment, sendMessage, joinChannel, removeChannelMember, selectThread, selectChannel, backToChannels, resolveTaskRefs, t }) {
38
+ /**
39
+ * The newest fact instant, or nothing when the entry's own Message is still the
40
+ * newest fact on its Thread. That difference is exactly what "has follow-up
41
+ * activity" means, and the Host projects both instants from the ledger.
42
+ */
43
+ function followUpAt(item) {
44
+ const lastActivityAt = item.lastActivityAt;
45
+ return lastActivityAt === undefined || lastActivityAt === item.message.occurredAt ? undefined : lastActivityAt;
46
+ }
47
+ /** Host unread per Thread, keyed for the feed's rows: zero unread is the absence of a badge, not a row. */
48
+ function unreadCounts(inbox) {
49
+ return new Map(inbox.items.filter(item => item.unreadCount > 0).map(item => [item.thread.threadRef, item.unreadCount]));
50
+ }
51
+ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscribeChanges, loadMembers, loadInbox, drafts, putAttachment, getAttachment, sendMessage, joinChannel, removeChannelMember, selectThread, selectChannel, backToChannels, resolveTaskRefs, t }) {
35
52
  const [view, setView] = useState();
36
53
  const [members, setMembers] = useState([]);
37
- const [error, setError] = useState();
54
+ const [unreadByThread, setUnreadByThread] = useState(new Map());
55
+ const [actionError, setError] = useState();
56
+ const [loadError, setLoadError] = useState();
57
+ const error = actionError ?? loadError;
38
58
  const [pendingFiles, setPendingFiles] = useState([]);
39
59
  const [statusMessage, setStatusMessage] = useState();
40
60
  const [loading, setLoading] = useState(true);
@@ -103,7 +123,7 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
103
123
  // Presence counts ride the header meta line; error and unavailable do not count as online.
104
124
  const onlineCount = channelMembers.filter(status => status.presence === 'available' || status.presence === 'working').length;
105
125
  const messageSender = (item) => item.message.sender;
106
- const mentionHandlesMap = new Map(members.map(status => [status.member.memberId, status.member.handle.replace(/^@/, '')]));
126
+ const handleByMember = new Map(members.map(status => [status.member.memberId, status.member.handle.replace(/^@/, '')]));
107
127
  const refresh = async (clearError = false) => {
108
128
  if (!mountedRef.current)
109
129
  return false;
@@ -114,13 +134,18 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
114
134
  if (!loadedRef.current)
115
135
  setLoading(true);
116
136
  if (clearError) {
117
- setError(undefined);
137
+ setLoadError(undefined);
118
138
  setStatusMessage(undefined);
119
139
  }
120
140
  try {
121
- const [loaded, loadedMembers] = await Promise.all([
141
+ const [loaded, loadedMembers, loadedInbox] = await Promise.all([
122
142
  loadChannels({ workspaceId, channelRef, direction: 'before', topLevelOnly: true, includeActivities: false, limit: 20 }),
123
143
  loadMembers({ workspaceId }),
144
+ // The non-direct slice of the Host's own Inbox is the authority for
145
+ // "what needs me on this Thread" — following activity, mentions, and my
146
+ // Task/Claim changes alike. Nothing here re-derives unread from message
147
+ // mentions.
148
+ loadInbox({ workspaceId, limit: 100 }),
124
149
  ]);
125
150
  if (!mountedRef.current || sequence !== refreshSequenceRef.current)
126
151
  return false;
@@ -129,16 +154,26 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
129
154
  loadedRef.current = true;
130
155
  }
131
156
  else
132
- setError(loaded.error.message);
157
+ setLoadError(loaded.error.message);
133
158
  if (loadedMembers.ok)
134
159
  setMembers(loadedMembers.value);
135
160
  else
136
- setError(loadedMembers.error.message);
137
- return loaded.ok && loadedMembers.ok;
161
+ setLoadError(loadedMembers.error.message);
162
+ // A failed unread read drops the badges instead of leaving counts the
163
+ // reader can no longer trust; the failure surfaces like any other read.
164
+ if (loadedInbox.ok)
165
+ setUnreadByThread(unreadCounts(loadedInbox.value));
166
+ else {
167
+ setUnreadByThread(new Map());
168
+ setLoadError(loadedInbox.error.message);
169
+ }
170
+ if (loaded.ok && loadedMembers.ok && loadedInbox.ok)
171
+ setLoadError(undefined);
172
+ return loaded.ok && loadedMembers.ok && loadedInbox.ok;
138
173
  }
139
174
  catch (cause) {
140
175
  if (mountedRef.current && sequence === refreshSequenceRef.current)
141
- setError(cause instanceof Error ? cause.message : String(cause));
176
+ setLoadError(cause instanceof Error ? cause.message : String(cause));
142
177
  return false;
143
178
  }
144
179
  finally {
@@ -158,18 +193,18 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
158
193
  if (loaded.ok)
159
194
  setMembers(loaded.value);
160
195
  else
161
- setError(loaded.error.message);
196
+ setLoadError(loaded.error.message);
162
197
  }
163
198
  catch (cause) {
164
199
  if (mountedRef.current)
165
- setError(cause instanceof Error ? cause.message : String(cause));
200
+ setLoadError(cause instanceof Error ? cause.message : String(cause));
166
201
  }
167
202
  };
168
203
  useEffect(() => {
169
204
  mountedRef.current = true;
170
205
  loadedRef.current = false;
171
206
  setView(undefined);
172
- setError(undefined);
207
+ setLoadError(undefined);
173
208
  setLoading(true);
174
209
  setManagingMembers(false);
175
210
  void refresh();
@@ -178,7 +213,7 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
178
213
  if (!mountedRef.current)
179
214
  return;
180
215
  if (update.type === 'failed') {
181
- setError(update.message);
216
+ setLoadError(update.message);
182
217
  return;
183
218
  }
184
219
  void refresh();
@@ -187,7 +222,7 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
187
222
  if (!mountedRef.current)
188
223
  return;
189
224
  if (update.type === 'failed') {
190
- setError(update.message);
225
+ setLoadError(update.message);
191
226
  return;
192
227
  }
193
228
  void refreshMembers();
@@ -198,7 +233,7 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
198
233
  if (!mountedRef.current)
199
234
  return;
200
235
  if (update.type === 'failed') {
201
- setError(update.message);
236
+ setLoadError(update.message);
202
237
  return;
203
238
  }
204
239
  void refreshMembers();
@@ -314,9 +349,16 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
314
349
  return _jsxs("main", { className: css.surface, "data-team-channel": channelRef, children: [_jsxs("div", { className: css.surfaceHeader, children: [_jsx("div", { className: css.backRow, children: _jsx(Button, { size: "sm", icon: _jsx(IconChevronLeftOutline14, {}), onClick: backToChannels, children: t('backToChannels') }) }), _jsxs("header", { className: css.headerRow, children: [_jsxs("div", { className: css.headerCopy, children: [_jsx("h1", { children: channel === undefined ? '# …' : `# ${channel.name}` }), channel !== undefined && _jsx("p", { children: channel.description }), channel !== undefined && _jsxs("div", { className: channelCss.headerMeta, children: [_jsx("span", { children: t('memberCount', { count: channelMembers.length }) }), _jsx("span", { children: t('onlineCount', { count: onlineCount }) })] })] }), channel !== undefined && _jsx("span", { ref: manageTriggerRef, children: _jsx(Button, { size: "sm", variant: "outline", "aria-haspopup": "dialog", onClick: () => { setManagingMembers(true); }, children: t('manageMembers') }) })] })] }), _jsx(Modal, { open: managingMembers, onClose: closeMembers, title: t('channelMembers'), ...(channel === undefined ? {} : { description: `# ${channel.name} · ${t('memberCount', { count: channelMembers.length })}` }), closeLabel: t('close'), contentClassName: channelCss.modalBody, children: _jsx("div", { ref: memberListRef, className: channelCss.memberList, children: members.filter(status => status.member.state !== 'inactive' && status.member.state !== 'archived').map(status => {
315
350
  const joined = channelMemberIds.has(status.member.memberId);
316
351
  const rowPending = membership.pending.has(status.member.memberId);
317
- const disabled = rowPending || (!joined && status.presence === 'unavailable');
352
+ // Joining needs an active Member the Host refuses any other
353
+ // availability. Leaving only needs the Membership fact, so removal
354
+ // stays offered while a joined Member is down.
355
+ const disabled = rowPending || (!joined && status.availability !== 'active');
318
356
  const rowError = membership.errors.get(status.member.memberId);
319
- return _jsxs("div", { className: channelCss.memberRow, children: [_jsx(TeamPresenceDot, { status: status, t: t }), _jsxs("span", { className: channelCss.memberCopy, children: [_jsxs("strong", { children: ["@", status.member.handle] }), _jsx("small", { children: status.member.description })] }), _jsx(Button, { className: channelCss.memberAction, size: "sm", disabled: disabled, onClick: () => { void membership.change({ workspaceId, channelRef, memberId: status.member.memberId, joined }); }, children: rowPending ? t('membershipUpdating') : joined ? t('removeFromChannel') : t('addToChannel') }), rowError !== undefined && _jsx("p", { className: channelCss.memberError, role: "alert", children: rowError })] }, status.member.memberId);
357
+ return _jsx(TeamMemberRow, { status: status, action: {
358
+ label: rowPending ? t('membershipUpdating') : joined ? t('removeFromChannel') : t('addToChannel'),
359
+ disabled,
360
+ onSelect: () => { void membership.change({ workspaceId, channelRef, memberId: status.member.memberId, joined }); },
361
+ }, ...(rowError === undefined ? {} : { error: rowError }), t: t }, status.member.memberId);
320
362
  }) }) }), _jsx("section", { ref: timeline.ref, onScroll: timeline.onScroll, className: css.timeline, "aria-label": t('timelineLabel'), children: _jsxs("div", { className: css.timelineContent, children: [loading && channel === undefined && error === undefined && _jsx("div", { className: css.emptySurface, children: _jsxs("p", { className: css.loadingState, children: [_jsx("span", { className: css.loadingMark, "aria-hidden": "true" }), t('loadingChannels')] }) }), !loading && channel === undefined && error === undefined && _jsx("div", { className: css.emptySurface, children: _jsx("p", { className: css.emptyState, children: t('emptyChannels') }) }), !loading && channel === undefined && error !== undefined && _jsxs("div", { className: css.errorState, role: "alert", children: [_jsx("span", { children: error }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => { void refresh(true); }, children: t('retry') })] }), view?.hasMore && _jsx("div", { className: css.timelineAction, children: _jsx(Button, { size: "sm", disabled: loadingOlder, onClick: () => { void loadOlder(); }, children: t('loadOlder') }) }), channel !== undefined && view?.items.length === 0 && _jsx("div", { className: css.emptySurface, children: _jsxs("div", { className: css.emptyState, children: [_jsx("strong", { children: t('emptyMessages') }), _jsx("span", { children: t('emptyMessagesHint') })] }) }), (view?.items.length ?? 0) > 0 && chunkRunsWithDays(view.items, messageSender, item => item.message.occurredAt).map((block, blockIndex) => block.kind === 'day'
321
363
  ? _jsx("p", { className: threadCss.daySeparator, children: _jsx("span", { children: block.label }) }, `day-${blockIndex}-${block.label}`)
322
364
  : _jsx("div", { className: css.messageRun, children: block.items.map((item, index) => {
@@ -324,7 +366,52 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
324
366
  const human = item.message.sender === view.humanMemberId;
325
367
  const sender = human ? t('human') : senderStatus?.member.handle ?? item.message.sender;
326
368
  const turnGap = isRunGap(index > 0 ? block.items[index - 1].message.occurredAt : undefined, item.message.occurredAt);
369
+ const task = item.task;
370
+ // The entry's own line is the gate under the body, and its state
371
+ // leads that line: ownership, status, and unread answer the reader
372
+ // where they are already reading. A reply inside a Thread has none
373
+ // of it — the Thread page owns that surface.
374
+ const unread = unreadByThread.get(item.thread.threadRef) ?? 0;
327
375
  // A turn divider already labels the gapped entry; its row stays chrome-free.
328
- return _jsxs(Fragment, { children: [turnGap && _jsx(TeamRunDivider, { occurredAt: item.message.occurredAt }), _jsxs(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), onOpenRef: openRef, onResolveTaskRefs: lookupTaskRefs, grouped: index > 0, showGroupedTime: item.message.topLevel === true && !turnGap, ...(senderStatus === undefined ? {} : { senderTitle: senderStatus.member.description }), children: [item.message.topLevel && item.task !== undefined && item.taskNumber !== undefined && _jsxs("button", { type: "button", className: channelCss.taskFooter, "aria-label": t('openTask', { number: item.taskNumber }), onClick: () => { selectThread(item.thread.threadRef, channelRef, item.task?.taskRef, item.taskNumber); }, children: [_jsx("span", { className: channelCss.taskDot, children: _jsx(TeamStateDot, { size: 8, state: taskStatusDot(item.task.status) }) }), _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.topLevel && item.task === undefined && _jsxs("button", { type: "button", className: channelCss.taskFooter, "aria-label": t('openThread'), onClick: () => { selectThread(item.thread.threadRef, channelRef); }, children: [_jsx("span", { className: channelCss.taskNumber, children: t('threadLabel') }), _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);
376
+ return _jsxs(Fragment, { children: [turnGap && _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, handleByMember), onOpenRef: openRef, onResolveTaskRefs: lookupTaskRefs, grouped: index > 0, showGroupedTime: item.message.topLevel === true && !turnGap, ...(senderStatus === undefined ? {} : { senderTitle: senderStatus.member.description }), children: item.message.topLevel && _jsx(ThreadEntryRow, { item: item, owners: item.claimOwners, unread: unread, t: t, onOpen: () => { selectThread(item.thread.threadRef, channelRef, task?.taskRef, item.taskNumber); } }) })] }, item.message.messageRef);
329
377
  }) }, `run-${block.items[0].message.messageRef}`))] }) }), channel !== undefined ? _jsx(TeamComposer, { members: channelMembers, drafts: drafts, draftKey: draftKey, pending: pending, ...(statusMessage === undefined ? {} : { confirmation: statusMessage }), ...(error === undefined ? {} : { error }), onEdit: clearSendState, onSubmit: () => { void send(); }, pendingFiles: pendingFiles, onFilesChange: setPendingFiles, asTask: asTask, onAsTaskChange: setAsTask, t: t }, draftKey) : _jsx("div", {})] });
330
378
  }
379
+ /**
380
+ * One entry's state: who is on the work, where it stands, and how much of it
381
+ * needs the reader. It leads the entry's own line rather than trailing the
382
+ * identity line, so a reader meets it where they are already reading instead of
383
+ * crossing the column for it — and the same placement holds whether this
384
+ * Message opened its run or continued one, where an identity line would have
385
+ * carried nothing else. The unread capsule is the only member a taskless
386
+ * discussion can carry: it needs no Task.
387
+ */
388
+ function ThreadStateCluster({ task, owners, unread, t }) {
389
+ if (task === undefined && unread === 0)
390
+ return null;
391
+ return _jsxs("span", { className: css.stateCluster, children: [task !== undefined && _jsx(TeamAvatarStack, { owners: owners, label: claimersLabel(owners, t) }), task !== undefined && _jsx(TeamStateDot, { size: 8, state: taskStatusDot(task.status) }), task !== undefined && _jsx("span", { className: css.statusWord, children: formatTaskStatus(task.status, t) }), _jsx(TeamCountBadge, { count: unread })] });
392
+ }
393
+ /**
394
+ * The gate under one top-level Message: the single row that opens its Thread,
395
+ * and the one line that says what stands there. State leads it, then what the
396
+ * entry is: a Task entry keeps its number so the status word never floats free
397
+ * of the Task it describes, follow-up activity adds when the work last moved,
398
+ * and an unanswered Thread says only 回复. The instant uses the Inbox's own
399
+ * 今天/昨天 form with the precise local time on the control's title, and an
400
+ * unread Thread says so in the control's label rather than only in pixels.
401
+ */
402
+ function ThreadEntryRow({ item, owners, unread, t, onOpen }) {
403
+ const taskNumber = item.taskNumber;
404
+ const followUp = followUpAt(item);
405
+ const label = taskNumber !== undefined
406
+ ? t('taskLabel', { number: taskNumber })
407
+ : followUp === undefined ? t('replyAction') : t('threadLabel');
408
+ const text = followUp === undefined ? label : `${label} · ${t('recentActivity', { time: formatInboxTime(followUp, t) })}`;
409
+ const openLabel = taskNumber === undefined
410
+ ? unread > 0 ? t('openThreadUnread', { count: unread }) : t('openThread')
411
+ : unread > 0 ? t('openTaskUnread', { number: taskNumber, count: unread }) : t('openTask', { number: taskNumber });
412
+ // The state is a label, not a control: it stays outside the button so the
413
+ // owner stack keeps its own accessible name — a labeled button prunes its
414
+ // descendants from the accessibility tree — and so the control's name says
415
+ // exactly what clicking it does.
416
+ return _jsxs("span", { className: css.entryLine, "data-thread-entry": "", children: [_jsx(ThreadStateCluster, { task: item.task, owners: owners, unread: unread, t: t }), _jsxs("button", { type: "button", className: css.entryRow, "aria-label": openLabel, ...(followUp === undefined ? {} : { title: formatAbsoluteTime(followUp) }), onClick: onOpen, children: [_jsx("span", { children: text }), _jsx("span", { className: css.entryArrow, "aria-hidden": "true", children: _jsx(IconChevronRightOutline14, { size: 12 }) })] })] });
417
+ }
@@ -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,kDAAkD,CAAA;AAEnF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAclD,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,cAAc,EAAE,gBAAgB,CAAC,gBAAgB,CAAC,CAAA;IAC3D,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,+BAmM9D"}
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,kDAAkD,CAAA;AAEnF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAelD,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,cAAc,EAAE,gBAAgB,CAAC,gBAAgB,CAAC,CAAA;IAC3D,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,+BAsM9D"}