@wowyuarm/dsh-agent-team 0.1.3 → 0.1.5
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 +7 -1
- package/README.zh.md +12 -3
- package/package.json +1 -1
- package/packages/agent-team/README.md +3 -3
- package/packages/agent-team/README.zh.md +3 -3
- package/packages/agent-team/lib/auto-compaction.js +39 -1
- package/packages/agent-team/lib/index.js +123 -14
- package/packages/agent-team/lib/ledger.js +355 -110
- package/packages/agent-team/lib/spec.js +39 -11
- package/packages/agent-team/lib/typert.host.js +257 -86
- package/packages/agent-team/lib/typert.remote-client.d.ts +5 -1
- package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
- package/packages/agent-team/lib/typert.remote-client.js +196 -59
- package/packages/agent-team/lib/types/auto-compaction.d.ts +9 -0
- package/packages/agent-team/lib/types/auto-compaction.d.ts.map +1 -1
- package/packages/agent-team/lib/types/index.d.ts +24 -7
- package/packages/agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/types/ledger.d.ts +36 -2
- package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
- package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types.d.ts +109 -30
- package/packages/agent-team/lib/types/types.d.ts.map +1 -1
- package/packages/agent-team/preset/team-member/agent.cordis.yml +1 -1
- package/packages/client-agent-team/README.md +2 -1
- package/packages/client-agent-team/README.zh.md +2 -1
- package/packages/client-agent-team/lib/client.js +1082 -380
- package/packages/client-agent-team/lib/client.js.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts +2 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +73 -15
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +34 -10
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +3 -1
- package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts +6 -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 +76 -20
- 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/TeamMemberSessionComposer.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.js +16 -1
- package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts +4 -1
- package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMessage.js +62 -25
- package/packages/client-agent-team/lib/types/client/TeamRowMenu.d.ts +3 -2
- package/packages/client-agent-team/lib/types/client/TeamRowMenu.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamSidebarSection.d.ts +5 -2
- package/packages/client-agent-team/lib/types/client/TeamSidebarSection.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamSidebarSection.js +2 -4
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts +2 -1
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +92 -18
- 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 +5 -2
- package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/index.js +16 -13
- package/packages/client-agent-team/lib/types/client/locales.d.ts +40 -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 +40 -0
- package/packages/client-agent-team/lib/types/client/navigation.d.ts +1 -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 +12 -6
- package/packages/client-agent-team/lib/types/client/sidebar-sections.d.ts +16 -0
- package/packages/client-agent-team/lib/types/client/sidebar-sections.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/sidebar-sections.js +102 -0
- package/packages/client-agent-team/lib/types/client/slots.d.ts +3 -1
- package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/task-refs.d.ts +1 -1
- package/packages/client-agent-team/lib/types/client/task-refs.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/task-refs.js +1 -1
- package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +9 -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 +22 -2
- package/packages/tool-agent-team/README.md +7 -7
- package/packages/tool-agent-team/README.zh.md +7 -7
- package/packages/tool-agent-team/lib/index.js +38 -28
- package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
|
@@ -45,8 +45,13 @@ export function TeamMemberSessionComposer({ controller, inputActions, overlay, p
|
|
|
45
45
|
setSubmissionNotice('Compact 未执行,请重试。');
|
|
46
46
|
return;
|
|
47
47
|
}
|
|
48
|
-
if (input.phase === 'plain')
|
|
48
|
+
if (input.phase === 'plain') {
|
|
49
|
+
// Reaching plain after a compact flight means the InputMachine adopted
|
|
50
|
+
// the clear-on-success draft; a stale retry notice must not outlive it.
|
|
51
|
+
if (compactWasSubmitting.current)
|
|
52
|
+
setSubmissionNotice(undefined);
|
|
49
53
|
compactWasSubmitting.current = false;
|
|
54
|
+
}
|
|
50
55
|
}, [input?.claim?.token, input?.phase]);
|
|
51
56
|
useEffect(() => {
|
|
52
57
|
if (input === undefined)
|
|
@@ -86,6 +91,16 @@ export function TeamMemberSessionComposer({ controller, inputActions, overlay, p
|
|
|
86
91
|
return;
|
|
87
92
|
if (event.repeat || composing.current || event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229)
|
|
88
93
|
return;
|
|
94
|
+
// Tab accepts the highlighted candidate, mirroring the team-mode composer
|
|
95
|
+
// and the Channel/Thread bars; Shift+Tab keeps native focus reversal.
|
|
96
|
+
if (event.key === 'Tab' && !event.shiftKey) {
|
|
97
|
+
const highlight = controller.menu.getSnapshot().highlight;
|
|
98
|
+
if (highlight !== null) {
|
|
99
|
+
event.preventDefault();
|
|
100
|
+
controller.pick(highlight.source, highlight.index);
|
|
101
|
+
}
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
89
104
|
const key = event.key === 'ArrowUp' ? 'up' : event.key === 'ArrowDown' ? 'down' : event.key === 'Escape' ? 'escape' : event.key === 'Enter' ? 'enter' : undefined;
|
|
90
105
|
if (key !== undefined) {
|
|
91
106
|
const outcome = controller.arbitrate(key, false);
|
|
@@ -14,6 +14,9 @@ export interface TeamMessageProps {
|
|
|
14
14
|
readonly senderTitle?: string;
|
|
15
15
|
/** Continuation of one same-sender run: suppress repeated identity chrome. */
|
|
16
16
|
readonly grouped?: boolean;
|
|
17
|
+
/** Continuation rows that carry their own footer chip render the time so the
|
|
18
|
+
hairline-separated entry stays self-identifying. */
|
|
19
|
+
readonly showGroupedTime?: boolean;
|
|
17
20
|
readonly attachments?: readonly AgentTeamMessageAttachment[] | undefined;
|
|
18
21
|
/** Cache readback for thumbnails; absent on surfaces without the remotes. */
|
|
19
22
|
readonly loadAttachment?: TeamConversationProps['getAttachment'] | undefined;
|
|
@@ -25,5 +28,5 @@ export interface TeamMessageProps {
|
|
|
25
28
|
readonly children?: ReactNode;
|
|
26
29
|
}
|
|
27
30
|
/** One chat message row with identity chrome and sender-appropriate rendering. */
|
|
28
|
-
export declare function TeamMessage({ senderName, memberId, human, body, occurredAt, mentionNames, senderTitle, grouped, attachments, loadAttachment, t, onOpenRef, onResolveTaskRefs, children }: TeamMessageProps): import("react").JSX.Element;
|
|
31
|
+
export declare function TeamMessage({ senderName, memberId, human, body, occurredAt, mentionNames, senderTitle, grouped, showGroupedTime, attachments, loadAttachment, t, onOpenRef, onResolveTaskRefs, children }: TeamMessageProps): import("react").JSX.Element;
|
|
29
32
|
//# sourceMappingURL=TeamMessage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TeamMessage.d.ts","sourceRoot":"","sources":["../../../src/client/TeamMessage.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA2F,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAE/H,OAAO,KAAK,EAAE,iBAAiB,EAAE,0BAA0B,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAA;AACrH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAEvD,OAAO,EAA4E,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAI/H,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,0FAA0F;IAC1F,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;IACxD,sFAAsF;IACtF,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE,SAAS,gBAAgB,EAAE,KAAK,OAAO,CAAC,SAAS,eAAe,EAAE,CAAC,CAAC,GAAG,SAAS,CAAA;IACzH,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,SAAS,EAAE,iBAAiB,EAAE,QAAQ,EAAE,EAAE,gBAAgB,+
|
|
1
|
+
{"version":3,"file":"TeamMessage.d.ts","sourceRoot":"","sources":["../../../src/client/TeamMessage.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA2F,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAE/H,OAAO,KAAK,EAAE,iBAAiB,EAAE,0BAA0B,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAA;AACrH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAEvD,OAAO,EAA4E,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAI/H,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;2DACuD;IACvD,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAA;IAClC,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,0FAA0F;IAC1F,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;IACxD,sFAAsF;IACtF,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE,SAAS,gBAAgB,EAAE,KAAK,OAAO,CAAC,SAAS,eAAe,EAAE,CAAC,CAAC,GAAG,SAAS,CAAA;IACzH,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,eAAe,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC,EAAE,SAAS,EAAE,iBAAiB,EAAE,QAAQ,EAAE,EAAE,gBAAgB,+BA8G3N"}
|
|
@@ -3,10 +3,10 @@ import { Fragment, useCallback, useEffect, useLayoutEffect, useRef, useState } f
|
|
|
3
3
|
import { MarkdownText, MessageText, Modal } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
4
4
|
import { cachedAttachmentDataUrl, formatByteSize, loadAttachmentDataUrl } from "./attachment-preview.js";
|
|
5
5
|
import { cachedResolvedTaskRef, resolveUnknownTaskRefs, useResolvedTaskRefVersion } from "./task-refs.js";
|
|
6
|
-
import { formatMessageTime, isSingleBrandedRef, memberHue, planMessageBody, splitBrandedRefs, splitMentionNames } from "./team-formatters.js";
|
|
6
|
+
import { formatMessageTime, isSingleBrandedRef, memberHue, planMessageBody, shouldClampMessage, splitBrandedRefs, splitMentionNames } from "./team-formatters.js";
|
|
7
7
|
import css from './conversation.module.css';
|
|
8
8
|
/** One chat message row with identity chrome and sender-appropriate rendering. */
|
|
9
|
-
export function TeamMessage({ senderName, memberId, human, body, occurredAt, mentionNames, senderTitle, grouped, attachments, loadAttachment, t, onOpenRef, onResolveTaskRefs, children }) {
|
|
9
|
+
export function TeamMessage({ senderName, memberId, human, body, occurredAt, mentionNames, senderTitle, grouped, showGroupedTime, attachments, loadAttachment, t, onOpenRef, onResolveTaskRefs, children }) {
|
|
10
10
|
const avatarStyle = human ? undefined : { '--team-avatar-hue': memberHue(memberId) };
|
|
11
11
|
// Literal bodies carry mention chips inline — Human input always, and
|
|
12
12
|
// plain-prose Agent bodies where literal rendering loses nothing. Rich
|
|
@@ -29,6 +29,13 @@ export function TeamMessage({ senderName, memberId, human, body, occurredAt, men
|
|
|
29
29
|
void resolveUnknownTaskRefs(bodyTaskRefs, onResolveTaskRefs);
|
|
30
30
|
}, [onResolveTaskRefs, bodyTaskRefKey, refVersion]);
|
|
31
31
|
const taskLabel = useCallback((taskNumber) => t?.('taskLabel', { number: taskNumber }) ?? `Task #${taskNumber}`, [t]);
|
|
32
|
+
const refLabel = useCallback((ref) => ref.startsWith('thread:') ? (t?.('threadLabel') ?? 'Thread') : ref.startsWith('channel:') ? (t?.('channels') ?? 'Channels') : ref, [t]);
|
|
33
|
+
// Long bodies start clamped behind the expand control. The default derives
|
|
34
|
+
// from the body alone; the toggle itself is per-mount view state nothing
|
|
35
|
+
// persists.
|
|
36
|
+
const [expanded, setExpanded] = useState(false);
|
|
37
|
+
const clampable = shouldClampMessage(displayBody);
|
|
38
|
+
const clamped = clampable && !expanded;
|
|
32
39
|
const markdownRef = useRef(null);
|
|
33
40
|
useLayoutEffect(() => {
|
|
34
41
|
const root = markdownRef.current;
|
|
@@ -43,16 +50,16 @@ export function TeamMessage({ senderName, memberId, human, body, occurredAt, men
|
|
|
43
50
|
if (onResolveTaskRefs !== undefined && taskRefs.length > 0)
|
|
44
51
|
void resolveUnknownTaskRefs(taskRefs, onResolveTaskRefs);
|
|
45
52
|
for (const code of styledRefCodes)
|
|
46
|
-
renderResolvedMarkdownCodeRef(code, taskLabel);
|
|
53
|
+
renderResolvedMarkdownCodeRef(code, taskLabel, refLabel);
|
|
47
54
|
for (const node of textNodes)
|
|
48
|
-
renderResolvedMarkdownText(node, mentionNames ?? [], taskLabel);
|
|
55
|
+
renderResolvedMarkdownText(node, mentionNames ?? [], taskLabel, refLabel);
|
|
49
56
|
for (const button of root.querySelectorAll('button[data-task-ref]')) {
|
|
50
57
|
const taskRef = button.dataset.taskRef;
|
|
51
58
|
const hit = taskRef === undefined ? undefined : cachedResolvedTaskRef(taskRef);
|
|
52
59
|
if (taskRef !== undefined && hit !== undefined)
|
|
53
60
|
button.textContent = taskLabel(hit.taskNumber);
|
|
54
61
|
}
|
|
55
|
-
}, [richAgentBody, onOpenRef, onResolveTaskRefs, refVersion, taskLabel, mentionNames]);
|
|
62
|
+
}, [richAgentBody, onOpenRef, onResolveTaskRefs, refVersion, taskLabel, refLabel, mentionNames]);
|
|
56
63
|
useEffect(() => {
|
|
57
64
|
const root = markdownRef.current;
|
|
58
65
|
if (!richAgentBody || root === null || onOpenRef === undefined)
|
|
@@ -61,23 +68,24 @@ export function TeamMessage({ senderName, memberId, human, body, occurredAt, men
|
|
|
61
68
|
const target = event.target;
|
|
62
69
|
if (!(target instanceof Element))
|
|
63
70
|
return;
|
|
64
|
-
const button = target.closest('button[data-task-ref]');
|
|
71
|
+
const button = target.closest('button[data-ref], button[data-task-ref]');
|
|
65
72
|
if (button === null || !root.contains(button))
|
|
66
73
|
return;
|
|
67
|
-
const
|
|
68
|
-
if (
|
|
69
|
-
onOpenRef(
|
|
74
|
+
const ref = button instanceof HTMLElement ? button.dataset.ref ?? button.dataset.taskRef : undefined;
|
|
75
|
+
if (ref !== undefined)
|
|
76
|
+
onOpenRef(ref);
|
|
70
77
|
};
|
|
71
78
|
root.addEventListener('click', openTask);
|
|
72
79
|
return () => { root.removeEventListener('click', openTask); };
|
|
73
80
|
}, [richAgentBody, onOpenRef]);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
+
const bodyNode = plan.render === 'inline' && plan.inline !== undefined
|
|
82
|
+
? _jsx("div", { className: css.messageText, children: plan.inline.segments.map((segment, index) => segment.mention
|
|
83
|
+
? _jsx("span", { className: css.mention, children: segment.text }, index)
|
|
84
|
+
: _jsx(Fragment, { children: renderRefs(segment.text, onOpenRef, taskLabel, refLabel) }, index)) })
|
|
85
|
+
: plan.render === 'literal'
|
|
86
|
+
? _jsx("div", { className: css.messageText, children: onOpenRef === undefined ? _jsx(MessageText, { text: displayBody }) : renderRefs(displayBody, onOpenRef, taskLabel, refLabel) })
|
|
87
|
+
: _jsx("div", { ref: markdownRef, className: css.messageMarkdown, children: _jsx(MarkdownText, { text: displayBody }, `${displayBody}:${onOpenRef === undefined ? 'literal' : 'refs'}`) });
|
|
88
|
+
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 || showGroupedTime === true) && (_jsxs("div", { className: css.nameRow, children: [!grouped && _jsx("strong", { ...(senderTitle === undefined ? {} : { title: senderTitle }), children: senderName }), occurredAt !== undefined && _jsx("span", { className: css.messageTime, children: formatMessageTime(occurredAt) })] })), clampable ? _jsx("div", { className: clamped ? css.messageClamp : undefined, children: bodyNode }) : bodyNode, clampable && (_jsx("button", { type: "button", className: css.messageExpand, "data-message-expand": "true", "aria-expanded": expanded, onClick: () => { setExpanded(value => !value); }, children: expanded ? (t?.('collapseMessage') ?? 'Show less') : (t?.('expandMessage') ?? 'Show more') })), (plan.fallbackNames.length > 0 || plan.fallbackRefs.length > 0) && (_jsxs("div", { className: css.mentionsRow, children: [plan.fallbackNames.map(name => _jsxs("span", { className: css.mention, children: ["@", name] }, name)), plan.fallbackRefs.map(ref => {
|
|
81
89
|
const resolved = cachedResolvedTaskRef(ref);
|
|
82
90
|
const label = resolved !== undefined && ref.startsWith('task:') ? taskLabel(resolved.taskNumber) : ref;
|
|
83
91
|
return _jsx("button", { type: "button", className: css.refLink, title: ref, onClick: () => { onOpenRef(ref); }, children: label }, ref);
|
|
@@ -125,17 +133,31 @@ function resolvedTaskRefButton(taskRef, label) {
|
|
|
125
133
|
return button;
|
|
126
134
|
}
|
|
127
135
|
/** Replace a whole-ref code span with its resolved Task chip once known. */
|
|
128
|
-
function renderResolvedMarkdownCodeRef(code, taskLabel) {
|
|
136
|
+
function renderResolvedMarkdownCodeRef(code, taskLabel, refLabel) {
|
|
129
137
|
const segments = splitBrandedRefs((code.textContent ?? '').trim());
|
|
130
138
|
const segment = segments.length === 1 ? segments[0] : undefined;
|
|
131
|
-
const
|
|
139
|
+
const ref = segment?.ref;
|
|
140
|
+
if (ref === undefined)
|
|
141
|
+
return;
|
|
142
|
+
const taskRef = ref.startsWith('task:') === true ? ref : undefined;
|
|
132
143
|
const resolved = taskRef === undefined ? undefined : cachedResolvedTaskRef(taskRef);
|
|
133
|
-
if (taskRef
|
|
144
|
+
if (taskRef !== undefined && resolved === undefined)
|
|
145
|
+
return;
|
|
146
|
+
if (taskRef !== undefined) {
|
|
147
|
+
code.replaceWith(resolvedTaskRefButton(taskRef, taskLabel(resolved.taskNumber)));
|
|
134
148
|
return;
|
|
135
|
-
|
|
149
|
+
}
|
|
150
|
+
const button = document.createElement('button');
|
|
151
|
+
button.type = 'button';
|
|
152
|
+
if (css.refLink !== undefined)
|
|
153
|
+
button.className = css.refLink;
|
|
154
|
+
button.dataset.ref = ref;
|
|
155
|
+
button.title = ref;
|
|
156
|
+
button.textContent = refLabel(ref);
|
|
157
|
+
code.replaceWith(button);
|
|
136
158
|
}
|
|
137
159
|
/** Replace resolved Task refs and structured mention handles in one prose text node without changing Markdown structure. */
|
|
138
|
-
function renderResolvedMarkdownText(node, mentionNames, taskLabel) {
|
|
160
|
+
function renderResolvedMarkdownText(node, mentionNames, taskLabel, refLabel) {
|
|
139
161
|
let changed = false;
|
|
140
162
|
const fragment = document.createDocumentFragment();
|
|
141
163
|
for (const refSegment of splitBrandedRefs(node.data)) {
|
|
@@ -156,25 +178,40 @@ function renderResolvedMarkdownText(node, mentionNames, taskLabel) {
|
|
|
156
178
|
}
|
|
157
179
|
const taskRef = refSegment.ref.startsWith('task:') === true ? refSegment.ref : undefined;
|
|
158
180
|
const resolved = taskRef === undefined ? undefined : cachedResolvedTaskRef(taskRef);
|
|
159
|
-
|
|
181
|
+
// Task refs need Host resolution before becoming human-readable links;
|
|
182
|
+
// channel/thread refs already have their canonical display text and can be
|
|
183
|
+
// linked immediately at the authored position.
|
|
184
|
+
if (taskRef !== undefined && resolved === undefined) {
|
|
160
185
|
fragment.append(refSegment.text);
|
|
161
186
|
continue;
|
|
162
187
|
}
|
|
163
188
|
changed = true;
|
|
164
|
-
|
|
189
|
+
if (taskRef !== undefined) {
|
|
190
|
+
fragment.append(resolvedTaskRefButton(taskRef, taskLabel(resolved.taskNumber)));
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
const button = document.createElement('button');
|
|
194
|
+
button.type = 'button';
|
|
195
|
+
if (css.refLink !== undefined)
|
|
196
|
+
button.className = css.refLink;
|
|
197
|
+
button.dataset.ref = refSegment.ref;
|
|
198
|
+
button.title = refSegment.ref;
|
|
199
|
+
button.textContent = refLabel(refSegment.ref);
|
|
200
|
+
fragment.append(button);
|
|
201
|
+
}
|
|
165
202
|
}
|
|
166
203
|
if (changed)
|
|
167
204
|
node.replaceWith(fragment);
|
|
168
205
|
}
|
|
169
206
|
/** Render one literal text run, linkifying branded refs when navigation is available. */
|
|
170
|
-
function renderRefs(text, onOpenRef, taskLabel) {
|
|
207
|
+
function renderRefs(text, onOpenRef, taskLabel, refLabel) {
|
|
171
208
|
if (onOpenRef === undefined)
|
|
172
209
|
return text;
|
|
173
210
|
return splitBrandedRefs(text).map((segment, index) => {
|
|
174
211
|
if (segment.ref === undefined)
|
|
175
212
|
return _jsx(Fragment, { children: segment.text }, index);
|
|
176
213
|
const resolved = cachedResolvedTaskRef(segment.ref);
|
|
177
|
-
const label = resolved !== undefined && segment.ref.startsWith('task:') ? taskLabel(resolved.taskNumber) : segment.ref;
|
|
214
|
+
const label = resolved !== undefined && segment.ref.startsWith('task:') ? taskLabel(resolved.taskNumber) : refLabel(segment.ref);
|
|
178
215
|
return _jsx("button", { type: "button", className: css.refLink, title: segment.ref, onClick: () => { onOpenRef(segment.ref); }, children: label }, index);
|
|
179
216
|
});
|
|
180
217
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type MenuEntry } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
2
2
|
/**
|
|
3
3
|
* Row-level overflow menu shared by sidebar Channel and Agent rows, mirroring
|
|
4
4
|
* the harness session-row pattern: a portal list anchored to a bare ellipsis
|
|
@@ -7,7 +7,8 @@ import type { MenuItem } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
|
7
7
|
export declare function TeamRowMenu({ label, items, onSelect, onOpenChange }: {
|
|
8
8
|
/** Localized action label for the trigger, e.g. "{name} 的操作". */
|
|
9
9
|
readonly label: string;
|
|
10
|
-
|
|
10
|
+
/** Menu rows plus optional non-interactive labels and separators. */
|
|
11
|
+
readonly items: readonly MenuEntry[];
|
|
11
12
|
readonly onSelect: (id: string) => void;
|
|
12
13
|
/** Lets the row pin its hover styling while the portal list is up. */
|
|
13
14
|
readonly onOpenChange?: (open: boolean) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TeamRowMenu.d.ts","sourceRoot":"","sources":["../../../src/client/TeamRowMenu.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TeamRowMenu.d.ts","sourceRoot":"","sources":["../../../src/client/TeamRowMenu.tsx"],"names":[],"mappings":"AACA,OAAO,EAA+B,KAAK,SAAS,EAAE,MAAM,uCAAuC,CAAA;AAGnG;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,EAAE;IACpE,iEAAiE;IACjE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,qEAAqE;IACrE,QAAQ,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,CAAA;IACpC,QAAQ,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IACvC,sEAAsE;IACtE,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;CAChD,+BAkCA"}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
2
|
/** Collapsible sidebar section header: disclosure toggle plus trailing actions. */
|
|
3
|
-
export declare function TeamSidebarSection({ title, actions, children }: {
|
|
3
|
+
export declare function TeamSidebarSection({ title, actions, open, onToggle, children }: {
|
|
4
4
|
readonly title: string;
|
|
5
5
|
/** Trailing header controls (the add button); never part of the toggle. */
|
|
6
6
|
readonly actions?: ReactNode;
|
|
7
|
+
/** Controlled disclosure state; the caller owns persistence. */
|
|
8
|
+
readonly open: boolean;
|
|
9
|
+
readonly onToggle: (open: boolean) => void;
|
|
7
10
|
readonly children: ReactNode;
|
|
8
11
|
}): import("react").JSX.Element;
|
|
9
12
|
//# sourceMappingURL=TeamSidebarSection.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TeamSidebarSection.d.ts","sourceRoot":"","sources":["../../../src/client/TeamSidebarSection.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"TeamSidebarSection.d.ts","sourceRoot":"","sources":["../../../src/client/TeamSidebarSection.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAItC,mFAAmF;AACnF,wBAAgB,kBAAkB,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE;IAC/E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,2EAA2E;IAC3E,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,CAAA;IAC5B,gEAAgE;IAChE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;IACtB,QAAQ,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IAC1C,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAA;CAC7B,+BAaA"}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState } from 'react';
|
|
3
2
|
import { IconChevronDownOutline14 } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
4
3
|
import css from './sidebar.module.css';
|
|
5
4
|
/** Collapsible sidebar section header: disclosure toggle plus trailing actions. */
|
|
6
|
-
export function TeamSidebarSection({ title, actions, children }) {
|
|
7
|
-
|
|
8
|
-
return (_jsxs("section", { className: css.section, children: [_jsxs("div", { className: css.sectionHeader, children: [_jsxs("button", { type: "button", className: css.sectionToggle, "aria-expanded": open, onClick: () => { setOpen(value => !value); }, children: [_jsx(IconChevronDownOutline14, { className: css.sectionChevron }), _jsx("span", { className: css.sectionTitle, children: title })] }), actions !== undefined && _jsx("span", { className: css.sectionActions, children: actions })] }), open && children] }));
|
|
5
|
+
export function TeamSidebarSection({ title, actions, open, onToggle, children }) {
|
|
6
|
+
return (_jsxs("section", { className: css.section, children: [_jsxs("div", { className: css.sectionHeader, children: [_jsxs("button", { type: "button", className: css.sectionToggle, "aria-expanded": open, onClick: () => { onToggle(!open); }, children: [_jsx(IconChevronDownOutline14, { className: css.sectionChevron }), _jsx("span", { className: css.sectionTitle, children: title })] }), actions !== undefined && _jsx("span", { className: css.sectionActions, children: actions })] }), open && children] }));
|
|
9
7
|
}
|
|
@@ -5,7 +5,7 @@ import type { TeamDraftStore } from './drafts.ts';
|
|
|
5
5
|
interface TeamThreadPageProps {
|
|
6
6
|
readonly workspaceId: WorkspaceId;
|
|
7
7
|
readonly channelRef?: AgentTeamChannelRef;
|
|
8
|
-
readonly taskRef
|
|
8
|
+
readonly taskRef?: AgentTeamTaskRef;
|
|
9
9
|
readonly threadRef: AgentTeamThreadRef;
|
|
10
10
|
readonly taskNumber?: number;
|
|
11
11
|
readonly backToWorkspace: TeamConversationProps['backToWorkspace'];
|
|
@@ -18,6 +18,7 @@ interface TeamThreadPageProps {
|
|
|
18
18
|
readonly getAttachment: TeamConversationProps['getAttachment'];
|
|
19
19
|
readonly reply: TeamConversationProps['reply'];
|
|
20
20
|
readonly changeTask: TeamConversationProps['changeTask'];
|
|
21
|
+
readonly promoteThread: TeamConversationProps['promoteThread'];
|
|
21
22
|
readonly putAttachment: TeamConversationProps['putAttachment'];
|
|
22
23
|
readonly selectChannel: TeamConversationProps['selectChannel'];
|
|
23
24
|
readonly selectThread: TeamConversationProps['selectThread'];
|
|
@@ -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,EAAE,gBAAgB,CAAA;
|
|
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,+BAgoBxD"}
|
|
@@ -33,7 +33,8 @@ function readMeta(facts) {
|
|
|
33
33
|
return new Map(facts.map(fact => [factKey(fact.fact), fact]));
|
|
34
34
|
}
|
|
35
35
|
export function TeamThreadPage(props) {
|
|
36
|
-
const { workspaceId, channelRef, taskRef, threadRef, taskNumber, backToWorkspace, selectChannel, selectThread, resolveTaskRefs, putAttachment, loadChannels, readThread, loadThreadHistory, subscribeChanges, loadMembers, drafts, getAttachment, reply, changeTask, t, } = props;
|
|
36
|
+
const { workspaceId, channelRef, taskRef, threadRef, taskNumber, backToWorkspace, selectChannel, selectThread, resolveTaskRefs, putAttachment, loadChannels, readThread, loadThreadHistory, subscribeChanges, loadMembers, drafts, getAttachment, reply, changeTask, promoteThread, t, } = props;
|
|
37
|
+
const threadRequest = { threadRef, ...(taskRef === undefined ? {} : { taskRef }) };
|
|
37
38
|
const [projection, setProjection] = useState();
|
|
38
39
|
const [channelView, setChannelView] = useState();
|
|
39
40
|
const [members, setMembers] = useState([]);
|
|
@@ -53,13 +54,16 @@ export function TeamThreadPage(props) {
|
|
|
53
54
|
// confirm dialog lists exactly what will be completed with the Task.
|
|
54
55
|
const [confirmingAccept, setConfirmingAccept] = useState(false);
|
|
55
56
|
useEffect(() => {
|
|
56
|
-
if (confirmingAccept && projection?.task
|
|
57
|
+
if (confirmingAccept && projection?.task?.resolution === 'accepted')
|
|
57
58
|
setConfirmingAccept(false);
|
|
58
|
-
}, [confirmingAccept, projection?.task
|
|
59
|
+
}, [confirmingAccept, projection?.task?.resolution]);
|
|
59
60
|
const [replyRequestId, setReplyRequestId] = useState();
|
|
60
61
|
const [confirmation, setConfirmation] = useState();
|
|
61
62
|
const [statusMessage, setStatusMessage] = useState();
|
|
62
63
|
const [pending, setPending] = useState(false);
|
|
64
|
+
// Label source for the promote/accept buttons only: a reply send also raises
|
|
65
|
+
// the shared `pending` gate, and must not retitle those buttons.
|
|
66
|
+
const [mutating, setMutating] = useState();
|
|
63
67
|
const [loading, setLoading] = useState(true);
|
|
64
68
|
const [error, setError] = useState();
|
|
65
69
|
const mountedRef = useRef(false);
|
|
@@ -91,7 +95,7 @@ export function TeamThreadPage(props) {
|
|
|
91
95
|
sequenceRef.current = sequence;
|
|
92
96
|
setLoading(true);
|
|
93
97
|
try {
|
|
94
|
-
const result = await readThread({ requestId: readRequestIdRef.current, workspaceId,
|
|
98
|
+
const result = await readThread({ requestId: readRequestIdRef.current, workspaceId, ...threadRequest });
|
|
95
99
|
if (!mountedRef.current || sequence !== sequenceRef.current)
|
|
96
100
|
return false;
|
|
97
101
|
if (!result.ok) {
|
|
@@ -135,7 +139,7 @@ export function TeamThreadPage(props) {
|
|
|
135
139
|
};
|
|
136
140
|
const refreshPassiveFacts = async () => {
|
|
137
141
|
try {
|
|
138
|
-
const result = await loadThreadHistory({ workspaceId,
|
|
142
|
+
const result = await loadThreadHistory({ workspaceId, ...threadRequest, limit: 100 });
|
|
139
143
|
if (!mountedRef.current || !result.ok)
|
|
140
144
|
return;
|
|
141
145
|
const incoming = result.value.facts;
|
|
@@ -153,7 +157,7 @@ export function TeamThreadPage(props) {
|
|
|
153
157
|
currentFactsRef.current = merged;
|
|
154
158
|
return merged;
|
|
155
159
|
});
|
|
156
|
-
setProjection(current => current === undefined ? current : { ...current, task: result.value.task, thread: result.value.thread, claims: result.value.claims });
|
|
160
|
+
setProjection(current => current === undefined ? current : { ...current, ...(result.value.task === undefined ? {} : { task: result.value.task }), thread: result.value.thread, claims: result.value.claims });
|
|
157
161
|
if (additions.length === 0)
|
|
158
162
|
return;
|
|
159
163
|
// A reader scrolled away from the tail must be offered the explicit
|
|
@@ -200,8 +204,8 @@ export function TeamThreadPage(props) {
|
|
|
200
204
|
// One parallel round covers the whole first paint. The durable read no
|
|
201
205
|
// longer wakes any change scope, so no second wave follows it.
|
|
202
206
|
const [read, history] = await Promise.all([
|
|
203
|
-
readThread({ requestId: readRequestIdRef.current, workspaceId,
|
|
204
|
-
loadThreadHistory({ workspaceId,
|
|
207
|
+
readThread({ requestId: readRequestIdRef.current, workspaceId, ...threadRequest }),
|
|
208
|
+
loadThreadHistory({ workspaceId, ...threadRequest, limit: 20 }).catch(() => undefined),
|
|
205
209
|
]);
|
|
206
210
|
if (!mountedRef.current || sequence !== sequenceRef.current)
|
|
207
211
|
return;
|
|
@@ -262,7 +266,7 @@ export function TeamThreadPage(props) {
|
|
|
262
266
|
return;
|
|
263
267
|
setLoading(true);
|
|
264
268
|
try {
|
|
265
|
-
const result = await loadThreadHistory({ workspaceId,
|
|
269
|
+
const result = await loadThreadHistory({ workspaceId, ...threadRequest, beforeSequence, limit: 20 });
|
|
266
270
|
if (!mountedRef.current)
|
|
267
271
|
return;
|
|
268
272
|
if (!result.ok) {
|
|
@@ -286,6 +290,7 @@ export function TeamThreadPage(props) {
|
|
|
286
290
|
const activeProjection = projection;
|
|
287
291
|
const task = activeProjection?.task;
|
|
288
292
|
const thread = activeProjection?.thread;
|
|
293
|
+
const resolvedTaskNumber = taskNumber ?? channelView?.taskNumbers.find(entry => entry.taskRef === task?.taskRef)?.taskNumber;
|
|
289
294
|
const taskTitle = activeProjection === undefined ? undefined : formatTaskTitle(activeProjection.anchor.body);
|
|
290
295
|
const taskClaims = activeProjection?.claims ?? [];
|
|
291
296
|
const effectiveChannelRef = task?.channelRef ?? channelRef;
|
|
@@ -309,8 +314,25 @@ export function TeamThreadPage(props) {
|
|
|
309
314
|
// Channel; the open Task's own refs are already on screen, and other Tasks
|
|
310
315
|
// are not resolvable from this surface, so they degrade to a no-op.
|
|
311
316
|
const openRef = (ref) => {
|
|
312
|
-
if (ref.startsWith('channel:') && ref !== channelRef)
|
|
317
|
+
if (ref.startsWith('channel:') && ref !== channelRef) {
|
|
313
318
|
selectChannel(ref);
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
if (ref.startsWith('thread:') && ref !== threadRef) {
|
|
322
|
+
// Thread refs do not have a dedicated resolver Remote. The Host view
|
|
323
|
+
// query still returns the target's home Channel and Task projection.
|
|
324
|
+
void loadChannels({ workspaceId, threadRef: ref, includeActivities: false, limit: 1 }).then(result => {
|
|
325
|
+
if (!result.ok)
|
|
326
|
+
return;
|
|
327
|
+
const target = result.value.items[0];
|
|
328
|
+
if (target === undefined)
|
|
329
|
+
return;
|
|
330
|
+
const targetChannel = result.value.channels.find(channel => channel.channelRef === target.message.channelRef);
|
|
331
|
+
if (targetChannel !== undefined)
|
|
332
|
+
selectThread(target.thread.threadRef, targetChannel.channelRef, target.task?.taskRef, target.taskNumber);
|
|
333
|
+
});
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
314
336
|
if (ref.startsWith('task:') && ref !== taskRef) {
|
|
315
337
|
// Another Task cited here: resolve its home Channel and jump.
|
|
316
338
|
jumpToTaskThread(resolveTaskRefs, workspaceId, ref, selectThread);
|
|
@@ -371,10 +393,59 @@ export function TeamThreadPage(props) {
|
|
|
371
393
|
await readCurrent(true);
|
|
372
394
|
await refreshSupplemental();
|
|
373
395
|
};
|
|
396
|
+
const convertToTask = async () => {
|
|
397
|
+
if (pending || thread === undefined || task !== undefined)
|
|
398
|
+
return;
|
|
399
|
+
setPending(true);
|
|
400
|
+
setMutating('promote');
|
|
401
|
+
setError(undefined);
|
|
402
|
+
const key = 'promote';
|
|
403
|
+
const requestId = mutationRequests.current.get(key) ?? mintRequestId();
|
|
404
|
+
mutationRequests.current.set(key, requestId);
|
|
405
|
+
try {
|
|
406
|
+
const result = await promoteThread({ requestId, workspaceId, threadRef, baseRevision: thread.revision });
|
|
407
|
+
if (!result.ok) {
|
|
408
|
+
setError(result.error.message);
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
if (result.value.kind === 'committed') {
|
|
412
|
+
mutationRequests.current.delete(key);
|
|
413
|
+
const committed = result.value;
|
|
414
|
+
setProjection(current => current === undefined ? current
|
|
415
|
+
: { ...current, task: committed.task, thread: committed.thread });
|
|
416
|
+
setCurrentFacts(current => {
|
|
417
|
+
const merged = mergeFacts(current, [{ kind: 'activity', sequence: committed.activity.sequence, activity: committed.activity }]);
|
|
418
|
+
currentFactsRef.current = merged;
|
|
419
|
+
return merged;
|
|
420
|
+
});
|
|
421
|
+
await readCurrent(true);
|
|
422
|
+
await refreshSupplemental();
|
|
423
|
+
}
|
|
424
|
+
else if (result.value.kind === 'unread_required') {
|
|
425
|
+
setError(t('unreadRequired', { count: result.value.unreadCount }));
|
|
426
|
+
mutationRequests.current.delete(key);
|
|
427
|
+
await refreshAfterFence();
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
setError(t('staleRevision'));
|
|
431
|
+
mutationRequests.current.delete(key);
|
|
432
|
+
await refreshPassiveFacts();
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
catch (cause) {
|
|
436
|
+
setError(cause instanceof Error ? cause.message : String(cause));
|
|
437
|
+
}
|
|
438
|
+
finally {
|
|
439
|
+
setPending(false);
|
|
440
|
+
setMutating(undefined);
|
|
441
|
+
}
|
|
442
|
+
};
|
|
374
443
|
const mutateTask = async (action) => {
|
|
375
444
|
if (pending || task === undefined || thread === undefined)
|
|
376
445
|
return;
|
|
377
446
|
setPending(true);
|
|
447
|
+
if (action === 'accept')
|
|
448
|
+
setMutating('accept');
|
|
378
449
|
setError(undefined);
|
|
379
450
|
const key = `task:${action}`;
|
|
380
451
|
const requestId = mutationRequests.current.get(key) ?? mintRequestId();
|
|
@@ -413,10 +484,11 @@ export function TeamThreadPage(props) {
|
|
|
413
484
|
}
|
|
414
485
|
finally {
|
|
415
486
|
setPending(false);
|
|
487
|
+
setMutating(undefined);
|
|
416
488
|
}
|
|
417
489
|
};
|
|
418
490
|
const sendReply = async () => {
|
|
419
|
-
if (pending ||
|
|
491
|
+
if (pending || thread === undefined || draft.trim() === '')
|
|
420
492
|
return;
|
|
421
493
|
const id = replyRequestId ?? mintRequestId();
|
|
422
494
|
setReplyRequestId(id);
|
|
@@ -440,7 +512,7 @@ export function TeamThreadPage(props) {
|
|
|
440
512
|
}
|
|
441
513
|
attachmentIds.push(uploaded.value.attachmentId);
|
|
442
514
|
}
|
|
443
|
-
const result = await reply({ requestId: id, workspaceId, taskRef: task.taskRef, body: draft.trim(), baseRevision: thread.revision, recipients: [...recipients].sort(), ...(attachmentIds.length === 0 ? {} : { attachments: attachmentIds }), ...(confirmation === undefined ? {} : { confirmationToken: confirmation }) });
|
|
515
|
+
const result = await reply({ requestId: id, workspaceId, threadRef, ...(task === undefined ? {} : { taskRef: task.taskRef }), body: draft.trim(), baseRevision: thread.revision, recipients: [...recipients].sort(), ...(attachmentIds.length === 0 ? {} : { attachments: attachmentIds }), ...(confirmation === undefined ? {} : { confirmationToken: confirmation }) });
|
|
444
516
|
if (!result.ok) {
|
|
445
517
|
setError(result.error.message);
|
|
446
518
|
return;
|
|
@@ -452,7 +524,7 @@ export function TeamThreadPage(props) {
|
|
|
452
524
|
currentFactsRef.current = merged;
|
|
453
525
|
return merged;
|
|
454
526
|
});
|
|
455
|
-
setProjection(current => current === undefined ? current : { ...current, task: committed.task, thread: committed.thread });
|
|
527
|
+
setProjection(current => current === undefined ? current : { ...current, ...(committed.task === undefined ? {} : { task: committed.task }), thread: committed.thread });
|
|
456
528
|
drafts.clear(draftKey);
|
|
457
529
|
setPendingFiles([]);
|
|
458
530
|
setReplyRequestId(undefined);
|
|
@@ -501,10 +573,12 @@ export function TeamThreadPage(props) {
|
|
|
501
573
|
// Threads are always entered through a Channel page, so a Channel origin
|
|
502
574
|
// returns to its timeline; a Thread restored without one returns further.
|
|
503
575
|
const backLabel = channelRef === undefined ? t('backToWorkspace') : t('backToChannel');
|
|
504
|
-
return _jsxs("main", { className: css.surface, "data-team-thread": threadRef, children: [_jsxs("div", { className: css.surfaceHeader, children: [_jsx("div", { className: css.backRow, children: _jsx(Button, { size: "sm", icon: _jsx(IconChevronLeftOutline14, {}), onClick: backToWorkspace, children: backLabel }) }), _jsxs("header", { className: css.headerRow, children: [_jsxs("div", { className: css.headerCopy, children: [_jsxs("div", { className: threadCss.titleLine, children: [_jsx("h1", { children: task === undefined ? t('
|
|
576
|
+
return _jsxs("main", { className: css.surface, "data-team-thread": threadRef, children: [_jsxs("div", { className: css.surfaceHeader, children: [_jsx("div", { className: css.backRow, children: _jsx(Button, { size: "sm", icon: _jsx(IconChevronLeftOutline14, {}), onClick: backToWorkspace, children: backLabel }) }), _jsxs("header", { className: css.headerRow, children: [_jsxs("div", { className: css.headerCopy, children: [_jsxs("div", { className: threadCss.titleLine, children: [_jsx("h1", { children: task === undefined ? t('threadLabel') : t('taskLabel', { number: resolvedTaskNumber ?? '…' }) }), task !== undefined && _jsx(Pill, { children: formatTaskStatus(task.status, t) })] }), taskTitle !== undefined && taskTitle !== '' && _jsx("p", { className: threadCss.taskTitle, title: taskTitle, children: taskTitle })] }), task === undefined && thread !== undefined && _jsx("div", { className: css.headerActions, children: _jsx(Button, { size: "sm", variant: "primary", disabled: pending, onClick: () => { void convertToTask(); }, children: mutating === 'promote' ? t('promotingTask') : t('promoteToTask') }) }), task !== undefined && thread !== undefined && task.resolution !== 'closed' && _jsxs("div", { className: css.headerActions, children: [(() => {
|
|
505
577
|
const activeClaims = projection?.claims.filter(claim => claim.taskRef === task.taskRef && claim.state === 'active') ?? [];
|
|
506
578
|
const earlyAccept = task.resolution === 'open' && task.status === 'in_progress' && activeClaims.length > 0;
|
|
507
|
-
|
|
579
|
+
// todo Tasks accept directly: the work finished outside the
|
|
580
|
+
// ledger, so there is nothing to confirm and no Claims to list.
|
|
581
|
+
if (!(task.status === 'in_review' || task.status === 'todo' || earlyAccept) || task.resolution !== 'open')
|
|
508
582
|
return null;
|
|
509
583
|
return _jsx(Button, { size: "sm", variant: "primary", disabled: pending, onClick: () => { if (earlyAccept) {
|
|
510
584
|
setConfirmingAccept(true);
|
|
@@ -518,11 +592,11 @@ export function TeamThreadPage(props) {
|
|
|
518
592
|
// Recomputed here so the confirm list never shows stale rows.
|
|
519
593
|
const activeClaims = projection?.claims.filter(claim => claim.taskRef === task.taskRef && claim.state === 'active') ?? [];
|
|
520
594
|
return _jsxs(Modal, { open: confirmingAccept, onClose: () => { if (!pending)
|
|
521
|
-
setConfirmingAccept(false); }, title: t('acceptEarlyTitle'), closeLabel: t('cancel'), footer: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", disabled: pending, onClick: () => { setConfirmingAccept(false); }, children: t('cancel') }), _jsx(Button, { variant: "primary", disabled: pending, onClick: () => { void mutateTask('accept'); }, children:
|
|
595
|
+
setConfirmingAccept(false); }, title: t('acceptEarlyTitle'), closeLabel: t('cancel'), footer: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", disabled: pending, onClick: () => { setConfirmingAccept(false); }, children: t('cancel') }), _jsx(Button, { variant: "primary", disabled: pending, onClick: () => { void mutateTask('accept'); }, children: mutating === 'accept' ? t('acceptingTask') : t('acceptTask') })] }), children: [_jsx("p", { className: css.confirmBody, children: t('acceptEarlyBody', { count: activeClaims.length }) }), _jsx("ul", { className: css.confirmList, children: activeClaims.map(claim => (_jsxs("li", { children: [memberName(claim.owner), " \u00B7 ", claim.direction] }, claim.claimRef))) })] });
|
|
522
596
|
})(), 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 => {
|
|
523
597
|
const ownerStatus = members.find(status => status.member.memberId === claim.owner);
|
|
524
598
|
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);
|
|
525
|
-
})] }) }) })] }), _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 &&
|
|
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) }), 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 && thread !== undefined ? (task?.resolution === 'closed'
|
|
526
600
|
? _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') })] })] })
|
|
527
|
-
: _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(); }, pendingFiles: pendingFiles, onFilesChange: setPendingFiles, t: t }, draftKey)) : _jsx("div", {})] });
|
|
601
|
+
: _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", {})] });
|
|
528
602
|
}
|
|
@@ -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, recoverMember, 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, clearMemberContext, 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;
|
|
1
|
+
{"version":3,"file":"TeamWorkspaceBrowser.d.ts","sourceRoot":"","sources":["../../../src/client/TeamWorkspaceBrowser.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAUlD,wBAAgB,oBAAoB,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE,aAAa,EAAE,CAAC,EAAE,aAAa,EAAE,WAAW,EAAE,gBAAgB,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,EAAE,kBAAkB,EAAE,WAAW,EAAE,mBAAmB,EAAE,UAAU,EAAE,iBAAiB,EAAE,EAAE,gBAAgB,+BA8DnV"}
|