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