@wowyuarm/dsh-agent-team 0.1.2 → 0.1.4

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 (90) hide show
  1. package/README.md +8 -1
  2. package/README.zh.md +9 -1
  3. package/package.json +12 -3
  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/attachments.js +11 -12
  7. package/packages/agent-team/lib/auto-compaction.js +163 -0
  8. package/packages/agent-team/lib/index.js +252 -139
  9. package/packages/agent-team/lib/ledger.js +344 -170
  10. package/packages/agent-team/lib/recovery.js +30 -35
  11. package/packages/agent-team/lib/spec.js +27 -11
  12. package/packages/agent-team/lib/typert.host.js +197 -88
  13. package/packages/agent-team/lib/typert.remote-client.d.ts +3 -1
  14. package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
  15. package/packages/agent-team/lib/typert.remote-client.js +139 -59
  16. package/packages/agent-team/lib/types/attachments.d.ts +2 -2
  17. package/packages/agent-team/lib/types/attachments.d.ts.map +1 -1
  18. package/packages/agent-team/lib/types/auto-compaction.d.ts +38 -0
  19. package/packages/agent-team/lib/types/auto-compaction.d.ts.map +1 -0
  20. package/packages/agent-team/lib/types/index.d.ts +52 -19
  21. package/packages/agent-team/lib/types/index.d.ts.map +1 -1
  22. package/packages/agent-team/lib/types/ledger.d.ts +29 -2
  23. package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
  24. package/packages/agent-team/lib/types/recovery.d.ts +15 -16
  25. package/packages/agent-team/lib/types/recovery.d.ts.map +1 -1
  26. package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
  27. package/packages/agent-team/lib/types/types.d.ts +69 -30
  28. package/packages/agent-team/lib/types/types.d.ts.map +1 -1
  29. package/packages/agent-team/preset/team-member/agent.cordis.yml +7 -2
  30. package/packages/client-agent-team/README.md +2 -1
  31. package/packages/client-agent-team/README.zh.md +2 -1
  32. package/packages/client-agent-team/lib/client.js +1577 -863
  33. package/packages/client-agent-team/lib/client.js.map +1 -1
  34. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
  35. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +35 -174
  36. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
  37. package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +20 -30
  38. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -1
  39. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +3 -2
  40. package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts +6 -1
  41. package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -1
  42. package/packages/client-agent-team/lib/types/client/TeamComposer.js +4 -5
  43. package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts +1 -1
  44. package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts.map +1 -1
  45. package/packages/client-agent-team/lib/types/client/TeamConversation.js +3 -3
  46. package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts +34 -0
  47. package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts.map +1 -0
  48. package/packages/client-agent-team/lib/types/client/TeamMemberEditor.js +169 -0
  49. package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.d.ts +17 -0
  50. package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.d.ts.map +1 -0
  51. package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.js +120 -0
  52. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts +4 -1
  53. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
  54. package/packages/client-agent-team/lib/types/client/TeamMessage.js +94 -47
  55. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts +2 -1
  56. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
  57. package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +81 -39
  58. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts.map +1 -1
  59. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +2 -1
  60. package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
  61. package/packages/client-agent-team/lib/types/client/index.js +72 -1
  62. package/packages/client-agent-team/lib/types/client/locales.d.ts +24 -0
  63. package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
  64. package/packages/client-agent-team/lib/types/client/locales.js +24 -0
  65. package/packages/client-agent-team/lib/types/client/member-session-input.d.ts +22 -0
  66. package/packages/client-agent-team/lib/types/client/member-session-input.d.ts.map +1 -0
  67. package/packages/client-agent-team/lib/types/client/member-session-input.js +98 -0
  68. package/packages/client-agent-team/lib/types/client/navigation.d.ts +1 -1
  69. package/packages/client-agent-team/lib/types/client/navigation.d.ts.map +1 -1
  70. package/packages/client-agent-team/lib/types/client/navigation.js +12 -6
  71. package/packages/client-agent-team/lib/types/client/requests.d.ts +16 -0
  72. package/packages/client-agent-team/lib/types/client/requests.d.ts.map +1 -0
  73. package/packages/client-agent-team/lib/types/client/requests.js +22 -0
  74. package/packages/client-agent-team/lib/types/client/slots.d.ts +2 -1
  75. package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
  76. package/packages/client-agent-team/lib/types/client/task-refs.d.ts +6 -1
  77. package/packages/client-agent-team/lib/types/client/task-refs.d.ts.map +1 -1
  78. package/packages/client-agent-team/lib/types/client/task-refs.js +19 -0
  79. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +52 -3
  80. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts.map +1 -1
  81. package/packages/client-agent-team/lib/types/client/team-formatters.js +74 -4
  82. package/packages/client-agent-team/lib/types/client/team-membership.d.ts.map +1 -1
  83. package/packages/client-agent-team/lib/types/client/team-membership.js +2 -1
  84. package/packages/tool-agent-team/README.md +7 -7
  85. package/packages/tool-agent-team/README.zh.md +7 -7
  86. package/packages/tool-agent-team/lib/index.js +38 -28
  87. package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
  88. package/packages/client-agent-team/lib/types/client/TeamInboxPanel.d.ts +0 -13
  89. package/packages/client-agent-team/lib/types/client/TeamInboxPanel.d.ts.map +0 -1
  90. package/packages/client-agent-team/lib/types/client/TeamInboxPanel.js +0 -90
@@ -0,0 +1,169 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from 'react';
3
+ import { Button, IconChevronDownOutline14, Input, Menu, Modal } from '@deepseek-ai/dsh-client-ui-primitives';
4
+ import { useChannelMembership } from "./team-membership.js";
5
+ import { mintRequestId } from "./requests.js";
6
+ import createCss from './create.module.css';
7
+ import css from './sidebar.module.css';
8
+ /** Model option key inside one editor; opaque and resolved against the loaded groups. */
9
+ function modelKey(provider, model) {
10
+ return `${provider}\u0000${model}`;
11
+ }
12
+ /**
13
+ * Shared provider/model dropdown for the create and edit forms. The option
14
+ * list rides the shared Menu primitive (one leading "follow Host default"
15
+ * row, then non-selectable provider headings) with a capped, internally
16
+ * scrolling card so growing model catalogs cannot stretch the dialog.
17
+ */
18
+ export function ModelPickerField({ model, onModelChange, loadModels, disabled, t }) {
19
+ const [groups, setGroups] = useState();
20
+ const [modelsError, setModelsError] = useState();
21
+ const [open, setModelOpen] = useState(false);
22
+ const [effortOpen, setEffortOpen] = useState(false);
23
+ useEffect(() => {
24
+ let mounted = true;
25
+ void loadModels().then(result => {
26
+ if (!mounted)
27
+ return;
28
+ if (result.ok) {
29
+ setGroups(result.value.groups);
30
+ setModelsError(undefined);
31
+ }
32
+ else {
33
+ setModelsError(result.error.message);
34
+ }
35
+ });
36
+ return () => { mounted = false; };
37
+ }, [loadModels]);
38
+ const items = [{ id: '', label: t('modelFollowDefault') }];
39
+ const byKey = new Map();
40
+ for (const group of groups ?? []) {
41
+ items.push({ type: 'label', id: `model-group:${group.id}`, text: group.name });
42
+ for (const entry of group.models) {
43
+ const key = modelKey(group.id, entry.id);
44
+ byKey.set(key, { provider: group.id, id: entry.id, name: entry.name, efforts: entry.reasoning?.efforts ?? [] });
45
+ items.push({ id: key, label: entry.name });
46
+ }
47
+ }
48
+ const selectedModelKey = model === undefined ? '' : modelKey(model.provider, model.model);
49
+ const triggerLabel = model === undefined
50
+ ? t('modelFollowDefault')
51
+ : byKey.get(selectedModelKey)?.name ?? `${model.provider} / ${model.model}`;
52
+ // The effort sub-row only makes sense for a pinned model with adapter-exposed
53
+ // efforts; following the Host default inherits the operator's whole selection.
54
+ const efforts = model === undefined ? [] : byKey.get(selectedModelKey)?.efforts ?? [];
55
+ const effortItems = [{ id: '', label: t('effortFollowDefault') }, ...efforts.map(effort => ({ id: effort.id, label: effort.name }))];
56
+ const selectedEffort = model?.reasoningEffort ?? '';
57
+ const effortTriggerLabel = model === undefined || selectedEffort === ''
58
+ ? t('effortFollowDefault')
59
+ : efforts.find(effort => effort.id === selectedEffort)?.name ?? selectedEffort;
60
+ return _jsxs("div", { className: createCss.field, children: [_jsx("span", { children: t('memberModel') }), groups === undefined && modelsError === undefined && _jsx("small", { className: css.editHint, children: t('modelsLoading') }), modelsError !== undefined && _jsx("small", { className: css.editHint, children: t('modelsLoadFailed', { message: modelsError }) }), groups !== undefined && (_jsx(Menu, { open: open, portal: true, className: createCss.menuCap, items: items, selectedId: selectedModelKey, onSelect: key => {
61
+ setModelOpen(false);
62
+ const choice = byKey.get(key);
63
+ onModelChange(choice === undefined ? undefined : { provider: choice.provider, model: choice.id });
64
+ }, onClose: () => { setModelOpen(false); }, anchor: _jsxs("button", { type: "button", className: createCss.selectTrigger, "aria-label": t('memberModel'), "aria-haspopup": "listbox", "aria-expanded": open, disabled: disabled, onClick: () => { setModelOpen(value => !value); }, children: [_jsx("span", { className: createCss.selectValue, children: triggerLabel }), _jsx("span", { className: `${createCss.chevron} ${open ? createCss.chevronOpen : ''}`, "aria-hidden": true, children: _jsx(IconChevronDownOutline14, {}) })] }) })), model !== undefined && efforts.length > 0 && (_jsx(Menu, { open: effortOpen, portal: true, className: createCss.menuCap, items: effortItems, selectedId: selectedEffort, onSelect: key => {
65
+ setEffortOpen(false);
66
+ onModelChange(key === ''
67
+ ? { provider: model.provider, model: model.model }
68
+ : { provider: model.provider, model: model.model, reasoningEffort: key });
69
+ }, onClose: () => { setEffortOpen(false); }, anchor: _jsxs("button", { type: "button", className: createCss.selectTrigger, "aria-label": t('reasoningEffort'), "aria-haspopup": "listbox", "aria-expanded": effortOpen, disabled: disabled, onClick: () => { setEffortOpen(value => !value); }, children: [_jsx("span", { className: createCss.selectValue, children: `${t('reasoningEffort')} · ${effortTriggerLabel}` }), _jsx("span", { className: `${createCss.chevron} ${effortOpen ? createCss.chevronOpen : ''}`, "aria-hidden": true, children: _jsx(IconChevronDownOutline14, {}) })] }) }))] });
70
+ }
71
+ /**
72
+ * Agent editor: handle, description, and per-Member model selection commit
73
+ * through one durable update; Channel membership below keeps its own
74
+ * immediate add/remove flow.
75
+ */
76
+ export function AgentEditorDialog({ status, channels, loadChannels, updateMember, joinChannel, removeChannelMember, loadModels, onCommitted, onClose, t }) {
77
+ const memberId = status.member.memberId;
78
+ const [handle, setHandle] = useState(status.member.handle);
79
+ const [description, setDescription] = useState(status.member.description);
80
+ const [model, setModel] = useState(status.member.model);
81
+ const [saving, setSaving] = useState(false);
82
+ const [error, setError] = useState();
83
+ const pendingRequest = useRef();
84
+ // Membership comes from a fresh projection at open time so stale checkbox
85
+ // state never flips a committed fact backwards.
86
+ const [memberships, setMemberships] = useState();
87
+ const [loadError, setLoadError] = useState();
88
+ useEffect(() => {
89
+ let mounted = true;
90
+ void loadChannels({ workspaceId: status.member.workspaceId, topLevelOnly: true, includeActivities: false, limit: 1 }).then(result => {
91
+ if (!mounted)
92
+ return;
93
+ if (result.ok) {
94
+ setMemberships(result.value.members);
95
+ setLoadError(undefined);
96
+ }
97
+ else {
98
+ setLoadError(result.error.message);
99
+ }
100
+ });
101
+ return () => { mounted = false; };
102
+ }, [loadChannels, status.member.workspaceId]);
103
+ const membership = useChannelMembership({ joinChannel, removeChannelMember }, change => change.channelRef, change => {
104
+ setMemberships(current => {
105
+ const base = current ?? [];
106
+ return change.joined
107
+ ? base.filter(item => !(item.channelRef === change.channelRef && item.memberId === change.memberId))
108
+ : [...base, { channelRef: change.channelRef, memberId: change.memberId }];
109
+ });
110
+ });
111
+ // A successful join appends the durable fact; removal filters it out above.
112
+ const joinedChannels = new Set((memberships ?? []).filter(item => item.memberId === memberId).map(item => item.channelRef));
113
+ const dirty = handle.trim() !== status.member.handle || description.trim() !== status.member.description
114
+ || !sameModel(model, status.member.model);
115
+ const submit = async (event) => {
116
+ event.preventDefault();
117
+ const normalizedHandle = handle.trim();
118
+ const normalizedDescription = description.trim();
119
+ if (saving || !dirty || normalizedHandle.length === 0)
120
+ return;
121
+ const payload = {
122
+ memberId,
123
+ handle: normalizedHandle,
124
+ description: normalizedDescription,
125
+ ...(model === undefined ? {} : { model }),
126
+ };
127
+ const samePending = pendingRequest.current !== undefined && pendingRequest.current.memberId === payload.memberId
128
+ && pendingRequest.current.handle === payload.handle && pendingRequest.current.description === payload.description
129
+ && sameModel(pendingRequest.current.model, model);
130
+ const request = samePending ? pendingRequest.current : {
131
+ requestId: mintRequestId(),
132
+ ...payload,
133
+ };
134
+ pendingRequest.current = request;
135
+ setSaving(true);
136
+ setError(undefined);
137
+ try {
138
+ const result = await updateMember(request);
139
+ if (result.ok) {
140
+ pendingRequest.current = undefined;
141
+ await onCommitted();
142
+ onClose();
143
+ }
144
+ else {
145
+ setError(result.error.message);
146
+ }
147
+ }
148
+ catch (cause) {
149
+ setError(cause instanceof Error ? cause.message : String(cause));
150
+ }
151
+ finally {
152
+ setSaving(false);
153
+ }
154
+ };
155
+ return (_jsx(Modal, { open: true, onClose: onClose, title: t('editAgent'), description: `@${status.member.handle}`, closeLabel: t('close'), contentClassName: createCss.dialogContent, footer: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", disabled: saving, onClick: onClose, children: t('cancel') }), _jsx(Button, { type: "submit", form: "team-agent-edit-form", variant: "primary", disabled: saving || !dirty || handle.trim().length === 0, children: saving ? t('editSaving') : t('editSave') })] }), children: _jsxs("form", { id: "team-agent-edit-form", className: createCss.form, onSubmit: event => { void submit(event); }, children: [_jsxs("label", { className: createCss.field, children: [_jsx("span", { children: t('agentName') }), _jsx(Input, { className: createCss.input, value: handle, onChange: event => { setHandle(event.target.value); pendingRequest.current = undefined; }, disabled: saving, autoFocus: true })] }), _jsxs("label", { className: createCss.field, children: [_jsxs("span", { children: [t('agentDescription'), t('optionalSuffix')] }), _jsx(Input, { className: createCss.input, value: description, placeholder: t('agentDescriptionPlaceholder'), onChange: event => { setDescription(event.target.value); pendingRequest.current = undefined; }, disabled: saving })] }), _jsx(ModelPickerField, { model: model, onModelChange: choice => { pendingRequest.current = undefined; setModel(choice); }, loadModels: loadModels, disabled: saving, t: t }), _jsxs("fieldset", { className: createCss.memberPicker, disabled: saving, children: [_jsx("legend", { children: t('channelMembersSection') }), channels.map(channel => {
156
+ const joined = joinedChannels.has(channel.channelRef);
157
+ const rowPending = membership.pending.has(channel.channelRef);
158
+ const disabled = rowPending || (!joined && status.presence === 'unavailable');
159
+ const rowError = membership.errors.get(channel.channelRef);
160
+ return _jsxs("div", { className: `${css.editMemberRow} ${css.editMemberRowChannels}`, children: [_jsxs("strong", { className: css.editChannelName, children: ["# ", channel.name] }), _jsx(Button, { size: "sm", variant: "outline", disabled: disabled, onClick: () => { void membership.change({ workspaceId: status.member.workspaceId, channelRef: channel.channelRef, memberId, joined }); }, children: rowPending ? t('membershipUpdating') : joined ? t('removeFromChannel') : t('addToChannel') }), rowError !== undefined && _jsx("p", { className: css.rowError, role: "alert", children: rowError })] }, channel.channelRef);
161
+ }), channels.length === 0 && _jsx("small", { children: t('noChannelsForAgent') }), loadError !== undefined && _jsx("p", { className: css.rowError, role: "alert", children: loadError })] }), error !== undefined && _jsx("p", { className: createCss.error, role: "alert", children: error })] }) }));
162
+ }
163
+ export function sameModel(left, right) {
164
+ if (left === undefined && right === undefined)
165
+ return true;
166
+ if (left === undefined || right === undefined)
167
+ return false;
168
+ return left.provider === right.provider && left.model === right.model && left.reasoningEffort === right.reasoningEffort;
169
+ }
@@ -0,0 +1,17 @@
1
+ import type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
2
+ import type { InputTriggerController } from '@deepseek-ai/dsh-client-ui-input-trigger/client';
3
+ /** Public composer-bar slot currency plus Team's scoped trigger controller. */
4
+ export type TeamMemberSessionComposerProps = PropsRuntime<'conversation.composer.bar'> & {
5
+ readonly controller: InputTriggerController;
6
+ /** Team inject provides public Session.cancel as the running stop action. */
7
+ readonly stop?: (() => void) | undefined;
8
+ /** Team inject keeps delivery inside the public InputMachine facade. */
9
+ readonly submitInput: (mode: 'queue' | 'steer') => void;
10
+ };
11
+ /**
12
+ * Narrow, Team-owned member-session composer. It deliberately uses the public
13
+ * input state/actions and trigger controller rather than the shipped InputBar;
14
+ * owner-provided overlay remains inside the card so the shared menu renders.
15
+ */
16
+ export declare function TeamMemberSessionComposer({ controller, inputActions, overlay, placeholder, disabled, blocked, stop, submitInput, useInput, useSession }: TeamMemberSessionComposerProps): import("react").JSX.Element;
17
+ //# sourceMappingURL=TeamMemberSessionComposer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TeamMemberSessionComposer.d.ts","sourceRoot":"","sources":["../../../src/client/TeamMemberSessionComposer.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AACpE,OAAO,KAAK,EAAE,sBAAsB,EAAc,MAAM,iDAAiD,CAAA;AAIzG,+EAA+E;AAC/E,MAAM,MAAM,8BAA8B,GAAG,YAAY,CAAC,2BAA2B,CAAC,GAAG;IACvF,QAAQ,CAAC,UAAU,EAAE,sBAAsB,CAAA;IAC3C,6EAA6E;IAC7E,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAA;IACxC,wEAAwE;IACxE,QAAQ,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,KAAK,IAAI,CAAA;CACxD,CAAA;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,EAAE,UAAU,EAAE,YAAY,EAAE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,8BAA8B,+BA6GvL"}
@@ -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
+ }
@@ -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,+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;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,+BA6G3N"}
@@ -3,33 +3,25 @@ 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, 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
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 === '')
@@ -37,26 +29,36 @@ export function TeamMessage({ senderName, memberId, human, body, occurredAt, men
37
29
  void resolveUnknownTaskRefs(bodyTaskRefs, onResolveTaskRefs);
38
30
  }, [onResolveTaskRefs, bodyTaskRefKey, refVersion]);
39
31
  const taskLabel = useCallback((taskNumber) => t?.('taskLabel', { number: taskNumber }) ?? `Task #${taskNumber}`, [t]);
32
+ // Long bodies start clamped behind the expand control. The default derives
33
+ // from the body alone; the toggle itself is per-mount view state nothing
34
+ // persists.
35
+ const [expanded, setExpanded] = useState(false);
36
+ const clampable = shouldClampMessage(displayBody);
37
+ const clamped = clampable && !expanded;
40
38
  const markdownRef = useRef(null);
41
39
  useLayoutEffect(() => {
42
40
  const root = markdownRef.current;
43
41
  if (!richAgentBody || root === null || onOpenRef === undefined)
44
42
  return;
45
43
  const textNodes = markdownProseTextNodes(root);
46
- const taskRefs = [...new Set(textNodes.flatMap(node => splitBrandedRefs(node.data)
44
+ const styledRefCodes = markdownStyledRefCodeElements(root);
45
+ const taskRefs = [...new Set([...textNodes.map(node => node.data), ...styledRefCodes.map(code => code.textContent ?? '')]
46
+ .flatMap(text => splitBrandedRefs(text)
47
47
  .filter(segment => segment.ref?.startsWith('task:') === true)
48
48
  .map(segment => segment.ref)))];
49
49
  if (onResolveTaskRefs !== undefined && taskRefs.length > 0)
50
50
  void resolveUnknownTaskRefs(taskRefs, onResolveTaskRefs);
51
+ for (const code of styledRefCodes)
52
+ renderResolvedMarkdownCodeRef(code, taskLabel);
51
53
  for (const node of textNodes)
52
- renderResolvedMarkdownTaskRefs(node, taskLabel);
54
+ renderResolvedMarkdownText(node, mentionNames ?? [], taskLabel);
53
55
  for (const button of root.querySelectorAll('button[data-task-ref]')) {
54
56
  const taskRef = button.dataset.taskRef;
55
57
  const hit = taskRef === undefined ? undefined : cachedResolvedTaskRef(taskRef);
56
58
  if (taskRef !== undefined && hit !== undefined)
57
59
  button.textContent = taskLabel(hit.taskNumber);
58
60
  }
59
- }, [richAgentBody, onOpenRef, onResolveTaskRefs, refVersion, taskLabel]);
61
+ }, [richAgentBody, onOpenRef, onResolveTaskRefs, refVersion, taskLabel, mentionNames]);
60
62
  useEffect(() => {
61
63
  const root = markdownRef.current;
62
64
  if (!richAgentBody || root === null || onOpenRef === undefined)
@@ -75,13 +77,14 @@ export function TeamMessage({ senderName, memberId, human, body, occurredAt, men
75
77
  root.addEventListener('click', openTask);
76
78
  return () => { root.removeEventListener('click', openTask); };
77
79
  }, [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
80
- ? _jsx("span", { className: css.mention, children: segment.text }, index)
81
- : _jsx(Fragment, { children: renderRefs(segment.text, onOpenRef, taskLabel) }, index)) })
82
- : human || (onOpenRef !== undefined && isPlainTextBody(displayBody) && splitBrandedRefs(displayBody).some(segment => segment.ref !== undefined))
83
- ? _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
+ const bodyNode = plan.render === 'inline' && plan.inline !== undefined
81
+ ? _jsx("div", { className: css.messageText, children: plan.inline.segments.map((segment, index) => segment.mention
82
+ ? _jsx("span", { className: css.mention, children: segment.text }, index)
83
+ : _jsx(Fragment, { children: renderRefs(segment.text, onOpenRef, taskLabel) }, index)) })
84
+ : plan.render === 'literal'
85
+ ? _jsx("div", { className: css.messageText, children: onOpenRef === undefined ? _jsx(MessageText, { text: displayBody }) : renderRefs(displayBody, onOpenRef, taskLabel) })
86
+ : _jsx("div", { ref: markdownRef, className: css.messageMarkdown, children: _jsx(MarkdownText, { text: displayBody }, `${displayBody}:${onOpenRef === undefined ? 'literal' : 'refs'}`) });
87
+ 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 => {
85
88
  const resolved = cachedResolvedTaskRef(ref);
86
89
  const label = resolved !== undefined && ref.startsWith('task:') ? taskLabel(resolved.taskNumber) : ref;
87
90
  return _jsx("button", { type: "button", className: css.refLink, title: ref, onClick: () => { onOpenRef(ref); }, children: label }, ref);
@@ -93,38 +96,82 @@ function markdownProseTextNodes(root) {
93
96
  const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
94
97
  for (let node = walker.nextNode(); node !== null; node = walker.nextNode()) {
95
98
  const parent = node.parentElement;
96
- if (parent !== null && parent.closest('code, pre, a, button') === null)
97
- nodes.push(node);
99
+ if (parent === null)
100
+ continue;
101
+ if (parent.closest('code, pre, a, button') !== null)
102
+ continue;
103
+ // Mention chips from an earlier pass stay untouched, or each effect rerun
104
+ // would wrap another chip around the last one.
105
+ if (css.mention !== undefined && parent.closest(`[class~="${css.mention}"]`) !== null)
106
+ continue;
107
+ nodes.push(node);
98
108
  }
99
109
  return nodes;
100
110
  }
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))
111
+ /** Code spans whose entire content is one branded ref: model styling around a ref, not code. */
112
+ function markdownStyledRefCodeElements(root) {
113
+ const elements = [];
114
+ for (const code of root.querySelectorAll('code')) {
115
+ if (code.closest('pre, a, button') !== null)
116
+ continue;
117
+ if (!isSingleBrandedRef(code.textContent ?? ''))
118
+ continue;
119
+ elements.push(code);
120
+ }
121
+ return elements;
122
+ }
123
+ /** One resolved Task-ref chip built outside React, matching the styled ref link. */
124
+ function resolvedTaskRefButton(taskRef, label) {
125
+ const button = document.createElement('button');
126
+ button.type = 'button';
127
+ if (css.refLink !== undefined)
128
+ button.className = css.refLink;
129
+ button.dataset.taskRef = taskRef;
130
+ button.title = taskRef;
131
+ button.textContent = label;
132
+ return button;
133
+ }
134
+ /** Replace a whole-ref code span with its resolved Task chip once known. */
135
+ function renderResolvedMarkdownCodeRef(code, taskLabel) {
136
+ const segments = splitBrandedRefs((code.textContent ?? '').trim());
137
+ const segment = segments.length === 1 ? segments[0] : undefined;
138
+ const taskRef = segment?.ref?.startsWith('task:') === true ? segment.ref : undefined;
139
+ const resolved = taskRef === undefined ? undefined : cachedResolvedTaskRef(taskRef);
140
+ if (taskRef === undefined || resolved === undefined)
105
141
  return;
142
+ code.replaceWith(resolvedTaskRefButton(taskRef, taskLabel(resolved.taskNumber)));
143
+ }
144
+ /** Replace resolved Task refs and structured mention handles in one prose text node without changing Markdown structure. */
145
+ function renderResolvedMarkdownText(node, mentionNames, taskLabel) {
146
+ let changed = false;
106
147
  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);
148
+ for (const refSegment of splitBrandedRefs(node.data)) {
149
+ if (refSegment.ref === undefined) {
150
+ for (const mentionSegment of splitMentionNames(refSegment.text, mentionNames).segments) {
151
+ if (!mentionSegment.mention) {
152
+ fragment.append(mentionSegment.text);
153
+ continue;
154
+ }
155
+ changed = true;
156
+ const chip = document.createElement('span');
157
+ if (css.mention !== undefined)
158
+ chip.className = css.mention;
159
+ chip.textContent = mentionSegment.text;
160
+ fragment.append(chip);
161
+ }
111
162
  continue;
112
163
  }
113
- const resolved = cachedResolvedTaskRef(taskRef);
164
+ const taskRef = refSegment.ref.startsWith('task:') === true ? refSegment.ref : undefined;
165
+ const resolved = taskRef === undefined ? undefined : cachedResolvedTaskRef(taskRef);
114
166
  if (resolved === undefined) {
115
- fragment.append(segment.text);
167
+ fragment.append(refSegment.text);
116
168
  continue;
117
169
  }
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);
170
+ changed = true;
171
+ fragment.append(resolvedTaskRefButton(taskRef, taskLabel(resolved.taskNumber)));
126
172
  }
127
- node.replaceWith(fragment);
173
+ if (changed)
174
+ node.replaceWith(fragment);
128
175
  }
129
176
  /** Render one literal text run, linkifying branded refs when navigation is available. */
130
177
  function renderRefs(text, onOpenRef, taskLabel) {
@@ -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;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,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,+BA+mBxD"}