@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.
Files changed (79) hide show
  1. package/README.md +7 -1
  2. package/README.zh.md +12 -3
  3. package/package.json +1 -1
  4. package/packages/agent-team/README.md +3 -3
  5. package/packages/agent-team/README.zh.md +3 -3
  6. package/packages/agent-team/lib/auto-compaction.js +39 -1
  7. package/packages/agent-team/lib/index.js +123 -14
  8. package/packages/agent-team/lib/ledger.js +355 -110
  9. package/packages/agent-team/lib/spec.js +39 -11
  10. package/packages/agent-team/lib/typert.host.js +257 -86
  11. package/packages/agent-team/lib/typert.remote-client.d.ts +5 -1
  12. package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
  13. package/packages/agent-team/lib/typert.remote-client.js +196 -59
  14. package/packages/agent-team/lib/types/auto-compaction.d.ts +9 -0
  15. package/packages/agent-team/lib/types/auto-compaction.d.ts.map +1 -1
  16. package/packages/agent-team/lib/types/index.d.ts +24 -7
  17. package/packages/agent-team/lib/types/index.d.ts.map +1 -1
  18. package/packages/agent-team/lib/types/ledger.d.ts +36 -2
  19. package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
  20. package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
  21. package/packages/agent-team/lib/types/types.d.ts +109 -30
  22. package/packages/agent-team/lib/types/types.d.ts.map +1 -1
  23. package/packages/agent-team/preset/team-member/agent.cordis.yml +1 -1
  24. package/packages/client-agent-team/README.md +2 -1
  25. package/packages/client-agent-team/README.zh.md +2 -1
  26. package/packages/client-agent-team/lib/client.js +1082 -380
  27. package/packages/client-agent-team/lib/client.js.map +1 -1
  28. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts +2 -1
  29. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
  30. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +73 -15
  31. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
  32. package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +34 -10
  33. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -1
  34. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +3 -1
  35. package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts +6 -1
  36. package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -1
  37. package/packages/client-agent-team/lib/types/client/TeamComposer.js +76 -20
  38. package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts +1 -1
  39. package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts.map +1 -1
  40. package/packages/client-agent-team/lib/types/client/TeamConversation.js +3 -3
  41. package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.d.ts.map +1 -1
  42. package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.js +16 -1
  43. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts +4 -1
  44. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
  45. package/packages/client-agent-team/lib/types/client/TeamMessage.js +62 -25
  46. package/packages/client-agent-team/lib/types/client/TeamRowMenu.d.ts +3 -2
  47. package/packages/client-agent-team/lib/types/client/TeamRowMenu.d.ts.map +1 -1
  48. package/packages/client-agent-team/lib/types/client/TeamSidebarSection.d.ts +5 -2
  49. package/packages/client-agent-team/lib/types/client/TeamSidebarSection.d.ts.map +1 -1
  50. package/packages/client-agent-team/lib/types/client/TeamSidebarSection.js +2 -4
  51. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts +2 -1
  52. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
  53. package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +92 -18
  54. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts +1 -1
  55. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts.map +1 -1
  56. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +5 -2
  57. package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
  58. package/packages/client-agent-team/lib/types/client/index.js +16 -13
  59. package/packages/client-agent-team/lib/types/client/locales.d.ts +40 -0
  60. package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
  61. package/packages/client-agent-team/lib/types/client/locales.js +40 -0
  62. package/packages/client-agent-team/lib/types/client/navigation.d.ts +1 -1
  63. package/packages/client-agent-team/lib/types/client/navigation.d.ts.map +1 -1
  64. package/packages/client-agent-team/lib/types/client/navigation.js +12 -6
  65. package/packages/client-agent-team/lib/types/client/sidebar-sections.d.ts +16 -0
  66. package/packages/client-agent-team/lib/types/client/sidebar-sections.d.ts.map +1 -0
  67. package/packages/client-agent-team/lib/types/client/sidebar-sections.js +102 -0
  68. package/packages/client-agent-team/lib/types/client/slots.d.ts +3 -1
  69. package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
  70. package/packages/client-agent-team/lib/types/client/task-refs.d.ts +1 -1
  71. package/packages/client-agent-team/lib/types/client/task-refs.d.ts.map +1 -1
  72. package/packages/client-agent-team/lib/types/client/task-refs.js +1 -1
  73. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +9 -0
  74. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts.map +1 -1
  75. package/packages/client-agent-team/lib/types/client/team-formatters.js +22 -2
  76. package/packages/tool-agent-team/README.md +7 -7
  77. package/packages/tool-agent-team/README.zh.md +7 -7
  78. package/packages/tool-agent-team/lib/index.js +38 -28
  79. 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,+BA6F1M"}
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 taskRef = button instanceof HTMLElement ? button.dataset.taskRef : undefined;
68
- if (taskRef !== undefined)
69
- onOpenRef(taskRef);
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
- 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) })] })), plan.render === 'inline' && plan.inline !== undefined
75
- ? _jsx("div", { className: css.messageText, children: plan.inline.segments.map((segment, index) => segment.mention
76
- ? _jsx("span", { className: css.mention, children: segment.text }, index)
77
- : _jsx(Fragment, { children: renderRefs(segment.text, onOpenRef, taskLabel) }, index)) })
78
- : plan.render === 'literal'
79
- ? _jsx("div", { className: css.messageText, children: onOpenRef === undefined ? _jsx(MessageText, { text: displayBody }) : renderRefs(displayBody, onOpenRef, taskLabel) })
80
- : _jsx("div", { ref: markdownRef, className: css.messageMarkdown, children: _jsx(MarkdownText, { text: displayBody }, `${displayBody}:${onOpenRef === undefined ? 'literal' : 'refs'}`) }), (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
+ 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 taskRef = segment?.ref?.startsWith('task:') === true ? segment.ref : undefined;
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 === undefined || resolved === undefined)
144
+ if (taskRef !== undefined && resolved === undefined)
145
+ return;
146
+ if (taskRef !== undefined) {
147
+ code.replaceWith(resolvedTaskRefButton(taskRef, taskLabel(resolved.taskNumber)));
134
148
  return;
135
- code.replaceWith(resolvedTaskRefButton(taskRef, taskLabel(resolved.taskNumber)));
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
- if (resolved === undefined) {
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
- fragment.append(resolvedTaskRefButton(taskRef, taskLabel(resolved.taskNumber)));
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 { MenuItem } from '@deepseek-ai/dsh-client-ui-primitives';
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
- readonly items: readonly MenuItem[];
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":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uCAAuC,CAAA;AAGrE;;;;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,QAAQ,CAAC,KAAK,EAAE,SAAS,QAAQ,EAAE,CAAA;IACnC,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
+ {"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 { type ReactNode } from 'react';
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,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAIhD,mFAAmF;AACnF,wBAAgB,kBAAkB,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE;IAC/D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,2EAA2E;IAC3E,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,CAAA;IAC5B,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAA;CAC7B,+BAcA"}
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
- const [open, setOpen] = useState(true);
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: AgentTeamTaskRef;
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;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,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,+BA0jBxD"}
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.resolution === 'accepted')
57
+ if (confirmingAccept && projection?.task?.resolution === 'accepted')
57
58
  setConfirmingAccept(false);
58
- }, [confirmingAccept, projection?.task.resolution]);
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, taskRef });
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, taskRef, limit: 100 });
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, taskRef }),
204
- loadThreadHistory({ workspaceId, taskRef, limit: 20 }).catch(() => undefined),
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, taskRef, beforeSequence, limit: 20 });
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 || task === undefined || thread === undefined || draft.trim() === '')
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('taskPending') : t('taskLabel', { number: taskNumber ?? '…' }) }), task !== undefined && _jsx(Pill, { children: formatTaskStatus(task.status, t) })] }), taskTitle !== undefined && taskTitle !== '' && _jsx("p", { className: threadCss.taskTitle, children: taskTitle })] }), task !== undefined && thread !== undefined && task.resolution !== 'closed' && _jsxs("div", { className: css.headerActions, children: [(() => {
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
- if (!(task.status === 'in_review' || earlyAccept) || task.resolution !== 'open')
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: pending ? 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))) })] });
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 && task !== undefined && thread !== undefined ? (task.resolution === 'closed'
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;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,WAAW,EAAE,mBAAmB,EAAE,UAAU,EAAE,iBAAiB,EAAE,EAAE,gBAAgB,+BA4D/T"}
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"}