@wowyuarm/dsh-agent-team 0.1.14-alpha.2 → 0.1.14

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 (127) hide show
  1. package/README.md +5 -3
  2. package/README.zh.md +5 -3
  3. package/cordis.patch.yml +2 -185
  4. package/package.json +52 -46
  5. package/packages/agent-team/README.md +1 -1
  6. package/packages/agent-team/README.zh.md +1 -1
  7. package/packages/agent-team/core-skills/member-memory-manager/SKILL.md +30 -0
  8. package/packages/agent-team/lib/context-continuity-host.js +91 -0
  9. package/packages/agent-team/lib/context-projection.js +266 -489
  10. package/packages/agent-team/lib/context-source.js +17 -106
  11. package/packages/agent-team/lib/human-avatar.js +61 -0
  12. package/packages/agent-team/lib/human-profile.js +46 -0
  13. package/packages/agent-team/lib/human-update-check.js +119 -0
  14. package/packages/agent-team/lib/index.js +408 -264
  15. package/packages/agent-team/lib/ledger.js +120 -15
  16. package/packages/agent-team/lib/member-context.js +9 -8
  17. package/packages/agent-team/lib/member-runtime.js +1 -1
  18. package/packages/agent-team/lib/member-time-context.js +2 -5
  19. package/packages/agent-team/lib/preset-roster.js +15 -0
  20. package/packages/agent-team/lib/pressure-policy.js +9 -6
  21. package/packages/agent-team/lib/session-remediation.js +493 -0
  22. package/packages/agent-team/lib/stored-session-reader.js +2 -1
  23. package/packages/agent-team/lib/typert.host.js +605 -291
  24. package/packages/agent-team/lib/typert.remote-client.d.ts +15 -6
  25. package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
  26. package/packages/agent-team/lib/typert.remote-client.js +367 -244
  27. package/packages/agent-team/lib/types/context-continuity-host.d.ts +84 -0
  28. package/packages/agent-team/lib/types/context-continuity-host.d.ts.map +1 -0
  29. package/packages/agent-team/lib/types/context-projection.d.ts +130 -185
  30. package/packages/agent-team/lib/types/context-projection.d.ts.map +1 -1
  31. package/packages/agent-team/lib/types/context-source.d.ts +17 -81
  32. package/packages/agent-team/lib/types/context-source.d.ts.map +1 -1
  33. package/packages/agent-team/lib/types/human-avatar.d.ts +29 -0
  34. package/packages/agent-team/lib/types/human-avatar.d.ts.map +1 -0
  35. package/packages/agent-team/lib/types/human-profile.d.ts +44 -0
  36. package/packages/agent-team/lib/types/human-profile.d.ts.map +1 -0
  37. package/packages/agent-team/lib/types/human-update-check.d.ts +73 -0
  38. package/packages/agent-team/lib/types/human-update-check.d.ts.map +1 -0
  39. package/packages/agent-team/lib/types/index.d.ts +112 -38
  40. package/packages/agent-team/lib/types/index.d.ts.map +1 -1
  41. package/packages/agent-team/lib/types/ledger.d.ts +31 -2
  42. package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
  43. package/packages/agent-team/lib/types/member-context.d.ts +0 -14
  44. package/packages/agent-team/lib/types/member-context.d.ts.map +1 -1
  45. package/packages/agent-team/lib/types/member-time-context.d.ts +0 -15
  46. package/packages/agent-team/lib/types/member-time-context.d.ts.map +1 -1
  47. package/packages/agent-team/lib/types/preset-roster.d.ts +5 -0
  48. package/packages/agent-team/lib/types/preset-roster.d.ts.map +1 -0
  49. package/packages/agent-team/lib/types/pressure-policy.d.ts +6 -0
  50. package/packages/agent-team/lib/types/pressure-policy.d.ts.map +1 -1
  51. package/packages/agent-team/lib/types/session-remediation.d.ts +172 -0
  52. package/packages/agent-team/lib/types/session-remediation.d.ts.map +1 -0
  53. package/packages/agent-team/lib/types/stored-session-reader.d.ts +2 -1
  54. package/packages/agent-team/lib/types/stored-session-reader.d.ts.map +1 -1
  55. package/packages/agent-team/lib/types/types/entities.d.ts +20 -3
  56. package/packages/agent-team/lib/types/types/entities.d.ts.map +1 -1
  57. package/packages/agent-team/lib/types/types/requests-results.d.ts +63 -0
  58. package/packages/agent-team/lib/types/types/requests-results.d.ts.map +1 -1
  59. package/packages/agent-team/preset/team-member/agent.cordis.yml +166 -0
  60. package/packages/client-agent-team/lib/client.js +1667 -516
  61. package/packages/client-agent-team/lib/client.js.map +1 -1
  62. package/packages/client-agent-team/lib/types/client/HumanSettingsSection.d.ts +15 -0
  63. package/packages/client-agent-team/lib/types/client/HumanSettingsSection.d.ts.map +1 -0
  64. package/packages/client-agent-team/lib/types/client/HumanSettingsSection.js +115 -0
  65. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
  66. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +12 -8
  67. package/packages/client-agent-team/lib/types/client/TeamAvatarStack.d.ts +22 -1
  68. package/packages/client-agent-team/lib/types/client/TeamAvatarStack.d.ts.map +1 -1
  69. package/packages/client-agent-team/lib/types/client/TeamAvatarStack.js +25 -2
  70. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts +6 -1
  71. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
  72. package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +69 -31
  73. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +4 -4
  74. package/packages/client-agent-team/lib/types/client/TeamComposer.js +3 -3
  75. package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts +1 -1
  76. package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts.map +1 -1
  77. package/packages/client-agent-team/lib/types/client/TeamConversation.js +12 -5
  78. package/packages/client-agent-team/lib/types/client/TeamFooterAction.js +2 -2
  79. package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts +4 -1
  80. package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts.map +1 -1
  81. package/packages/client-agent-team/lib/types/client/TeamInboxPage.js +22 -7
  82. package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts +11 -1
  83. package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts.map +1 -1
  84. package/packages/client-agent-team/lib/types/client/TeamMemberEditor.js +69 -8
  85. package/packages/client-agent-team/lib/types/client/TeamMembersAction.js +2 -2
  86. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts +18 -3
  87. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
  88. package/packages/client-agent-team/lib/types/client/TeamMessage.js +213 -67
  89. package/packages/client-agent-team/lib/types/client/TeamRowMenu.d.ts.map +1 -1
  90. package/packages/client-agent-team/lib/types/client/TeamRowMenu.js +2 -2
  91. package/packages/client-agent-team/lib/types/client/TeamSidebarSection.js +2 -2
  92. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts +5 -0
  93. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
  94. package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +55 -29
  95. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +3 -3
  96. package/packages/client-agent-team/lib/types/client/TeamWorkspaceSelector.js +2 -2
  97. package/packages/client-agent-team/lib/types/client/avatar-image.d.ts +21 -0
  98. package/packages/client-agent-team/lib/types/client/avatar-image.d.ts.map +1 -0
  99. package/packages/client-agent-team/lib/types/client/avatar-image.js +9 -0
  100. package/packages/client-agent-team/lib/types/client/human-identity.d.ts +94 -0
  101. package/packages/client-agent-team/lib/types/client/human-identity.d.ts.map +1 -0
  102. package/packages/client-agent-team/lib/types/client/human-identity.js +117 -0
  103. package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
  104. package/packages/client-agent-team/lib/types/client/index.js +112 -41
  105. package/packages/client-agent-team/lib/types/client/locales.d.ts +72 -2
  106. package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
  107. package/packages/client-agent-team/lib/types/client/locales.js +78 -2
  108. package/packages/client-agent-team/lib/types/client/multi-menu-field.js +2 -2
  109. package/packages/client-agent-team/lib/types/client/refs.d.ts +71 -0
  110. package/packages/client-agent-team/lib/types/client/refs.d.ts.map +1 -0
  111. package/packages/client-agent-team/lib/types/client/refs.js +181 -0
  112. package/packages/client-agent-team/lib/types/client/slots.d.ts +8 -2
  113. package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
  114. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +57 -9
  115. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts.map +1 -1
  116. package/packages/client-agent-team/lib/types/client/team-formatters.js +85 -17
  117. package/packages/tool-agent-team/README.md +1 -1
  118. package/packages/tool-agent-team/README.zh.md +1 -1
  119. package/packages/tool-agent-team/lib/context-tools.js +20 -6
  120. package/packages/tool-agent-team/lib/index.js +2 -2
  121. package/packages/tool-agent-team/lib/types/context-tools.d.ts.map +1 -1
  122. package/packages/agent-team/lib/context-management.js +0 -371
  123. package/packages/agent-team/lib/types/context-management.d.ts +0 -154
  124. package/packages/agent-team/lib/types/context-management.d.ts.map +0 -1
  125. package/packages/client-agent-team/lib/types/client/task-refs.d.ts +0 -31
  126. package/packages/client-agent-team/lib/types/client/task-refs.d.ts.map +0 -1
  127. package/packages/client-agent-team/lib/types/client/task-refs.js +0 -99
@@ -0,0 +1,15 @@
1
+ import type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
2
+ import { type TeamHumanIdentityFace } from './human-identity.ts';
3
+ export interface HumanSettingsSectionInjected {
4
+ /** The shared Human identity projection: profile facts plus post-write refresh. */
5
+ identity: TeamHumanIdentityFace;
6
+ /** Persist one renamed display name; the failure message when the Host refuses it. */
7
+ saveName: (name: string) => Promise<string | undefined>;
8
+ /** Upload one image file, persist its reference; the failure message when it does not stick. */
9
+ uploadAvatar: (file: File) => Promise<string | undefined>;
10
+ /** Clear the avatar; the identity falls back to the initial. */
11
+ removeAvatar: () => Promise<string | undefined>;
12
+ }
13
+ export type HumanSettingsSectionProps = PropsRuntime<'settings.section'> & PropsLocale<'team'> & HumanSettingsSectionInjected;
14
+ export declare function HumanSettingsSection(props: HumanSettingsSectionProps): import("react").JSX.Element;
15
+ //# sourceMappingURL=HumanSettingsSection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HumanSettingsSection.d.ts","sourceRoot":"","sources":["../../../src/client/HumanSettingsSection.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AAEjF,OAAO,EAAoB,KAAK,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAiBlF,MAAM,WAAW,4BAA4B;IAC3C,mFAAmF;IACnF,QAAQ,EAAE,qBAAqB,CAAA;IAC/B,sFAAsF;IACtF,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;IACvD,gGAAgG;IAChG,YAAY,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;IACzD,gEAAgE;IAChE,YAAY,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;CAChD;AAED,MAAM,MAAM,yBAAyB,GACjC,YAAY,CAAC,kBAAkB,CAAC,GAChC,WAAW,CAAC,MAAM,CAAC,GACnB,4BAA4B,CAAA;AAsBhC,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,yBAAyB,+BAiKpE"}
@@ -0,0 +1,115 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useRef, useState } from 'react';
3
+ import { Button, Input } from '@deepseek-ai/dsh-client-ui-primitives';
4
+ import { useHumanIdentity } from "./human-identity.js";
5
+ import { useAvatarImage } from "./avatar-image.js";
6
+ import css from './human-settings.module.css';
7
+ /**
8
+ * The Human's own settings page: display name, avatar, and the version
9
+ * footnote.
10
+ *
11
+ * The page owns no durable fact and no copy of one. Name, avatar, and version
12
+ * all arrive from the shared identity projection, so a save here moves the
13
+ * message rows and member refs at the same moment; a failed write keeps the
14
+ * typed name in the field and reports the Host's own reason.
15
+ */
16
+ /** Host-side avatar ceiling (`ATTACHMENT_MAX_BYTES`): the settings page refuses larger files before the round trip. */
17
+ const AVATAR_MAX_BYTES = 10 * 1024 * 1024;
18
+ /**
19
+ * Run one durable write and reduce every failure to the message the page
20
+ * reports. A rejected Remote call (a dropped carrier, a refused write) is a
21
+ * failure like any other: without this the field would sit on "saving" forever
22
+ * and report nothing.
23
+ */
24
+ async function failureOf(action) {
25
+ try {
26
+ return await action();
27
+ }
28
+ catch (error) {
29
+ return error instanceof Error ? error.message : String(error);
30
+ }
31
+ }
32
+ /** First visible character of a display name, or the neutral `H` before one is known. */
33
+ function avatarInitial(name) {
34
+ const trimmed = (name ?? '').replace(/^@/, '').trim();
35
+ return trimmed === '' ? 'H' : trimmed.slice(0, 1).toUpperCase();
36
+ }
37
+ export function HumanSettingsSection(props) {
38
+ const { t, identity } = props;
39
+ const profile = useHumanIdentity(identity);
40
+ // Undefined means "follow the Host value": the field re-syncs whenever the
41
+ // profile changes underneath, without a background read clobbering a name
42
+ // the reader is still editing.
43
+ const [draft, setDraft] = useState(undefined);
44
+ const [saving, setSaving] = useState(false);
45
+ const [uploading, setUploading] = useState(false);
46
+ const [notice, setNotice] = useState(null);
47
+ const filePicker = useRef(null);
48
+ const name = draft ?? profile.name ?? '';
49
+ const dirty = draft !== undefined && draft.trim() !== (profile.name ?? '');
50
+ const emptyName = draft !== undefined && draft.trim() === '';
51
+ const hasAvatar = profile.avatarRef !== undefined;
52
+ // The circle answers to decoded bytes, not to a stored reference: the Host
53
+ // accepts any `image/…` payload, so a file this browser cannot read has to
54
+ // fall back to the initial exactly as a removed one does.
55
+ const avatar = useAvatarImage(profile.avatarUrl);
56
+ const submitName = async () => {
57
+ if (!dirty || emptyName || saving)
58
+ return;
59
+ setSaving(true);
60
+ setNotice(null);
61
+ const failure = await failureOf(() => props.saveName(name.trim()));
62
+ setSaving(false);
63
+ if (failure !== undefined) {
64
+ setNotice(t('humanSettingsNameFailed', { message: failure }));
65
+ return;
66
+ }
67
+ setDraft(undefined);
68
+ };
69
+ const pickAvatar = async (file) => {
70
+ if (file === undefined)
71
+ return;
72
+ setNotice(null);
73
+ if (!file.type.startsWith('image/')) {
74
+ setNotice(t('humanSettingsAvatarNotImage'));
75
+ return;
76
+ }
77
+ if (file.size > AVATAR_MAX_BYTES) {
78
+ setNotice(t('humanSettingsAvatarTooLarge'));
79
+ return;
80
+ }
81
+ setUploading(true);
82
+ const failure = await failureOf(() => props.uploadAvatar(file));
83
+ setUploading(false);
84
+ if (failure !== undefined)
85
+ setNotice(t('humanSettingsAvatarFailed', { message: failure }));
86
+ };
87
+ const removeAvatar = async () => {
88
+ setNotice(null);
89
+ const failure = await failureOf(() => props.removeAvatar());
90
+ if (failure !== undefined)
91
+ setNotice(t('humanSettingsAvatarFailed', { message: failure }));
92
+ };
93
+ // The page says what it is before it says what happened: the settings nav
94
+ // lists it beside the Harness's own pages, so the title alone leaves "whose
95
+ // profile is this, and where does it apply?" unanswered — and every state,
96
+ // including a failed read, has to answer it.
97
+ const pageHeader = _jsxs(_Fragment, { children: [_jsx("h2", { className: css.heading, children: t('humanSettingsTitle') }), _jsx("p", { className: css.intro, children: t('humanSettingsIntro') })] });
98
+ if (profile.status === 'loading' && profile.name === undefined) {
99
+ return _jsxs("div", { className: css.section, children: [pageHeader, _jsx("p", { className: css.state, role: "status", children: t('humanSettingsLoading') })] });
100
+ }
101
+ if (profile.name === undefined) {
102
+ return _jsxs("div", { className: css.section, children: [pageHeader, _jsx("p", { className: css.state, role: "alert", children: t('humanSettingsUnavailable', { message: profile.error ?? '' }) }), _jsx("div", { className: css.stateAction, children: _jsx(Button, { variant: "outline", onClick: () => { void identity.refresh(); }, children: t('retry') }) })] });
103
+ }
104
+ return (_jsxs("div", { className: css.section, children: [pageHeader, _jsxs("div", { className: css.rows, children: [_jsxs("div", { className: css.row, children: [_jsxs("div", { className: css.rowText, children: [_jsx("div", { className: css.title, children: t('humanSettingsName') }), _jsx("div", { className: css.desc, children: t('humanSettingsNameHint') })] }), _jsxs("form", { className: css.controls, onSubmit: (event) => {
105
+ event.preventDefault();
106
+ void submitName();
107
+ }, children: [_jsx(Input, { className: css.nameInput, "aria-label": t('humanSettingsName'), "aria-invalid": emptyName || undefined, value: name, disabled: saving, onChange: (event) => { setDraft(event.target.value); } }), _jsx(Button, { type: "submit", variant: "primary", disabled: saving || !dirty || emptyName, children: saving ? t('humanSettingsSaving') : t('humanSettingsSave') })] })] }), _jsxs("div", { className: css.row, children: [_jsxs("div", { className: css.rowText, children: [_jsx("div", { className: css.title, children: t('humanSettingsAvatar') }), _jsx("div", { className: css.desc, children: t('humanSettingsAvatarHint') })] }), _jsxs("div", { className: css.controls, children: [avatar.src === undefined
108
+ ? _jsx("span", { className: `${css.identity} ${css.identityFallback}`, "data-avatar": "initial", "aria-hidden": "true", children: avatarInitial(profile.name) })
109
+ : _jsx("img", { className: `${css.identity} ${css.identityImage}`, "data-avatar": "image", src: avatar.src, alt: "", onError: avatar.failed }), _jsx("input", { ref: filePicker, type: "file", accept: "image/*", tabIndex: -1, "aria-hidden": "true", hidden: true, onChange: (event) => {
110
+ void pickAvatar(event.target.files?.[0]);
111
+ event.target.value = '';
112
+ } }), _jsx(Button, { variant: "outline", disabled: uploading, onClick: () => { filePicker.current?.click(); }, children: uploading ? t('humanSettingsUploading') : hasAvatar ? t('humanSettingsReplace') : t('humanSettingsUpload') }), hasAvatar && _jsx(Button, { disabled: uploading, onClick: () => { void removeAvatar(); }, children: t('humanSettingsRemoveAvatar') })] })] })] }), _jsxs("div", { className: css.footnote, children: [_jsx("span", { children: t('humanSettingsVersion', { version: profile.version ?? '' }) }), _jsx("span", { "aria-hidden": "true", children: "\u00B7" }), _jsx("a", { className: css.link, href: profile.repoUrl ?? '', target: "_blank", rel: "noreferrer", children: "GitHub" }), profile.updateAvailable && profile.latestVersion !== undefined
113
+ ? _jsx("a", { className: css.link, href: `${profile.repoUrl ?? ''}/releases`, target: "_blank", rel: "noreferrer", children: t('humanSettingsUpdateAvailable', { version: profile.latestVersion }) })
114
+ : null] }), emptyName && _jsx("p", { className: css.notice, children: t('humanSettingsNameEmpty') }), notice === null ? null : _jsx("p", { className: css.notice, role: "alert", children: notice })] }));
115
+ }
@@ -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;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
+ {"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,+BA+O9P"}
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
- import { Button, IconArchiveOutlineRegular, IconEditOutlineRegular, IconPlayOutlineRegular, IconPlusOutlineRegular, IconRefreshOutlineRegular, Input, Modal, Tooltip } from '@deepseek-ai/dsh-client-ui-primitives';
3
+ import { Button, IconArchiveOutline20, IconEditOutline16, IconPlayOutline16, IconPlusOutline16, IconRefreshOutline16, Input, Modal, Tooltip } from '@deepseek-ai/dsh-client-ui-primitives';
4
4
  import { TeamMemberIdentity } from "./TeamMemberRow.js";
5
5
  import { SortableRow, useSidebarRowDrag } from "./sidebar-drag.js";
6
6
  import { moveSidebarItem, useSidebarOrder } from "./sidebar-order.js";
@@ -9,7 +9,7 @@ import { mintRequestId } from "./requests.js";
9
9
  import { diagnosticText, restartOffered } from "./TeamPresenceDot.js";
10
10
  import { TeamRowMenu } from "./TeamRowMenu.js";
11
11
  import { TeamSidebarSection } from "./TeamSidebarSection.js";
12
- import { AgentEditorDialog, ModelPickerField, sameModel } from "./TeamMemberEditor.js";
12
+ import { AgentEditorDialog, ModelPickerField, sameModel, warmModelCatalog } from "./TeamMemberEditor.js";
13
13
  import { TeamAgentImport } from "./TeamAgentImport.js";
14
14
  import createCss from './create.module.css';
15
15
  import css from './sidebar.module.css';
@@ -112,6 +112,10 @@ export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, ad
112
112
  setLoading(false);
113
113
  }, [loadMembers, workspaceId, followRollover]);
114
114
  useEffect(() => { void refresh(); }, [refresh]);
115
+ // Warm the Host model catalog while the roster loads, so the create and
116
+ // edit pickers open with rows instead of paying the first read on open.
117
+ // Mount-scoped (not per refresh): presence wakes must not refetch it.
118
+ useEffect(() => { warmModelCatalog(loadModels); }, [loadModels]);
115
119
  useEffect(() => subscribeChanges({ kind: 'workspace', workspaceId }, update => {
116
120
  if (update.type === 'failed') {
117
121
  setError(update.message);
@@ -144,7 +148,7 @@ export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, ad
144
148
  try {
145
149
  const result = await addMember(request);
146
150
  if (result.ok) {
147
- const committed = { ...result.value.status, workspaceIds: [result.value.status.member.workspaceId] };
151
+ const committed = { ...result.value.status, workspaceIds: result.value.workspaceIds };
148
152
  setMembers(current => {
149
153
  const retained = current.filter(status => status.member.memberId !== committed.member.memberId);
150
154
  return committed.member.state === 'inactive' || committed.member.state === 'archived' ? retained : [...retained, committed];
@@ -196,7 +200,7 @@ export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, ad
196
200
  ...(model === undefined ? {} : { model }),
197
201
  });
198
202
  };
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(IconPlusOutlineRegular, { 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') })] }))] }));
203
+ 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') })] }))] }));
200
204
  }
201
205
  /**
202
206
  * One sidebar Agent row: the select button opens the Member's own Session
@@ -267,10 +271,10 @@ function AgentRow({ workspaceId, leaveWorkspace, status, current, updateMember,
267
271
  }
268
272
  };
269
273
  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: [
270
- { id: 'edit', label: t('editAgent'), icon: _jsx(IconEditOutlineRegular, {}) },
271
- ...(status.presence === 'error' ? [{ id: 'resume', label: t('resumeAgent'), icon: _jsx(IconPlayOutlineRegular, {}) }] : []),
272
- ...(status.availability === 'unavailable' && restartOffered(status) ? [{ id: 'restart', label: t('restartAgent'), icon: _jsx(IconRefreshOutlineRegular, {}) }] : []),
273
- { id: 'archive', label: t(withdrawing ? 'withdrawAgent' : 'archiveAgent'), icon: _jsx(IconArchiveOutlineRegular, { size: 16 }), danger: true },
274
+ { id: 'edit', label: t('editAgent'), icon: _jsx(IconEditOutline16, {}) },
275
+ ...(status.presence === 'error' ? [{ id: 'resume', label: t('resumeAgent'), icon: _jsx(IconPlayOutline16, {}) }] : []),
276
+ ...(status.availability === 'unavailable' && restartOffered(status) ? [{ id: 'restart', label: t('restartAgent'), icon: _jsx(IconRefreshOutline16, {}) }] : []),
277
+ { id: 'archive', label: t(withdrawing ? 'withdrawAgent' : 'archiveAgent'), icon: _jsx(IconArchiveOutline20, { size: 16 }), danger: true },
274
278
  ], onSelect: (id) => {
275
279
  if (id === 'edit')
276
280
  setEditing(true);
@@ -5,14 +5,35 @@ export interface TeamAvatarOwner {
5
5
  /** Public handle, or the raw Member id when the roster no longer names them. */
6
6
  readonly name: string;
7
7
  }
8
+ /**
9
+ * The reader's own identity, as the Client's one Human projection holds it.
10
+ *
11
+ * The stack draws every owner from the shared Member language — hue plus
12
+ * initial — which is the whole identity an Agent has. The Human is the one
13
+ * Member with a picture, so that one chip draws the picture and falls back to
14
+ * this name's initial, exactly as the timeline already does.
15
+ */
16
+ export interface TeamAvatarHuman {
17
+ readonly memberId: AgentTeamMemberId;
18
+ readonly name: string;
19
+ readonly avatarUrl?: string | undefined;
20
+ }
21
+ /**
22
+ * Owners as a seat names them: the Client's Human identity outranks the name
23
+ * the Host projected for that actor, so a rename moves label and initial
24
+ * together in every seat that draws the stack.
25
+ */
26
+ export declare function namedAvatarOwners(owners: readonly TeamAvatarOwner[], human: TeamAvatarHuman | undefined): readonly TeamAvatarOwner[];
8
27
  /**
9
28
  * The compact "who is on this work" stack: overlapping 18px Member circles in
10
29
  * the shared identity language, capped at three plus a `+N` chip. The circles
11
30
  * are presentational, so the stack is one `role="img"` whose label carries the
12
31
  * whole roster — three anonymous initials would read as noise.
13
32
  */
14
- export declare function TeamAvatarStack({ owners, label }: {
33
+ export declare function TeamAvatarStack({ owners, label, human }: {
15
34
  readonly owners: readonly TeamAvatarOwner[];
16
35
  readonly label: string;
36
+ /** The reader's own identity; one owner matching it draws the Human's picture. */
37
+ readonly human?: TeamAvatarHuman | undefined;
17
38
  }): import("react").JSX.Element | null;
18
39
  //# sourceMappingURL=TeamAvatarStack.d.ts.map
@@ -1 +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"}
1
+ {"version":3,"file":"TeamAvatarStack.d.ts","sourceRoot":"","sources":["../../../src/client/TeamAvatarStack.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAQvE,qFAAqF;AACrF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAA;IACpC,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAA;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CACxC;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE,EAAE,KAAK,EAAE,eAAe,GAAG,SAAS,GAAG,SAAS,eAAe,EAAE,CAGpI;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;IACxD,QAAQ,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE,CAAA;IAC3C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,kFAAkF;IAClF,QAAQ,CAAC,KAAK,CAAC,EAAE,eAAe,GAAG,SAAS,CAAA;CAC7C,sCAQA"}
@@ -1,20 +1,43 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useAvatarImage } from "./avatar-image.js";
2
3
  import { memberHue } from "./team-formatters.js";
3
4
  import css from './avatar-stack.module.css';
4
5
  /** Distinct owners past this count collapse into one `+N` chip. */
5
6
  const MAX_VISIBLE = 3;
7
+ /**
8
+ * Owners as a seat names them: the Client's Human identity outranks the name
9
+ * the Host projected for that actor, so a rename moves label and initial
10
+ * together in every seat that draws the stack.
11
+ */
12
+ export function namedAvatarOwners(owners, human) {
13
+ if (human === undefined)
14
+ return owners;
15
+ return owners.map(owner => owner.memberId === human.memberId ? { memberId: owner.memberId, name: human.name } : owner);
16
+ }
6
17
  /**
7
18
  * The compact "who is on this work" stack: overlapping 18px Member circles in
8
19
  * the shared identity language, capped at three plus a `+N` chip. The circles
9
20
  * are presentational, so the stack is one `role="img"` whose label carries the
10
21
  * whole roster — three anonymous initials would read as noise.
11
22
  */
12
- export function TeamAvatarStack({ owners, label }) {
23
+ export function TeamAvatarStack({ owners, label, human }) {
13
24
  if (owners.length === 0)
14
25
  return null;
15
26
  const shown = owners.slice(0, MAX_VISIBLE);
16
27
  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}` })] });
28
+ return _jsxs("span", { className: css.stack, role: "img", "aria-label": label, children: [shown.map(owner => _jsx(OwnerAvatar, { owner: owner, human: human }, owner.memberId)), overflow > 0 && _jsx("span", { className: css.overflow, children: `+${overflow}` })] });
29
+ }
30
+ /**
31
+ * One circle. Only an owner the seat identifies as the Human consults an image
32
+ * at all, and those bytes keep the initial whenever they do not decode — the
33
+ * same promise, and the same hook, the timeline avatar makes.
34
+ */
35
+ function OwnerAvatar({ owner, human }) {
36
+ const image = useAvatarImage(human !== undefined && owner.memberId === human.memberId ? human.avatarUrl : undefined);
37
+ const hue = { '--team-avatar-hue': memberHue(owner.memberId) };
38
+ if (image.src === undefined)
39
+ return _jsx("span", { className: css.avatar, style: hue, children: initial(owner.name) });
40
+ return _jsx("img", { className: css.avatarImage, style: hue, src: image.src, alt: "", "aria-hidden": "true", onError: image.failed });
18
41
  }
19
42
  /** First visible character of a handle — and of a raw Member id when that is all there is. */
20
43
  function initial(name) {
@@ -5,6 +5,9 @@ import type { TeamDraftStore } from './drafts.ts';
5
5
  interface TeamChannelPageProps {
6
6
  readonly workspaceId: WorkspaceId;
7
7
  readonly channelRef: AgentTeamChannelRef;
8
+ /** The Human's own display name and avatar, from the shared identity projection. */
9
+ readonly humanName: string;
10
+ readonly humanAvatarUrl?: string | undefined;
8
11
  readonly loadChannels: TeamConversationProps['loadChannels'];
9
12
  readonly subscribeChanges: TeamConversationProps['subscribeChanges'];
10
13
  readonly loadMembers: TeamConversationProps['loadMembers'];
@@ -19,9 +22,11 @@ interface TeamChannelPageProps {
19
22
  readonly selectThread: TeamConversationProps['selectThread'];
20
23
  readonly selectChannel: TeamConversationProps['selectChannel'];
21
24
  readonly resolveTaskRefs: TeamConversationProps['resolveTaskRefs'];
25
+ readonly resolveThreadRefs: TeamConversationProps['resolveThreadRefs'];
26
+ readonly openMemberSession: TeamConversationProps['openMemberSession'];
22
27
  readonly backToChannels: TeamConversationProps['backToChannels'];
23
28
  readonly t: TeamConversationProps['t'];
24
29
  }
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;
30
+ export declare function TeamChannelPage({ workspaceId, channelRef, humanName, humanAvatarUrl, loadChannels, subscribeChanges, loadMembers, loadInbox, drafts, putAttachment, getAttachment, sendMessage, joinChannel, removeChannelMember, selectThread, selectChannel, backToChannels, resolveTaskRefs, resolveThreadRefs, openMemberSession, t }: TeamChannelPageProps): import("react").JSX.Element;
26
31
  export {};
27
32
  //# 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;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"}
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;AAmB/D,UAAU,oBAAoB;IAC5B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,QAAQ,CAAC,UAAU,EAAE,mBAAmB,CAAA;IACxC,oFAAoF;IACpF,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC5C,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,iBAAiB,EAAE,qBAAqB,CAAC,mBAAmB,CAAC,CAAA;IACtE,QAAQ,CAAC,iBAAiB,EAAE,qBAAqB,CAAC,mBAAmB,CAAC,CAAA;IACtE,QAAQ,CAAC,cAAc,EAAE,qBAAqB,CAAC,gBAAgB,CAAC,CAAA;IAChE,QAAQ,CAAC,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC,CAAA;CACvC;AAkDD,wBAAgB,eAAe,CAAC,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,cAAc,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,iBAAiB,EAAE,iBAAiB,EAAE,CAAC,EAAE,EAAE,oBAAoB,+BA4Y/V"}
@@ -1,18 +1,20 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Fragment, useCallback, useEffect, useRef, useState } from 'react';
3
- import { Button, IconChevronLeftOutlineRegular, IconChevronRightOutlineRegular, Modal } from '@deepseek-ai/dsh-client-ui-primitives';
2
+ import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react';
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
6
  import { TeamMemberRow } from "./TeamMemberRow.js";
7
7
  import { TeamMessage } from "./TeamMessage.js";
8
- import { TeamAvatarStack } from "./TeamAvatarStack.js";
8
+ import { namedAvatarOwners, TeamAvatarStack } from "./TeamAvatarStack.js";
9
9
  import { TeamCountBadge } from "./TeamCountBadge.js";
10
10
  import { TeamRunDivider } from "./TeamRunDivider.js";
11
11
  import { claimersLabel, formatAbsoluteTime, formatInboxTime, formatTaskStatus, taskStatusDot, mentionNamesOf } from "./team-formatters.js";
12
12
  import { TeamStateDot } from "./TeamStateDot.js";
13
13
  import { useChannelMembership } from "./team-membership.js";
14
14
  import { useTimelineScroll } from "./timeline-scroll.js";
15
- import { hostTaskRefLookup, jumpToTaskThread } from "./task-refs.js";
15
+ import { hostTaskRefLookup, jumpToTaskThread } from "./refs.js";
16
+ import { hostThreadRefLookup, jumpToThread } from "./refs.js";
17
+ import { rosterChannelName, rosterMember } from "./refs.js";
16
18
  import { chunkRunsWithDays, isRunGap } from "./team-separators.js";
17
19
  import channelCss from './channel.module.css';
18
20
  import css from './conversation.module.css';
@@ -44,11 +46,24 @@ function followUpAt(item) {
44
46
  const lastActivityAt = item.lastActivityAt;
45
47
  return lastActivityAt === undefined || lastActivityAt === item.message.occurredAt ? undefined : lastActivityAt;
46
48
  }
49
+ /**
50
+ * What the door prints about follow-up activity, or nothing. A resolved Task
51
+ * stops printing it: the status word beside the door already says nothing is
52
+ * moving, and the instant it would print is the moment it resolved — repeated
53
+ * on every finished row. The precise instant stays on the control's title, one
54
+ * hover away. A taskless Thread never resolves, so a discussion keeps the
55
+ * recency that is the whole of its state line: it wears no status word, dot, or
56
+ * owner stack.
57
+ */
58
+ function printedActivityAt(item) {
59
+ const status = item.task?.status;
60
+ return status === 'done' || status === 'closed' ? undefined : followUpAt(item);
61
+ }
47
62
  /** Host unread per Thread, keyed for the feed's rows: zero unread is the absence of a badge, not a row. */
48
63
  function unreadCounts(inbox) {
49
64
  return new Map(inbox.items.filter(item => item.unreadCount > 0).map(item => [item.thread.threadRef, item.unreadCount]));
50
65
  }
51
- export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscribeChanges, loadMembers, loadInbox, drafts, putAttachment, getAttachment, sendMessage, joinChannel, removeChannelMember, selectThread, selectChannel, backToChannels, resolveTaskRefs, t }) {
66
+ export function TeamChannelPage({ workspaceId, channelRef, humanName, humanAvatarUrl, loadChannels, subscribeChanges, loadMembers, loadInbox, drafts, putAttachment, getAttachment, sendMessage, joinChannel, removeChannelMember, selectThread, selectChannel, backToChannels, resolveTaskRefs, resolveThreadRefs, openMemberSession, t }) {
52
67
  const [view, setView] = useState();
53
68
  const [members, setMembers] = useState([]);
54
69
  const [unreadByThread, setUnreadByThread] = useState(new Map());
@@ -76,8 +91,10 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
76
91
  const refreshSequenceRef = useRef(0);
77
92
  const channelLastItem = view?.items[view.items.length - 1];
78
93
  // Branded-ref navigation for message bodies: channel refs hop directly,
79
- // task refs resolve against this Channel's loaded timeline and degrade to a
80
- // no-op when the target is not reachable from here.
94
+ // task and thread refs resolve against this Channel's loaded timeline and
95
+ // fall back to the Host resolver, which also accepts abbreviated spellings.
96
+ // Unresolvable refs never become links (see TeamMessage), so this path only
97
+ // fires for refs the Host already confirmed.
81
98
  const openRef = (ref) => {
82
99
  if (ref.startsWith('channel:')) {
83
100
  if (ref !== channelRef)
@@ -90,18 +107,9 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
90
107
  selectThread(match.thread.threadRef, channelRef, match.task?.taskRef, match.taskNumber);
91
108
  return;
92
109
  }
93
- // A ref may point outside the currently loaded Channel window. Ask the
94
- // Host for the bounded Thread view so it can provide the home Channel.
95
- void loadChannels({ workspaceId, threadRef: ref, includeActivities: false, limit: 1 }).then(result => {
96
- if (!result.ok)
97
- return;
98
- const target = result.value.items[0];
99
- if (target !== undefined) {
100
- const targetChannel = result.value.channels.find(channel => channel.channelRef === target.message.channelRef);
101
- if (targetChannel !== undefined)
102
- selectThread(target.thread.threadRef, targetChannel.channelRef, target.task?.taskRef, target.taskNumber);
103
- }
104
- });
110
+ // Not in the loaded timeline (or cited by abbreviation): resolve through
111
+ // the Host and jump to the Thread's home Channel.
112
+ jumpToThread(resolveThreadRefs, workspaceId, ref, selectThread);
105
113
  return;
106
114
  }
107
115
  if (ref.startsWith('task:')) {
@@ -116,6 +124,33 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
116
124
  }
117
125
  };
118
126
  const lookupTaskRefs = hostTaskRefLookup(resolveTaskRefs, workspaceId);
127
+ const lookupThreadRefs = hostThreadRefLookup(resolveThreadRefs, workspaceId);
128
+ // Roster chips resolve synchronously from loaded data: channel names from
129
+ // the loaded feed windows, member facts from the member list. Anything
130
+ // outside stays plain text — the same rule unresolvable refs follow. The
131
+ // lookups key on roster content, not array identity: every refresh hands
132
+ // over fresh arrays, and the memoized rows must survive change bursts that
133
+ // leave the roster itself untouched.
134
+ const rosterKey = useMemo(() => [
135
+ (view?.channels ?? []).map(channel => `${channel.channelRef}=${channel.name}=${channel.state}`).join(','),
136
+ members.map(status => `${status.member.memberId}=${status.member.handle}=${status.member.sessionId}=${status.availability}`).join(','),
137
+ view?.humanMemberId ?? '',
138
+ ].join(';'), [view, members]);
139
+ const channelNameOf = useMemo(() => {
140
+ const channels = view?.channels ?? [];
141
+ return (ref) => rosterChannelName(channels, ref);
142
+ }, [rosterKey]);
143
+ const memberOf = useMemo(() => {
144
+ const humanMemberId = view?.humanMemberId;
145
+ return (ref) => rosterMember(members, humanMemberId, humanName, ref);
146
+ }, [rosterKey, humanName]);
147
+ // The Thread entry row's owner stack speaks the same grammar as the Inbox
148
+ // row's, Human included: the Host names which actor is the reader, the
149
+ // Client's identity projection says what that actor looks like.
150
+ const humanMemberId = view?.humanMemberId;
151
+ const humanIdentity = humanMemberId === undefined
152
+ ? undefined
153
+ : { memberId: humanMemberId, name: humanName, ...(humanAvatarUrl === undefined ? {} : { avatarUrl: humanAvatarUrl }) };
119
154
  const timeline = useTimelineScroll(`${view?.items.length ?? 0}:${channelLastItem?.message.messageRef ?? ''}`);
120
155
  const channel = view?.channels.find(item => item.channelRef === channelRef);
121
156
  const channelMemberIds = new Set(view?.members.filter(item => item.channelRef === channelRef).map(item => item.memberId) ?? []);
@@ -346,7 +381,7 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
346
381
  setPending(false);
347
382
  }
348
383
  };
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(IconChevronLeftOutlineRegular, {}), 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 => {
384
+ 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 => {
350
385
  const joined = channelMemberIds.has(status.member.memberId);
351
386
  const rowPending = membership.pending.has(status.member.memberId);
352
387
  // Joining needs an active Member — the Host refuses any other
@@ -364,7 +399,7 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
364
399
  : _jsx("div", { className: css.messageRun, children: block.items.map((item, index) => {
365
400
  const senderStatus = members.find(member => member.member.memberId === item.message.sender);
366
401
  const human = item.message.sender === view.humanMemberId;
367
- const sender = human ? t('human') : senderStatus?.member.handle ?? item.message.sender;
402
+ const sender = human ? humanName : senderStatus?.member.handle ?? item.message.sender;
368
403
  const turnGap = isRunGap(index > 0 ? block.items[index - 1].message.occurredAt : undefined, item.message.occurredAt);
369
404
  const task = item.task;
370
405
  // The entry's own line is the gate under the body, and its state
@@ -373,7 +408,7 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
373
408
  // of it — the Thread page owns that surface.
374
409
  const unread = unreadByThread.get(item.thread.threadRef) ?? 0;
375
410
  // A turn divider already labels the gapped entry; its row stays chrome-free.
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);
411
+ return _jsxs(Fragment, { children: [turnGap && _jsx(TeamRunDivider, { occurredAt: item.message.occurredAt }), _jsx(TeamMessage, { senderName: sender, memberId: item.message.sender, human: human, ...(humanAvatarUrl === undefined ? {} : { avatarUrl: humanAvatarUrl }), body: item.message.body, attachments: item.message.attachments, loadAttachment: getAttachment, t: t, occurredAt: item.message.occurredAt, mentionNames: mentionNamesOf(item.mentions, handleByMember, humanName), onOpenRef: openRef, onResolveTaskRefs: lookupTaskRefs, onResolveThreadRefs: lookupThreadRefs, channelNameOf: channelNameOf, memberOf: memberOf, onOpenMemberSession: openMemberSession, 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, human: humanIdentity, t: t, onOpen: () => { selectThread(item.thread.threadRef, channelRef, task?.taskRef, item.taskNumber); } }) })] }, item.message.messageRef);
377
412
  }) }, `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", {})] });
378
413
  }
379
414
  /**
@@ -385,27 +420,30 @@ export function TeamChannelPage({ workspaceId, channelRef, loadChannels, subscri
385
420
  * carried nothing else. The unread capsule is the only member a taskless
386
421
  * discussion can carry: it needs no Task.
387
422
  */
388
- function ThreadStateCluster({ task, owners, unread, t }) {
423
+ function ThreadStateCluster({ task, owners, unread, human, t }) {
389
424
  if (task === undefined && unread === 0)
390
425
  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 })] });
426
+ const namedOwners = namedAvatarOwners(owners, human);
427
+ return _jsxs("span", { className: css.stateCluster, children: [task !== undefined && _jsx(TeamAvatarStack, { owners: namedOwners, label: claimersLabel(namedOwners, t), human: human }), 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
428
  }
393
429
  /**
394
430
  * The gate under one top-level Message: the single row that opens its Thread,
395
431
  * and the one line that says what stands there. State leads it, then what the
396
432
  * 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.
433
+ * of the Task it describes, follow-up activity adds when the work last moved —
434
+ * while the work is still unfinished — and an unanswered Thread says only 回复.
435
+ * The instant uses the Inbox's own 今天/昨天 form with the precise local time on
436
+ * the control's title, and an unread Thread says so in the control's label
437
+ * rather than only in pixels.
401
438
  */
402
- function ThreadEntryRow({ item, owners, unread, t, onOpen }) {
439
+ function ThreadEntryRow({ item, owners, unread, human, t, onOpen }) {
403
440
  const taskNumber = item.taskNumber;
404
441
  const followUp = followUpAt(item);
442
+ const printedActivity = printedActivityAt(item);
405
443
  const label = taskNumber !== undefined
406
444
  ? t('taskLabel', { number: taskNumber })
407
445
  : followUp === undefined ? t('replyAction') : t('threadLabel');
408
- const text = followUp === undefined ? label : `${label} · ${t('recentActivity', { time: formatInboxTime(followUp, t) })}`;
446
+ const text = printedActivity === undefined ? label : `${label} · ${t('recentActivity', { time: formatInboxTime(printedActivity, t) })}`;
409
447
  const openLabel = taskNumber === undefined
410
448
  ? unread > 0 ? t('openThreadUnread', { count: unread }) : t('openThread')
411
449
  : unread > 0 ? t('openTaskUnread', { number: taskNumber, count: unread }) : t('openTask', { number: taskNumber });
@@ -413,5 +451,5 @@ function ThreadEntryRow({ item, owners, unread, t, onOpen }) {
413
451
  // owner stack keeps its own accessible name — a labeled button prunes its
414
452
  // descendants from the accessibility tree — and so the control's name says
415
453
  // 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(IconChevronRightOutlineRegular, { size: 12 }) })] })] });
454
+ return _jsxs("span", { className: css.entryLine, "data-thread-entry": "", children: [_jsx(ThreadStateCluster, { task: item.task, owners: owners, unread: unread, human: human, 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
455
  }
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
- import { Button, IconArchiveOutlineRegular, IconEditOutlineRegular, IconPlusOutlineRegular, Input, Modal, Tooltip } from '@deepseek-ai/dsh-client-ui-primitives';
3
+ import { Button, IconArchiveOutline20, IconEditOutline16, IconPlusOutline16, Input, Modal, Tooltip } from '@deepseek-ai/dsh-client-ui-primitives';
4
4
  import { TeamPresenceDot } from "./TeamPresenceDot.js";
5
5
  import { TeamMemberRow } from "./TeamMemberRow.js";
6
6
  import { MultiMenuField } from "./multi-menu-field.js";
@@ -153,7 +153,7 @@ export function TeamChannelsPanel(props) {
153
153
  return next;
154
154
  });
155
155
  setPendingCreate(undefined);
156
- }, triggerEmptyLabel: t('membersPickerEmpty'), formatCount: count => t('membersPickerCount', { count }) }), error !== undefined && _jsx("p", { className: createCss.error, role: "alert", children: error })] }) }), _jsxs(TeamSidebarSection, { title: t('channels'), open: sectionOpen, onToggle: open => { setSidebarSectionOpen(workspaceId, 'channels', open); }, actions: (_jsx(Tooltip, { label: t('addChannel'), delayMs: 500, children: _jsx("button", { ref: triggerRef, type: "button", className: css.iconButton, "aria-label": t('addChannel'), onClick: () => { setError(undefined); setFormOpen(true); }, children: _jsx(IconPlusOutlineRegular, { size: 14 }) }) })), children: [loading && view === undefined && _jsx("p", { className: css.emptyState, children: t('loadingChannels') }), !loading && error === undefined && view !== undefined && view.channels.length === 0 && _jsx("p", { className: css.emptyState, children: t('emptyChannels') }), _jsx("div", { className: css.channelList, children: orderedChannels.map(channel => {
156
+ }, triggerEmptyLabel: t('membersPickerEmpty'), formatCount: count => t('membersPickerCount', { count }) }), error !== undefined && _jsx("p", { className: createCss.error, role: "alert", children: error })] }) }), _jsxs(TeamSidebarSection, { title: t('channels'), open: sectionOpen, onToggle: open => { setSidebarSectionOpen(workspaceId, 'channels', open); }, actions: (_jsx(Tooltip, { label: t('addChannel'), delayMs: 500, children: _jsx("button", { ref: triggerRef, type: "button", className: css.iconButton, "aria-label": t('addChannel'), onClick: () => { setError(undefined); setFormOpen(true); }, children: _jsx(IconPlusOutline16, { size: 14 }) }) })), children: [loading && view === undefined && _jsx("p", { className: css.emptyState, children: t('loadingChannels') }), !loading && error === undefined && view !== undefined && view.channels.length === 0 && _jsx("p", { className: css.emptyState, children: t('emptyChannels') }), _jsx("div", { className: css.channelList, children: orderedChannels.map(channel => {
157
157
  const joined = membership.get(channel.channelRef) ?? new Set();
158
158
  return (_jsx(SortableRow, { drag: drag, orderKey: channel.channelRef, children: _jsx(ChannelRow, { channel: channel, members: members, joinedIds: joined, selected: selectedChannelRef === channel.channelRef, updateChannel: updateChannel, archiveChannel: props.archiveChannel, joinChannel: props.joinChannel, removeChannelMember: props.removeChannelMember, onCommitted: () => { void refresh(); }, selectChannel: selectChannel, t: t }) }, channel.channelRef));
159
159
  }) })] }), !formOpen && error !== undefined && _jsx("p", { className: css.error, role: "alert", children: error })] }));
@@ -184,8 +184,8 @@ function ChannelRow({ channel, members, joinedIds, selected, updateChannel, arch
184
184
  }
185
185
  };
186
186
  return (_jsxs(_Fragment, { children: [_jsxs("article", { className: css.channelRow, "data-menu-open": menuOpen || undefined, "aria-current": selected ? 'page' : undefined, children: [_jsx("button", { type: "button", className: css.channelSelect, "aria-label": `# ${channel.name}`, onClick: () => { selectChannel(channel.channelRef); }, children: _jsxs("strong", { className: css.channelName, children: ["# ", channel.name] }) }), _jsx("span", { className: css.rowMenu, children: _jsx(TeamRowMenu, { label: t('actionsChannel', { name: channel.name }), items: [
187
- { id: 'edit', label: t('editChannel'), icon: _jsx(IconEditOutlineRegular, {}) },
188
- { id: 'archive', label: t('archiveChannel'), icon: _jsx(IconArchiveOutlineRegular, { size: 16 }), danger: true },
187
+ { id: 'edit', label: t('editChannel'), icon: _jsx(IconEditOutline16, {}) },
188
+ { id: 'archive', label: t('archiveChannel'), icon: _jsx(IconArchiveOutline20, { size: 16 }), danger: true },
189
189
  ], onSelect: (id) => {
190
190
  if (id === 'edit')
191
191
  setEditing(true);