@wowyuarm/dsh-agent-team 0.1.0 → 0.1.1
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 +14 -2
- package/README.zh.md +10 -2
- package/package.json +2 -2
- package/packages/agent-team/lib/attachments.js +157 -0
- package/packages/agent-team/lib/index.js +274 -5
- package/packages/agent-team/lib/ledger.js +65 -13
- package/packages/agent-team/lib/recovery.js +87 -0
- package/packages/agent-team/lib/spec.js +17 -1
- package/packages/agent-team/lib/typert.host.js +321 -21
- package/packages/agent-team/lib/typert.remote-client.d.ts +9 -1
- package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
- package/packages/agent-team/lib/typert.remote-client.js +249 -17
- package/packages/agent-team/lib/types/attachments.d.ts +69 -0
- package/packages/agent-team/lib/types/attachments.d.ts.map +1 -0
- package/packages/agent-team/lib/types/index.d.ts +66 -1
- package/packages/agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/types/ledger.d.ts +17 -1
- package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
- package/packages/agent-team/lib/types/recovery.d.ts +42 -0
- package/packages/agent-team/lib/types/recovery.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 +85 -2
- package/packages/agent-team/lib/types/types.d.ts.map +1 -1
- package/packages/client-agent-team/README.md +2 -2
- package/packages/client-agent-team/README.zh.md +2 -2
- package/packages/client-agent-team/lib/client.js +1176 -281
- package/packages/client-agent-team/lib/client.js.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts +6 -2
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +104 -58
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts +5 -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 +44 -19
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +23 -18
- package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts +4 -1
- package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamComposer.js +40 -4
- package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts +1 -1
- package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamConversation.js +3 -3
- package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts +7 -2
- package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMessage.js +34 -8
- package/packages/client-agent-team/lib/types/client/TeamRunDivider.d.ts +10 -0
- package/packages/client-agent-team/lib/types/client/TeamRunDivider.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamRunDivider.js +12 -0
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts +3 -0
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +18 -11
- 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 +6 -5
- package/packages/client-agent-team/lib/types/client/attachment-preview.d.ts +22 -0
- package/packages/client-agent-team/lib/types/client/attachment-preview.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/attachment-preview.js +35 -0
- package/packages/client-agent-team/lib/types/client/drafts.d.ts +41 -0
- package/packages/client-agent-team/lib/types/client/drafts.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/drafts.js +144 -0
- package/packages/client-agent-team/lib/types/client/index.d.ts +2 -0
- package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/index.js +50 -10
- package/packages/client-agent-team/lib/types/client/locales.d.ts +28 -0
- package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/locales.js +28 -0
- package/packages/client-agent-team/lib/types/client/multi-menu-field.d.ts +33 -0
- package/packages/client-agent-team/lib/types/client/multi-menu-field.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/multi-menu-field.js +30 -0
- package/packages/client-agent-team/lib/types/client/navigation.d.ts +16 -1
- package/packages/client-agent-team/lib/types/client/navigation.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/navigation.js +45 -8
- package/packages/client-agent-team/lib/types/client/slots.d.ts +18 -2
- package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +2 -0
- 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 +4 -0
- package/packages/client-agent-team/lib/types/client/team-separators.d.ts +8 -0
- package/packages/client-agent-team/lib/types/client/team-separators.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/team-separators.js +14 -0
- package/packages/tool-agent-team/lib/index.js +6 -2
- package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AgentTeamClientMemberStatus, AgentTeamMemberId } from '@wowyuarm/dsh-agent-team/types';
|
|
2
2
|
import type { TeamConversationProps } from './slots.ts';
|
|
3
|
-
export declare function TeamComposer({ members, recipients, draft, pending, confirmation, error, onDraftChange, onRecipientsChange, onSubmit, t }: {
|
|
3
|
+
export declare function TeamComposer({ members, recipients, draft, pending, confirmation, error, onDraftChange, onRecipientsChange, onSubmit, pendingFiles, onFilesChange, t }: {
|
|
4
4
|
readonly members: readonly AgentTeamClientMemberStatus[];
|
|
5
5
|
readonly recipients: ReadonlySet<AgentTeamMemberId>;
|
|
6
6
|
readonly draft: string;
|
|
@@ -10,6 +10,9 @@ export declare function TeamComposer({ members, recipients, draft, pending, conf
|
|
|
10
10
|
readonly onDraftChange: (draft: string) => void;
|
|
11
11
|
readonly onRecipientsChange: (recipients: ReadonlySet<AgentTeamMemberId>) => void;
|
|
12
12
|
readonly onSubmit: () => void;
|
|
13
|
+
/** Upload-capable surfaces pass this to enable the "+" file picker; the reply path omits it. */
|
|
14
|
+
readonly pendingFiles?: readonly File[];
|
|
15
|
+
readonly onFilesChange?: (files: readonly File[]) => void;
|
|
13
16
|
readonly t: TeamConversationProps['t'];
|
|
14
17
|
}): import("react").JSX.Element;
|
|
15
18
|
//# sourceMappingURL=TeamComposer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TeamComposer.d.ts","sourceRoot":"","sources":["../../../src/client/TeamComposer.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,2BAA2B,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAEpG,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAoCvD,wBAAgB,YAAY,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE;
|
|
1
|
+
{"version":3,"file":"TeamComposer.d.ts","sourceRoot":"","sources":["../../../src/client/TeamComposer.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,2BAA2B,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAEpG,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAoCvD,wBAAgB,YAAY,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,QAAQ,EAAE,YAAY,EAAE,aAAa,EAAE,CAAC,EAAE,EAAE;IACtK,QAAQ,CAAC,OAAO,EAAE,SAAS,2BAA2B,EAAE,CAAA;IACxD,QAAQ,CAAC,UAAU,EAAE,WAAW,CAAC,iBAAiB,CAAC,CAAA;IACnD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/C,QAAQ,CAAC,kBAAkB,EAAE,CAAC,UAAU,EAAE,WAAW,CAAC,iBAAiB,CAAC,KAAK,IAAI,CAAA;IACjF,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAA;IAC7B,gGAAgG;IAChG,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,IAAI,EAAE,CAAA;IACvC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,KAAK,IAAI,CAAA;IACzD,QAAQ,CAAC,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC,CAAA;CACvC,+BAyNA"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
3
|
-
import { IconSendOutline16, useAnchoredMaxHeight, useDismissOnOutsidePointer } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
3
|
+
import { IconPlusOutline16, IconSendOutline16, useAnchoredMaxHeight, useDismissOnOutsidePointer } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
4
4
|
import { TeamPresenceDot } from "./TeamPresenceDot.js";
|
|
5
5
|
import css from './composer.module.css';
|
|
6
6
|
function findMention(draft, caret) {
|
|
@@ -29,8 +29,9 @@ function mentionCandidates(members, query) {
|
|
|
29
29
|
&& status.member.state !== 'inactive'
|
|
30
30
|
&& status.member.handle.toLocaleLowerCase().startsWith(normalized));
|
|
31
31
|
}
|
|
32
|
-
export function TeamComposer({ members, recipients, draft, pending, confirmation, error, onDraftChange, onRecipientsChange, onSubmit, t }) {
|
|
32
|
+
export function TeamComposer({ members, recipients, draft, pending, confirmation, error, onDraftChange, onRecipientsChange, onSubmit, pendingFiles, onFilesChange, t }) {
|
|
33
33
|
const inputRef = useRef(null);
|
|
34
|
+
const fileInputRef = useRef(null);
|
|
34
35
|
const rootRef = useRef(null);
|
|
35
36
|
const menuRef = useRef(null);
|
|
36
37
|
const composingRef = useRef(false);
|
|
@@ -57,6 +58,22 @@ export function TeamComposer({ members, recipients, draft, pending, confirmation
|
|
|
57
58
|
}
|
|
58
59
|
setHighlight(current => Math.min(current, candidates.length - 1));
|
|
59
60
|
}, [mention, candidates.length]);
|
|
61
|
+
// Match the resident DSH composer without stealing a later user choice: Team
|
|
62
|
+
// data can load after navigation, so a dialog or another control may already
|
|
63
|
+
// own focus by the time this composer appears.
|
|
64
|
+
useEffect(() => {
|
|
65
|
+
const active = document.activeElement;
|
|
66
|
+
if (active !== document.body && active?.closest('[aria-current="page"]') === null)
|
|
67
|
+
return;
|
|
68
|
+
inputRef.current?.focus({ preventScroll: true });
|
|
69
|
+
}, []);
|
|
70
|
+
// Confirmation settles after a read-only submission span. Restore focus in
|
|
71
|
+
// case the browser moved it so the second Enter can confirm immediately.
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (confirmation === undefined || pending)
|
|
74
|
+
return;
|
|
75
|
+
inputRef.current?.focus({ preventScroll: true });
|
|
76
|
+
}, [confirmation, pending]);
|
|
60
77
|
const pruneRecipients = (nextDraft) => {
|
|
61
78
|
const knownMembers = new Map(members.map(status => [status.member.memberId, status.member]));
|
|
62
79
|
const next = new Set([...recipients].filter(memberId => {
|
|
@@ -66,6 +83,15 @@ export function TeamComposer({ members, recipients, draft, pending, confirmation
|
|
|
66
83
|
if (next.size !== recipients.size)
|
|
67
84
|
onRecipientsChange(next);
|
|
68
85
|
};
|
|
86
|
+
// Restored drafts may carry recipients that no longer match the text (or
|
|
87
|
+
// unknown Members); converge on mount and on every state change so the
|
|
88
|
+
// cached entry never stays stale — the same rule user edits already apply.
|
|
89
|
+
// An unloaded roster must never judge recipients unknown.
|
|
90
|
+
useEffect(() => {
|
|
91
|
+
if (members.length === 0)
|
|
92
|
+
return;
|
|
93
|
+
pruneRecipients(draft);
|
|
94
|
+
}, [draft, recipients, members]);
|
|
69
95
|
const updateMention = (nextDraft, caret) => {
|
|
70
96
|
const match = findMention(nextDraft, caret);
|
|
71
97
|
setMention(match);
|
|
@@ -133,6 +159,7 @@ export function TeamComposer({ members, recipients, draft, pending, confirmation
|
|
|
133
159
|
onSubmit();
|
|
134
160
|
};
|
|
135
161
|
return _jsxs("form", { ref: rootRef, className: css.root, onSubmit: event => {
|
|
162
|
+
console.log('DBG composer submit pending=%s draft=%j', pending, draft);
|
|
136
163
|
event.preventDefault();
|
|
137
164
|
if (!pending && draft.trim() !== '')
|
|
138
165
|
onSubmit();
|
|
@@ -140,5 +167,14 @@ export function TeamComposer({ members, recipients, draft, pending, confirmation
|
|
|
140
167
|
const optionId = `${listId}-${status.member.memberId}`;
|
|
141
168
|
const selected = index === highlight;
|
|
142
169
|
return _jsxs("button", { id: optionId, type: "button", role: "option", "aria-selected": selected, className: css.mentionOption, onMouseDown: event => { event.preventDefault(); }, onClick: () => { selectMention(status); }, children: [_jsx(TeamPresenceDot, { status: status, t: t }), _jsxs("span", { className: css.mentionName, children: ["@", status.member.handle] }), _jsx("span", { className: css.mentionDescription, children: status.member.description })] }, status.member.memberId);
|
|
143
|
-
}) }), _jsx("textarea", { ref: inputRef, "aria-label": t('messageDraft'), "aria-autocomplete": "list", "aria-controls": menuOpen ? listId : undefined, "aria-activedescendant": menuOpen && activeCandidate !== undefined ? `${listId}-${activeCandidate.member.memberId}` : undefined, "aria-expanded": menuOpen, value: draft,
|
|
170
|
+
}) }), _jsx("textarea", { ref: inputRef, "aria-label": t('messageDraft'), "aria-autocomplete": "list", "aria-controls": menuOpen ? listId : undefined, "aria-activedescendant": menuOpen && activeCandidate !== undefined ? `${listId}-${activeCandidate.member.memberId}` : undefined, "aria-expanded": menuOpen, value: draft, readOnly: pending, placeholder: t('messagePlaceholder'), rows: 1, onChange: onChange, onKeyDown: onKeyDown, onSelect: event => { updateMention(event.currentTarget.value, event.currentTarget.selectionStart ?? event.currentTarget.value.length); }, onCompositionStart: () => { composingRef.current = true; }, onCompositionEnd: () => { setTimeout(() => { composingRef.current = false; }, 10); } })] }), recipients.size > 0 && _jsx("p", { className: css.notifyRow, "data-team-notify": true, children: t('composerNotify', { ids: [...recipients].sort().map(memberId => `@${members.find(candidate => candidate.member.memberId === memberId)?.member.handle ?? memberId}`).join(', ') }) }), onFilesChange !== undefined && pendingFiles !== undefined && pendingFiles.length > 0 && (_jsx("ul", { className: css.fileChips, "aria-label": t('attachFiles'), children: pendingFiles.map((file, index) => _jsxs("li", { className: css.fileChip, children: [_jsx("span", { className: css.fileChipName, title: file.name, children: file.name }), _jsx("button", { type: "button", className: css.fileChipRemove, "aria-label": t('removeFile', { name: file.name }), disabled: pending, onClick: () => { onFilesChange(pendingFiles.filter((_, candidate) => candidate !== index)); }, children: "\u00D7" })] }, `${file.name}-${index}`)) })), _jsxs("div", { className: css.toolbar, children: [onFilesChange !== undefined && (_jsxs(_Fragment, { children: [_jsx("input", { ref: fileInputRef, type: "file", multiple: true, className: css.fileInput, "aria-hidden": "true", tabIndex: -1, onChange: event => {
|
|
171
|
+
const chosen = [...event.target.files ?? []];
|
|
172
|
+
if (chosen.length > 0 && pendingFiles !== undefined)
|
|
173
|
+
onFilesChange([...pendingFiles, ...chosen]);
|
|
174
|
+
event.target.value = '';
|
|
175
|
+
} }), _jsx("button", { type: "button", className: css.attachButton, "aria-label": t('attachFiles'), title: t('attachFiles'), disabled: pending, onClick: () => { fileInputRef.current?.click(); }, children: _jsx(IconPlusOutline16, { size: 14 }) })] })), _jsx("button", { type: "submit", className: css.sendButton, "aria-label": pending ? t('sendingMessage') : t('sendMessage'), disabled: pending || draft.trim() === '', onMouseDown: event => {
|
|
176
|
+
// Keep the composer focused when the send control is clicked.
|
|
177
|
+
event.preventDefault();
|
|
178
|
+
inputRef.current?.focus({ preventScroll: true });
|
|
179
|
+
}, children: _jsx(IconSendOutline16, { size: 16 }) })] })] }), error !== undefined && _jsx("p", { className: css.error, role: "alert", children: error })] });
|
|
144
180
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { TeamConversationProps } from './slots.ts';
|
|
2
|
-
export declare function TeamConversation({ t, useWorkspaces, navigation, loadChannels, readThread, loadThreadHistory, subscribeChanges, loadMembers, sendMessage, joinChannel, removeChannelMember, reply, changeTask, selectThread, backToWorkspace, backToChannels }: TeamConversationProps): import("react").JSX.Element;
|
|
2
|
+
export declare function TeamConversation({ t, useWorkspaces, navigation, drafts, putAttachment, getAttachment, loadChannels, readThread, loadThreadHistory, subscribeChanges, loadMembers, sendMessage, joinChannel, removeChannelMember, reply, changeTask, selectThread, backToWorkspace, backToChannels }: TeamConversationProps): import("react").JSX.Element;
|
|
3
3
|
//# sourceMappingURL=TeamConversation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TeamConversation.d.ts","sourceRoot":"","sources":["../../../src/client/TeamConversation.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAKvD,wBAAgB,gBAAgB,CAAC,EAAE,CAAC,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,mBAAmB,EAAE,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,EAAE,qBAAqB,+
|
|
1
|
+
{"version":3,"file":"TeamConversation.d.ts","sourceRoot":"","sources":["../../../src/client/TeamConversation.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAKvD,wBAAgB,gBAAgB,CAAC,EAAE,CAAC,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,UAAU,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,mBAAmB,EAAE,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,eAAe,EAAE,cAAc,EAAE,EAAE,qBAAqB,+BAoB1T"}
|
|
@@ -3,15 +3,15 @@ import { useSyncExternalStore } from 'react';
|
|
|
3
3
|
import { TeamChannelPage } from "./TeamChannelPage.js";
|
|
4
4
|
import { TeamThreadPage } from "./TeamThreadPage.js";
|
|
5
5
|
import css from './conversation.module.css';
|
|
6
|
-
export function TeamConversation({ t, useWorkspaces, navigation, loadChannels, readThread, loadThreadHistory, subscribeChanges, loadMembers, sendMessage, joinChannel, removeChannelMember, reply, changeTask, selectThread, backToWorkspace, backToChannels }) {
|
|
6
|
+
export function TeamConversation({ t, useWorkspaces, navigation, drafts, putAttachment, getAttachment, loadChannels, readThread, loadThreadHistory, subscribeChanges, loadMembers, sendMessage, joinChannel, removeChannelMember, reply, changeTask, selectThread, backToWorkspace, backToChannels }) {
|
|
7
7
|
const navigationState = useSyncExternalStore(navigation.subscribe, navigation.getSnapshot, navigation.getSnapshot);
|
|
8
8
|
const workspaces = useWorkspaces(state => state.items);
|
|
9
9
|
const current = workspaces.find(workspace => workspace.workspaceId === navigationState.workspaceId);
|
|
10
10
|
if (current !== undefined && navigationState.taskRef !== undefined && navigationState.threadRef !== undefined) {
|
|
11
|
-
return _jsx(TeamThreadPage, { workspaceId: current.workspaceId, taskRef: navigationState.taskRef, threadRef: navigationState.threadRef, backToWorkspace: backToWorkspace, ...(navigationState.channelRef === undefined ? {} : { channelRef: navigationState.channelRef }), ...(navigationState.taskNumber === undefined ? {} : { taskNumber: navigationState.taskNumber }), readThread: readThread, loadChannels: loadChannels, loadThreadHistory: loadThreadHistory, subscribeChanges: subscribeChanges, loadMembers: loadMembers, reply: reply, changeTask: changeTask, t: t }, navigationState.threadRef);
|
|
11
|
+
return _jsx(TeamThreadPage, { workspaceId: current.workspaceId, taskRef: navigationState.taskRef, threadRef: navigationState.threadRef, backToWorkspace: backToWorkspace, ...(navigationState.channelRef === undefined ? {} : { channelRef: navigationState.channelRef }), ...(navigationState.taskNumber === undefined ? {} : { taskNumber: navigationState.taskNumber }), drafts: drafts, getAttachment: getAttachment, readThread: readThread, loadChannels: loadChannels, loadThreadHistory: loadThreadHistory, subscribeChanges: subscribeChanges, loadMembers: loadMembers, reply: reply, changeTask: changeTask, t: t }, navigationState.threadRef);
|
|
12
12
|
}
|
|
13
13
|
if (current !== undefined && navigationState.channelRef !== undefined) {
|
|
14
|
-
return _jsx(TeamChannelPage, { workspaceId: current.workspaceId, channelRef: navigationState.channelRef, loadChannels: loadChannels, subscribeChanges: subscribeChanges, loadMembers: loadMembers, sendMessage: sendMessage, joinChannel: joinChannel, removeChannelMember: removeChannelMember, selectThread: selectThread, backToChannels: backToChannels, t: t }, navigationState.channelRef);
|
|
14
|
+
return _jsx(TeamChannelPage, { workspaceId: current.workspaceId, channelRef: navigationState.channelRef, drafts: drafts, putAttachment: putAttachment, getAttachment: getAttachment, loadChannels: loadChannels, subscribeChanges: subscribeChanges, loadMembers: loadMembers, sendMessage: sendMessage, joinChannel: joinChannel, removeChannelMember: removeChannelMember, selectThread: selectThread, backToChannels: backToChannels, t: t }, navigationState.channelRef);
|
|
15
15
|
}
|
|
16
16
|
const welcome = current === undefined
|
|
17
17
|
? { eyebrow: t('teamMode'), title: t('team'), body: t('empty') }
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
|
-
import type { AgentTeamMemberId } from '@wowyuarm/dsh-agent-team/types';
|
|
2
|
+
import type { AgentTeamMemberId, AgentTeamMessageAttachment } from '@wowyuarm/dsh-agent-team/types';
|
|
3
|
+
import type { TeamConversationProps } from './slots.ts';
|
|
3
4
|
export interface TeamMessageProps {
|
|
4
5
|
readonly senderName: string;
|
|
5
6
|
readonly memberId: AgentTeamMemberId;
|
|
@@ -12,8 +13,12 @@ export interface TeamMessageProps {
|
|
|
12
13
|
readonly senderTitle?: string;
|
|
13
14
|
/** Continuation of one same-sender run: suppress repeated identity chrome. */
|
|
14
15
|
readonly grouped?: boolean;
|
|
16
|
+
readonly attachments?: readonly AgentTeamMessageAttachment[] | undefined;
|
|
17
|
+
/** Cache readback for thumbnails; absent on surfaces without the remotes. */
|
|
18
|
+
readonly loadAttachment?: TeamConversationProps['getAttachment'] | undefined;
|
|
19
|
+
readonly t?: TeamConversationProps['t'] | undefined;
|
|
15
20
|
readonly children?: ReactNode;
|
|
16
21
|
}
|
|
17
22
|
/** One chat message row with identity chrome and sender-appropriate rendering. */
|
|
18
|
-
export declare function TeamMessage({ senderName, memberId, human, body, occurredAt, mentionNames, senderTitle, grouped, children }: TeamMessageProps): import("react").JSX.Element;
|
|
23
|
+
export declare function TeamMessage({ senderName, memberId, human, body, occurredAt, mentionNames, senderTitle, grouped, attachments, loadAttachment, t, children }: TeamMessageProps): import("react").JSX.Element;
|
|
19
24
|
//# sourceMappingURL=TeamMessage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TeamMessage.d.ts","sourceRoot":"","sources":["../../../src/client/TeamMessage.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"TeamMessage.d.ts","sourceRoot":"","sources":["../../../src/client/TeamMessage.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAqD,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAEzF,OAAO,KAAK,EAAgC,iBAAiB,EAAE,0BAA0B,EAAE,MAAM,gCAAgC,CAAA;AACjI,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAKvD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAA;IACpC,wEAAwE;IACxE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,uFAAuF;IACvF,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACzC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,0BAA0B,EAAE,GAAG,SAAS,CAAA;IACxE,6EAA6E;IAC7E,QAAQ,CAAC,cAAc,CAAC,EAAE,qBAAqB,CAAC,eAAe,CAAC,GAAG,SAAS,CAAA;IAC5E,QAAQ,CAAC,CAAC,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC,GAAG,SAAS,CAAA;IACnD,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;CAC9B;AAED,kFAAkF;AAClF,wBAAgB,WAAW,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC,EAAE,QAAQ,EAAE,EAAE,gBAAgB,+BA8C5K"}
|
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Fragment } from 'react';
|
|
3
|
-
import { MarkdownText, MessageText } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
4
|
-
import {
|
|
2
|
+
import { Fragment, useEffect, useState } from 'react';
|
|
3
|
+
import { MarkdownText, MessageText, Modal } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
4
|
+
import { cachedAttachmentDataUrl, formatByteSize, loadAttachmentDataUrl } from "./attachment-preview.js";
|
|
5
|
+
import { formatMessageTime, isPlainTextBody, memberHue, splitMentionNames, stripAttachmentLines } from "./team-formatters.js";
|
|
5
6
|
import css from './conversation.module.css';
|
|
6
7
|
/** One chat message row with identity chrome and sender-appropriate rendering. */
|
|
7
|
-
export function TeamMessage({ senderName, memberId, human, body, occurredAt, mentionNames, senderTitle, grouped, children }) {
|
|
8
|
+
export function TeamMessage({ senderName, memberId, human, body, occurredAt, mentionNames, senderTitle, grouped, attachments, loadAttachment, t, children }) {
|
|
8
9
|
const avatarStyle = human ? undefined : { '--team-avatar-hue': memberHue(memberId) };
|
|
9
10
|
// Literal bodies carry the chips inline — Human input always, and plain-
|
|
10
11
|
// prose Agent bodies where literal rendering loses nothing. Rich Markdown
|
|
11
12
|
// Agent bodies fall back to the trailing chip row: the Markdown primitive
|
|
12
13
|
// renders block-level documents, so chips cannot interleave mid-paragraph.
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
// The stored body carries machine-facing `[attachment] <path>` prompt lines;
|
|
15
|
+
// humans see the attachment strip rendered from the message metadata instead.
|
|
16
|
+
const displayBody = stripAttachmentLines(body) === '' ? body : stripAttachmentLines(body);
|
|
17
|
+
const inline = (human || isPlainTextBody(displayBody)) && mentionNames !== undefined && mentionNames.length > 0
|
|
18
|
+
? splitMentionNames(displayBody, mentionNames)
|
|
15
19
|
: undefined;
|
|
16
20
|
const fallbackNames = inline === undefined ? (mentionNames ?? []) : inline.unmatched;
|
|
17
21
|
return (_jsxs("article", { className: css.messageRow, "data-human": human || undefined, "data-grouped": grouped || undefined, children: [_jsx("div", { className: css.messageIdentity, style: avatarStyle, "aria-hidden": "true", children: senderName.replace('@', '').slice(0, 1).toUpperCase() }), _jsxs("div", { className: css.messageBody, children: [!grouped && (_jsxs("div", { className: css.nameRow, children: [_jsx("strong", { ...(senderTitle === undefined ? {} : { title: senderTitle }), children: senderName }), occurredAt !== undefined && _jsx("span", { className: css.messageTime, children: formatMessageTime(occurredAt) })] })), inline !== undefined
|
|
@@ -19,6 +23,28 @@ export function TeamMessage({ senderName, memberId, human, body, occurredAt, men
|
|
|
19
23
|
? _jsx("span", { className: css.mention, children: segment.text }, index)
|
|
20
24
|
: _jsx(Fragment, { children: segment.text }, index)) })
|
|
21
25
|
: human
|
|
22
|
-
? _jsx("div", { className: css.messageText, children: _jsx(MessageText, { text:
|
|
23
|
-
: _jsx("div", { className: css.messageMarkdown, children: _jsx(MarkdownText, { text:
|
|
26
|
+
? _jsx("div", { className: css.messageText, children: _jsx(MessageText, { text: displayBody }) })
|
|
27
|
+
: _jsx("div", { className: css.messageMarkdown, children: _jsx(MarkdownText, { text: displayBody }) }), fallbackNames.length > 0 && (_jsx("div", { className: css.mentionsRow, children: fallbackNames.map(name => _jsxs("span", { className: css.mention, children: ["@", name] }, name)) })), attachments !== undefined && attachments.length > 0 && _jsx(TeamAttachmentStrip, { attachments: attachments, ...(loadAttachment === undefined ? {} : { loadAttachment }), ...(t === undefined ? {} : { t }) }), children] })] }));
|
|
28
|
+
}
|
|
29
|
+
/** One message's attachment strip: image thumbnails with a large view, or name chips when bytes are gone. */
|
|
30
|
+
function TeamAttachmentStrip({ attachments, loadAttachment, t }) {
|
|
31
|
+
const [zoomed, setZoomed] = useState();
|
|
32
|
+
return _jsxs("div", { className: css.attachmentStrip, children: [attachments.map(attachment => _jsx(TeamAttachment, { attachment: attachment, ...(loadAttachment === undefined ? {} : { loadAttachment }), ...(t === undefined ? {} : { t }), onZoom: setZoomed }, attachment.attachmentId)), zoomed !== undefined && _jsx(Modal, { open: true, ...(css.attachmentModal === undefined ? {} : { className: css.attachmentModal }), title: zoomed.name, onClose: () => { setZoomed(undefined); }, children: _jsx("img", { className: css.attachmentZoom, src: cachedAttachmentDataUrl(zoomed.attachmentId) ?? undefined, alt: zoomed.name }) })] });
|
|
33
|
+
}
|
|
34
|
+
function TeamAttachment({ attachment, loadAttachment, t, onZoom }) {
|
|
35
|
+
const wantsPreview = loadAttachment !== undefined && attachment.mediaType.startsWith('image/');
|
|
36
|
+
const [dataUrl, setDataUrl] = useState(wantsPreview ? cachedAttachmentDataUrl(attachment.attachmentId) : null);
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (!wantsPreview || dataUrl !== undefined)
|
|
39
|
+
return;
|
|
40
|
+
let mounted = true;
|
|
41
|
+
void loadAttachmentDataUrl(loadAttachment, attachment).then(url => { if (mounted)
|
|
42
|
+
setDataUrl(url); });
|
|
43
|
+
return () => { mounted = false; };
|
|
44
|
+
}, [wantsPreview, dataUrl, loadAttachment, attachment]);
|
|
45
|
+
const expired = t?.('attachmentExpired') ?? 'File no longer cached';
|
|
46
|
+
if (wantsPreview && dataUrl !== null) {
|
|
47
|
+
return _jsx("button", { type: "button", className: css.attachmentThumb, "aria-label": t?.('viewImage', { name: attachment.name }) ?? attachment.name, title: attachment.name, onClick: () => { onZoom(attachment); }, children: _jsx("img", { src: dataUrl, alt: attachment.name }) });
|
|
48
|
+
}
|
|
49
|
+
return _jsxs("span", { className: css.attachmentChip, title: wantsPreview ? expired : `${attachment.name} · ${formatByteSize(attachment.byteSize)}`, children: [_jsx("span", { className: css.attachmentChipName, children: attachment.name }), _jsx("span", { className: css.attachmentChipSize, children: wantsPreview ? expired : formatByteSize(attachment.byteSize) })] });
|
|
24
50
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Explicit boundary between two same-sender Messages of one run separated by
|
|
3
|
+
* a real waiting gap: the hairline restores the block boundary that grouping
|
|
4
|
+
* removed, and the label below it restores the instant that the suppressed
|
|
5
|
+
* identity chrome would have shown.
|
|
6
|
+
*/
|
|
7
|
+
export declare function TeamRunDivider({ occurredAt }: {
|
|
8
|
+
readonly occurredAt: string;
|
|
9
|
+
}): import("react").JSX.Element;
|
|
10
|
+
//# sourceMappingURL=TeamRunDivider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TeamRunDivider.d.ts","sourceRoot":"","sources":["../../../src/client/TeamRunDivider.tsx"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,EAAE,UAAU,EAAE,EAAE;IAAE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;CAAE,+BAM7E"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { formatMessageTime } from "./team-formatters.js";
|
|
3
|
+
import css from './conversation.module.css';
|
|
4
|
+
/**
|
|
5
|
+
* Explicit boundary between two same-sender Messages of one run separated by
|
|
6
|
+
* a real waiting gap: the hairline restores the block boundary that grouping
|
|
7
|
+
* removed, and the label below it restores the instant that the suppressed
|
|
8
|
+
* identity chrome would have shown.
|
|
9
|
+
*/
|
|
10
|
+
export function TeamRunDivider({ occurredAt }) {
|
|
11
|
+
return (_jsx("div", { className: css.runDivider, role: "separator", children: _jsx("time", { dateTime: occurredAt, children: formatMessageTime(occurredAt) }) }));
|
|
12
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AgentTeamChannelRef, AgentTeamTaskRef, AgentTeamThreadRef } from '@wowyuarm/dsh-agent-team/types';
|
|
2
2
|
import type { WorkspaceId } from '@deepseek-ai/dsh-client-runtime/client';
|
|
3
3
|
import type { TeamConversationProps } from './slots.ts';
|
|
4
|
+
import type { TeamDraftStore } from './drafts.ts';
|
|
4
5
|
interface TeamThreadPageProps {
|
|
5
6
|
readonly workspaceId: WorkspaceId;
|
|
6
7
|
readonly channelRef?: AgentTeamChannelRef;
|
|
@@ -13,6 +14,8 @@ interface TeamThreadPageProps {
|
|
|
13
14
|
readonly loadThreadHistory: TeamConversationProps['loadThreadHistory'];
|
|
14
15
|
readonly subscribeChanges: TeamConversationProps['subscribeChanges'];
|
|
15
16
|
readonly loadMembers: TeamConversationProps['loadMembers'];
|
|
17
|
+
readonly drafts: TeamDraftStore;
|
|
18
|
+
readonly getAttachment: TeamConversationProps['getAttachment'];
|
|
16
19
|
readonly reply: TeamConversationProps['reply'];
|
|
17
20
|
readonly changeTask: TeamConversationProps['changeTask'];
|
|
18
21
|
readonly t: TeamConversationProps['t'];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TeamThreadPage.d.ts","sourceRoot":"","sources":["../../../src/client/TeamThreadPage.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,mBAAmB,EAInB,gBAAgB,EAGhB,kBAAkB,EAEnB,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AAEzE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"TeamThreadPage.d.ts","sourceRoot":"","sources":["../../../src/client/TeamThreadPage.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,mBAAmB,EAInB,gBAAgB,EAGhB,kBAAkB,EAEnB,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AAEzE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AACvD,OAAO,KAAK,EAAgB,cAAc,EAAE,MAAM,aAAa,CAAA;AAW/D,UAAU,mBAAmB;IAC3B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,QAAQ,CAAC,UAAU,CAAC,EAAE,mBAAmB,CAAA;IACzC,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAA;IAClC,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,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC,CAAA;CACvC;AA8BD,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,+BAgfxD"}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Fragment, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
|
+
import { Fragment, useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';
|
|
3
3
|
import { Button, DisclosureRow, IconChevronLeftOutline14, IconChecklistOutline14, Pill } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
4
4
|
import { TeamComposer } from "./TeamComposer.js";
|
|
5
5
|
import { TeamPresenceDot } from "./TeamPresenceDot.js";
|
|
6
6
|
import { TeamMessage } from "./TeamMessage.js";
|
|
7
|
+
import { TeamRunDivider } from "./TeamRunDivider.js";
|
|
7
8
|
import { formatActivity, formatClaimState, formatTaskStatus, formatTaskTitle, mentionNamesOf } from "./team-formatters.js";
|
|
8
|
-
import { daySeparatorLabel, timelineDayKey } from "./team-separators.js";
|
|
9
|
+
import { daySeparatorLabel, isRunGap, timelineDayKey } from "./team-separators.js";
|
|
9
10
|
import { useTimelineScroll } from "./timeline-scroll.js";
|
|
10
11
|
import css from './conversation.module.css';
|
|
11
12
|
import threadCss from './thread.module.css';
|
|
@@ -29,7 +30,7 @@ function readMeta(facts) {
|
|
|
29
30
|
return new Map(facts.map(fact => [factKey(fact.fact), fact]));
|
|
30
31
|
}
|
|
31
32
|
export function TeamThreadPage(props) {
|
|
32
|
-
const { workspaceId, channelRef, taskRef, threadRef, taskNumber, backToWorkspace, loadChannels, readThread, loadThreadHistory, subscribeChanges, loadMembers, reply, changeTask, t, } = props;
|
|
33
|
+
const { workspaceId, channelRef, taskRef, threadRef, taskNumber, backToWorkspace, loadChannels, readThread, loadThreadHistory, subscribeChanges, loadMembers, drafts, getAttachment, reply, changeTask, t, } = props;
|
|
33
34
|
const [projection, setProjection] = useState();
|
|
34
35
|
const [channelView, setChannelView] = useState();
|
|
35
36
|
const [members, setMembers] = useState([]);
|
|
@@ -40,8 +41,10 @@ export function TeamThreadPage(props) {
|
|
|
40
41
|
const [historyHasMore, setHistoryHasMore] = useState(false);
|
|
41
42
|
const [remainingUnreadCount, setRemainingUnreadCount] = useState(0);
|
|
42
43
|
const [newFactsCount, setNewFactsCount] = useState(0);
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
// The reply draft lives in the keyed draft cache: view switches unmount
|
|
45
|
+
// this page, and a refresh must not cost the half-written message either.
|
|
46
|
+
const draftKey = `thread:${threadRef}`;
|
|
47
|
+
const { draft, recipients } = useSyncExternalStore(drafts.subscribe, () => drafts.getSnapshot(draftKey));
|
|
45
48
|
const [claimsOpen, setClaimsOpen] = useState(false);
|
|
46
49
|
const [replyRequestId, setReplyRequestId] = useState();
|
|
47
50
|
const [confirmation, setConfirmation] = useState();
|
|
@@ -295,7 +298,7 @@ export function TeamThreadPage(props) {
|
|
|
295
298
|
if (fact.kind === 'message') {
|
|
296
299
|
const sender = memberName(fact.message.sender);
|
|
297
300
|
const senderStatus = members.find(candidate => candidate.member.memberId === fact.message.sender);
|
|
298
|
-
return _jsx(TeamMessage, { senderName: sender, memberId: fact.message.sender, human: fact.message.sender === channelView?.humanMemberId, body: fact.message.body, occurredAt: fact.message.occurredAt, mentionNames: mentionNamesOf(fact.mentions, mentionHandlesMap), grouped: grouped, ...(senderStatus === undefined ? {} : { senderTitle: senderStatus.member.description }) }, factKey(fact));
|
|
301
|
+
return _jsx(TeamMessage, { senderName: sender, memberId: fact.message.sender, human: fact.message.sender === channelView?.humanMemberId, body: fact.message.body, attachments: fact.message.attachments, loadAttachment: getAttachment, t: t, occurredAt: fact.message.occurredAt, mentionNames: mentionNamesOf(fact.mentions, mentionHandlesMap), grouped: grouped, ...(senderStatus === undefined ? {} : { senderTitle: senderStatus.member.description }) }, factKey(fact));
|
|
299
302
|
}
|
|
300
303
|
return _jsxs("p", { className: threadCss.activityRow, children: [_jsx("span", { className: threadCss.activityMark, "aria-hidden": "true" }), _jsx("span", { className: threadCss.activityText, children: formatActivity(fact.activity, { t, actorName: memberName, claims: taskClaims }) })] }, factKey(fact));
|
|
301
304
|
};
|
|
@@ -305,8 +308,13 @@ export function TeamThreadPage(props) {
|
|
|
305
308
|
let run = [];
|
|
306
309
|
let lastDay;
|
|
307
310
|
const flushRun = () => {
|
|
308
|
-
if (run.length > 0)
|
|
309
|
-
nodes.push(_jsx("div", { className: css.messageRun, children: run.map((entry, entryIndex) =>
|
|
311
|
+
if (run.length > 0) {
|
|
312
|
+
nodes.push(_jsx("div", { className: css.messageRun, children: run.map((entry, entryIndex) => {
|
|
313
|
+
const previous = entryIndex > 0 ? run[entryIndex - 1] : undefined;
|
|
314
|
+
const turnGap = entry.kind === 'message' && isRunGap(previous?.kind === 'message' ? previous.message.occurredAt : undefined, entry.message.occurredAt);
|
|
315
|
+
return _jsxs(Fragment, { children: [turnGap && _jsx(TeamRunDivider, { occurredAt: entry.message.occurredAt }), renderFact(entry, entryIndex > 0)] }, factKey(entry));
|
|
316
|
+
}) }, `run-${factKey(run[0])}`));
|
|
317
|
+
}
|
|
310
318
|
run = [];
|
|
311
319
|
};
|
|
312
320
|
facts.forEach((fact, index) => {
|
|
@@ -405,8 +413,7 @@ export function TeamThreadPage(props) {
|
|
|
405
413
|
return merged;
|
|
406
414
|
});
|
|
407
415
|
setProjection(current => current === undefined ? current : { ...current, task: committed.task, thread: committed.thread });
|
|
408
|
-
|
|
409
|
-
setRecipients(new Set());
|
|
416
|
+
drafts.clear(draftKey);
|
|
410
417
|
setReplyRequestId(undefined);
|
|
411
418
|
setConfirmation(undefined);
|
|
412
419
|
setStatusMessage(undefined);
|
|
@@ -460,5 +467,5 @@ export function TeamThreadPage(props) {
|
|
|
460
467
|
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);
|
|
461
468
|
})] }) }) })] }), _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) }), newFactsCount > 0 && _jsxs("div", { className: threadCss.newUpdates, role: "status", children: [_jsx("span", { children: t('readNewUpdates', { count: newFactsCount }) }), _jsx(Button, { size: "sm", variant: "outline", disabled: loading, onClick: () => { timeline.jumpToLatest(); void readCurrent(true); }, children: t('markRead') })] }), remainingUnreadCount > 0 && _jsxs("div", { className: css.timelineAction, role: "status", children: [_jsx("span", { children: t('remainingUnread', { count: remainingUnreadCount }) }), _jsx(Button, { size: "sm", onClick: () => { timeline.jumpToBoundary(); void readCurrent(true); }, disabled: loading, children: t('continueReading') })] })] }) }), projection !== undefined && task !== undefined && thread !== undefined ? (task.resolution === 'closed'
|
|
462
469
|
? _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') })] })] })
|
|
463
|
-
: _jsx(TeamComposer, { members: channelMembers, recipients: recipients, draft: draft, pending: pending, ...(statusMessage === undefined ? {} : { confirmation: statusMessage }), ...(error === undefined ? {} : { error }), onDraftChange: next => {
|
|
470
|
+
: _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(); }, t: t }, draftKey)) : _jsx("div", {})] });
|
|
464
471
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { TeamSidebarProps } from './slots.ts';
|
|
2
|
-
export declare function TeamWorkspaceBrowser({ wide, expandSidebar, navigation, selectWorkspace, selectChannel, t, useWorkspaces, loadMembers, subscribeChanges, addMember, loadChannels, createChannel, updateChannel, updateMember, joinChannel, removeChannelMember, loadModels, openMemberSession }: TeamSidebarProps): import("react").JSX.Element;
|
|
2
|
+
export declare function TeamWorkspaceBrowser({ wide, expandSidebar, navigation, selectWorkspace, selectChannel, t, useWorkspaces, loadMembers, subscribeChanges, addMember, loadChannels, createChannel, updateChannel, updateMember, recoverMember, restartMember, joinChannel, removeChannelMember, loadModels, openMemberSession }: TeamSidebarProps): import("react").JSX.Element;
|
|
3
3
|
//# sourceMappingURL=TeamWorkspaceBrowser.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TeamWorkspaceBrowser.d.ts","sourceRoot":"","sources":["../../../src/client/TeamWorkspaceBrowser.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAQlD,wBAAgB,oBAAoB,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE,aAAa,EAAE,CAAC,EAAE,aAAa,EAAE,WAAW,EAAE,gBAAgB,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,WAAW,EAAE,mBAAmB,EAAE,UAAU,EAAE,iBAAiB,EAAE,EAAE,gBAAgB,+
|
|
1
|
+
{"version":3,"file":"TeamWorkspaceBrowser.d.ts","sourceRoot":"","sources":["../../../src/client/TeamWorkspaceBrowser.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAQlD,wBAAgB,oBAAoB,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE,aAAa,EAAE,CAAC,EAAE,aAAa,EAAE,WAAW,EAAE,gBAAgB,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,WAAW,EAAE,mBAAmB,EAAE,UAAU,EAAE,iBAAiB,EAAE,EAAE,gBAAgB,+BA4D9U"}
|
|
@@ -5,16 +5,17 @@ import { TeamWorkspaceRow } from "./TeamWorkspaceRow.js";
|
|
|
5
5
|
import { TeamAgentsPanel } from "./TeamAgentsPanel.js";
|
|
6
6
|
import { TeamChannelsPanel } from "./TeamChannelsPanel.js";
|
|
7
7
|
import css from './sidebar.module.css';
|
|
8
|
-
export function TeamWorkspaceBrowser({ wide, expandSidebar, navigation, selectWorkspace, selectChannel, t, useWorkspaces, loadMembers, subscribeChanges, addMember, loadChannels, createChannel, updateChannel, updateMember, joinChannel, removeChannelMember, loadModels, openMemberSession }) {
|
|
8
|
+
export function TeamWorkspaceBrowser({ wide, expandSidebar, navigation, selectWorkspace, selectChannel, t, useWorkspaces, loadMembers, subscribeChanges, addMember, loadChannels, createChannel, updateChannel, updateMember, recoverMember, restartMember, joinChannel, removeChannelMember, loadModels, openMemberSession }) {
|
|
9
9
|
const navigationState = useSyncExternalStore(navigation.subscribe, navigation.getSnapshot, navigation.getSnapshot);
|
|
10
10
|
const workspaces = useWorkspaces(state => state.items);
|
|
11
11
|
const selected = navigationState.workspaceId;
|
|
12
12
|
const selectedExists = selected !== undefined && workspaces.some(workspace => workspace.workspaceId === selected);
|
|
13
13
|
const selectedId = selectedExists ? selected : workspaces[0]?.workspaceId;
|
|
14
14
|
// Exactly one sidebar row carries aria-current='page': the open Channel while
|
|
15
|
-
// one is set,
|
|
16
|
-
//
|
|
17
|
-
|
|
15
|
+
// one is set, the selected Agent card while a Member Session view is open,
|
|
16
|
+
// otherwise the browsed Workspace's overview. The selected row keeps its
|
|
17
|
+
// quiet folder tint (data-selected) in every case.
|
|
18
|
+
const overviewIsCurrent = navigationState.channelRef === undefined && navigationState.memberSessionId === undefined;
|
|
18
19
|
const [creatingAgents, setCreatingAgents] = useState([]);
|
|
19
20
|
// Rail icons request expansion and name the section to reveal once wide.
|
|
20
21
|
const [pendingSection, setPendingSection] = useState();
|
|
@@ -35,5 +36,5 @@ export function TeamWorkspaceBrowser({ wide, expandSidebar, navigation, selectWo
|
|
|
35
36
|
if (!wide) {
|
|
36
37
|
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 }) }) })] });
|
|
37
38
|
}
|
|
38
|
-
return _jsxs("section", { className: css.workspaceBrowser, "aria-label": t('workspaces'), children: [_jsx("div", { className: css.workspaceHeader, children: t('workspaces') }), _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.channelRef === undefined ? {} : { selectedChannelRef: navigationState.channelRef }), selectChannel: selectChannel, t: t }, selectedId) }), _jsx("div", { ref: agentsRef, children: _jsx(TeamAgentsPanel, { workspaceId: selectedId, loadMembers: loadMembers, subscribeChanges: subscribeChanges, loadChannels: loadChannels, addMember: addMember, updateMember: updateMember, joinChannel: joinChannel, removeChannelMember: removeChannelMember, loadModels: loadModels, 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) })] })] });
|
|
39
|
+
return _jsxs("section", { className: css.workspaceBrowser, "aria-label": t('workspaces'), children: [_jsx("div", { className: css.workspaceHeader, children: t('workspaces') }), _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, loadChannels: loadChannels, addMember: addMember, updateMember: updateMember, recoverMember: recoverMember, restartMember: restartMember, joinChannel: joinChannel, removeChannelMember: removeChannelMember, 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) })] })] });
|
|
39
40
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { AgentTeamAttachmentId, AgentTeamGetAttachmentRequest, AgentTeamGetAttachmentResult } from '@wowyuarm/dsh-agent-team/types';
|
|
2
|
+
/** Base64 one file payload in chunks so large uploads stay off the call-stack limit. */
|
|
3
|
+
export declare function bytesToBase64(bytes: Uint8Array): string;
|
|
4
|
+
/** Mirrors the slot's Remote result union without importing the slots module. */
|
|
5
|
+
type GetAttachment = (request: AgentTeamGetAttachmentRequest) => Promise<{
|
|
6
|
+
ok: true;
|
|
7
|
+
value: AgentTeamGetAttachmentResult;
|
|
8
|
+
} | {
|
|
9
|
+
ok: false;
|
|
10
|
+
error: {
|
|
11
|
+
message: string;
|
|
12
|
+
};
|
|
13
|
+
}>;
|
|
14
|
+
export declare function cachedAttachmentDataUrl(attachmentId: AgentTeamAttachmentId): string | null | undefined;
|
|
15
|
+
export declare function loadAttachmentDataUrl(getAttachment: GetAttachment, attachment: {
|
|
16
|
+
attachmentId: AgentTeamAttachmentId;
|
|
17
|
+
mediaType: string;
|
|
18
|
+
}): Promise<string | null>;
|
|
19
|
+
/** Human-readable byte size for attachment chips. */
|
|
20
|
+
export declare function formatByteSize(byteSize: number): string;
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=attachment-preview.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachment-preview.d.ts","sourceRoot":"","sources":["../../../src/client/attachment-preview.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,6BAA6B,EAAE,4BAA4B,EAAE,MAAM,gCAAgC,CAAA;AAExI,wFAAwF;AACxF,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAOvD;AAED,iFAAiF;AACjF,KAAK,aAAa,GAAG,CAAC,OAAO,EAAE,6BAA6B,KAAK,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,4BAA4B,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC,CAAA;AASvK,wBAAgB,uBAAuB,CAAC,YAAY,EAAE,qBAAqB,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,CAEtG;AAED,wBAAsB,qBAAqB,CAAC,aAAa,EAAE,aAAa,EAAE,UAAU,EAAE;IAAE,YAAY,EAAE,qBAAqB,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAOxK;AAED,qDAAqD;AACrD,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAIvD"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Base64 one file payload in chunks so large uploads stay off the call-stack limit. */
|
|
2
|
+
export function bytesToBase64(bytes) {
|
|
3
|
+
let binary = '';
|
|
4
|
+
const chunk = 0x8000;
|
|
5
|
+
for (let start = 0; start < bytes.length; start += chunk) {
|
|
6
|
+
binary += String.fromCharCode(...bytes.subarray(start, start + chunk));
|
|
7
|
+
}
|
|
8
|
+
return btoa(binary);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Thumbnail data URLs live in one session-wide cache: message lists re-render
|
|
12
|
+
* often, and each miss costs a Host round-trip plus a base64 decode. Failed
|
|
13
|
+
* loads (bytes already GC'd) are cached as `null` so the chip fallback is stable.
|
|
14
|
+
*/
|
|
15
|
+
const dataUrlCache = new Map();
|
|
16
|
+
export function cachedAttachmentDataUrl(attachmentId) {
|
|
17
|
+
return dataUrlCache.get(attachmentId);
|
|
18
|
+
}
|
|
19
|
+
export async function loadAttachmentDataUrl(getAttachment, attachment) {
|
|
20
|
+
const cached = dataUrlCache.get(attachment.attachmentId);
|
|
21
|
+
if (cached !== undefined)
|
|
22
|
+
return cached;
|
|
23
|
+
const result = await getAttachment({ attachmentId: attachment.attachmentId });
|
|
24
|
+
const url = result.ok && attachment.mediaType.startsWith('image/') ? `data:${attachment.mediaType};base64,${result.value.bytesBase64}` : null;
|
|
25
|
+
dataUrlCache.set(attachment.attachmentId, url);
|
|
26
|
+
return url;
|
|
27
|
+
}
|
|
28
|
+
/** Human-readable byte size for attachment chips. */
|
|
29
|
+
export function formatByteSize(byteSize) {
|
|
30
|
+
if (byteSize < 1024)
|
|
31
|
+
return `${byteSize} B`;
|
|
32
|
+
if (byteSize < 1024 * 1024)
|
|
33
|
+
return `${(byteSize / 1024).toFixed(0)} KB`;
|
|
34
|
+
return `${(byteSize / (1024 * 1024)).toFixed(1)} MB`;
|
|
35
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { AgentTeamChannelRef, AgentTeamMemberId, AgentTeamThreadRef } from '@wowyuarm/dsh-agent-team/types';
|
|
2
|
+
/**
|
|
3
|
+
* Keyed draft cache for the Channel/Thread composers. Drafts are local UI
|
|
4
|
+
* convenience state — never Host-authoritative facts — so localStorage is the
|
|
5
|
+
* right home: switching views unmounts the pages, and a refresh should not
|
|
6
|
+
* cost the Human their half-written message.
|
|
7
|
+
*/
|
|
8
|
+
export type TeamDraftKey = `channel:${AgentTeamChannelRef}` | `thread:${AgentTeamThreadRef}`;
|
|
9
|
+
/** One cached composer state; `recipients` mirrors the structured mention set. */
|
|
10
|
+
export interface TeamDraftState {
|
|
11
|
+
readonly draft: string;
|
|
12
|
+
readonly recipients: ReadonlySet<AgentTeamMemberId>;
|
|
13
|
+
}
|
|
14
|
+
declare const STORAGE_KEY = "dsh.agent-team.drafts.v1";
|
|
15
|
+
/**
|
|
16
|
+
* Root-scoped draft cache, one instance per Client context (created in
|
|
17
|
+
* `applyUi` and injected like the navigation service — never a module
|
|
18
|
+
* singleton). Slot lifetimes subscribe per key.
|
|
19
|
+
*/
|
|
20
|
+
export declare class TeamDraftStore {
|
|
21
|
+
private entries;
|
|
22
|
+
private readonly snapshots;
|
|
23
|
+
private readonly listeners;
|
|
24
|
+
readonly subscribe: (listener: () => void) => (() => void);
|
|
25
|
+
/** Release listeners when the owning Client context disposes. */
|
|
26
|
+
dispose(): void;
|
|
27
|
+
/** Stable per-key snapshot identity for useSyncExternalStore. */
|
|
28
|
+
readonly getSnapshot: (key: TeamDraftKey) => TeamDraftState;
|
|
29
|
+
writeDraft(key: TeamDraftKey, draft: string): void;
|
|
30
|
+
writeRecipients(key: TeamDraftKey, recipients: Iterable<AgentTeamMemberId>): void;
|
|
31
|
+
/** Drop one key entirely — the success path after a committed send. */
|
|
32
|
+
clear(key: TeamDraftKey): void;
|
|
33
|
+
/** Test seam: forget everything, including what localStorage still holds. */
|
|
34
|
+
reset(): void;
|
|
35
|
+
/** Re-read persisted content — what a fresh page load starts from. */
|
|
36
|
+
reload(): void;
|
|
37
|
+
private write;
|
|
38
|
+
private evict;
|
|
39
|
+
}
|
|
40
|
+
export { STORAGE_KEY as TEAM_DRAFTS_STORAGE_KEY };
|
|
41
|
+
//# sourceMappingURL=drafts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drafts.d.ts","sourceRoot":"","sources":["../../../src/client/drafts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAA;AAEhH;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,WAAW,mBAAmB,EAAE,GAAG,UAAU,kBAAkB,EAAE,CAAA;AAE5F,kFAAkF;AAClF,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,UAAU,EAAE,WAAW,CAAC,iBAAiB,CAAC,CAAA;CACpD;AAQD,QAAA,MAAM,WAAW,6BAA6B,CAAA;AAuC9C;;;;GAIG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,OAAO,CAAe;IAC9B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA0C;IACpE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwB;IAElD,QAAQ,CAAC,SAAS,GAAI,UAAU,MAAM,IAAI,KAAG,CAAC,MAAM,IAAI,CAAC,CAGxD;IAED,iEAAiE;IACjE,OAAO,IAAI,IAAI;IAIf,iEAAiE;IACjE,QAAQ,CAAC,WAAW,GAAI,KAAK,YAAY,KAAG,cAAc,CAQzD;IAED,UAAU,CAAC,GAAG,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAIlD,eAAe,CAAC,GAAG,EAAE,YAAY,EAAE,UAAU,EAAE,QAAQ,CAAC,iBAAiB,CAAC,GAAG,IAAI;IAIjF,uEAAuE;IACvE,KAAK,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI;IAO9B,6EAA6E;IAC7E,KAAK,IAAI,IAAI;IAOb,sEAAsE;IACtE,MAAM,IAAI,IAAI;IAMd,OAAO,CAAC,KAAK;IAwBb,OAAO,CAAC,KAAK;CAed;AAED,OAAO,EAAE,WAAW,IAAI,uBAAuB,EAAE,CAAA"}
|