@wowyuarm/dsh-agent-team 0.1.5 → 0.1.6
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 +15 -1
- package/README.zh.md +15 -1
- package/package.json +7 -2
- package/packages/agent-team/README.md +6 -0
- package/packages/agent-team/README.zh.md +6 -0
- package/packages/agent-team/core-skills/member-skill-manager/SKILL.md +41 -0
- package/packages/agent-team/core-skills/member-skill-manager/references/auth-and-config.md +30 -0
- package/packages/agent-team/core-skills/member-skill-manager/references/writing-great-skills.md +17 -0
- package/packages/agent-team/lib/index.js +255 -2
- package/packages/agent-team/lib/ledger.js +139 -5
- package/packages/agent-team/lib/member-context.js +2 -2
- package/packages/agent-team/lib/member-skills.js +94 -0
- package/packages/agent-team/lib/spec.js +26 -0
- package/packages/agent-team/lib/typert.host.js +131 -28
- package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
- package/packages/agent-team/lib/typert.remote-client.js +99 -23
- package/packages/agent-team/lib/types/index.d.ts +69 -1
- package/packages/agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/types/ledger.d.ts +29 -1
- package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
- package/packages/agent-team/lib/types/member-skills.d.ts +45 -0
- package/packages/agent-team/lib/types/member-skills.d.ts.map +1 -0
- package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types.d.ts +87 -2
- package/packages/agent-team/lib/types/types.d.ts.map +1 -1
- package/packages/agent-team/preset/team-member/agent.cordis.yml +7 -4
- 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 +217 -299
- package/packages/client-agent-team/lib/client.js.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts +1 -4
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +11 -23
- package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts +4 -8
- package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMemberEditor.js +7 -40
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +61 -20
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +1 -1
- package/packages/client-agent-team/lib/types/client/locales.d.ts +4 -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 +4 -16
- package/packages/client-agent-team/lib/types/client/timeline-scroll.d.ts +5 -7
- package/packages/client-agent-team/lib/types/client/timeline-scroll.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/timeline-scroll.js +9 -22
- package/packages/tool-agent-team/lib/index.js +25 -5
- package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
|
@@ -5,13 +5,10 @@ interface TeamAgentsPanelProps {
|
|
|
5
5
|
readonly workspaceId: WorkspaceId;
|
|
6
6
|
readonly loadMembers: TeamSidebarProps['loadMembers'];
|
|
7
7
|
readonly subscribeChanges: TeamSidebarProps['subscribeChanges'];
|
|
8
|
-
readonly loadChannels: TeamSidebarProps['loadChannels'];
|
|
9
8
|
readonly addMember: TeamSidebarProps['addMember'];
|
|
10
9
|
readonly updateMember: TeamSidebarProps['updateMember'];
|
|
11
10
|
readonly recoverMember: TeamSidebarProps['recoverMember'];
|
|
12
11
|
readonly clearMemberContext: TeamSidebarProps['clearMemberContext'];
|
|
13
|
-
readonly joinChannel: TeamSidebarProps['joinChannel'];
|
|
14
|
-
readonly removeChannelMember: TeamSidebarProps['removeChannelMember'];
|
|
15
12
|
readonly loadModels: TeamSidebarProps['loadModels'];
|
|
16
13
|
/** The Member Session currently embedded in the conversation seat, if any. */
|
|
17
14
|
readonly memberSessionId?: AgentTeamClientMemberStatus['member']['sessionId'];
|
|
@@ -19,6 +16,6 @@ interface TeamAgentsPanelProps {
|
|
|
19
16
|
readonly onCreatingChange: (request: AgentTeamAddMemberRequest, creating: boolean) => void;
|
|
20
17
|
readonly t: TeamSidebarProps['t'];
|
|
21
18
|
}
|
|
22
|
-
export declare function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges,
|
|
19
|
+
export declare function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, addMember, updateMember, recoverMember, clearMemberContext, loadModels, memberSessionId, openMemberSession, onCreatingChange, t }: TeamAgentsPanelProps): import("react").JSX.Element;
|
|
23
20
|
export {};
|
|
24
21
|
//# 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,
|
|
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,wCAAwC,CAAA;AAEzE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAYlD,UAAU,oBAAoB;IAC5B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAA;IACrD,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,kBAAkB,CAAC,CAAA;IAC/D,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAC,WAAW,CAAC,CAAA;IACjD,QAAQ,CAAC,YAAY,EAAE,gBAAgB,CAAC,cAAc,CAAC,CAAA;IACvD,QAAQ,CAAC,aAAa,EAAE,gBAAgB,CAAC,eAAe,CAAC,CAAA;IACzD,QAAQ,CAAC,kBAAkB,EAAE,gBAAgB,CAAC,oBAAoB,CAAC,CAAA;IACnE,QAAQ,CAAC,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,kBAAkB,EAAE,UAAU,EAAE,eAAe,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,CAAC,EAAE,EAAE,oBAAoB,+BAmKpO"}
|
|
@@ -8,14 +8,11 @@ import { useSidebarSectionOpen, setSidebarSectionOpen } from "./sidebar-sections
|
|
|
8
8
|
import { mintRequestId } from "./requests.js";
|
|
9
9
|
import { TeamRowMenu } from "./TeamRowMenu.js";
|
|
10
10
|
import { TeamSidebarSection } from "./TeamSidebarSection.js";
|
|
11
|
-
import { MultiMenuField } from "./multi-menu-field.js";
|
|
12
11
|
import { AgentEditorDialog, ModelPickerField, sameModel } from "./TeamMemberEditor.js";
|
|
13
12
|
import createCss from './create.module.css';
|
|
14
13
|
import css from './sidebar.module.css';
|
|
15
|
-
export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges,
|
|
14
|
+
export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, addMember, updateMember, recoverMember, clearMemberContext, loadModels, memberSessionId, openMemberSession, onCreatingChange, t }) {
|
|
16
15
|
const [members, setMembers] = useState([]);
|
|
17
|
-
const [channels, setChannels] = useState([]);
|
|
18
|
-
const [channelRefs, setChannelRefs] = useState([]);
|
|
19
16
|
const [loading, setLoading] = useState(true);
|
|
20
17
|
const [error, setError] = useState();
|
|
21
18
|
const [formOpen, setFormOpen] = useState(false);
|
|
@@ -51,22 +48,15 @@ export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, lo
|
|
|
51
48
|
setLoading(false);
|
|
52
49
|
}, [loadMembers, workspaceId]);
|
|
53
50
|
useEffect(() => { void refresh(); }, [refresh]);
|
|
54
|
-
const loadWorkspaceChannels = useCallback(async () => {
|
|
55
|
-
const result = await loadChannels({ workspaceId, topLevelOnly: true, includeActivities: false, limit: 1 });
|
|
56
|
-
if (result.ok)
|
|
57
|
-
setChannels(result.value.channels);
|
|
58
|
-
}, [loadChannels, workspaceId]);
|
|
59
|
-
useEffect(() => { void loadWorkspaceChannels(); }, [loadWorkspaceChannels]);
|
|
60
51
|
useEffect(() => subscribeChanges({ kind: 'workspace', workspaceId }, update => {
|
|
61
52
|
if (update.type === 'failed') {
|
|
62
53
|
setError(update.message);
|
|
63
54
|
return;
|
|
64
55
|
}
|
|
65
|
-
// The section stays mounted across Channel creation, so
|
|
66
|
-
//
|
|
56
|
+
// The section stays mounted across Channel creation, so the Member roster
|
|
57
|
+
// rides every workspace invalidation.
|
|
67
58
|
void refresh();
|
|
68
|
-
|
|
69
|
-
}), [subscribeChanges, refresh, loadWorkspaceChannels, workspaceId]);
|
|
59
|
+
}), [subscribeChanges, refresh, workspaceId]);
|
|
70
60
|
const closeForm = () => {
|
|
71
61
|
if (creating)
|
|
72
62
|
return;
|
|
@@ -88,7 +78,6 @@ export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, lo
|
|
|
88
78
|
setHandle('');
|
|
89
79
|
setDescription('');
|
|
90
80
|
setModel(undefined);
|
|
91
|
-
setChannelRefs([]);
|
|
92
81
|
setFormOpen(false);
|
|
93
82
|
if (result.value.status.presence === 'unavailable') {
|
|
94
83
|
setError(result.value.status.diagnostic ?? t('statusUnavailable'));
|
|
@@ -120,28 +109,27 @@ export function TeamAgentsPanel({ workspaceId, loadMembers, subscribeChanges, lo
|
|
|
120
109
|
const sameRequest = retryRequest !== undefined && retryRequest.workspaceId === workspaceId
|
|
121
110
|
&& retryRequest.handle === normalizedHandle && retryRequest.description === normalizedDescription
|
|
122
111
|
&& sameModel(retryRequest.model, model)
|
|
123
|
-
&& retryRequest.channelRefs.length ===
|
|
112
|
+
&& retryRequest.channelRefs.length === 0;
|
|
124
113
|
void provision(sameRequest ? retryRequest : {
|
|
125
114
|
requestId: mintRequestId(),
|
|
126
115
|
workspaceId,
|
|
127
116
|
handle: normalizedHandle,
|
|
128
117
|
description: normalizedDescription,
|
|
129
118
|
presetId: 'team-member',
|
|
130
|
-
|
|
119
|
+
// No initial Channels at creation: the Member joins Channels later from
|
|
120
|
+
// the Channel side and stays reachable through its DM view meanwhile.
|
|
121
|
+
channelRefs: [],
|
|
131
122
|
...(model === undefined ? {} : { model }),
|
|
132
123
|
});
|
|
133
124
|
};
|
|
134
|
-
return (_jsxs("div", { className: css.panel, children: [_jsx(Modal, { open: formOpen, onClose: closeForm, title: t('addAgent'), closeLabel: t('close'), contentClassName: createCss.dialogContent, footer: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", disabled: creating, onClick: closeForm, children: t('cancel') }), _jsx(Button, { type: "submit", form: "team-agent-create-form", variant: "primary", disabled: creating || handle.trim().length === 0, children: creating ? t('creatingAgent') : t('createAgent') })] }), children: _jsxs("form", { id: "team-agent-create-form", className: createCss.form, onSubmit: submit, children: [_jsxs("label", { className: createCss.field, children: [_jsx("span", { children: t('agentName') }), _jsx(Input, { className: createCss.input, value: handle, onChange: event => { setHandle(event.target.value); setRetryRequest(undefined); }, disabled: creating, autoFocus: true })] }), _jsxs("label", { className: createCss.field, children: [_jsxs("span", { children: [t('agentDescription'), t('optionalSuffix')] }), _jsx(Input, { className: createCss.input, value: description, placeholder: t('agentDescriptionPlaceholder'), onChange: event => { setDescription(event.target.value); setRetryRequest(undefined); }, disabled: creating })] }), _jsx(ModelPickerField, { model: model, onModelChange: choice => { setModel(choice); setRetryRequest(undefined); }, loadModels: loadModels, disabled: creating, t: t }), _jsx(
|
|
135
|
-
setChannelRefs(current => current.includes(ref) ? current.filter(item => item !== ref) : [...current, ref]);
|
|
136
|
-
setRetryRequest(undefined);
|
|
137
|
-
}, emptyText: t('noChannelsForAgent'), triggerEmptyLabel: t('channelsPickerEmpty'), formatCount: count => t('channelsPickerCount', { count }) }), formOpen && error !== undefined && _jsx("p", { className: createCss.error, role: "alert", children: error })] }) }), _jsxs(TeamSidebarSection, { title: t('agents'), open: sectionOpen, onToggle: open => { setSidebarSectionOpen(workspaceId, 'agents', open); }, actions: (_jsx(Tooltip, { label: t('addAgent'), delayMs: 500, children: _jsx("button", { ref: triggerRef, type: "button", className: css.iconButton, "aria-label": t('addAgent'), onClick: () => { setError(undefined); setFormOpen(true); }, children: _jsx(IconPlusOutline16, { size: 14 }) }) })), children: [loading && members.length === 0 && _jsx("p", { className: css.emptyState, children: t('loadingAgents') }), !loading && members.length === 0 && _jsx("p", { className: css.emptyState, children: t('emptyAgents') }), _jsx("div", { className: css.agentList, children: orderedMembers.map(status => (_jsx(SortableRow, { drag: drag, orderKey: status.member.memberId, children: _jsx(AgentRow, { status: status, ...(memberSessionId === undefined ? {} : { current: status.member.sessionId === memberSessionId }), channels: channels, loadChannels: loadChannels, updateMember: updateMember, recoverMember: recoverMember, clearMemberContext: clearMemberContext, joinChannel: joinChannel, removeChannelMember: removeChannelMember, loadModels: loadModels, openMemberSession: openMemberSession, onUpdated: () => { void refresh(); }, t: t }) }, status.member.memberId))) })] }), !formOpen && error !== undefined && (_jsxs("div", { className: css.retryError, role: "alert", children: [_jsx("span", { children: error }), retryRequest !== undefined && _jsx("button", { type: "button", className: css.textButton, disabled: creating, onClick: () => { setFormOpen(true); }, children: t('retry') })] }))] }));
|
|
125
|
+
return (_jsxs("div", { className: css.panel, children: [_jsx(Modal, { open: formOpen, onClose: closeForm, title: t('addAgent'), closeLabel: t('close'), contentClassName: createCss.dialogContent, footer: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", disabled: creating, onClick: closeForm, children: t('cancel') }), _jsx(Button, { type: "submit", form: "team-agent-create-form", variant: "primary", disabled: creating || handle.trim().length === 0, children: creating ? t('creatingAgent') : t('createAgent') })] }), children: _jsxs("form", { id: "team-agent-create-form", className: createCss.form, onSubmit: submit, children: [_jsxs("label", { className: createCss.field, children: [_jsx("span", { children: t('agentName') }), _jsx(Input, { className: createCss.input, value: handle, onChange: event => { setHandle(event.target.value); setRetryRequest(undefined); }, disabled: creating, autoFocus: true })] }), _jsxs("label", { className: createCss.field, children: [_jsxs("span", { children: [t('agentDescription'), t('optionalSuffix')] }), _jsx(Input, { className: createCss.input, value: description, placeholder: t('agentDescriptionPlaceholder'), onChange: event => { setDescription(event.target.value); setRetryRequest(undefined); }, disabled: creating })] }), _jsx(ModelPickerField, { model: model, onModelChange: choice => { setModel(choice); setRetryRequest(undefined); }, loadModels: loadModels, disabled: creating, t: t }), formOpen && error !== undefined && _jsx("p", { className: createCss.error, role: "alert", children: error })] }) }), _jsxs(TeamSidebarSection, { title: t('agents'), open: sectionOpen, onToggle: open => { setSidebarSectionOpen(workspaceId, 'agents', open); }, actions: (_jsx(Tooltip, { label: t('addAgent'), delayMs: 500, children: _jsx("button", { ref: triggerRef, type: "button", className: css.iconButton, "aria-label": t('addAgent'), onClick: () => { setError(undefined); setFormOpen(true); }, children: _jsx(IconPlusOutline16, { size: 14 }) }) })), children: [loading && members.length === 0 && _jsx("p", { className: css.emptyState, children: t('loadingAgents') }), !loading && members.length === 0 && _jsx("p", { className: css.emptyState, children: t('emptyAgents') }), _jsx("div", { className: css.agentList, children: orderedMembers.map(status => (_jsx(SortableRow, { drag: drag, orderKey: status.member.memberId, children: _jsx(AgentRow, { status: status, ...(memberSessionId === undefined ? {} : { current: status.member.sessionId === memberSessionId }), updateMember: updateMember, recoverMember: recoverMember, clearMemberContext: clearMemberContext, loadModels: loadModels, openMemberSession: openMemberSession, onUpdated: () => { void refresh(); }, t: t }) }, status.member.memberId))) })] }), !formOpen && error !== undefined && (_jsxs("div", { className: css.retryError, role: "alert", children: [_jsx("span", { children: error }), retryRequest !== undefined && _jsx("button", { type: "button", className: css.textButton, disabled: creating, onClick: () => { setFormOpen(true); }, children: t('retry') })] }))] }));
|
|
138
126
|
}
|
|
139
127
|
/**
|
|
140
128
|
* One sidebar Agent row: the select button opens the Member's own Session
|
|
141
129
|
* conversation page, the avatar carries identity plus the presence badge, and
|
|
142
130
|
* the row menu opens the editor.
|
|
143
131
|
*/
|
|
144
|
-
function AgentRow({ status, current,
|
|
132
|
+
function AgentRow({ status, current, updateMember, recoverMember, clearMemberContext, loadModels, openMemberSession, onUpdated, t }) {
|
|
145
133
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
146
134
|
const [editing, setEditing] = useState(false);
|
|
147
135
|
const [clearing, setClearing] = useState(false);
|
|
@@ -216,5 +204,5 @@ function AgentRow({ status, current, channels, loadChannels, updateMember, recov
|
|
|
216
204
|
setClearing(true);
|
|
217
205
|
else
|
|
218
206
|
void recover();
|
|
219
|
-
}, onOpenChange: setMenuOpen }) })] }), rowAlert !== undefined && _jsx("div", { className: css.rowAlert, role: "alert", children: rowAlert }), clearing && (_jsx(Modal, { open: true, onClose: () => { setClearing(false); }, title: t('clearContextTitle', { name: status.member.handle }), closeLabel: t('close'), contentClassName: createCss.dialogContent, footer: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", onClick: () => { setClearing(false); }, children: t('cancel') }), _jsx(Button, { variant: "primary", onClick: () => { setClearing(false); void clearContext(); }, children: t('clearContextConfirm') })] }), children: _jsx("p", { className: createCss.error, children: t('clearContextNotice', { name: status.member.handle }) }) })), editing && (_jsx(AgentEditorDialog, { status: status,
|
|
207
|
+
}, onOpenChange: setMenuOpen }) })] }), rowAlert !== undefined && _jsx("div", { className: css.rowAlert, role: "alert", children: rowAlert }), clearing && (_jsx(Modal, { open: true, onClose: () => { setClearing(false); }, title: t('clearContextTitle', { name: status.member.handle }), closeLabel: t('close'), contentClassName: createCss.dialogContent, footer: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", onClick: () => { setClearing(false); }, children: t('cancel') }), _jsx(Button, { variant: "primary", onClick: () => { setClearing(false); void clearContext(); }, children: t('clearContextConfirm') })] }), children: _jsx("p", { className: createCss.error, children: t('clearContextNotice', { name: status.member.handle }) }) })), editing && (_jsx(AgentEditorDialog, { status: status, updateMember: updateMember, loadModels: loadModels, onCommitted: onUpdated, onClose: () => { setEditing(false); }, t: t }))] }));
|
|
220
208
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AgentTeamClientMemberStatus, AgentTeamModelSelection } from '@wowyuarm/dsh-agent-team/types';
|
|
2
2
|
import type { TeamSidebarProps } from './slots.ts';
|
|
3
3
|
/**
|
|
4
4
|
* Shared provider/model dropdown for the create and edit forms. The option
|
|
@@ -15,16 +15,12 @@ export declare function ModelPickerField({ model, onModelChange, loadModels, dis
|
|
|
15
15
|
}): import("react").JSX.Element;
|
|
16
16
|
/**
|
|
17
17
|
* Agent editor: handle, description, and per-Member model selection commit
|
|
18
|
-
* through one durable update
|
|
19
|
-
*
|
|
18
|
+
* through one durable update. Channel membership is managed from the Channel
|
|
19
|
+
* side, not here.
|
|
20
20
|
*/
|
|
21
|
-
export declare function AgentEditorDialog({ status,
|
|
21
|
+
export declare function AgentEditorDialog({ status, updateMember, loadModels, onCommitted, onClose, t }: {
|
|
22
22
|
readonly status: AgentTeamClientMemberStatus;
|
|
23
|
-
readonly channels: readonly AgentTeamChannel[];
|
|
24
|
-
readonly loadChannels: TeamSidebarProps['loadChannels'];
|
|
25
23
|
readonly updateMember: TeamSidebarProps['updateMember'];
|
|
26
|
-
readonly joinChannel: TeamSidebarProps['joinChannel'];
|
|
27
|
-
readonly removeChannelMember: TeamSidebarProps['removeChannelMember'];
|
|
28
24
|
readonly loadModels: TeamSidebarProps['loadModels'];
|
|
29
25
|
readonly onCommitted: () => Promise<void> | void;
|
|
30
26
|
readonly onClose: () => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TeamMemberEditor.d.ts","sourceRoot":"","sources":["../../../src/client/TeamMemberEditor.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"TeamMemberEditor.d.ts","sourceRoot":"","sources":["../../../src/client/TeamMemberEditor.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,2BAA2B,EAAE,uBAAuB,EAAgC,MAAM,gCAAgC,CAAA;AACxI,OAAO,KAAK,EAAiD,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAYjG;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE;IAClF,QAAQ,CAAC,KAAK,EAAE,uBAAuB,GAAG,SAAS,CAAA;IACnD,QAAQ,CAAC,aAAa,EAAE,CAAC,MAAM,EAAE,uBAAuB,GAAG,SAAS,KAAK,IAAI,CAAA;IAC7E,QAAQ,CAAC,UAAU,EAAE,gBAAgB,CAAC,YAAY,CAAC,CAAA;IACnD,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,CAAC,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAA;CAClC,+BA0GA;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE;IAC/F,QAAQ,CAAC,MAAM,EAAE,2BAA2B,CAAA;IAC5C,QAAQ,CAAC,YAAY,EAAE,gBAAgB,CAAC,cAAc,CAAC,CAAA;IACvD,QAAQ,CAAC,UAAU,EAAE,gBAAgB,CAAC,YAAY,CAAC,CAAA;IACnD,QAAQ,CAAC,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAChD,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAA;IAC5B,QAAQ,CAAC,CAAC,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAA;CAClC,+BA0EA;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,uBAAuB,GAAG,SAAS,EAAE,KAAK,EAAE,uBAAuB,GAAG,SAAS,GAAG,OAAO,CAIxH"}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef, useState } from 'react';
|
|
3
3
|
import { Button, IconChevronDownOutline14, Input, Menu, Modal } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
4
|
-
import { useChannelMembership } from "./team-membership.js";
|
|
5
4
|
import { mintRequestId } from "./requests.js";
|
|
6
5
|
import createCss from './create.module.css';
|
|
7
6
|
import css from './sidebar.module.css';
|
|
@@ -70,10 +69,10 @@ export function ModelPickerField({ model, onModelChange, loadModels, disabled, t
|
|
|
70
69
|
}
|
|
71
70
|
/**
|
|
72
71
|
* Agent editor: handle, description, and per-Member model selection commit
|
|
73
|
-
* through one durable update
|
|
74
|
-
*
|
|
72
|
+
* through one durable update. Channel membership is managed from the Channel
|
|
73
|
+
* side, not here.
|
|
75
74
|
*/
|
|
76
|
-
export function AgentEditorDialog({ status,
|
|
75
|
+
export function AgentEditorDialog({ status, updateMember, loadModels, onCommitted, onClose, t }) {
|
|
77
76
|
const memberId = status.member.memberId;
|
|
78
77
|
const [handle, setHandle] = useState(status.member.handle);
|
|
79
78
|
const [description, setDescription] = useState(status.member.description);
|
|
@@ -81,35 +80,6 @@ export function AgentEditorDialog({ status, channels, loadChannels, updateMember
|
|
|
81
80
|
const [saving, setSaving] = useState(false);
|
|
82
81
|
const [error, setError] = useState();
|
|
83
82
|
const pendingRequest = useRef();
|
|
84
|
-
// Membership comes from a fresh projection at open time so stale checkbox
|
|
85
|
-
// state never flips a committed fact backwards.
|
|
86
|
-
const [memberships, setMemberships] = useState();
|
|
87
|
-
const [loadError, setLoadError] = useState();
|
|
88
|
-
useEffect(() => {
|
|
89
|
-
let mounted = true;
|
|
90
|
-
void loadChannels({ workspaceId: status.member.workspaceId, topLevelOnly: true, includeActivities: false, limit: 1 }).then(result => {
|
|
91
|
-
if (!mounted)
|
|
92
|
-
return;
|
|
93
|
-
if (result.ok) {
|
|
94
|
-
setMemberships(result.value.members);
|
|
95
|
-
setLoadError(undefined);
|
|
96
|
-
}
|
|
97
|
-
else {
|
|
98
|
-
setLoadError(result.error.message);
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
return () => { mounted = false; };
|
|
102
|
-
}, [loadChannels, status.member.workspaceId]);
|
|
103
|
-
const membership = useChannelMembership({ joinChannel, removeChannelMember }, change => change.channelRef, change => {
|
|
104
|
-
setMemberships(current => {
|
|
105
|
-
const base = current ?? [];
|
|
106
|
-
return change.joined
|
|
107
|
-
? base.filter(item => !(item.channelRef === change.channelRef && item.memberId === change.memberId))
|
|
108
|
-
: [...base, { channelRef: change.channelRef, memberId: change.memberId }];
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
// A successful join appends the durable fact; removal filters it out above.
|
|
112
|
-
const joinedChannels = new Set((memberships ?? []).filter(item => item.memberId === memberId).map(item => item.channelRef));
|
|
113
83
|
const dirty = handle.trim() !== status.member.handle || description.trim() !== status.member.description
|
|
114
84
|
|| !sameModel(model, status.member.model);
|
|
115
85
|
const submit = async (event) => {
|
|
@@ -123,6 +93,9 @@ export function AgentEditorDialog({ status, channels, loadChannels, updateMember
|
|
|
123
93
|
handle: normalizedHandle,
|
|
124
94
|
description: normalizedDescription,
|
|
125
95
|
...(model === undefined ? {} : { model }),
|
|
96
|
+
// The editor owns no capabilities UI, but an absent field would clear a
|
|
97
|
+
// Remote-written override; echo the stored intent through the edit.
|
|
98
|
+
...(status.member.capabilities === undefined ? {} : { capabilities: status.member.capabilities }),
|
|
126
99
|
};
|
|
127
100
|
const samePending = pendingRequest.current !== undefined && pendingRequest.current.memberId === payload.memberId
|
|
128
101
|
&& pendingRequest.current.handle === payload.handle && pendingRequest.current.description === payload.description
|
|
@@ -152,13 +125,7 @@ export function AgentEditorDialog({ status, channels, loadChannels, updateMember
|
|
|
152
125
|
setSaving(false);
|
|
153
126
|
}
|
|
154
127
|
};
|
|
155
|
-
return (_jsx(Modal, { open: true, onClose: onClose, title: t('editAgent'), description: `@${status.member.handle}`, closeLabel: t('close'), contentClassName: createCss.dialogContent, footer: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", disabled: saving, onClick: onClose, children: t('cancel') }), _jsx(Button, { type: "submit", form: "team-agent-edit-form", variant: "primary", disabled: saving || !dirty || handle.trim().length === 0, children: saving ? t('editSaving') : t('editSave') })] }), children: _jsxs("form", { id: "team-agent-edit-form", className: createCss.form, onSubmit: event => { void submit(event); }, 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); pendingRequest.current = undefined; }, disabled: saving, 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); pendingRequest.current = undefined; }, disabled: saving })] }), _jsx(ModelPickerField, { model: model, onModelChange: choice => { pendingRequest.current = undefined; setModel(choice); }, loadModels: loadModels, disabled: saving, t: t }),
|
|
156
|
-
const joined = joinedChannels.has(channel.channelRef);
|
|
157
|
-
const rowPending = membership.pending.has(channel.channelRef);
|
|
158
|
-
const disabled = rowPending || (!joined && status.presence === 'unavailable');
|
|
159
|
-
const rowError = membership.errors.get(channel.channelRef);
|
|
160
|
-
return _jsxs("div", { className: `${css.editMemberRow} ${css.editMemberRowChannels}`, children: [_jsxs("strong", { className: css.editChannelName, children: ["# ", channel.name] }), _jsx(Button, { size: "sm", variant: "outline", disabled: disabled, onClick: () => { void membership.change({ workspaceId: status.member.workspaceId, channelRef: channel.channelRef, memberId, joined }); }, children: rowPending ? t('membershipUpdating') : joined ? t('removeFromChannel') : t('addToChannel') }), rowError !== undefined && _jsx("p", { className: css.rowError, role: "alert", children: rowError })] }, channel.channelRef);
|
|
161
|
-
}), channels.length === 0 && _jsx("small", { children: t('noChannelsForAgent') }), loadError !== undefined && _jsx("p", { className: css.rowError, role: "alert", children: loadError })] }), error !== undefined && _jsx("p", { className: createCss.error, role: "alert", children: error })] }) }));
|
|
128
|
+
return (_jsx(Modal, { open: true, onClose: onClose, title: t('editAgent'), description: `@${status.member.handle}`, closeLabel: t('close'), contentClassName: createCss.dialogContent, footer: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", disabled: saving, onClick: onClose, children: t('cancel') }), _jsx(Button, { type: "submit", form: "team-agent-edit-form", variant: "primary", disabled: saving || !dirty || handle.trim().length === 0, children: saving ? t('editSaving') : t('editSave') })] }), children: _jsxs("form", { id: "team-agent-edit-form", className: createCss.form, onSubmit: event => { void submit(event); }, 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); pendingRequest.current = undefined; }, disabled: saving, 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); pendingRequest.current = undefined; }, disabled: saving })] }), _jsx(ModelPickerField, { model: model, onModelChange: choice => { pendingRequest.current = undefined; setModel(choice); }, loadModels: loadModels, disabled: saving, t: t }), error !== undefined && _jsx("p", { className: createCss.error, role: "alert", children: error })] }) }));
|
|
162
129
|
}
|
|
163
130
|
export function sameModel(left, right) {
|
|
164
131
|
if (left === undefined && right === undefined)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TeamThreadPage.d.ts","sourceRoot":"","sources":["../../../src/client/TeamThreadPage.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAGV,mBAAmB,EAInB,gBAAgB,EAGhB,kBAAkB,EAEnB,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AAEzE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AACvD,OAAO,KAAK,EAAgB,cAAc,EAAE,MAAM,aAAa,CAAA;AAc/D,UAAU,mBAAmB;IAC3B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,QAAQ,CAAC,UAAU,CAAC,EAAE,mBAAmB,CAAA;IACzC,QAAQ,CAAC,OAAO,CAAC,EAAE,gBAAgB,CAAA;IACnC,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAA;IACtC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,eAAe,EAAE,qBAAqB,CAAC,iBAAiB,CAAC,CAAA;IAClE,QAAQ,CAAC,YAAY,EAAE,qBAAqB,CAAC,cAAc,CAAC,CAAA;IAC5D,QAAQ,CAAC,UAAU,EAAE,qBAAqB,CAAC,YAAY,CAAC,CAAA;IACxD,QAAQ,CAAC,iBAAiB,EAAE,qBAAqB,CAAC,mBAAmB,CAAC,CAAA;IACtE,QAAQ,CAAC,gBAAgB,EAAE,qBAAqB,CAAC,kBAAkB,CAAC,CAAA;IACpE,QAAQ,CAAC,WAAW,EAAE,qBAAqB,CAAC,aAAa,CAAC,CAAA;IAC1D,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAA;IAC/B,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,KAAK,EAAE,qBAAqB,CAAC,OAAO,CAAC,CAAA;IAC9C,QAAQ,CAAC,UAAU,EAAE,qBAAqB,CAAC,YAAY,CAAC,CAAA;IACxD,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,YAAY,EAAE,qBAAqB,CAAC,cAAc,CAAC,CAAA;IAC5D,QAAQ,CAAC,eAAe,EAAE,qBAAqB,CAAC,iBAAiB,CAAC,CAAA;IAClE,QAAQ,CAAC,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC,CAAA;CACvC;AA8BD,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,+
|
|
1
|
+
{"version":3,"file":"TeamThreadPage.d.ts","sourceRoot":"","sources":["../../../src/client/TeamThreadPage.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAGV,mBAAmB,EAInB,gBAAgB,EAGhB,kBAAkB,EAEnB,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AAEzE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AACvD,OAAO,KAAK,EAAgB,cAAc,EAAE,MAAM,aAAa,CAAA;AAc/D,UAAU,mBAAmB;IAC3B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,QAAQ,CAAC,UAAU,CAAC,EAAE,mBAAmB,CAAA;IACzC,QAAQ,CAAC,OAAO,CAAC,EAAE,gBAAgB,CAAA;IACnC,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAA;IACtC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,eAAe,EAAE,qBAAqB,CAAC,iBAAiB,CAAC,CAAA;IAClE,QAAQ,CAAC,YAAY,EAAE,qBAAqB,CAAC,cAAc,CAAC,CAAA;IAC5D,QAAQ,CAAC,UAAU,EAAE,qBAAqB,CAAC,YAAY,CAAC,CAAA;IACxD,QAAQ,CAAC,iBAAiB,EAAE,qBAAqB,CAAC,mBAAmB,CAAC,CAAA;IACtE,QAAQ,CAAC,gBAAgB,EAAE,qBAAqB,CAAC,kBAAkB,CAAC,CAAA;IACpE,QAAQ,CAAC,WAAW,EAAE,qBAAqB,CAAC,aAAa,CAAC,CAAA;IAC1D,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAA;IAC/B,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,KAAK,EAAE,qBAAqB,CAAC,OAAO,CAAC,CAAA;IAC9C,QAAQ,CAAC,UAAU,EAAE,qBAAqB,CAAC,YAAY,CAAC,CAAA;IACxD,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,YAAY,EAAE,qBAAqB,CAAC,cAAc,CAAC,CAAA;IAC5D,QAAQ,CAAC,eAAe,EAAE,qBAAqB,CAAC,iBAAiB,CAAC,CAAA;IAClE,QAAQ,CAAC,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC,CAAA;CACvC;AA8BD,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,+BAuqBxD"}
|
|
@@ -43,7 +43,13 @@ export function TeamThreadPage(props) {
|
|
|
43
43
|
const [readFacts, setReadFacts] = useState([]);
|
|
44
44
|
const [historyCursor, setHistoryCursor] = useState();
|
|
45
45
|
const [historyHasMore, setHistoryHasMore] = useState(false);
|
|
46
|
-
|
|
46
|
+
// A bounded read acknowledges at most 20 unread facts; larger backlogs need
|
|
47
|
+
// continuation reads. Reads also never self-wake a change scope, so a
|
|
48
|
+
// backlog beyond a handful of batches would otherwise linger forever. The
|
|
49
|
+
// cap stops a pathological feed (facts arriving faster than they are read)
|
|
50
|
+
// from looping without bound; the error surface keeps the remainder visible.
|
|
51
|
+
const MAX_AUTO_READ_ROUNDS = 50;
|
|
52
|
+
const [autoReadExhausted, setAutoReadExhausted] = useState(false);
|
|
47
53
|
const [newFactsCount, setNewFactsCount] = useState(0);
|
|
48
54
|
// The reply draft lives in the keyed draft cache: view switches unmount
|
|
49
55
|
// this page, and a refresh must not cost the half-written message either.
|
|
@@ -70,13 +76,14 @@ export function TeamThreadPage(props) {
|
|
|
70
76
|
const currentFactsRef = useRef([]);
|
|
71
77
|
const sequenceRef = useRef(0);
|
|
72
78
|
const readRequestIdRef = useRef(mintRequestId());
|
|
79
|
+
const projectionRef = useRef();
|
|
73
80
|
const mutationRequests = useRef(new Map());
|
|
74
81
|
const threadLastFact = currentFacts[currentFacts.length - 1];
|
|
75
82
|
const timeline = useTimelineScroll(`${currentFacts.length}:${olderFacts.length}:${threadLastFact === undefined ? '' : factKey(threadLastFact)}`);
|
|
76
83
|
const updateProjection = (next) => {
|
|
84
|
+
projectionRef.current = next;
|
|
77
85
|
setProjection(next);
|
|
78
86
|
setReadFacts(next.facts);
|
|
79
|
-
setRemainingUnreadCount(next.remainingUnreadCount);
|
|
80
87
|
const anchor = messageFact(next.anchor, next.anchorMentions);
|
|
81
88
|
const batch = [anchor, ...next.facts.map(fact => fact.fact)];
|
|
82
89
|
setCurrentFacts(current => {
|
|
@@ -84,7 +91,28 @@ export function TeamThreadPage(props) {
|
|
|
84
91
|
currentFactsRef.current = merged;
|
|
85
92
|
return merged;
|
|
86
93
|
});
|
|
87
|
-
|
|
94
|
+
};
|
|
95
|
+
// Continue a bounded read while unread facts remain. Each round must mint a
|
|
96
|
+
// fresh requestId: the Host replays a repeated id from its idempotency
|
|
97
|
+
// cache, which would return the same batch forever. The loop terminates on
|
|
98
|
+
// a zero remainder, a failed read (the existing error surface offers
|
|
99
|
+
// retry), a superseding sequence, or the round cap.
|
|
100
|
+
const drainUnread = async () => {
|
|
101
|
+
for (let round = 1; round <= MAX_AUTO_READ_ROUNDS; round += 1) {
|
|
102
|
+
const snapshot = projectionRef.current;
|
|
103
|
+
if (snapshot === undefined || snapshot.remainingUnreadCount <= 0)
|
|
104
|
+
return;
|
|
105
|
+
const beforeSequence = sequenceRef.current;
|
|
106
|
+
if (!await readCurrent(true))
|
|
107
|
+
return;
|
|
108
|
+
// A newer read or remount owns the tail now; it drains the remainder.
|
|
109
|
+
if (!mountedRef.current || sequenceRef.current !== beforeSequence + 1)
|
|
110
|
+
return;
|
|
111
|
+
// A stalled remainder (Host fault) must not spin the loop.
|
|
112
|
+
if (projectionRef.current?.remainingUnreadCount === snapshot.remainingUnreadCount)
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
setAutoReadExhausted(true);
|
|
88
116
|
};
|
|
89
117
|
const readCurrent = async (newRequest = false) => {
|
|
90
118
|
if (!mountedRef.current)
|
|
@@ -157,23 +185,26 @@ export function TeamThreadPage(props) {
|
|
|
157
185
|
currentFactsRef.current = merged;
|
|
158
186
|
return merged;
|
|
159
187
|
});
|
|
160
|
-
setProjection(current =>
|
|
188
|
+
setProjection(current => {
|
|
189
|
+
const next = current === undefined ? current : { ...current, ...(result.value.task === undefined ? {} : { task: result.value.task }), thread: result.value.thread, claims: result.value.claims };
|
|
190
|
+
if (next !== undefined)
|
|
191
|
+
projectionRef.current = next;
|
|
192
|
+
return next;
|
|
193
|
+
});
|
|
161
194
|
if (additions.length === 0)
|
|
162
195
|
return;
|
|
163
|
-
//
|
|
164
|
-
//
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
if (!timeline.isPinned())
|
|
196
|
+
// Every arrival while the Thread is open is acknowledged durably: the
|
|
197
|
+
// timeline renders it either way, and the Human has no manual read
|
|
198
|
+
// action anymore. The count only feeds the pure jump hint for a reader
|
|
199
|
+
// away from the bottom; returning to the bottom clears it.
|
|
200
|
+
if (!timeline.isPinned())
|
|
168
201
|
setNewFactsCount(current => current + additions.length);
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
202
|
const priorSequence = sequenceRef.current;
|
|
172
203
|
if (await readCurrent(true))
|
|
173
204
|
return;
|
|
174
205
|
// The durable acknowledgment did not happen (it failed, a newer read
|
|
175
|
-
// superseded it, or the page unmounted);
|
|
176
|
-
//
|
|
206
|
+
// superseded it, or the page unmounted); the count keeps the jump hint
|
|
207
|
+
// visible unless that newer read now owns the tail.
|
|
177
208
|
if (!mountedRef.current || sequenceRef.current !== priorSequence + 1)
|
|
178
209
|
return;
|
|
179
210
|
setNewFactsCount(current => current + additions.length);
|
|
@@ -184,6 +215,7 @@ export function TeamThreadPage(props) {
|
|
|
184
215
|
};
|
|
185
216
|
useEffect(() => {
|
|
186
217
|
mountedRef.current = true;
|
|
218
|
+
projectionRef.current = undefined;
|
|
187
219
|
setProjection(undefined);
|
|
188
220
|
setChannelView(undefined);
|
|
189
221
|
setMembers([]);
|
|
@@ -193,7 +225,7 @@ export function TeamThreadPage(props) {
|
|
|
193
225
|
setReadFacts([]);
|
|
194
226
|
setHistoryCursor(undefined);
|
|
195
227
|
setHistoryHasMore(false);
|
|
196
|
-
|
|
228
|
+
setAutoReadExhausted(false);
|
|
197
229
|
setNewFactsCount(0);
|
|
198
230
|
setError(undefined);
|
|
199
231
|
setStatusMessage(undefined);
|
|
@@ -215,9 +247,9 @@ export function TeamThreadPage(props) {
|
|
|
215
247
|
return;
|
|
216
248
|
}
|
|
217
249
|
updateProjection(read.value);
|
|
218
|
-
//
|
|
219
|
-
|
|
220
|
-
|
|
250
|
+
// The Thread opens at the latest fact; the unread boundary stays
|
|
251
|
+
// rendered as information, but reading is automatic from here on.
|
|
252
|
+
timeline.scrollToBottom();
|
|
221
253
|
if (history !== undefined && history.ok) {
|
|
222
254
|
setCurrentFacts(current => {
|
|
223
255
|
const merged = mergeFacts(current, history.value.facts);
|
|
@@ -229,6 +261,7 @@ export function TeamThreadPage(props) {
|
|
|
229
261
|
}
|
|
230
262
|
setError(undefined);
|
|
231
263
|
setLoading(false);
|
|
264
|
+
await drainUnread();
|
|
232
265
|
})();
|
|
233
266
|
void refreshSupplemental();
|
|
234
267
|
const disposers = [
|
|
@@ -258,6 +291,13 @@ export function TeamThreadPage(props) {
|
|
|
258
291
|
dispose();
|
|
259
292
|
};
|
|
260
293
|
}, [workspaceId, taskRef, threadRef]);
|
|
294
|
+
// Returning to the bottom is the reader's answer to the jump hint: the
|
|
295
|
+
// arrivals are on screen, their durable read already happened (or will be
|
|
296
|
+
// retried by the next change wake), and the hint has nothing left to say.
|
|
297
|
+
useEffect(() => {
|
|
298
|
+
if (newFactsCount > 0 && timeline.isPinned())
|
|
299
|
+
setNewFactsCount(0);
|
|
300
|
+
}, [newFactsCount, currentFacts, olderFacts, timeline]);
|
|
261
301
|
const loadOlder = async () => {
|
|
262
302
|
if (historyHasMore === false && historyCursor === undefined)
|
|
263
303
|
return;
|
|
@@ -379,7 +419,7 @@ export function TeamThreadPage(props) {
|
|
|
379
419
|
}
|
|
380
420
|
flushRun();
|
|
381
421
|
if (boundaryIndex === index)
|
|
382
|
-
nodes.push(_jsx("p", { className: threadCss.unreadBoundary, role: "separator",
|
|
422
|
+
nodes.push(_jsx("p", { className: threadCss.unreadBoundary, role: "separator", children: _jsx("span", { children: t('unreadBoundary') }) }, `boundary-${index}`));
|
|
383
423
|
if (sender !== undefined)
|
|
384
424
|
run.push(fact);
|
|
385
425
|
else
|
|
@@ -389,9 +429,10 @@ export function TeamThreadPage(props) {
|
|
|
389
429
|
return nodes;
|
|
390
430
|
};
|
|
391
431
|
const refreshAfterFence = async () => {
|
|
392
|
-
timeline.
|
|
432
|
+
timeline.scrollToBottom();
|
|
393
433
|
await readCurrent(true);
|
|
394
434
|
await refreshSupplemental();
|
|
435
|
+
await drainUnread();
|
|
395
436
|
};
|
|
396
437
|
const convertToTask = async () => {
|
|
397
438
|
if (pending || thread === undefined || task !== undefined)
|
|
@@ -596,7 +637,7 @@ export function TeamThreadPage(props) {
|
|
|
596
637
|
})(), task !== undefined && thread !== undefined && _jsx("section", { className: threadCss.workSection, "aria-label": t('claims'), children: _jsx(DisclosureRow, { expandOnRowClick: true, expandable: true, open: claimsOpen, onToggle: () => { setClaimsOpen(current => !current); }, icon: _jsx(IconChecklistOutline14, { size: 14 }), title: `${t('claims')} · ${taskClaims.length}`, children: _jsxs("div", { className: threadCss.claimList, children: [taskClaims.length === 0 && _jsx("p", { className: threadCss.emptyClaims, children: t('noClaims') }), taskClaims.map(claim => {
|
|
597
638
|
const ownerStatus = members.find(status => status.member.memberId === claim.owner);
|
|
598
639
|
return _jsxs("article", { className: threadCss.claimRow, children: [ownerStatus === undefined ? _jsx("span", {}) : _jsx(TeamPresenceDot, { status: ownerStatus, t: t }), _jsx("strong", { className: threadCss.claimOwner, children: memberName(claim.owner) }), _jsx("span", { className: threadCss.claimDirection, children: claim.direction }), _jsx("small", { className: threadCss.claimState, children: formatClaimState(claim.state, t) })] }, claim.claimRef);
|
|
599
|
-
})] }) }) })] }), _jsx("section", { ref: timeline.ref, onScroll: timeline.onScroll, className: css.timeline, "aria-label": t('timelineLabel'), children: _jsxs("div", { className: css.timelineContent, children: [loading && projection === undefined && error === undefined && _jsx("div", { className: css.emptySurface, children: _jsxs("p", { className: css.loadingState, children: [_jsx("span", { className: css.loadingMark, "aria-hidden": "true" }), t('loadingThread')] }) }), projection === undefined && error !== undefined && _jsxs("div", { className: css.errorState, role: "alert", children: [_jsx("span", { children: error }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => { void readCurrent(); }, children: t('retry') })] }), olderFacts.length > 0 && _jsxs("section", { className: threadCss.historySection, "aria-label": t('olderHistory'), children: [_jsx("h2", { children: t('olderHistory') }), renderFactBlocks(olderFacts, undefined)] }), historyHasMore && _jsx("div", { className: css.timelineAction, children: _jsx(Button, { size: "sm", onClick: () => { void loadOlder(); }, children: t('loadOlder') }) }), currentFactsWithAnchor.length > 0 && _jsx("section", { className: threadCss.publicSection, children: renderFactBlocks(currentFactsWithAnchor, unreadBoundary) }),
|
|
640
|
+
})] }) }) })] }), _jsx("section", { ref: timeline.ref, onScroll: timeline.onScroll, className: css.timeline, "aria-label": t('timelineLabel'), children: _jsxs("div", { className: css.timelineContent, children: [loading && projection === undefined && error === undefined && _jsx("div", { className: css.emptySurface, children: _jsxs("p", { className: css.loadingState, children: [_jsx("span", { className: css.loadingMark, "aria-hidden": "true" }), t('loadingThread')] }) }), projection === undefined && error !== undefined && _jsxs("div", { className: css.errorState, role: "alert", children: [_jsx("span", { children: error }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => { void readCurrent(); }, children: t('retry') })] }), olderFacts.length > 0 && _jsxs("section", { className: threadCss.historySection, "aria-label": t('olderHistory'), children: [_jsx("h2", { children: t('olderHistory') }), renderFactBlocks(olderFacts, undefined)] }), historyHasMore && _jsx("div", { className: css.timelineAction, children: _jsx(Button, { size: "sm", onClick: () => { void loadOlder(); }, children: t('loadOlder') }) }), currentFactsWithAnchor.length > 0 && _jsx("section", { className: threadCss.publicSection, children: renderFactBlocks(currentFactsWithAnchor, unreadBoundary) }), autoReadExhausted && _jsxs("div", { className: css.timelineAction, role: "alert", children: [_jsx("span", { children: t('autoReadIncomplete') }), _jsx(Button, { size: "sm", onClick: () => { setAutoReadExhausted(false); void drainUnread(); }, disabled: loading, children: t('retry') })] }), newFactsCount > 0 && _jsx("div", { className: threadCss.newUpdates, role: "status", children: _jsx("button", { type: "button", className: threadCss.newUpdatesJump, onClick: timeline.scrollToBottom, children: t('newUpdatesJump', { count: newFactsCount }) }) })] }) }), projection !== undefined && thread !== undefined ? (task?.resolution === 'closed'
|
|
600
641
|
? _jsxs("div", { className: threadCss.closedBar, "data-team-closed": true, children: [error !== undefined && _jsx("p", { className: css.error, role: "alert", children: error }), _jsxs("div", { className: threadCss.closedNotice, children: [_jsx("span", { children: t('taskClosedNotice') }), _jsx(Button, { size: "sm", variant: "outline", disabled: pending, onClick: () => { void mutateTask('reopen'); }, children: t('reopenTask') })] })] })
|
|
601
642
|
: _jsx(TeamComposer, { members: channelMembers, recipients: recipients, draft: draft, pending: pending, ...(statusMessage === undefined ? {} : { confirmation: statusMessage }), ...(error === undefined ? {} : { error }), onDraftChange: next => { drafts.writeDraft(draftKey, next); setConfirmation(undefined); setReplyRequestId(undefined); setStatusMessage(undefined); }, onRecipientsChange: next => { drafts.writeRecipients(draftKey, next); setConfirmation(undefined); setReplyRequestId(undefined); setStatusMessage(undefined); }, onSubmit: () => { void sendReply(); }, placeholder: t('replyPlaceholder'), pendingFiles: pendingFiles, onFilesChange: setPendingFiles, t: t }, draftKey)) : _jsx("div", {})] });
|
|
602
643
|
}
|
|
@@ -39,5 +39,5 @@ export function TeamWorkspaceBrowser({ wide, expandSidebar, navigation, selectWo
|
|
|
39
39
|
if (!wide) {
|
|
40
40
|
return _jsxs("nav", { className: css.railWorkspace, "aria-label": t('workspaceSections'), children: [_jsx(Tooltip, { label: t('channels'), side: "right", children: _jsx("button", { type: "button", className: css.railButton, "aria-label": t('channels'), onClick: () => { setPendingSection('channels'); expandSidebar(); }, children: _jsx(IconListPenOutline16, { size: 16 }) }) }), _jsx(Tooltip, { label: t('agents'), side: "right", children: _jsx("button", { type: "button", className: css.railButton, "aria-label": t('agents'), onClick: () => { setPendingSection('agents'); expandSidebar(); }, children: _jsx(IconAgentPresetOutline16, { size: 16 }) }) })] });
|
|
41
41
|
}
|
|
42
|
-
return _jsxs("section", { className: css.workspaceBrowser, "aria-label": t('workspaces'), children: [_jsx(TeamSidebarSection, { title: t('workspaces'), open: workspacesOpen, onToggle: open => { setSidebarSectionOpen(undefined, 'workspaces', open); }, children: _jsxs("div", { className: css.workspaceList, children: [workspaces.map(workspace => _jsx(TeamWorkspaceRow, { workspaceId: workspace.workspaceId, title: workspace.title, path: workspace.path, selected: workspace.workspaceId === selectedId, current: workspace.workspaceId === selectedId && overviewIsCurrent, onSelect: selectWorkspace }, workspace.workspaceId)), workspaces.length === 0 && _jsx("p", { className: css.emptyState, children: t('empty') })] }) }), selectedId !== undefined && _jsxs("div", { className: css.workspaceSection, children: [_jsx("div", { ref: channelsRef, children: _jsx(TeamChannelsPanel, { workspaceId: selectedId, loadMembers: loadMembers, loadChannels: loadChannels, subscribeChanges: subscribeChanges, createChannel: createChannel, updateChannel: updateChannel, joinChannel: joinChannel, removeChannelMember: removeChannelMember, creatingAgents: creatingAgents.filter(request => request.workspaceId === selectedId), ...(navigationState.memberSessionId !== undefined || navigationState.channelRef === undefined ? {} : { selectedChannelRef: navigationState.channelRef }), selectChannel: selectChannel, t: t }, selectedId) }), _jsx("div", { ref: agentsRef, children: _jsx(TeamAgentsPanel, { workspaceId: selectedId, loadMembers: loadMembers, subscribeChanges: subscribeChanges,
|
|
42
|
+
return _jsxs("section", { className: css.workspaceBrowser, "aria-label": t('workspaces'), children: [_jsx(TeamSidebarSection, { title: t('workspaces'), open: workspacesOpen, onToggle: open => { setSidebarSectionOpen(undefined, 'workspaces', open); }, children: _jsxs("div", { className: css.workspaceList, children: [workspaces.map(workspace => _jsx(TeamWorkspaceRow, { workspaceId: workspace.workspaceId, title: workspace.title, path: workspace.path, selected: workspace.workspaceId === selectedId, current: workspace.workspaceId === selectedId && overviewIsCurrent, onSelect: selectWorkspace }, workspace.workspaceId)), workspaces.length === 0 && _jsx("p", { className: css.emptyState, children: t('empty') })] }) }), selectedId !== undefined && _jsxs("div", { className: css.workspaceSection, children: [_jsx("div", { ref: channelsRef, children: _jsx(TeamChannelsPanel, { workspaceId: selectedId, loadMembers: loadMembers, loadChannels: loadChannels, subscribeChanges: subscribeChanges, createChannel: createChannel, updateChannel: updateChannel, joinChannel: joinChannel, removeChannelMember: removeChannelMember, creatingAgents: creatingAgents.filter(request => request.workspaceId === selectedId), ...(navigationState.memberSessionId !== undefined || navigationState.channelRef === undefined ? {} : { selectedChannelRef: navigationState.channelRef }), selectChannel: selectChannel, t: t }, selectedId) }), _jsx("div", { ref: agentsRef, children: _jsx(TeamAgentsPanel, { workspaceId: selectedId, loadMembers: loadMembers, subscribeChanges: subscribeChanges, addMember: addMember, updateMember: updateMember, recoverMember: recoverMember, clearMemberContext: clearMemberContext, loadModels: loadModels, ...(navigationState.memberSessionId === undefined ? {} : { memberSessionId: navigationState.memberSessionId }), openMemberSession: openMemberSession, onCreatingChange: (request, creating) => { setCreatingAgents(current => creating ? [...current.filter(item => item.requestId !== request.requestId), request] : current.filter(item => item.requestId !== request.requestId)); }, t: t }, selectedId) })] })] });
|
|
43
43
|
}
|
|
@@ -15,8 +15,6 @@ export declare const zh: {
|
|
|
15
15
|
readonly retry: "重试";
|
|
16
16
|
readonly agentName: "名称";
|
|
17
17
|
readonly agentDescription: "说明";
|
|
18
|
-
readonly initialChannels: "初始频道";
|
|
19
|
-
readonly noChannelsForAgent: "请先创建至少一个频道";
|
|
20
18
|
readonly createAgent: "创建 Agent";
|
|
21
19
|
readonly creatingAgent: "正在创建…";
|
|
22
20
|
readonly loadingAgents: "正在加载 Agents…";
|
|
@@ -49,8 +47,6 @@ export declare const zh: {
|
|
|
49
47
|
readonly clearContextFailed: "清空上下文失败:{message}";
|
|
50
48
|
readonly optionalSuffix: "(可选)";
|
|
51
49
|
readonly agentDescriptionPlaceholder: "留空则暂无描述";
|
|
52
|
-
readonly channelsPickerEmpty: "选择初始频道";
|
|
53
|
-
readonly channelsPickerCount: "已选 {count} 个频道";
|
|
54
50
|
readonly membersPickerEmpty: "选择初始成员";
|
|
55
51
|
readonly membersPickerCount: "已选 {count} 个成员";
|
|
56
52
|
readonly reasoningEffort: "推理强度";
|
|
@@ -112,10 +108,8 @@ export declare const zh: {
|
|
|
112
108
|
readonly backToWorkspace: "返回工作区";
|
|
113
109
|
readonly loadingThread: "正在加载 Thread…";
|
|
114
110
|
readonly unreadBoundary: "以下是本次打开收到的更新";
|
|
115
|
-
readonly
|
|
116
|
-
readonly
|
|
117
|
-
readonly remainingUnread: "还有 {count} 条未读更新";
|
|
118
|
-
readonly continueReading: "继续阅读";
|
|
111
|
+
readonly newUpdatesJump: "↓ {count} 条新更新";
|
|
112
|
+
readonly autoReadIncomplete: "仍有未读更新未能自动读取";
|
|
119
113
|
readonly olderHistory: "更早历史";
|
|
120
114
|
readonly runtimeRisk: "当前运行风险";
|
|
121
115
|
readonly runtimeRiskDetail: "@{member} 当前不可用:{diagnostic}";
|
|
@@ -163,8 +157,6 @@ export declare const en: {
|
|
|
163
157
|
readonly retry: "Retry";
|
|
164
158
|
readonly agentName: "Name";
|
|
165
159
|
readonly agentDescription: "Description";
|
|
166
|
-
readonly initialChannels: "Initial Channels";
|
|
167
|
-
readonly noChannelsForAgent: "Create at least one Channel first";
|
|
168
160
|
readonly createAgent: "Create Agent";
|
|
169
161
|
readonly creatingAgent: "Creating…";
|
|
170
162
|
readonly loadingAgents: "Loading Agents…";
|
|
@@ -197,8 +189,6 @@ export declare const en: {
|
|
|
197
189
|
readonly clearContextFailed: "Failed to clear context: {message}";
|
|
198
190
|
readonly optionalSuffix: " (optional)";
|
|
199
191
|
readonly agentDescriptionPlaceholder: "Leave empty if not needed";
|
|
200
|
-
readonly channelsPickerEmpty: "Choose initial channels";
|
|
201
|
-
readonly channelsPickerCount: "{count} selected";
|
|
202
192
|
readonly membersPickerEmpty: "Choose initial members";
|
|
203
193
|
readonly membersPickerCount: "{count} selected";
|
|
204
194
|
readonly reasoningEffort: "Reasoning effort";
|
|
@@ -260,10 +250,8 @@ export declare const en: {
|
|
|
260
250
|
readonly backToWorkspace: "Back to Workspace";
|
|
261
251
|
readonly loadingThread: "Loading Thread…";
|
|
262
252
|
readonly unreadBoundary: "Updates received when you opened this Thread";
|
|
263
|
-
readonly
|
|
264
|
-
readonly
|
|
265
|
-
readonly remainingUnread: "{count} unread update(s) remain";
|
|
266
|
-
readonly continueReading: "Continue reading";
|
|
253
|
+
readonly newUpdatesJump: "↓ {count} new update(s)";
|
|
254
|
+
readonly autoReadIncomplete: "Some unread updates could not be read automatically";
|
|
267
255
|
readonly olderHistory: "Older history";
|
|
268
256
|
readonly runtimeRisk: "Current runtime risk";
|
|
269
257
|
readonly runtimeRiskDetail: "@{member} is unavailable: {diagnostic}";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,EAAE
|
|
1
|
+
{"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6IL,CAAA;AAEV,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA6IL,CAAA;AAEV,MAAM,MAAM,OAAO,GAAG,MAAM,OAAO,EAAE,CAAA"}
|
|
@@ -15,8 +15,6 @@ export const zh = {
|
|
|
15
15
|
retry: '重试',
|
|
16
16
|
agentName: '名称',
|
|
17
17
|
agentDescription: '说明',
|
|
18
|
-
initialChannels: '初始频道',
|
|
19
|
-
noChannelsForAgent: '请先创建至少一个频道',
|
|
20
18
|
createAgent: '创建 Agent',
|
|
21
19
|
creatingAgent: '正在创建…',
|
|
22
20
|
loadingAgents: '正在加载 Agents…',
|
|
@@ -49,8 +47,6 @@ export const zh = {
|
|
|
49
47
|
clearContextFailed: '清空上下文失败:{message}',
|
|
50
48
|
optionalSuffix: '(可选)',
|
|
51
49
|
agentDescriptionPlaceholder: '留空则暂无描述',
|
|
52
|
-
channelsPickerEmpty: '选择初始频道',
|
|
53
|
-
channelsPickerCount: '已选 {count} 个频道',
|
|
54
50
|
membersPickerEmpty: '选择初始成员',
|
|
55
51
|
membersPickerCount: '已选 {count} 个成员',
|
|
56
52
|
reasoningEffort: '推理强度',
|
|
@@ -112,10 +108,8 @@ export const zh = {
|
|
|
112
108
|
backToWorkspace: '返回工作区',
|
|
113
109
|
loadingThread: '正在加载 Thread…',
|
|
114
110
|
unreadBoundary: '以下是本次打开收到的更新',
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
remainingUnread: '还有 {count} 条未读更新',
|
|
118
|
-
continueReading: '继续阅读',
|
|
111
|
+
newUpdatesJump: '↓ {count} 条新更新',
|
|
112
|
+
autoReadIncomplete: '仍有未读更新未能自动读取',
|
|
119
113
|
olderHistory: '更早历史',
|
|
120
114
|
runtimeRisk: '当前运行风险',
|
|
121
115
|
runtimeRiskDetail: '@{member} 当前不可用:{diagnostic}',
|
|
@@ -163,8 +157,6 @@ export const en = {
|
|
|
163
157
|
retry: 'Retry',
|
|
164
158
|
agentName: 'Name',
|
|
165
159
|
agentDescription: 'Description',
|
|
166
|
-
initialChannels: 'Initial Channels',
|
|
167
|
-
noChannelsForAgent: 'Create at least one Channel first',
|
|
168
160
|
createAgent: 'Create Agent',
|
|
169
161
|
creatingAgent: 'Creating…',
|
|
170
162
|
loadingAgents: 'Loading Agents…',
|
|
@@ -197,8 +189,6 @@ export const en = {
|
|
|
197
189
|
clearContextFailed: 'Failed to clear context: {message}',
|
|
198
190
|
optionalSuffix: ' (optional)',
|
|
199
191
|
agentDescriptionPlaceholder: 'Leave empty if not needed',
|
|
200
|
-
channelsPickerEmpty: 'Choose initial channels',
|
|
201
|
-
channelsPickerCount: '{count} selected',
|
|
202
192
|
membersPickerEmpty: 'Choose initial members',
|
|
203
193
|
membersPickerCount: '{count} selected',
|
|
204
194
|
reasoningEffort: 'Reasoning effort',
|
|
@@ -260,10 +250,8 @@ export const en = {
|
|
|
260
250
|
backToWorkspace: 'Back to Workspace',
|
|
261
251
|
loadingThread: 'Loading Thread…',
|
|
262
252
|
unreadBoundary: 'Updates received when you opened this Thread',
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
remainingUnread: '{count} unread update(s) remain',
|
|
266
|
-
continueReading: 'Continue reading',
|
|
253
|
+
newUpdatesJump: '↓ {count} new update(s)',
|
|
254
|
+
autoReadIncomplete: 'Some unread updates could not be read automatically',
|
|
267
255
|
olderHistory: 'Older history',
|
|
268
256
|
runtimeRisk: 'Current runtime risk',
|
|
269
257
|
runtimeRiskDetail: '@{member} is unavailable: {diagnostic}',
|