@wowyuarm/dsh-agent-team 0.1.2 → 0.1.3

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 (67) hide show
  1. package/README.md +1 -0
  2. package/README.zh.md +2 -0
  3. package/package.json +12 -3
  4. package/packages/agent-team/README.md +1 -1
  5. package/packages/agent-team/README.zh.md +1 -1
  6. package/packages/agent-team/lib/attachments.js +11 -12
  7. package/packages/agent-team/lib/auto-compaction.js +163 -0
  8. package/packages/agent-team/lib/index.js +214 -130
  9. package/packages/agent-team/lib/ledger.js +77 -70
  10. package/packages/agent-team/lib/recovery.js +30 -35
  11. package/packages/agent-team/lib/typert.host.js +33 -23
  12. package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
  13. package/packages/agent-team/lib/typert.remote-client.js +21 -21
  14. package/packages/agent-team/lib/types/attachments.d.ts +2 -2
  15. package/packages/agent-team/lib/types/attachments.d.ts.map +1 -1
  16. package/packages/agent-team/lib/types/auto-compaction.d.ts +38 -0
  17. package/packages/agent-team/lib/types/auto-compaction.d.ts.map +1 -0
  18. package/packages/agent-team/lib/types/index.d.ts +40 -12
  19. package/packages/agent-team/lib/types/index.d.ts.map +1 -1
  20. package/packages/agent-team/lib/types/ledger.d.ts +13 -0
  21. package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
  22. package/packages/agent-team/lib/types/recovery.d.ts +15 -16
  23. package/packages/agent-team/lib/types/recovery.d.ts.map +1 -1
  24. package/packages/agent-team/preset/team-member/agent.cordis.yml +6 -1
  25. package/packages/client-agent-team/README.md +1 -1
  26. package/packages/client-agent-team/README.zh.md +1 -1
  27. package/packages/client-agent-team/lib/client.js +942 -534
  28. package/packages/client-agent-team/lib/client.js.map +1 -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 +5 -166
  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 +6 -20
  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 -2
  35. package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -1
  36. package/packages/client-agent-team/lib/types/client/TeamComposer.js +0 -1
  37. package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts +34 -0
  38. package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts.map +1 -0
  39. package/packages/client-agent-team/lib/types/client/TeamMemberEditor.js +169 -0
  40. package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.d.ts +17 -0
  41. package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.d.ts.map +1 -0
  42. package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.js +120 -0
  43. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
  44. package/packages/client-agent-team/lib/types/client/TeamMessage.js +83 -43
  45. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
  46. package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +10 -23
  47. package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
  48. package/packages/client-agent-team/lib/types/client/index.js +71 -1
  49. package/packages/client-agent-team/lib/types/client/member-session-input.d.ts +22 -0
  50. package/packages/client-agent-team/lib/types/client/member-session-input.d.ts.map +1 -0
  51. package/packages/client-agent-team/lib/types/client/member-session-input.js +98 -0
  52. package/packages/client-agent-team/lib/types/client/requests.d.ts +16 -0
  53. package/packages/client-agent-team/lib/types/client/requests.d.ts.map +1 -0
  54. package/packages/client-agent-team/lib/types/client/requests.js +22 -0
  55. package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
  56. package/packages/client-agent-team/lib/types/client/task-refs.d.ts +6 -1
  57. package/packages/client-agent-team/lib/types/client/task-refs.d.ts.map +1 -1
  58. package/packages/client-agent-team/lib/types/client/task-refs.js +19 -0
  59. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +43 -3
  60. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts.map +1 -1
  61. package/packages/client-agent-team/lib/types/client/team-formatters.js +61 -4
  62. package/packages/client-agent-team/lib/types/client/team-membership.d.ts.map +1 -1
  63. package/packages/client-agent-team/lib/types/client/team-membership.js +2 -1
  64. package/packages/tool-agent-team/lib/index.js +1 -1
  65. package/packages/client-agent-team/lib/types/client/TeamInboxPanel.d.ts +0 -13
  66. package/packages/client-agent-team/lib/types/client/TeamInboxPanel.d.ts.map +0 -1
  67. package/packages/client-agent-team/lib/types/client/TeamInboxPanel.js +0 -90
@@ -0,0 +1,120 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useLayoutEffect, useRef, useState } from 'react';
3
+ import { IconSendOutline16, IconStopFill16 } from '@deepseek-ai/dsh-client-ui-primitives';
4
+ import { allowsMemberSessionSubmit, TEAM_COMMAND_SOURCE, TEAM_MEMBER_SOURCE } from "./member-session-input.js";
5
+ import css from './composer.module.css';
6
+ /**
7
+ * Narrow, Team-owned member-session composer. It deliberately uses the public
8
+ * input state/actions and trigger controller rather than the shipped InputBar;
9
+ * owner-provided overlay remains inside the card so the shared menu renders.
10
+ */
11
+ export function TeamMemberSessionComposer({ controller, inputActions, overlay, placeholder, disabled, blocked, stop, submitInput, useInput, useSession }) {
12
+ const input = useInput(state => state);
13
+ const session = useSession(state => state);
14
+ const textarea = useRef(null);
15
+ const editedByHuman = useRef(undefined);
16
+ const compactWasSubmitting = useRef(false);
17
+ const composing = useRef(false);
18
+ const [submissionNotice, setSubmissionNotice] = useState();
19
+ const draft = input?.draft ?? '';
20
+ const isLocked = disabled === true || blocked !== undefined || inputActions === undefined || input === undefined;
21
+ useEffect(() => {
22
+ if (isLocked)
23
+ return;
24
+ const active = document.activeElement;
25
+ if (active !== document.body && active?.closest('[aria-current="page"]') === null)
26
+ return;
27
+ textarea.current?.focus({ preventScroll: true });
28
+ }, [isLocked]);
29
+ useLayoutEffect(() => {
30
+ const element = textarea.current;
31
+ if (element === null)
32
+ return;
33
+ element.style.height = 'auto';
34
+ element.style.height = `${Math.min(element.scrollHeight, 336)}px`;
35
+ }, [draft]);
36
+ useEffect(() => {
37
+ if (input === undefined)
38
+ return;
39
+ if (input.phase === 'submitting' && input.claim?.token === '/compact') {
40
+ compactWasSubmitting.current = true;
41
+ return;
42
+ }
43
+ if (input.phase === 'claimed' && input.claim?.token === '/compact' && compactWasSubmitting.current) {
44
+ compactWasSubmitting.current = false;
45
+ setSubmissionNotice('Compact 未执行,请重试。');
46
+ return;
47
+ }
48
+ if (input.phase === 'plain')
49
+ compactWasSubmitting.current = false;
50
+ }, [input?.claim?.token, input?.phase]);
51
+ useEffect(() => {
52
+ if (input === undefined)
53
+ return;
54
+ const edit = editedByHuman.current;
55
+ if (edit === undefined || edit.draft !== input.draft)
56
+ return;
57
+ editedByHuman.current = undefined;
58
+ const hit = triggerHit(input.draft, edit.caret, input.draftRev);
59
+ // toggleSource intentionally closes its own already-open launcher; reset
60
+ // first so consecutive real DOM edits always refresh candidates, while a
61
+ // pick-driven machine draft update never reopens a menu.
62
+ controller.dismiss();
63
+ if (hit !== undefined)
64
+ controller.toggleSource(hit.trigger === '/' ? TEAM_COMMAND_SOURCE : TEAM_MEMBER_SOURCE, hit);
65
+ }, [controller, input?.draft, input?.draftRev]);
66
+ const onChange = (event) => {
67
+ const caret = event.currentTarget.selectionStart;
68
+ editedByHuman.current = { draft: event.currentTarget.value, caret };
69
+ setSubmissionNotice(undefined);
70
+ inputActions?.setDraft(event.currentTarget.value);
71
+ };
72
+ const submit = () => {
73
+ if (input === undefined || inputActions === undefined)
74
+ return;
75
+ if (!allowsMemberSessionSubmit(input.draft, input.claim?.token)) {
76
+ setSubmissionNotice('此 Member Session 仅支持 /compact 命令。');
77
+ return;
78
+ }
79
+ setSubmissionNotice(undefined);
80
+ submitInput(running ? 'steer' : 'queue');
81
+ };
82
+ const onKeyDown = (event) => {
83
+ // Shift+Enter is always a native newline, even while a candidate menu is
84
+ // open. Keep this ahead of IME/menu handling to match the resident bar.
85
+ if (event.key === 'Enter' && event.shiftKey)
86
+ return;
87
+ if (event.repeat || composing.current || event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229)
88
+ return;
89
+ const key = event.key === 'ArrowUp' ? 'up' : event.key === 'ArrowDown' ? 'down' : event.key === 'Escape' ? 'escape' : event.key === 'Enter' ? 'enter' : undefined;
90
+ if (key !== undefined) {
91
+ const outcome = controller.arbitrate(key, false);
92
+ if (outcome !== 'pass') {
93
+ event.preventDefault();
94
+ return;
95
+ }
96
+ }
97
+ if (event.key === 'Enter') {
98
+ event.preventDefault();
99
+ submit();
100
+ }
101
+ };
102
+ const running = session?.running ?? false;
103
+ const promptError = session?.promptError;
104
+ return _jsxs("div", { className: `${css.root} ${css.memberSessionRoot}`, "data-team-member-composer": "true", children: [(submissionNotice !== undefined || promptError !== null && promptError !== undefined) && (_jsx("p", { className: css.memberSessionStatus, role: "alert", children: submissionNotice ?? promptError?.error.message })), _jsxs("div", { className: `${css.card} ${css.memberSessionCard}`, "data-composer-card": "true", children: [_jsxs("div", { className: `${css.inputArea} ${css.memberSessionInputArea}`, children: [_jsx("textarea", { ref: textarea, value: draft, disabled: isLocked, placeholder: blocked?.reason ?? placeholder ?? '给成员发送直接消息', onChange: onChange, onKeyDown: onKeyDown, onCompositionStart: () => { composing.current = true; }, onCompositionEnd: () => { composing.current = false; } }), overlay] }), _jsx("div", { className: `${css.toolbar} ${css.memberSessionToolbar}`, children: _jsx("button", { className: `${css.sendButton} ${css.memberSessionPrimary}`, type: "button", "aria-label": running ? '停止生成' : '发送消息', disabled: isLocked || (!running && draft.trim() === ''), onClick: () => { if (running)
105
+ stop?.();
106
+ else
107
+ submit(); }, children: running ? _jsx(IconStopFill16, { size: 16 }) : _jsx(IconSendOutline16, { size: 16 }) }) })] })] });
108
+ }
109
+ function triggerHit(draft, caret, draftRev) {
110
+ const before = draft.slice(0, caret);
111
+ const leading = before.match(/^\s*\/([^\s]*)$/u);
112
+ if (leading !== null) {
113
+ const start = before.indexOf('/');
114
+ return { trigger: '/', query: leading[1], quoted: false, position: 'leading', span: { start, end: caret, draftRev } };
115
+ }
116
+ const at = before.lastIndexOf('@');
117
+ if (at < 0 || /[\p{L}\p{N}_]/u.test(before[at - 1] ?? '') || /\s/u.test(before.slice(at + 1)))
118
+ return undefined;
119
+ return { trigger: '@', query: before.slice(at + 1), quoted: false, position: 'inline', span: { start: at, end: caret, draftRev } };
120
+ }
@@ -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,+BAkG1M"}
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"}
@@ -3,7 +3,7 @@ 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, isPlainTextBody, memberHue, splitBrandedRefs, splitMentionNames, stripAttachmentLines } from "./team-formatters.js";
6
+ import { formatMessageTime, isSingleBrandedRef, memberHue, planMessageBody, 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
9
  export function TeamMessage({ senderName, memberId, human, body, occurredAt, mentionNames, senderTitle, grouped, attachments, loadAttachment, t, onOpenRef, onResolveTaskRefs, children }) {
@@ -13,23 +13,15 @@ export function TeamMessage({ senderName, memberId, human, body, occurredAt, men
13
13
  // Markdown keeps unmatched structured mentions in the trailing row.
14
14
  // The stored body carries machine-facing `[attachment] <path>` prompt lines;
15
15
  // humans see the attachment strip rendered from the message metadata instead.
16
- const displayBody = stripAttachmentLines(body) === '' ? body : stripAttachmentLines(body);
17
- const inline = (human || isPlainTextBody(displayBody)) && mentionNames !== undefined && mentionNames.length > 0
18
- ? splitMentionNames(displayBody, mentionNames)
19
- : undefined;
20
- const fallbackNames = inline === undefined ? (mentionNames ?? []) : inline.unmatched;
21
- const richAgentBody = !human && !isPlainTextBody(displayBody);
22
- // Non-Task branded refs retain the legacy fallback row. Task refs instead
23
- // stay at their authored prose position; unresolved refs remain literal.
24
- const fallbackRefs = richAgentBody && onOpenRef !== undefined
25
- ? splitBrandedRefs(displayBody).filter(segment => segment.ref !== undefined && !segment.ref.startsWith('task:')).map(segment => segment.ref)
26
- : [];
16
+ // One pure plan resolves the stored body into the rendering branch, the
17
+ // trailing fallback rows, and the literal Task refs that resolve in place.
18
+ const plan = planMessageBody(body, { human, ...(mentionNames === undefined ? {} : { mentionNames }), canOpenRefs: onOpenRef !== undefined });
19
+ const displayBody = plan.displayBody;
20
+ const { richAgentBody } = plan;
27
21
  // Resolved refs re-label once the Host lookup lands; the version token
28
22
  // refreshes literal links and rendered Markdown prose.
29
23
  const refVersion = useResolvedTaskRefVersion();
30
- const bodyTaskRefs = onOpenRef === undefined || richAgentBody ? [] : splitBrandedRefs(displayBody)
31
- .filter(segment => segment.ref !== undefined && segment.ref.startsWith('task:'))
32
- .map(segment => segment.ref);
24
+ const bodyTaskRefs = plan.taskRefs;
33
25
  const bodyTaskRefKey = bodyTaskRefs.join(',');
34
26
  useEffect(() => {
35
27
  if (onResolveTaskRefs === undefined || bodyTaskRefKey === '')
@@ -43,20 +35,24 @@ export function TeamMessage({ senderName, memberId, human, body, occurredAt, men
43
35
  if (!richAgentBody || root === null || onOpenRef === undefined)
44
36
  return;
45
37
  const textNodes = markdownProseTextNodes(root);
46
- const taskRefs = [...new Set(textNodes.flatMap(node => splitBrandedRefs(node.data)
38
+ const styledRefCodes = markdownStyledRefCodeElements(root);
39
+ const taskRefs = [...new Set([...textNodes.map(node => node.data), ...styledRefCodes.map(code => code.textContent ?? '')]
40
+ .flatMap(text => splitBrandedRefs(text)
47
41
  .filter(segment => segment.ref?.startsWith('task:') === true)
48
42
  .map(segment => segment.ref)))];
49
43
  if (onResolveTaskRefs !== undefined && taskRefs.length > 0)
50
44
  void resolveUnknownTaskRefs(taskRefs, onResolveTaskRefs);
45
+ for (const code of styledRefCodes)
46
+ renderResolvedMarkdownCodeRef(code, taskLabel);
51
47
  for (const node of textNodes)
52
- renderResolvedMarkdownTaskRefs(node, taskLabel);
48
+ renderResolvedMarkdownText(node, mentionNames ?? [], taskLabel);
53
49
  for (const button of root.querySelectorAll('button[data-task-ref]')) {
54
50
  const taskRef = button.dataset.taskRef;
55
51
  const hit = taskRef === undefined ? undefined : cachedResolvedTaskRef(taskRef);
56
52
  if (taskRef !== undefined && hit !== undefined)
57
53
  button.textContent = taskLabel(hit.taskNumber);
58
54
  }
59
- }, [richAgentBody, onOpenRef, onResolveTaskRefs, refVersion, taskLabel]);
55
+ }, [richAgentBody, onOpenRef, onResolveTaskRefs, refVersion, taskLabel, mentionNames]);
60
56
  useEffect(() => {
61
57
  const root = markdownRef.current;
62
58
  if (!richAgentBody || root === null || onOpenRef === undefined)
@@ -75,13 +71,13 @@ export function TeamMessage({ senderName, memberId, human, body, occurredAt, men
75
71
  root.addEventListener('click', openTask);
76
72
  return () => { root.removeEventListener('click', openTask); };
77
73
  }, [richAgentBody, onOpenRef]);
78
- return (_jsxs("article", { className: css.messageRow, "data-human": human || undefined, "data-grouped": grouped || undefined, children: [_jsx("div", { className: css.messageIdentity, style: avatarStyle, "aria-hidden": "true", children: senderName.replace('@', '').slice(0, 1).toUpperCase() }), _jsxs("div", { className: css.messageBody, children: [!grouped && (_jsxs("div", { className: css.nameRow, children: [_jsx("strong", { ...(senderTitle === undefined ? {} : { title: senderTitle }), children: senderName }), occurredAt !== undefined && _jsx("span", { className: css.messageTime, children: formatMessageTime(occurredAt) })] })), inline !== undefined
79
- ? _jsx("div", { className: css.messageText, children: inline.segments.map((segment, index) => segment.mention
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
80
76
  ? _jsx("span", { className: css.mention, children: segment.text }, index)
81
77
  : _jsx(Fragment, { children: renderRefs(segment.text, onOpenRef, taskLabel) }, index)) })
82
- : human || (onOpenRef !== undefined && isPlainTextBody(displayBody) && splitBrandedRefs(displayBody).some(segment => segment.ref !== undefined))
78
+ : plan.render === 'literal'
83
79
  ? _jsx("div", { className: css.messageText, children: onOpenRef === undefined ? _jsx(MessageText, { text: displayBody }) : renderRefs(displayBody, onOpenRef, taskLabel) })
84
- : _jsx("div", { ref: markdownRef, className: css.messageMarkdown, children: _jsx(MarkdownText, { text: displayBody }, `${displayBody}:${onOpenRef === undefined ? 'literal' : 'refs'}`) }), (fallbackNames.length > 0 || fallbackRefs.length > 0) && (_jsxs("div", { className: css.mentionsRow, children: [fallbackNames.map(name => _jsxs("span", { className: css.mention, children: ["@", name] }, name)), fallbackRefs.map(ref => {
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 => {
85
81
  const resolved = cachedResolvedTaskRef(ref);
86
82
  const label = resolved !== undefined && ref.startsWith('task:') ? taskLabel(resolved.taskNumber) : ref;
87
83
  return _jsx("button", { type: "button", className: css.refLink, title: ref, onClick: () => { onOpenRef(ref); }, children: label }, ref);
@@ -93,38 +89,82 @@ function markdownProseTextNodes(root) {
93
89
  const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
94
90
  for (let node = walker.nextNode(); node !== null; node = walker.nextNode()) {
95
91
  const parent = node.parentElement;
96
- if (parent !== null && parent.closest('code, pre, a, button') === null)
97
- nodes.push(node);
92
+ if (parent === null)
93
+ continue;
94
+ if (parent.closest('code, pre, a, button') !== null)
95
+ continue;
96
+ // Mention chips from an earlier pass stay untouched, or each effect rerun
97
+ // would wrap another chip around the last one.
98
+ if (css.mention !== undefined && parent.closest(`[class~="${css.mention}"]`) !== null)
99
+ continue;
100
+ nodes.push(node);
98
101
  }
99
102
  return nodes;
100
103
  }
101
- /** Replace resolved refs in one prose text node without changing Markdown structure. */
102
- function renderResolvedMarkdownTaskRefs(node, taskLabel) {
103
- const segments = splitBrandedRefs(node.data);
104
- if (!segments.some(segment => segment.ref?.startsWith('task:') === true && cachedResolvedTaskRef(segment.ref) !== undefined))
104
+ /** Code spans whose entire content is one branded ref: model styling around a ref, not code. */
105
+ function markdownStyledRefCodeElements(root) {
106
+ const elements = [];
107
+ for (const code of root.querySelectorAll('code')) {
108
+ if (code.closest('pre, a, button') !== null)
109
+ continue;
110
+ if (!isSingleBrandedRef(code.textContent ?? ''))
111
+ continue;
112
+ elements.push(code);
113
+ }
114
+ return elements;
115
+ }
116
+ /** One resolved Task-ref chip built outside React, matching the styled ref link. */
117
+ function resolvedTaskRefButton(taskRef, label) {
118
+ const button = document.createElement('button');
119
+ button.type = 'button';
120
+ if (css.refLink !== undefined)
121
+ button.className = css.refLink;
122
+ button.dataset.taskRef = taskRef;
123
+ button.title = taskRef;
124
+ button.textContent = label;
125
+ return button;
126
+ }
127
+ /** Replace a whole-ref code span with its resolved Task chip once known. */
128
+ function renderResolvedMarkdownCodeRef(code, taskLabel) {
129
+ const segments = splitBrandedRefs((code.textContent ?? '').trim());
130
+ const segment = segments.length === 1 ? segments[0] : undefined;
131
+ const taskRef = segment?.ref?.startsWith('task:') === true ? segment.ref : undefined;
132
+ const resolved = taskRef === undefined ? undefined : cachedResolvedTaskRef(taskRef);
133
+ if (taskRef === undefined || resolved === undefined)
105
134
  return;
135
+ code.replaceWith(resolvedTaskRefButton(taskRef, taskLabel(resolved.taskNumber)));
136
+ }
137
+ /** Replace resolved Task refs and structured mention handles in one prose text node without changing Markdown structure. */
138
+ function renderResolvedMarkdownText(node, mentionNames, taskLabel) {
139
+ let changed = false;
106
140
  const fragment = document.createDocumentFragment();
107
- for (const segment of segments) {
108
- const taskRef = segment.ref?.startsWith('task:') === true ? segment.ref : undefined;
109
- if (taskRef === undefined) {
110
- fragment.append(segment.text);
141
+ for (const refSegment of splitBrandedRefs(node.data)) {
142
+ if (refSegment.ref === undefined) {
143
+ for (const mentionSegment of splitMentionNames(refSegment.text, mentionNames).segments) {
144
+ if (!mentionSegment.mention) {
145
+ fragment.append(mentionSegment.text);
146
+ continue;
147
+ }
148
+ changed = true;
149
+ const chip = document.createElement('span');
150
+ if (css.mention !== undefined)
151
+ chip.className = css.mention;
152
+ chip.textContent = mentionSegment.text;
153
+ fragment.append(chip);
154
+ }
111
155
  continue;
112
156
  }
113
- const resolved = cachedResolvedTaskRef(taskRef);
157
+ const taskRef = refSegment.ref.startsWith('task:') === true ? refSegment.ref : undefined;
158
+ const resolved = taskRef === undefined ? undefined : cachedResolvedTaskRef(taskRef);
114
159
  if (resolved === undefined) {
115
- fragment.append(segment.text);
160
+ fragment.append(refSegment.text);
116
161
  continue;
117
162
  }
118
- const button = document.createElement('button');
119
- button.type = 'button';
120
- if (css.refLink !== undefined)
121
- button.className = css.refLink;
122
- button.dataset.taskRef = taskRef;
123
- button.title = taskRef;
124
- button.textContent = taskLabel(resolved.taskNumber);
125
- fragment.append(button);
163
+ changed = true;
164
+ fragment.append(resolvedTaskRefButton(taskRef, taskLabel(resolved.taskNumber)));
126
165
  }
127
- node.replaceWith(fragment);
166
+ if (changed)
167
+ node.replaceWith(fragment);
128
168
  }
129
169
  /** Render one literal text run, linkifying branded refs when navigation is available. */
130
170
  function renderRefs(text, onOpenRef, taskLabel) {
@@ -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;AAa/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,+BAgkBxD"}
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"}
@@ -6,9 +6,10 @@ import { TeamPresenceDot } from "./TeamPresenceDot.js";
6
6
  import { TeamMessage } from "./TeamMessage.js";
7
7
  import { TeamRunDivider } from "./TeamRunDivider.js";
8
8
  import { formatActivity, formatClaimState, formatTaskStatus, formatTaskTitle, mentionNamesOf } from "./team-formatters.js";
9
+ import { mintRequestId } from "./requests.js";
9
10
  import { daySeparatorLabel, isRunGap, timelineDayKey } from "./team-separators.js";
10
11
  import { useTimelineScroll } from "./timeline-scroll.js";
11
- import { rememberResolvedTaskRef } from "./task-refs.js";
12
+ import { hostTaskRefLookup, jumpToTaskThread } from "./task-refs.js";
12
13
  import { bytesToBase64 } from "./attachment-preview.js";
13
14
  import css from './conversation.module.css';
14
15
  import threadCss from './thread.module.css';
@@ -64,7 +65,7 @@ export function TeamThreadPage(props) {
64
65
  const mountedRef = useRef(false);
65
66
  const currentFactsRef = useRef([]);
66
67
  const sequenceRef = useRef(0);
67
- const readRequestIdRef = useRef(crypto.randomUUID());
68
+ const readRequestIdRef = useRef(mintRequestId());
68
69
  const mutationRequests = useRef(new Map());
69
70
  const threadLastFact = currentFacts[currentFacts.length - 1];
70
71
  const timeline = useTimelineScroll(`${currentFacts.length}:${olderFacts.length}:${threadLastFact === undefined ? '' : factKey(threadLastFact)}`);
@@ -85,7 +86,7 @@ export function TeamThreadPage(props) {
85
86
  if (!mountedRef.current)
86
87
  return false;
87
88
  if (newRequest)
88
- readRequestIdRef.current = crypto.randomUUID();
89
+ readRequestIdRef.current = mintRequestId();
89
90
  const sequence = sequenceRef.current + 1;
90
91
  sequenceRef.current = sequence;
91
92
  setLoading(true);
@@ -293,7 +294,7 @@ export function TeamThreadPage(props) {
293
294
  const mentionHandlesMap = useMemo(() => new Map(members.map(status => [status.member.memberId, status.member.handle.replace(/^@/, '')])), [members]);
294
295
  const metadata = useMemo(() => readMeta(readFacts), [readFacts]);
295
296
  const unreadIndex = useMemo(() => {
296
- const all = mergeFacts([messageFact(activeProjection?.anchor ?? { messageRef: 'missing', sequence: 0 }, activeProjection?.anchorMentions), ...readFacts.map(fact => fact.fact), ...currentFacts]);
297
+ const all = mergeFacts(...(activeProjection === undefined ? [] : [[messageFact(activeProjection.anchor, activeProjection.anchorMentions)]]), readFacts.map(fact => fact.fact), currentFacts);
297
298
  return all.findIndex(fact => metadata.get(factKey(fact))?.unread === true);
298
299
  }, [activeProjection?.anchor, readFacts, currentFacts, metadata]);
299
300
  const memberName = (memberId) => {
@@ -312,24 +313,10 @@ export function TeamThreadPage(props) {
312
313
  selectChannel(ref);
313
314
  if (ref.startsWith('task:') && ref !== taskRef) {
314
315
  // Another Task cited here: resolve its home Channel and jump.
315
- void resolveTaskRefs({ workspaceId, taskRefs: [ref] })
316
- .then(result => {
317
- if (!result.ok)
318
- return;
319
- const hit = result.value.resolved[0];
320
- if (hit !== undefined)
321
- selectThread(hit.taskRef, hit.threadRef, hit.channelRef, hit.taskNumber);
322
- });
316
+ jumpToTaskThread(resolveTaskRefs, workspaceId, ref, selectThread);
323
317
  }
324
318
  };
325
- const lookupTaskRefs = (refs) => resolveTaskRefs({ workspaceId, taskRefs: refs })
326
- .then(result => {
327
- if (!result.ok)
328
- return [];
329
- for (const entry of result.value.resolved)
330
- rememberResolvedTaskRef(entry);
331
- return result.value.resolved;
332
- });
319
+ const lookupTaskRefs = hostTaskRefLookup(resolveTaskRefs, workspaceId);
333
320
  const renderFact = (fact, grouped = false) => {
334
321
  if (fact.kind === 'message') {
335
322
  const sender = memberName(fact.message.sender);
@@ -390,7 +377,7 @@ export function TeamThreadPage(props) {
390
377
  setPending(true);
391
378
  setError(undefined);
392
379
  const key = `task:${action}`;
393
- const requestId = mutationRequests.current.get(key) ?? crypto.randomUUID();
380
+ const requestId = mutationRequests.current.get(key) ?? mintRequestId();
394
381
  mutationRequests.current.set(key, requestId);
395
382
  try {
396
383
  const result = await changeTask({ requestId, workspaceId, taskRef: task.taskRef, action, baseRevision: thread.revision });
@@ -431,7 +418,7 @@ export function TeamThreadPage(props) {
431
418
  const sendReply = async () => {
432
419
  if (pending || task === undefined || thread === undefined || draft.trim() === '')
433
420
  return;
434
- const id = replyRequestId ?? crypto.randomUUID();
421
+ const id = replyRequestId ?? mintRequestId();
435
422
  setReplyRequestId(id);
436
423
  setPending(true);
437
424
  setError(undefined);
@@ -441,7 +428,7 @@ export function TeamThreadPage(props) {
441
428
  const attachmentIds = [];
442
429
  for (const file of pendingFiles) {
443
430
  const uploaded = await putAttachment({
444
- requestId: crypto.randomUUID(),
431
+ requestId: mintRequestId(),
445
432
  workspaceId,
446
433
  name: file.name,
447
434
  mediaType: file.type === '' ? undefined : file.type,
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAa,MAAM,wCAAwC,CAAA;AA8BtF,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEhD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAK5C,OAAO,EAAU,KAAK,OAAO,EAAE,MAAM,cAAc,CAAA;AAEnD,YAAY,EAAE,QAAQ,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAA;AAC9F,YAAY,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAIhD,eAAO,MAAM,MAAM,UAElB,CAAA;AAED,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,kBAAkB;QAC1B,IAAI,EAAE,OAAO,CAAA;KACd;CACF;AAED,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf,cAAc,EAAE,cAAc,CAAA;QAC9B,UAAU,EAAE,cAAc,CAAA;KAC3B;CACF;AAoJD,wBAAsB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAI7D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAa,MAAM,wCAAwC,CAAA;AA+BtF,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEhD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAO5C,OAAO,EAAU,KAAK,OAAO,EAAE,MAAM,cAAc,CAAA;AAEnD,YAAY,EAAE,QAAQ,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAA;AAC9F,YAAY,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAIhD,eAAO,MAAM,MAAM,UAElB,CAAA;AAED,OAAO,QAAQ,kCAAkC,CAAC;IAChD,UAAU,kBAAkB;QAC1B,IAAI,EAAE,OAAO,CAAA;KACd;CACF;AAED,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,OAAO;QACf,cAAc,EAAE,cAAc,CAAA;QAC9B,UAAU,EAAE,cAAc,CAAA;KAC3B;CACF;AAuND,wBAAsB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAI7D"}
@@ -6,11 +6,13 @@ import { TeamFooterAction } from "./TeamFooterAction.js";
6
6
  import { TeamSettings } from "./TeamSettings.js";
7
7
  import { TeamConversation } from "./TeamConversation.js";
8
8
  import { TeamWorkspaceBrowser } from "./TeamWorkspaceBrowser.js";
9
+ import { TeamMemberSessionComposer } from "./TeamMemberSessionComposer.js";
10
+ import { admitTeamCompact, createTeamMemberSessionSources } from "./member-session-input.js";
9
11
  import { en, zh } from "./locales.js";
10
12
  export { TeamNavigation } from "./navigation.js";
11
13
  const NS = 'team';
12
14
  export const inject = [
13
- 'slots', 'workspaces', 'locale', 'remote', 'sessions', 'connection',
15
+ 'slots', 'workspaces', 'locale', 'remote', 'sessions', 'connection', 'conversation', 'inputTriggers',
14
16
  ];
15
17
  function registerModeShadow(ctx, navigation, changes, drafts, name, component, extraInject) {
16
18
  // Remote bindings shared by every Team slot; surface-specific entries extend it below.
@@ -93,6 +95,49 @@ function registerModeShadow(ctx, navigation, changes, drafts, name, component, e
93
95
  };
94
96
  });
95
97
  }
98
+ function registerMemberSessionComposer(ctx, navigation) {
99
+ ctx.slots.inject('conversation.composer.bar', () => {
100
+ let dispose;
101
+ let registeredSessionId;
102
+ const reconcile = () => {
103
+ const memberSessionId = navigation.getSnapshot().memberSessionId;
104
+ const active = navigation.getSnapshot().mode === 'team' && memberSessionId !== undefined;
105
+ if (dispose !== undefined && (!active || registeredSessionId !== memberSessionId)) {
106
+ dispose();
107
+ dispose = undefined;
108
+ registeredSessionId = undefined;
109
+ }
110
+ if (active && dispose === undefined) {
111
+ registeredSessionId = memberSessionId;
112
+ dispose = ctx.slots.register({
113
+ name: 'conversation.composer.bar',
114
+ priority: -100,
115
+ // The registration may remain live during a session transition, but
116
+ // it elects only when the shell's current Session is the embedded
117
+ // Member. This avoids taking an ordinary Session if selection races.
118
+ select: () => ctx.sessions.list.getSnapshot().current === memberSessionId ? {} : null,
119
+ inject: (sessionId) => {
120
+ const actx = ctx.sessions.scope(sessionId);
121
+ if (actx === undefined)
122
+ throw new Error(`agent-team: Member session ${String(sessionId)} has no client scope`);
123
+ const sessions = ctx.sessions;
124
+ return {
125
+ controller: ctx.get('inputTriggers').sessionOf(actx),
126
+ stop: () => { void sessions.sessionOf(actx)?.cancel(); },
127
+ submitInput: (mode) => ctx.conversation.input.for(actx).submit(mode),
128
+ };
129
+ },
130
+ }, TeamMemberSessionComposer);
131
+ }
132
+ };
133
+ const offNavigation = navigation.subscribe(reconcile);
134
+ reconcile();
135
+ return () => {
136
+ offNavigation();
137
+ dispose?.();
138
+ };
139
+ });
140
+ }
96
141
  function applyUi(ctx) {
97
142
  ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'agent-team: dictionaries');
98
143
  const navigation = new TeamNavigation();
@@ -106,6 +151,30 @@ function applyUi(ctx) {
106
151
  void disposeDrafts();
107
152
  }, 'agent-team: navigation service');
108
153
  const changes = new TeamChangeStream((request, signal) => ctx.remote.agentTeam.changes(request, signal));
154
+ const triggerSources = createTeamMemberSessionSources({
155
+ isEmbeddedMemberSession: sessionId => {
156
+ const snapshot = navigation.getSnapshot();
157
+ return snapshot.mode === 'team' && snapshot.memberSessionId === sessionId && snapshot.workspaceId !== undefined;
158
+ },
159
+ members: async () => {
160
+ const workspaceId = navigation.getSnapshot().workspaceId;
161
+ if (workspaceId === undefined)
162
+ return [];
163
+ const result = await ctx.remote.agentTeam.members({ workspaceId });
164
+ return result.ok ? result.value : [];
165
+ },
166
+ executeCompact: async (sessionId) => {
167
+ const actx = ctx.sessions.scope(sessionId);
168
+ const session = actx === undefined ? undefined : ctx.sessions.sessionOf(actx);
169
+ return session === undefined ? { kind: 'error', text: 'current Member Session is unavailable' } : admitTeamCompact(session);
170
+ },
171
+ });
172
+ const inputTriggers = ctx.get('inputTriggers');
173
+ ctx.effect(() => {
174
+ const disposers = triggerSources.map(source => inputTriggers.registerSource(source));
175
+ return () => { for (const dispose of disposers)
176
+ dispose(); };
177
+ }, 'agent-team: Member session trigger sources');
109
178
  const loadMemberGroups = async () => {
110
179
  const workspaces = ctx.workspaces.list.getSnapshot().items;
111
180
  const groups = await Promise.all(workspaces.map(async (workspace) => {
@@ -144,6 +213,7 @@ function applyUi(ctx) {
144
213
  }, TeamFooterAction));
145
214
  registerModeShadow(ctx, navigation, changes, drafts, 'sidebar.workspaces', TeamWorkspaceBrowser);
146
215
  registerModeShadow(ctx, navigation, changes, drafts, 'conversation', TeamConversation);
216
+ registerMemberSessionComposer(ctx, navigation);
147
217
  registerModeShadow(ctx, navigation, changes, drafts, 'sidebar.settings', TeamSettings, () => ({ loadMemberGroups }));
148
218
  }
149
219
  export async function apply(ctx) {
@@ -0,0 +1,22 @@
1
+ import type { AgentTeamClientMemberStatus } from '@wowyuarm/dsh-agent-team/types';
2
+ import type { ISession } from '@deepseek-ai/dsh-client-runtime/client';
3
+ import type { ClientSessionContext, InputTriggerSource, SubmitOutcome } from '@deepseek-ai/dsh-client-ui-input-trigger/client';
4
+ export declare const TEAM_COMMAND_SOURCE = "agent-team-command";
5
+ export declare const TEAM_MEMBER_SOURCE = "agent-team-member";
6
+ export interface TeamMemberSessionInputOptions {
7
+ readonly isEmbeddedMemberSession: (sessionId: ClientSessionContext['sessionId']) => boolean;
8
+ readonly members: () => Promise<readonly AgentTeamClientMemberStatus[]>;
9
+ readonly executeCompact: (sessionId: ClientSessionContext['sessionId']) => Promise<SubmitOutcome>;
10
+ }
11
+ /** Preserve the public Session command admission contract, not handler outcome. */
12
+ export declare function admitTeamCompact(session: Pick<ISession, 'command'>): Promise<SubmitOutcome>;
13
+ /**
14
+ * The two Team-only sources live in the shared public registry, but their
15
+ * candidate plane is inert for every ordinary Session. The member source owns
16
+ * its stable ref→label cache so serialization never falls back to guessing a
17
+ * handle from plain text.
18
+ */
19
+ export declare function createTeamMemberSessionSources(options: TeamMemberSessionInputOptions): readonly InputTriggerSource[];
20
+ /** Unknown leading slash must never fall into the shipped global palette. */
21
+ export declare function allowsMemberSessionSubmit(draft: string, claimToken: string | undefined): boolean;
22
+ //# sourceMappingURL=member-session-input.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"member-session-input.d.ts","sourceRoot":"","sources":["../../../src/client/member-session-input.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,2BAA2B,EAAqB,MAAM,gCAAgC,CAAA;AACpG,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wCAAwC,CAAA;AACtE,OAAO,KAAK,EACV,oBAAoB,EAAgB,kBAAkB,EAAmB,aAAa,EACvF,MAAM,iDAAiD,CAAA;AAExD,eAAO,MAAM,mBAAmB,uBAAuB,CAAA;AACvD,eAAO,MAAM,kBAAkB,sBAAsB,CAAA;AAErD,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,uBAAuB,EAAE,CAAC,SAAS,EAAE,oBAAoB,CAAC,WAAW,CAAC,KAAK,OAAO,CAAA;IAC3F,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,SAAS,2BAA2B,EAAE,CAAC,CAAA;IACvE,QAAQ,CAAC,cAAc,EAAE,CAAC,SAAS,EAAE,oBAAoB,CAAC,WAAW,CAAC,KAAK,OAAO,CAAC,aAAa,CAAC,CAAA;CAClG;AAED,mFAAmF;AACnF,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,GAAG,OAAO,CAAC,aAAa,CAAC,CAIjG;AAED;;;;;GAKG;AACH,wBAAgB,8BAA8B,CAAC,OAAO,EAAE,6BAA6B,GAAG,SAAS,kBAAkB,EAAE,CAiEpH;AAED,6EAA6E;AAC7E,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAGhG"}