@wowyuarm/dsh-agent-team 0.1.11 → 0.1.12

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 (83) hide show
  1. package/README.md +4 -4
  2. package/README.zh.md +4 -4
  3. package/package.json +3 -2
  4. package/packages/agent-team/README.md +2 -2
  5. package/packages/agent-team/README.zh.md +2 -2
  6. package/packages/agent-team/lib/context-projection.js +5 -1
  7. package/packages/agent-team/lib/index.js +11 -44
  8. package/packages/agent-team/lib/ledger.js +251 -142
  9. package/packages/agent-team/lib/member-runtime.js +47 -9
  10. package/packages/agent-team/lib/mentions.js +56 -0
  11. package/packages/agent-team/lib/spec.js +1 -0
  12. package/packages/agent-team/lib/typert.host.js +80 -31
  13. package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
  14. package/packages/agent-team/lib/typert.remote-client.js +71 -26
  15. package/packages/agent-team/lib/types/context-projection.d.ts.map +1 -1
  16. package/packages/agent-team/lib/types/index.d.ts +1 -7
  17. package/packages/agent-team/lib/types/index.d.ts.map +1 -1
  18. package/packages/agent-team/lib/types/ledger.d.ts +75 -17
  19. package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
  20. package/packages/agent-team/lib/types/member-runtime.d.ts +2 -1
  21. package/packages/agent-team/lib/types/member-runtime.d.ts.map +1 -1
  22. package/packages/agent-team/lib/types/mentions.d.ts +34 -0
  23. package/packages/agent-team/lib/types/mentions.d.ts.map +1 -0
  24. package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
  25. package/packages/agent-team/lib/types/types/entities.d.ts +0 -18
  26. package/packages/agent-team/lib/types/types/entities.d.ts.map +1 -1
  27. package/packages/agent-team/lib/types/types/requests-results.d.ts +57 -15
  28. package/packages/agent-team/lib/types/types/requests-results.d.ts.map +1 -1
  29. package/packages/agent-team/preset/team-member/agent.cordis.yml +48 -8
  30. package/packages/client-agent-team/lib/client.js +1366 -909
  31. package/packages/client-agent-team/lib/client.js.map +1 -1
  32. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
  33. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +3 -3
  34. package/packages/client-agent-team/lib/types/client/TeamAvatarStack.d.ts +18 -0
  35. package/packages/client-agent-team/lib/types/client/TeamAvatarStack.d.ts.map +1 -0
  36. package/packages/client-agent-team/lib/types/client/TeamAvatarStack.js +22 -0
  37. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts +3 -1
  38. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
  39. package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +125 -13
  40. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -1
  41. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +10 -3
  42. package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -1
  43. package/packages/client-agent-team/lib/types/client/TeamComposer.js +11 -17
  44. package/packages/client-agent-team/lib/types/client/TeamConversation.js +1 -1
  45. package/packages/client-agent-team/lib/types/client/TeamCountBadge.d.ts +20 -0
  46. package/packages/client-agent-team/lib/types/client/TeamCountBadge.d.ts.map +1 -0
  47. package/packages/client-agent-team/lib/types/client/TeamCountBadge.js +25 -0
  48. package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts +9 -5
  49. package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts.map +1 -1
  50. package/packages/client-agent-team/lib/types/client/TeamInboxPage.js +82 -14
  51. package/packages/client-agent-team/lib/types/client/TeamMemberRow.d.ts +43 -0
  52. package/packages/client-agent-team/lib/types/client/TeamMemberRow.d.ts.map +1 -0
  53. package/packages/client-agent-team/lib/types/client/TeamMemberRow.js +27 -0
  54. package/packages/client-agent-team/lib/types/client/TeamMembersAction.d.ts +2 -2
  55. package/packages/client-agent-team/lib/types/client/TeamMembersAction.d.ts.map +1 -1
  56. package/packages/client-agent-team/lib/types/client/TeamMembersAction.js +2 -2
  57. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
  58. package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +6 -2
  59. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts.map +1 -1
  60. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +28 -10
  61. package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
  62. package/packages/client-agent-team/lib/types/client/index.js +3 -4
  63. package/packages/client-agent-team/lib/types/client/locales.d.ts +36 -16
  64. package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
  65. package/packages/client-agent-team/lib/types/client/locales.js +36 -16
  66. package/packages/client-agent-team/lib/types/client/slots.d.ts +0 -1
  67. package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
  68. package/packages/client-agent-team/lib/types/client/team-changes.d.ts +7 -1
  69. package/packages/client-agent-team/lib/types/client/team-changes.d.ts.map +1 -1
  70. package/packages/client-agent-team/lib/types/client/team-changes.js +62 -31
  71. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +38 -5
  72. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts.map +1 -1
  73. package/packages/client-agent-team/lib/types/client/team-formatters.js +54 -7
  74. package/packages/tool-agent-team/README.md +2 -2
  75. package/packages/tool-agent-team/README.zh.md +2 -2
  76. package/packages/tool-agent-team/lib/index.js +29 -16
  77. package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
  78. package/packages/agent-team/lib/progress-nudge.js +0 -328
  79. package/packages/agent-team/lib/types/progress-nudge.d.ts +0 -136
  80. package/packages/agent-team/lib/types/progress-nudge.d.ts.map +0 -1
  81. package/packages/client-agent-team/lib/types/client/TeamSettings.d.ts +0 -3
  82. package/packages/client-agent-team/lib/types/client/TeamSettings.d.ts.map +0 -1
  83. package/packages/client-agent-team/lib/types/client/TeamSettings.js +0 -5
@@ -1,11 +1,21 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useRef, useState } from 'react';
3
3
  import { Button } from '@deepseek-ai/dsh-client-ui-primitives';
4
4
  import { formatAbsoluteTime, formatInboxTime } from "./team-formatters.js";
5
+ import { TeamAvatarStack } from "./TeamAvatarStack.js";
6
+ import { TeamCountBadge } from "./TeamCountBadge.js";
5
7
  import css from './conversation.module.css';
6
8
  import inboxCss from './inbox.module.css';
7
9
  /**
8
- * Queue order: newest mention first, one total order across Workspaces. Each
10
+ * How many 「最近活跃」 rows the merged page may show. The Host bounds each
11
+ * Workspace's own slice; this is the single bound across every Workspace on
12
+ * screen, so the section stays the same size no matter how many are open — and
13
+ * it stays a way back into work rather than a second queue, which is why it is
14
+ * shorter than the queue the reader is actually being asked to work through.
15
+ */
16
+ const RECENT_ROWS_LIMIT = 5;
17
+ /**
18
+ * Queue order: newest unread first, one total order across Workspaces. Each
9
19
  * Workspace slice arrives in the Host's own order (that order is the Host's
10
20
  * truncation policy); the merged display order is decided here, where the
11
21
  * merge happens, and the ledger sequence breaks ties so the queue never
@@ -18,16 +28,21 @@ function compareInboxRows(left, right) {
18
28
  return byTime !== 0 ? byTime : right.item.newestSequence - left.item.newestSequence;
19
29
  }
20
30
  /**
21
- * The Human 「提到我」 queue: one direct-only Inbox call per visible Workspace,
22
- * merged into a single recency-ordered list — newest mention first across every
23
- * Workspace, since a queue is read by recency rather than by Workspace.
31
+ * The Human Inbox: one Inbox call per visible Workspace, rendering the Host's
32
+ * two slices — the unread queue (「需要我」, mentions counted inside it rather
33
+ * than alone) and the 「最近活跃」 tail of Threads the reader took part in.
34
+ * Both merge across every Workspace into one recency-ordered list, newest
35
+ * first, since a queue is read by recency rather than by Workspace.
24
36
  * Opening the page never acknowledges anything — only a durable Thread read
25
- * consumes a mention marker, so rows and the badge drop after the Thread is
26
- * opened through the existing auto-ack path.
37
+ * advances the watermark and consumes a mention marker, so rows and the badge
38
+ * drop after the Thread is opened through the existing auto-ack path. A Thread
39
+ * holding unread is only ever in the queue: the Host already excludes it from
40
+ * the tail, and this page never re-derives that judgement.
27
41
  */
28
42
  export function TeamInboxPage({ useWorkspaces, loadInbox, subscribeChanges, selectWorkspace, selectThread, t }) {
29
43
  const workspaces = useWorkspaces(state => state.items);
30
44
  const [rows, setRows] = useState();
45
+ const [recentRows, setRecentRows] = useState([]);
31
46
  const [loading, setLoading] = useState(true);
32
47
  const [error, setError] = useState();
33
48
  // Only the first refresh owns the loading surface; later wakes refresh the
@@ -37,15 +52,19 @@ export function TeamInboxPage({ useWorkspaces, loadInbox, subscribeChanges, sele
37
52
  if (!loadedRef.current)
38
53
  setLoading(true);
39
54
  const results = await Promise.all(workspaces.map(async (workspace) => {
40
- const result = await loadInbox({ workspaceId: workspace.workspaceId, directOnly: true, limit: 100 });
55
+ const result = await loadInbox({ workspaceId: workspace.workspaceId, limit: 100 });
41
56
  return result.ok
42
- ? { ok: true, workspaceId: workspace.workspaceId, workspaceTitle: workspace.title, items: result.value.items }
57
+ ? { ok: true, workspaceId: workspace.workspaceId, workspaceTitle: workspace.title, items: result.value.items, recent: result.value.recent }
43
58
  : { ok: false, message: result.error.message };
44
59
  }));
45
60
  const failure = results.find(result => !result.ok);
46
- setRows(results.flatMap(result => result.ok
47
- ? result.items.map(item => ({ workspaceId: result.workspaceId, workspaceTitle: result.workspaceTitle, item }))
48
- : []).sort(compareInboxRows));
61
+ const asRows = (items, workspaceId, workspaceTitle) => items.map(item => ({ workspaceId, workspaceTitle, item }));
62
+ setRows(results.flatMap(result => result.ok ? asRows(result.items, result.workspaceId, result.workspaceTitle) : []).sort(compareInboxRows));
63
+ // The tail is one global bound rather than one per Workspace: the reader was
64
+ // promised ten Threads to step back into, and every Workspace's slice is
65
+ // already capped on its own, so the merged list is trimmed here.
66
+ setRecentRows(results.flatMap(result => result.ok ? asRows(result.recent, result.workspaceId, result.workspaceTitle) : [])
67
+ .sort(compareInboxRows).slice(0, RECENT_ROWS_LIMIT));
49
68
  setError(failure?.ok === false ? failure.message : undefined);
50
69
  loadedRef.current = true;
51
70
  setLoading(false);
@@ -65,7 +84,56 @@ export function TeamInboxPage({ useWorkspaces, loadInbox, subscribeChanges, sele
65
84
  // The page rides the shared conversation seat: the same header band, 880px
66
85
  // reading column, responsive gutters, and scrollbar gutter as Channel and
67
86
  // Thread, so switching surfaces does not shift the content column.
68
- return _jsxs("main", { className: css.surface, "data-team-inbox": true, children: [_jsx("div", { className: css.surfaceHeader, children: _jsx("header", { className: css.headerRow, children: _jsxs("div", { className: css.headerCopy, children: [_jsx("h1", { children: t('mentionsOfMe') }), rows !== undefined && rows.length > 0 && _jsx("p", { className: inboxCss.headerMeta, children: t('inboxHeaderMeta', { threads: rows.length, mentions: rows.reduce((sum, row) => sum + row.item.directCount, 0) }) })] }) }) }), _jsx("div", { className: css.timeline, children: _jsxs("div", { className: css.timelineContent, children: [loading && rows === undefined && error === undefined && _jsx("div", { className: css.emptySurface, children: _jsxs("p", { className: css.loadingState, children: [_jsx("span", { className: css.loadingMark, "aria-hidden": "true" }), t('loadingInbox')] }) }), !loading && rows === undefined && error !== undefined && _jsxs("div", { className: css.errorState, role: "alert", children: [_jsx("span", { children: error }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => { void refresh(); }, children: t('retry') })] }), rows !== undefined && (rows.length === 0
87
+ const totalUnread = rows?.reduce((sum, row) => sum + row.item.unreadCount, 0) ?? 0;
88
+ const totalMentions = rows?.reduce((sum, row) => sum + row.item.directCount, 0) ?? 0;
89
+ // A row names its Workspace only while the rows on screen span more than one.
90
+ // With a single Workspace in the list the segment is a constant printed down
91
+ // every row, which spends the row's most readable position on something that
92
+ // never varies; the moment a second Workspace reaches the list it comes back,
93
+ // because two rows then have to be tellable apart. This reads the rows rather
94
+ // than the open Workspaces: a Workspace holding nothing to show here is not a
95
+ // reason to keep printing the name of the one that is.
96
+ const shownWorkspaces = new Set();
97
+ for (const row of rows ?? [])
98
+ shownWorkspaces.add(row.workspaceTitle);
99
+ for (const row of recentRows)
100
+ shownWorkspaces.add(row.workspaceTitle);
101
+ const showWorkspace = shownWorkspaces.size > 1;
102
+ return _jsxs("main", { className: css.surface, "data-team-inbox": true, children: [_jsx("div", { className: css.surfaceHeader, children: _jsx("header", { className: css.headerRow, children: _jsxs("div", { className: css.headerCopy, children: [_jsx("h1", { children: t('inboxTitle') }), rows !== undefined && rows.length > 0 && _jsxs("p", { className: inboxCss.headerMeta, children: [_jsx("span", { children: t('inboxHeaderThreads', { count: rows.length }) }), _jsx("span", { className: inboxCss.headerUnread, children: t('inboxHeaderUnread', { count: totalUnread }) }), totalMentions > 0 && _jsx("span", { children: t('inboxHeaderMentions', { count: totalMentions }) })] })] }) }) }), _jsx("div", { className: css.timeline, children: _jsxs("div", { className: css.timelineContent, children: [loading && rows === undefined && error === undefined && _jsx("div", { className: css.emptySurface, children: _jsxs("p", { className: css.loadingState, children: [_jsx("span", { className: css.loadingMark, "aria-hidden": "true" }), t('loadingInbox')] }) }), !loading && rows === undefined && error !== undefined && _jsxs("div", { className: css.errorState, role: "alert", children: [_jsx("span", { children: error }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => { void refresh(); }, children: t('retry') })] }), rows !== undefined && (rows.length === 0 && recentRows.length === 0
69
103
  ? _jsx("div", { className: css.emptySurface, children: _jsxs("div", { className: css.emptyState, children: [_jsx("strong", { children: t('inboxEmptyTitle') }), _jsx("span", { children: t('inboxEmptyHint') })] }) })
70
- : _jsx("div", { className: inboxCss.list, children: rows.map(row => (_jsxs("button", { type: "button", className: inboxCss.row, onClick: () => { open(row); }, children: [_jsxs("span", { className: inboxCss.rowCrumb, children: [row.workspaceTitle, " / ", _jsxs("span", { className: inboxCss.rowChannel, children: ["#", row.item.channelName] }), row.item.taskNumber !== undefined && _jsx("span", { className: inboxCss.rowTask, children: t('taskLabel', { number: row.item.taskNumber }) })] }), _jsx("span", { className: inboxCss.rowPreview, children: row.item.previewText }), _jsx("time", { className: inboxCss.rowTime, dateTime: row.item.newestOccurredAt, title: formatAbsoluteTime(row.item.newestOccurredAt), children: formatInboxTime(row.item.newestOccurredAt, t) })] }, `${row.workspaceId} ${row.item.thread.threadRef}`))) })), rows !== undefined && error !== undefined && _jsx("p", { className: css.error, role: "alert", children: error })] }) })] });
104
+ : _jsxs(_Fragment, { children: [rows.length > 0 && _jsxs("section", { className: inboxCss.section, children: [_jsxs("h2", { className: inboxCss.sectionTitle, children: [t('inboxSectionNeedsMe'), _jsx("span", { className: inboxCss.sectionCount, children: rows.length })] }), _jsx("div", { className: inboxCss.list, children: rows.map(row => _jsx(InboxQueueRow, { row: row, t: t, showWorkspace: showWorkspace, onOpen: () => { open(row); } }, `${row.workspaceId} ${row.item.thread.threadRef}`)) })] }), recentRows.length > 0 && _jsxs("section", { className: inboxCss.section, children: [_jsxs("h2", { className: inboxCss.sectionTitle, children: [t('inboxSectionRecent'), _jsx("span", { className: inboxCss.sectionCount, children: recentRows.length })] }), _jsx("div", { className: inboxCss.list, children: recentRows.map(row => _jsx(InboxQueueRow, { row: row, t: t, showWorkspace: showWorkspace, onOpen: () => { open(row); } }, `${row.workspaceId} ${row.item.thread.threadRef}`)) })] })] })), rows !== undefined && error !== undefined && _jsx("p", { className: css.error, role: "alert", children: error })] }) })] });
105
+ }
106
+ /**
107
+ * One queue row, shaped like the shipped two-line result row: who is on this
108
+ * Thread leads in the gutter, the identity line answers which Thread this is,
109
+ * how much is waiting, and when it last moved, and the gist sits under it on the
110
+ * same column as evidence for that identity rather than as the row's subject.
111
+ *
112
+ * The gutter carries the one thing every row has — who is on the work, or who
113
+ * moved a Thread nobody has claimed — so a Thread that merely arrived and one
114
+ * that named the reader open on the same edge instead of the quieter one opening
115
+ * on a slot reserved for a count it does not hold. It is the grammar the Channel
116
+ * feed's Thread entry row already speaks, where the people on the work lead the
117
+ * row.
118
+ *
119
+ * Before the queue admitted every unread Thread, each row was a mention and the
120
+ * rows were interchangeable; now that named and ambient unread share one list,
121
+ * the row has to carry that difference itself. The count closes the identity
122
+ * line and only its ink changes — the shared capsule fill for a row that names
123
+ * the reader, a hairline for one that merely moved. The two numbers behind that
124
+ * ink (unread, mentions) reach assistive tech through the capsule's own name,
125
+ * because a second visible count beside the first would cost the row the one
126
+ * thing it needs to stay scannable.
127
+ */
128
+ function InboxQueueRow({ row, t, showWorkspace, onOpen }) {
129
+ const { item } = row;
130
+ const actor = item.newestActor;
131
+ const owners = item.claimOwners;
132
+ const named = item.directCount > 0;
133
+ const countLabel = named
134
+ ? t('inboxRowUnreadMentions', { count: item.unreadCount, mentions: item.directCount })
135
+ : t('inboxRowUnread', { count: item.unreadCount });
136
+ return _jsxs("button", { type: "button", className: inboxCss.row, "data-named": named || undefined, onClick: onOpen, children: [_jsx("span", { className: inboxCss.rowActor, children: owners.length > 0
137
+ ? _jsx(TeamAvatarStack, { owners: owners, label: t('claimers', { names: owners.map(owner => `@${owner.name}`).join(', ') }) })
138
+ : _jsx(TeamAvatarStack, { owners: [actor], label: t('inboxRowActor', { name: `@${actor.name}` }) }) }), _jsxs("span", { className: inboxCss.rowLine, children: [_jsxs("span", { className: inboxCss.rowCrumb, children: [showWorkspace && _jsx("span", { className: inboxCss.rowWorkspace, children: row.workspaceTitle }), showWorkspace && ' / ', _jsxs("span", { className: inboxCss.rowChannel, children: ["#", item.channelName] }), ' ', item.taskNumber !== undefined && _jsx("span", { className: inboxCss.rowTask, children: t('taskLabel', { number: item.taskNumber }) })] }), _jsx(TeamCountBadge, { count: item.unreadCount, tone: named ? 'solid' : 'hairline', label: countLabel }), _jsx("time", { className: inboxCss.rowTime, dateTime: item.newestOccurredAt, title: formatAbsoluteTime(item.newestOccurredAt), children: formatInboxTime(item.newestOccurredAt, t) })] }), _jsx("span", { className: inboxCss.rowPreview, children: item.previewText })] });
71
139
  }
@@ -0,0 +1,43 @@
1
+ import type { AgentTeamClientMemberStatus } from '@wowyuarm/dsh-agent-team/types';
2
+ import type { TeamSidebarProps } from './slots.ts';
3
+ /** One membership action: what it says and which Member it acts on. */
4
+ export interface TeamMemberAction {
5
+ readonly label: string;
6
+ readonly onSelect: () => void;
7
+ readonly disabled?: boolean;
8
+ }
9
+ /**
10
+ * The one place a Member's identity is drawn: the presence-bearing avatar plus
11
+ * the handle over its description. Read-only rosters, the rosters with a
12
+ * membership action, and the sidebar Agent list all render this, so identity,
13
+ * tone and truncation cannot drift between surfaces that show the same person.
14
+ *
15
+ * A fragment, not a row: each surface owns its own row element, hit target and
16
+ * grid, and places this identity in that grid's first two tracks. The handle
17
+ * spelling is the surface's call — rosters address Members the way the
18
+ * composer does (`@handle`), the sidebar names them as the directory does.
19
+ */
20
+ export declare function TeamMemberIdentity({ status, name, className, t }: {
21
+ readonly status: AgentTeamClientMemberStatus;
22
+ /** Handle spelling for this surface; defaults to the mention form. */
23
+ readonly name?: string | undefined;
24
+ /** Surface-local copy hook (sidebar line heights, dialog density). */
25
+ readonly className?: string | undefined;
26
+ readonly t: TeamSidebarProps['t'];
27
+ }): import("react").JSX.Element;
28
+ /**
29
+ * The one roster row: identity, an optional membership action, and the row's
30
+ * own failure line. The action is the row's only chrome, so the eye lands on
31
+ * the handle first and on the action second. A read-only roster omits it and
32
+ * the trailing track collapses, handing its width back to the description.
33
+ */
34
+ export declare function TeamMemberRow({ status, action, error, className, t }: {
35
+ readonly status: AgentTeamClientMemberStatus;
36
+ readonly action?: TeamMemberAction | undefined;
37
+ /** Transport failure for this row's last mutation, announced in place. */
38
+ readonly error?: string | undefined;
39
+ /** Surface-local hook (dialog, sidebar, footer) for its own row rules. */
40
+ readonly className?: string | undefined;
41
+ readonly t: TeamSidebarProps['t'];
42
+ }): import("react").JSX.Element;
43
+ //# sourceMappingURL=TeamMemberRow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TeamMemberRow.d.ts","sourceRoot":"","sources":["../../../src/client/TeamMemberRow.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,gCAAgC,CAAA;AACjF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAIlD,uEAAuE;AACvE,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAA;IAC7B,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAC5B;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,EAAE,EAAE;IACjE,QAAQ,CAAC,MAAM,EAAE,2BAA2B,CAAA;IAC5C,sEAAsE;IACtE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAClC,sEAAsE;IACtE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,QAAQ,CAAC,CAAC,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAA;CAClC,+BAQA;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,EAAE,EAAE;IACrE,QAAQ,CAAC,MAAM,EAAE,2BAA2B,CAAA;IAC5C,QAAQ,CAAC,MAAM,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAA;IAC9C,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACnC,0EAA0E;IAC1E,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,QAAQ,CAAC,CAAC,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAA;CAClC,+BAMA"}
@@ -0,0 +1,27 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Button } from '@deepseek-ai/dsh-client-ui-primitives';
3
+ import { TeamMemberAvatar } from "./TeamMemberAvatar.js";
4
+ import css from './member-row.module.css';
5
+ /**
6
+ * The one place a Member's identity is drawn: the presence-bearing avatar plus
7
+ * the handle over its description. Read-only rosters, the rosters with a
8
+ * membership action, and the sidebar Agent list all render this, so identity,
9
+ * tone and truncation cannot drift between surfaces that show the same person.
10
+ *
11
+ * A fragment, not a row: each surface owns its own row element, hit target and
12
+ * grid, and places this identity in that grid's first two tracks. The handle
13
+ * spelling is the surface's call — rosters address Members the way the
14
+ * composer does (`@handle`), the sidebar names them as the directory does.
15
+ */
16
+ export function TeamMemberIdentity({ status, name, className, t }) {
17
+ return _jsxs(_Fragment, { children: [_jsx(TeamMemberAvatar, { status: status, t: t }), _jsxs("span", { className: className === undefined ? css.copy : `${css.copy} ${className}`, children: [_jsx("strong", { children: name ?? `@${status.member.handle.replace(/^@/, '')}` }), _jsx("small", { children: status.member.description })] })] });
18
+ }
19
+ /**
20
+ * The one roster row: identity, an optional membership action, and the row's
21
+ * own failure line. The action is the row's only chrome, so the eye lands on
22
+ * the handle first and on the action second. A read-only roster omits it and
23
+ * the trailing track collapses, handing its width back to the description.
24
+ */
25
+ export function TeamMemberRow({ status, action, error, className, t }) {
26
+ return _jsxs("div", { className: className === undefined ? css.row : `${css.row} ${className}`, "data-team-member-row": true, children: [_jsx(TeamMemberIdentity, { status: status, t: t }), action !== undefined && _jsx(Button, { size: "sm", variant: "outline", className: css.action, disabled: action.disabled === true, onClick: action.onSelect, children: action.label }), error !== undefined && _jsx("p", { className: css.error, role: "alert", children: error })] });
27
+ }
@@ -1,5 +1,5 @@
1
- import type { TeamFooterProps } from './slots.ts';
2
- type TeamMembersActionProps = Pick<TeamFooterProps, 'wide' | 'loadMemberGroups' | 't'>;
1
+ import type { TeamSettingsProps } from './slots.ts';
2
+ type TeamMembersActionProps = Pick<TeamSettingsProps, 'wide' | 'loadMemberGroups' | 't'>;
3
3
  export declare function TeamMembersAction({ wide, loadMemberGroups, t }: TeamMembersActionProps): import("react").JSX.Element;
4
4
  export {};
5
5
  //# sourceMappingURL=TeamMembersAction.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"TeamMembersAction.d.ts","sourceRoot":"","sources":["../../../src/client/TeamMembersAction.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAKjD,KAAK,sBAAsB,GAAG,IAAI,CAAC,eAAe,EAAE,MAAM,GAAG,kBAAkB,GAAG,GAAG,CAAC,CAAA;AAEtF,wBAAgB,iBAAiB,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC,EAAE,EAAE,sBAAsB,+BAuDtF"}
1
+ {"version":3,"file":"TeamMembersAction.d.ts","sourceRoot":"","sources":["../../../src/client/TeamMembersAction.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAKnD,KAAK,sBAAsB,GAAG,IAAI,CAAC,iBAAiB,EAAE,MAAM,GAAG,kBAAkB,GAAG,GAAG,CAAC,CAAA;AAExF,wBAAgB,iBAAiB,CAAC,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC,EAAE,EAAE,sBAAsB,+BAkDtF"}
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useEffect, useRef, useState } from 'react';
3
3
  import { IconUserOutline16, Modal, Tooltip } from '@deepseek-ai/dsh-client-ui-primitives';
4
- import { TeamPresenceDot } from "./TeamPresenceDot.js";
4
+ import { TeamMemberRow } from "./TeamMemberRow.js";
5
5
  import membersCss from './members.module.css';
6
6
  import css from './team.module.css';
7
7
  export function TeamMembersAction({ wide, loadMemberGroups, t }) {
@@ -28,5 +28,5 @@ export function TeamMembersAction({ wide, loadMemberGroups, t }) {
28
28
  setPanelOpen(false);
29
29
  queueMicrotask(() => { triggerRef.current?.focus(); });
30
30
  };
31
- return (_jsxs(_Fragment, { children: [_jsx(Tooltip, { label: t('members'), delayMs: 500, disabled: wide, children: _jsxs("button", { ref: triggerRef, type: "button", className: wide ? css.settingsAction : `${css.settingsAction} ${css.rail}`, "aria-label": t('members'), "aria-haspopup": "dialog", onClick: openMembers, children: [_jsx(IconUserOutline16, { size: wide ? 16 : 18 }), wide && _jsx("span", { children: t('members') })] }) }), _jsx(Modal, { open: panelOpen, onClose: closeMembers, title: t('members'), closeLabel: t('close'), contentClassName: membersCss.body, children: _jsxs("div", { ref: contentRef, className: membersCss.content, tabIndex: -1, children: [loading && _jsx("p", { className: membersCss.state, role: "status", children: t('loadingAgents') }), !loading && groups.length === 0 && error === undefined && _jsx("p", { className: membersCss.state, children: t('emptyAgents') }), !loading && groups.map(group => (_jsxs("section", { className: membersCss.group, "aria-labelledby": `team-members-${group.workspaceId}`, children: [_jsx("h3", { id: `team-members-${group.workspaceId}`, children: group.workspaceTitle }), group.members.map(status => (_jsxs("div", { className: membersCss.member, children: [_jsx(TeamPresenceDot, { status: status, t: t }), _jsxs("span", { className: membersCss.copy, children: [_jsxs("strong", { children: ["@", status.member.handle] }), _jsx("small", { children: status.member.description })] })] }, status.member.memberId)))] }, group.workspaceId))), error !== undefined && _jsx("p", { className: membersCss.error, role: "alert", children: error })] }) })] }));
31
+ return (_jsxs(_Fragment, { children: [_jsx(Tooltip, { label: t('members'), delayMs: 500, disabled: wide, children: _jsxs("button", { ref: triggerRef, type: "button", className: wide ? css.settingsAction : `${css.settingsAction} ${css.rail}`, "aria-label": t('members'), "aria-haspopup": "dialog", onClick: openMembers, children: [_jsx(IconUserOutline16, { size: wide ? 16 : 18 }), wide && _jsx("span", { children: t('members') })] }) }), _jsx(Modal, { open: panelOpen, onClose: closeMembers, title: t('members'), closeLabel: t('close'), contentClassName: membersCss.body, children: _jsxs("div", { ref: contentRef, className: membersCss.content, tabIndex: -1, children: [loading && _jsx("p", { className: membersCss.state, role: "status", children: t('loadingAgents') }), !loading && groups.length === 0 && error === undefined && _jsx("p", { className: membersCss.state, children: t('emptyAgents') }), !loading && groups.map(group => (_jsxs("section", { className: membersCss.group, "aria-labelledby": `team-members-${group.workspaceId}`, children: [_jsx("h3", { id: `team-members-${group.workspaceId}`, children: group.workspaceTitle }), group.members.map(status => _jsx(TeamMemberRow, { status: status, className: membersCss.member, t: t }, status.member.memberId))] }, group.workspaceId))), error !== undefined && _jsx("p", { className: membersCss.error, role: "alert", children: error })] }) })] }));
32
32
  }
@@ -1 +1 @@
1
- {"version":3,"file":"TeamThreadPage.d.ts","sourceRoot":"","sources":["../../../src/client/TeamThreadPage.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,mBAAmB,EAInB,gBAAgB,EAGhB,kBAAkB,EAEnB,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kDAAkD,CAAA;AAEnF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AACvD,OAAO,KAAK,EAAgB,cAAc,EAAE,MAAM,aAAa,CAAA;AAe/D,UAAU,mBAAmB;IAC3B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,QAAQ,CAAC,UAAU,CAAC,EAAE,mBAAmB,CAAA;IACzC,QAAQ,CAAC,OAAO,CAAC,EAAE,gBAAgB,CAAA;IACnC,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAA;IACtC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,eAAe,EAAE,qBAAqB,CAAC,iBAAiB,CAAC,CAAA;IAClE,QAAQ,CAAC,YAAY,EAAE,qBAAqB,CAAC,cAAc,CAAC,CAAA;IAC5D,QAAQ,CAAC,UAAU,EAAE,qBAAqB,CAAC,YAAY,CAAC,CAAA;IACxD,QAAQ,CAAC,iBAAiB,EAAE,qBAAqB,CAAC,mBAAmB,CAAC,CAAA;IACtE,QAAQ,CAAC,kBAAkB,EAAE,qBAAqB,CAAC,oBAAoB,CAAC,CAAA;IACxE,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,KAAK,EAAE,qBAAqB,CAAC,OAAO,CAAC,CAAA;IAC9C,QAAQ,CAAC,UAAU,EAAE,qBAAqB,CAAC,YAAY,CAAC,CAAA;IACxD,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,YAAY,EAAE,qBAAqB,CAAC,cAAc,CAAC,CAAA;IAC5D,QAAQ,CAAC,eAAe,EAAE,qBAAqB,CAAC,iBAAiB,CAAC,CAAA;IAClE,QAAQ,CAAC,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC,CAAA;CACvC;AA+CD,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,+BAmvBxD"}
1
+ {"version":3,"file":"TeamThreadPage.d.ts","sourceRoot":"","sources":["../../../src/client/TeamThreadPage.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,mBAAmB,EAInB,gBAAgB,EAGhB,kBAAkB,EAEnB,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kDAAkD,CAAA;AAEnF,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AACvD,OAAO,KAAK,EAAgB,cAAc,EAAE,MAAM,aAAa,CAAA;AAe/D,UAAU,mBAAmB;IAC3B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,QAAQ,CAAC,UAAU,CAAC,EAAE,mBAAmB,CAAA;IACzC,QAAQ,CAAC,OAAO,CAAC,EAAE,gBAAgB,CAAA;IACnC,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAA;IACtC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,eAAe,EAAE,qBAAqB,CAAC,iBAAiB,CAAC,CAAA;IAClE,QAAQ,CAAC,YAAY,EAAE,qBAAqB,CAAC,cAAc,CAAC,CAAA;IAC5D,QAAQ,CAAC,UAAU,EAAE,qBAAqB,CAAC,YAAY,CAAC,CAAA;IACxD,QAAQ,CAAC,iBAAiB,EAAE,qBAAqB,CAAC,mBAAmB,CAAC,CAAA;IACtE,QAAQ,CAAC,kBAAkB,EAAE,qBAAqB,CAAC,oBAAoB,CAAC,CAAA;IACxE,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,KAAK,EAAE,qBAAqB,CAAC,OAAO,CAAC,CAAA;IAC9C,QAAQ,CAAC,UAAU,EAAE,qBAAqB,CAAC,YAAY,CAAC,CAAA;IACxD,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,YAAY,EAAE,qBAAqB,CAAC,cAAc,CAAC,CAAA;IAC5D,QAAQ,CAAC,eAAe,EAAE,qBAAqB,CAAC,iBAAiB,CAAC,CAAA;IAClE,QAAQ,CAAC,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC,CAAA;CACvC;AA+CD,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,+BAuvBxD"}
@@ -5,7 +5,7 @@ import { TeamComposer } from "./TeamComposer.js";
5
5
  import { diagnosticText, TeamPresenceDot } from "./TeamPresenceDot.js";
6
6
  import { TeamMessage } from "./TeamMessage.js";
7
7
  import { TeamRunDivider } from "./TeamRunDivider.js";
8
- import { formatActivity, formatClaimState, formatTaskStatus, formatTaskTitle, mentionNamesOf, taskStatusDot } from "./team-formatters.js";
8
+ import { formatActivity, formatClaimState, formatTaskStatus, formatTaskTitle, mentionNamesOf, mentionedMemberIds, taskStatusDot } from "./team-formatters.js";
9
9
  import { TeamStateDot } from "./TeamStateDot.js";
10
10
  import { mintRequestId, uploadComposerFiles } from "./requests.js";
11
11
  import { ScopeCoverage } from "./scope-coverage.js";
@@ -659,8 +659,12 @@ export function TeamThreadPage(props) {
659
659
  }
660
660
  if (result.value.kind === 'committed') {
661
661
  const committed = result.value;
662
+ // The optimistic row chips what the Host is about to report: a
663
+ // hand-typed `@Handle` delivers exactly like a picked recipient, and the
664
+ // committed facts replace this row as soon as they arrive.
665
+ const optimisticMentions = [...new Set([...recipients, ...mentionedMemberIds(draft, channelMembers)])].sort();
662
666
  setCurrentFacts(current => {
663
- const merged = mergeFacts(current, [{ kind: 'message', sequence: committed.message.sequence, message: committed.message, mentions: [...recipients].sort(), occurredAt: committed.receipt.occurredAt }]);
667
+ const merged = mergeFacts(current, [{ kind: 'message', sequence: committed.message.sequence, message: committed.message, mentions: optimisticMentions, occurredAt: committed.receipt.occurredAt }]);
664
668
  currentFactsRef.current = merged;
665
669
  return merged;
666
670
  });
@@ -1 +1 @@
1
- {"version":3,"file":"TeamWorkspaceBrowser.d.ts","sourceRoot":"","sources":["../../../src/client/TeamWorkspaceBrowser.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAUlD,wBAAgB,oBAAoB,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE,aAAa,EAAE,WAAW,EAAE,CAAC,EAAE,aAAa,EAAE,WAAW,EAAE,SAAS,EAAE,gBAAgB,EAAE,cAAc,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,mBAAmB,EAAE,UAAU,EAAE,iBAAiB,EAAE,EAAE,gBAAgB,+BA+GtY"}
1
+ {"version":3,"file":"TeamWorkspaceBrowser.d.ts","sourceRoot":"","sources":["../../../src/client/TeamWorkspaceBrowser.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAyBlD,wBAAgB,oBAAoB,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE,aAAa,EAAE,WAAW,EAAE,CAAC,EAAE,aAAa,EAAE,WAAW,EAAE,SAAS,EAAE,gBAAgB,EAAE,cAAc,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,mBAAmB,EAAE,UAAU,EAAE,iBAAiB,EAAE,EAAE,gBAAgB,+BAoHtY"}
@@ -7,6 +7,17 @@ import { useSidebarSectionOpen, setSidebarSectionOpen } from "./sidebar-sections
7
7
  import { TeamAgentsPanel } from "./TeamAgentsPanel.js";
8
8
  import { TeamChannelsPanel } from "./TeamChannelsPanel.js";
9
9
  import css from './sidebar.module.css';
10
+ /**
11
+ * The Inbox entry's icon with its unread mark. The quantity left this surface
12
+ * and lives in the control's accessible name: the sidebar answers whether
13
+ * anything is waiting at a glance, and the number — which moves on every fact —
14
+ * is what a reader asks for on purpose. The mark hangs off the icon rather than
15
+ * off the control, so the wide card and the 36px rail button put the same dot
16
+ * on the same corner of the same glyph.
17
+ */
18
+ function InboxMark({ unread }) {
19
+ return _jsxs("span", { className: css.inboxMark, children: [_jsx(IconQueueOutline14, { size: 16 }), unread > 0 && _jsx("span", { className: css.inboxDot, "data-team-inbox-dot": true, "aria-hidden": "true" })] });
20
+ }
10
21
  export function TeamWorkspaceBrowser({ wide, expandSidebar, navigation, selectWorkspace, selectChannel, selectInbox, t, useWorkspaces, loadMembers, loadInbox, subscribeChanges, subscribeReads, addMember, loadChannels, createChannel, updateChannel, archiveChannel, updateMember, recoverMember, archiveMember, joinChannel, removeChannelMember, loadModels, openMemberSession }) {
11
22
  const navigationState = useSyncExternalStore(navigation.subscribe, navigation.getSnapshot, navigation.getSnapshot);
12
23
  const workspaces = useWorkspaces(state => state.items);
@@ -17,26 +28,33 @@ export function TeamWorkspaceBrowser({ wide, expandSidebar, navigation, selectWo
17
28
  // one is set, the selected Agent card while a Member Session view is open,
18
29
  // otherwise the browsed Workspace's overview. The selected row keeps its
19
30
  // quiet folder tint (data-selected) in every case. The mention-Inbox card
20
- // takes the marker while the Inbox page stands.
31
+ // takes the marker while the Inbox page stands, and stands down while the
32
+ // embedded Member Session covers that page: the Inbox stays the remembered
33
+ // face underneath — closing the overlay returns to it, marker included —
34
+ // but the card is not a second current page while an Agent holds the seat.
21
35
  const overviewIsCurrent = navigationState.channelRef === undefined && navigationState.memberSessionId === undefined && navigationState.inbox !== true;
22
- const inboxIsCurrent = navigationState.inbox === true;
36
+ const inboxIsCurrent = navigationState.inbox === true && navigationState.memberSessionId === undefined;
23
37
  const [creatingAgents, setCreatingAgents] = useState([]);
24
38
  // Rail icons request expansion and name the section to reveal once wide.
25
39
  const [pendingSection, setPendingSection] = useState();
26
40
  const channelsRef = useRef(null);
27
41
  const agentsRef = useRef(null);
28
42
  const workspacesOpen = useSidebarSectionOpen(undefined, 'workspaces');
29
- // The cross-Workspace mention badge: one scope-less subscription while the
30
- // Team sidebar stands; every wake re-pulls each Workspace's direct-only
31
- // total (limit 1 — totals cover every row, never the list). Same-burst
32
- // wakes coalesce behind a short debounce; the total is still whatever the
33
- // Host's direct-only projection returns.
43
+ // The cross-Workspace Inbox badge: one scope-less subscription while the
44
+ // Team sidebar stands; every wake re-pulls each Workspace's unread total
45
+ // (limit 1 — totals cover every row, never the list). Same-burst wakes
46
+ // coalesce behind a short debounce; the number is whatever the Host's whole
47
+ // unread slice returns, mentions included rather than alone.
34
48
  const [inboxTotal, setInboxTotal] = useState(0);
49
+ // One name for both entries: what the control is, and how much waits behind
50
+ // it. The rail repeats it as its hover hint, which is the only place a reader
51
+ // still meets the quantity without opening the page.
52
+ const inboxLabel = inboxTotal > 0 ? t('inboxTitleWithCount', { count: inboxTotal }) : t('inboxTitle');
35
53
  useEffect(() => {
36
54
  let disposed = false;
37
55
  let scheduled;
38
56
  const refresh = async () => {
39
- const results = await Promise.all(workspaces.map(workspace => loadInbox({ workspaceId: workspace.workspaceId, directOnly: true, limit: 1 })));
57
+ const results = await Promise.all(workspaces.map(workspace => loadInbox({ workspaceId: workspace.workspaceId, limit: 1 })));
40
58
  if (disposed)
41
59
  return;
42
60
  setInboxTotal(results.reduce((sum, result) => result.ok ? sum + result.value.totalUnreadCount : sum, 0));
@@ -80,7 +98,7 @@ export function TeamWorkspaceBrowser({ wide, expandSidebar, navigation, selectWo
80
98
  queueMicrotask(() => { node?.querySelector('button')?.focus(); });
81
99
  }, [wide, pendingSection]);
82
100
  if (!wide) {
83
- return _jsxs("nav", { className: css.railWorkspace, "aria-label": t('workspaceSections'), children: [_jsx(Tooltip, { label: t('mentionsOfMe'), side: "right", children: _jsxs("button", { type: "button", className: css.railButton, "aria-label": inboxTotal > 0 ? t('mentionsOfMeWithCount', { count: inboxTotal }) : t('mentionsOfMe'), "aria-current": inboxIsCurrent ? 'page' : undefined, onClick: () => { selectInbox(); expandSidebar(); }, children: [_jsx(IconQueueOutline14, { size: 16 }), inboxTotal > 0 && _jsx("span", { className: css.inboxBadge, "aria-hidden": "true", children: inboxTotal > 99 ? '99+' : inboxTotal })] }) }), _jsx(Tooltip, { label: t('channels'), side: "right", children: _jsx("button", { type: "button", className: css.railButton, "aria-label": t('channels'), onClick: () => { setPendingSection('channels'); expandSidebar(); }, children: _jsx(IconListPenOutline16, { size: 16 }) }) }), _jsx(Tooltip, { label: t('agents'), side: "right", children: _jsx("button", { type: "button", className: css.railButton, "aria-label": t('agents'), onClick: () => { setPendingSection('agents'); expandSidebar(); }, children: _jsx(IconAgentPresetOutline16, { size: 16 }) }) })] });
101
+ return _jsxs("nav", { className: css.railWorkspace, "aria-label": t('workspaceSections'), children: [_jsx(Tooltip, { label: inboxLabel, side: "right", children: _jsx("button", { type: "button", className: css.railButton, "aria-label": inboxLabel, "aria-current": inboxIsCurrent ? 'page' : undefined, onClick: () => { selectInbox(); expandSidebar(); }, children: _jsx(InboxMark, { unread: inboxTotal }) }) }), _jsx(Tooltip, { label: t('channels'), side: "right", children: _jsx("button", { type: "button", className: css.railButton, "aria-label": t('channels'), onClick: () => { setPendingSection('channels'); expandSidebar(); }, children: _jsx(IconListPenOutline16, { size: 16 }) }) }), _jsx(Tooltip, { label: t('agents'), side: "right", children: _jsx("button", { type: "button", className: css.railButton, "aria-label": t('agents'), onClick: () => { setPendingSection('agents'); expandSidebar(); }, children: _jsx(IconAgentPresetOutline16, { size: 16 }) }) })] });
84
102
  }
85
- return _jsxs("section", { className: css.workspaceBrowser, "aria-label": t('workspaces'), children: [_jsxs("button", { type: "button", className: css.inboxCard, "aria-label": inboxTotal > 0 ? t('mentionsOfMeWithCount', { count: inboxTotal }) : t('mentionsOfMe'), "aria-current": inboxIsCurrent ? 'page' : undefined, onClick: selectInbox, children: [_jsx(IconQueueOutline14, { size: 16 }), _jsx("span", { className: css.inboxCardLabel, children: t('mentionsOfMe') }), inboxTotal > 0 && _jsx("span", { className: css.inboxBadge, "aria-hidden": "true", children: inboxTotal > 99 ? '99+' : inboxTotal })] }), _jsx(TeamSidebarSection, { title: t('workspaces'), open: workspacesOpen, onToggle: open => { setSidebarSectionOpen(undefined, 'workspaces', open); }, children: _jsxs("div", { className: css.workspaceList, children: [workspaces.map(workspace => _jsx(TeamWorkspaceRow, { workspaceId: workspace.workspaceId, title: workspace.title, path: workspace.path, selected: workspace.workspaceId === selectedId, current: workspace.workspaceId === selectedId && overviewIsCurrent, onSelect: selectWorkspace }, workspace.workspaceId)), workspaces.length === 0 && _jsx("p", { className: css.emptyState, children: t('empty') })] }) }), selectedId !== undefined && _jsxs("div", { className: css.workspaceSection, children: [_jsx("div", { ref: channelsRef, children: _jsx(TeamChannelsPanel, { workspaceId: selectedId, loadMembers: loadMembers, loadChannels: loadChannels, subscribeChanges: subscribeChanges, createChannel: createChannel, updateChannel: updateChannel, archiveChannel: archiveChannel, joinChannel: joinChannel, removeChannelMember: removeChannelMember, creatingAgents: creatingAgents.filter(request => request.workspaceId === selectedId), ...(navigationState.memberSessionId !== undefined || navigationState.channelRef === undefined ? {} : { selectedChannelRef: navigationState.channelRef }), selectChannel: selectChannel, t: t }, selectedId) }), _jsx("div", { ref: agentsRef, children: _jsx(TeamAgentsPanel, { workspaceId: selectedId, loadMembers: loadMembers, subscribeChanges: subscribeChanges, addMember: addMember, updateMember: updateMember, recoverMember: recoverMember, archiveMember: archiveMember, loadModels: loadModels, ...(navigationState.memberSessionId === undefined ? {} : { memberSessionId: navigationState.memberSessionId }), openMemberSession: openMemberSession, onCreatingChange: (request, creating) => { setCreatingAgents(current => creating ? [...current.filter(item => item.requestId !== request.requestId), request] : current.filter(item => item.requestId !== request.requestId)); }, t: t }, selectedId) })] })] });
103
+ return _jsxs("section", { className: css.workspaceBrowser, "aria-label": t('workspaces'), children: [_jsxs("button", { type: "button", className: css.inboxCard, "aria-label": inboxLabel, "aria-current": inboxIsCurrent ? 'page' : undefined, onClick: selectInbox, children: [_jsx(InboxMark, { unread: inboxTotal }), _jsx("span", { className: css.inboxCardLabel, children: t('inboxTitle') })] }), _jsx(TeamSidebarSection, { title: t('workspaces'), open: workspacesOpen, onToggle: open => { setSidebarSectionOpen(undefined, 'workspaces', open); }, children: _jsxs("div", { className: css.workspaceList, children: [workspaces.map(workspace => _jsx(TeamWorkspaceRow, { workspaceId: workspace.workspaceId, title: workspace.title, path: workspace.path, selected: workspace.workspaceId === selectedId, current: workspace.workspaceId === selectedId && overviewIsCurrent, onSelect: selectWorkspace }, workspace.workspaceId)), workspaces.length === 0 && _jsx("p", { className: css.emptyState, children: t('empty') })] }) }), selectedId !== undefined && _jsxs("div", { className: css.workspaceSection, children: [_jsx("div", { ref: channelsRef, children: _jsx(TeamChannelsPanel, { workspaceId: selectedId, loadMembers: loadMembers, loadChannels: loadChannels, subscribeChanges: subscribeChanges, createChannel: createChannel, updateChannel: updateChannel, archiveChannel: archiveChannel, joinChannel: joinChannel, removeChannelMember: removeChannelMember, creatingAgents: creatingAgents.filter(request => request.workspaceId === selectedId), ...(navigationState.memberSessionId !== undefined || navigationState.channelRef === undefined ? {} : { selectedChannelRef: navigationState.channelRef }), selectChannel: selectChannel, t: t }, selectedId) }), _jsx("div", { ref: agentsRef, children: _jsx(TeamAgentsPanel, { workspaceId: selectedId, loadMembers: loadMembers, subscribeChanges: subscribeChanges, addMember: addMember, updateMember: updateMember, recoverMember: recoverMember, archiveMember: archiveMember, loadModels: loadModels, ...(navigationState.memberSessionId === undefined ? {} : { memberSessionId: navigationState.memberSessionId }), openMemberSession: openMemberSession, onCreatingChange: (request, creating) => { setCreatingAgents(current => creating ? [...current.filter(item => item.requestId !== request.requestId), request] : current.filter(item => item.requestId !== request.requestId)); }, t: t }, selectedId) })] })] });
86
104
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAqCnE,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEhD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAK5C,OAAO,EAAU,KAAK,OAAO,EAAE,MAAM,cAAc,CAAA;AAEnD,YAAY,EAAE,QAAQ,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAA;AAC9F,YAAY,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAIhD,eAAO,MAAM,MAAM,UAElB,CAAA;AAED,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,kBAAkB;QAC1B,IAAI,EAAE,OAAO,CAAA;KACd;CACF;AAED,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf,cAAc,EAAE,cAAc,CAAA;QAC9B,UAAU,EAAE,cAAc,CAAA;KAC3B;CACF;AA8LD,wBAAsB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAI7D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAoCnE,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEhD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAK5C,OAAO,EAAU,KAAK,OAAO,EAAE,MAAM,cAAc,CAAA;AAEnD,YAAY,EAAE,QAAQ,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAA;AAC9F,YAAY,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAIhD,eAAO,MAAM,MAAM,UAElB,CAAA;AAED,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,kBAAkB;QAC1B,IAAI,EAAE,OAAO,CAAA;KACd;CACF;AAED,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf,cAAc,EAAE,cAAc,CAAA;QAC9B,UAAU,EAAE,cAAc,CAAA;KAC3B;CACF;AA6LD,wBAAsB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAI7D"}
@@ -1,9 +1,9 @@
1
- import agentTeamRemote from '../../../agent-team/lib/typert.remote-client.js';
1
+ import agentTeamRemote from '@wowyuarm/dsh-agent-team/remote';
2
2
  import { TeamNavigation } from "./navigation.js";
3
3
  import { TeamChangeStream, TeamReadStream } from "./team-changes.js";
4
4
  import { TeamDraftStore } from "./drafts.js";
5
5
  import { TeamFooterAction } from "./TeamFooterAction.js";
6
- import { TeamSettings } from "./TeamSettings.js";
6
+ import { TeamMembersAction } from "./TeamMembersAction.js";
7
7
  import { TeamConversation } from "./TeamConversation.js";
8
8
  import { TeamWorkspaceBrowser } from "./TeamWorkspaceBrowser.js";
9
9
  import { en, zh } from "./locales.js";
@@ -183,12 +183,11 @@ function applyUi(ctx) {
183
183
  navigation.actions().exitMemberSession();
184
184
  navigation.actions().leaveTeam();
185
185
  },
186
- loadMemberGroups,
187
186
  }),
188
187
  }, TeamFooterAction));
189
188
  registerModeShadow(ctx, navigation, changes, reads, drafts, 'sidebar.workspaces', TeamWorkspaceBrowser);
190
189
  registerModeShadow(ctx, navigation, changes, reads, drafts, 'main', TeamConversation, undefined, 'conversation');
191
- registerModeShadow(ctx, navigation, changes, reads, drafts, 'sidebar.settings', TeamSettings, () => ({ loadMemberGroups }));
190
+ registerModeShadow(ctx, navigation, changes, reads, drafts, 'sidebar.settings', TeamMembersAction, () => ({ loadMemberGroups }));
192
191
  }
193
192
  export async function apply(ctx) {
194
193
  const disposeRemote = await ctx.remote.$mount(agentTeamRemote);
@@ -27,14 +27,20 @@ export declare const zh: {
27
27
  readonly statusError: "错误";
28
28
  readonly statusUnavailable: "不可用";
29
29
  readonly addChannel: "新建频道";
30
- readonly mentionsOfMe: "提到我";
31
- readonly mentionsOfMeWithCount: "提到我,{count} 条未读提及";
32
- readonly loadingInbox: "正在加载提到我…";
33
- readonly inboxEmptyTitle: "还没有人提到你";
34
- readonly inboxEmptyHint: "需要你知道或做决定时,成员会提到你";
35
- readonly inboxTimeToday: "今天";
30
+ readonly inboxTitle: "收件箱";
31
+ readonly inboxTitleWithCount: "收件箱,{count} 条未读";
32
+ readonly loadingInbox: "正在加载收件箱…";
33
+ readonly inboxEmptyTitle: "收件箱是空的";
34
+ readonly inboxEmptyHint: "你参与的 Thread 有新活动、或有人提到你时,会出现在这里";
36
35
  readonly inboxTimeYesterday: "昨天";
37
- readonly inboxHeaderMeta: "共 {threads} 个 Thread · {mentions} 条提及,按最新提及排序";
36
+ readonly inboxHeaderThreads: "{count} 个 Thread";
37
+ readonly inboxHeaderUnread: "{count} 条未读";
38
+ readonly inboxHeaderMentions: "{count} 条提及";
39
+ readonly inboxRowUnread: "{count} 条未读";
40
+ readonly inboxRowUnreadMentions: "{count} 条未读,其中 {mentions} 条提及";
41
+ readonly inboxRowActor: "最新来自 {name}";
42
+ readonly inboxSectionNeedsMe: "需要我";
43
+ readonly inboxSectionRecent: "最近活跃";
38
44
  readonly channelName: "名称";
39
45
  readonly channelDescription: "说明";
40
46
  readonly initialMembers: "初始成员";
@@ -100,12 +106,16 @@ export declare const zh: {
100
106
  readonly taskStatusInReview: "待验收";
101
107
  readonly taskStatusDone: "已完成";
102
108
  readonly taskStatusClosed: "已关闭";
103
- readonly taskMessageCount: "{count} 条消息";
104
109
  readonly taskLabel: "Task #{number}";
105
110
  readonly taskPending: "Task …";
106
111
  readonly openTask: "打开 Task #{number}";
107
112
  readonly openThread: "打开讨论";
113
+ readonly openTaskUnread: "打开 Task #{number}({count} 条新动态)";
114
+ readonly openThreadUnread: "打开讨论({count} 条新动态)";
108
115
  readonly threadLabel: "讨论";
116
+ readonly recentActivity: "最近活动 {time}";
117
+ readonly replyAction: "回复";
118
+ readonly claimers: "由 {names} 处理";
109
119
  readonly asTask: "作为任务";
110
120
  readonly promoteToTask: "转为 Task";
111
121
  readonly promotingTask: "正在转为 Task…";
@@ -184,14 +194,20 @@ export declare const en: {
184
194
  readonly statusError: "Error";
185
195
  readonly statusUnavailable: "Unavailable";
186
196
  readonly addChannel: "New Channel";
187
- readonly mentionsOfMe: "Mentions of me";
188
- readonly mentionsOfMeWithCount: "Mentions of me, {count} unread mention(s)";
189
- readonly loadingInbox: "Loading mentions…";
190
- readonly inboxEmptyTitle: "Nobody has mentioned you yet";
191
- readonly inboxEmptyHint: "Members will mention you when you must know or decide";
192
- readonly inboxTimeToday: "Today";
197
+ readonly inboxTitle: "Inbox";
198
+ readonly inboxTitleWithCount: "Inbox, {count} unread";
199
+ readonly loadingInbox: "Loading Inbox…";
200
+ readonly inboxEmptyTitle: "Your Inbox is empty";
201
+ readonly inboxEmptyHint: "Threads you take part in appear here when they have new activity or mention you";
193
202
  readonly inboxTimeYesterday: "Yesterday";
194
- readonly inboxHeaderMeta: "{threads} Thread(s) · {mentions} mention(s), newest first";
203
+ readonly inboxHeaderThreads: "{count} Thread(s)";
204
+ readonly inboxHeaderUnread: "{count} unread";
205
+ readonly inboxHeaderMentions: "{count} mention(s)";
206
+ readonly inboxRowUnread: "{count} unread";
207
+ readonly inboxRowUnreadMentions: "{count} unread, {mentions} mention(s)";
208
+ readonly inboxRowActor: "Latest from {name}";
209
+ readonly inboxSectionNeedsMe: "Needs me";
210
+ readonly inboxSectionRecent: "Recently active";
195
211
  readonly channelName: "Name";
196
212
  readonly channelDescription: "Description";
197
213
  readonly initialMembers: "Initial members";
@@ -257,12 +273,16 @@ export declare const en: {
257
273
  readonly taskStatusInReview: "In review";
258
274
  readonly taskStatusDone: "Done";
259
275
  readonly taskStatusClosed: "Closed";
260
- readonly taskMessageCount: "{count} messages";
261
276
  readonly taskLabel: "Task #{number}";
262
277
  readonly taskPending: "Task …";
263
278
  readonly openTask: "Open Task #{number}";
264
279
  readonly openThread: "Open thread";
280
+ readonly openTaskUnread: "Open Task #{number} ({count} new updates)";
281
+ readonly openThreadUnread: "Open thread ({count} new updates)";
265
282
  readonly threadLabel: "Thread";
283
+ readonly recentActivity: "Last activity {time}";
284
+ readonly replyAction: "Reply";
285
+ readonly claimers: "Handled by {names}";
266
286
  readonly asTask: "As task";
267
287
  readonly promoteToTask: "Convert to Task";
268
288
  readonly promotingTask: "Converting to Task…";
@@ -1 +1 @@
1
- {"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4JL,CAAA;AAEV,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4JL,CAAA;AAEV,MAAM,MAAM,OAAO,GAAG,MAAM,OAAO,EAAE,CAAA"}
1
+ {"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsKL,CAAA;AAEV,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsKL,CAAA;AAEV,MAAM,MAAM,OAAO,GAAG,MAAM,OAAO,EAAE,CAAA"}