@wowyuarm/dsh-agent-team 0.1.1 → 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 (92) hide show
  1. package/README.md +1 -0
  2. package/README.zh.md +2 -0
  3. package/package.json +42 -25
  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 +262 -189
  9. package/packages/agent-team/lib/ledger.js +140 -109
  10. package/packages/agent-team/lib/recovery.js +30 -35
  11. package/packages/agent-team/lib/spec.js +10 -4
  12. package/packages/agent-team/lib/typert.host.js +68 -71
  13. package/packages/agent-team/lib/typert.remote-client.d.ts +3 -3
  14. package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
  15. package/packages/agent-team/lib/typert.remote-client.js +41 -56
  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 +49 -24
  21. package/packages/agent-team/lib/types/index.d.ts.map +1 -1
  22. package/packages/agent-team/lib/types/ledger.d.ts +24 -11
  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 +20 -12
  28. package/packages/agent-team/lib/types/types.d.ts.map +1 -1
  29. package/packages/agent-team/preset/team-member/agent.cordis.yml +8 -3
  30. package/packages/client-agent-team/README.md +3 -2
  31. package/packages/client-agent-team/README.zh.md +3 -2
  32. package/packages/client-agent-team/lib/client.js +1507 -525
  33. package/packages/client-agent-team/lib/client.js.map +1 -1
  34. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts +1 -2
  35. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
  36. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +24 -182
  37. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts +3 -1
  38. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
  39. package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +27 -5
  40. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -1
  41. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +19 -4
  42. package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -1
  43. package/packages/client-agent-team/lib/types/client/TeamComposer.js +22 -2
  44. package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts +1 -1
  45. package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts.map +1 -1
  46. package/packages/client-agent-team/lib/types/client/TeamConversation.js +3 -3
  47. package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts +34 -0
  48. package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts.map +1 -0
  49. package/packages/client-agent-team/lib/types/client/TeamMemberEditor.js +169 -0
  50. package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.d.ts +17 -0
  51. package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.d.ts.map +1 -0
  52. package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.js +120 -0
  53. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts +7 -2
  54. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
  55. package/packages/client-agent-team/lib/types/client/TeamMessage.js +170 -18
  56. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts +4 -0
  57. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
  58. package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +70 -13
  59. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts +1 -1
  60. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts.map +1 -1
  61. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +2 -2
  62. package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
  63. package/packages/client-agent-team/lib/types/client/index.js +72 -2
  64. package/packages/client-agent-team/lib/types/client/locales.d.ts +8 -2
  65. package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
  66. package/packages/client-agent-team/lib/types/client/locales.js +8 -2
  67. package/packages/client-agent-team/lib/types/client/member-session-input.d.ts +22 -0
  68. package/packages/client-agent-team/lib/types/client/member-session-input.d.ts.map +1 -0
  69. package/packages/client-agent-team/lib/types/client/member-session-input.js +98 -0
  70. package/packages/client-agent-team/lib/types/client/requests.d.ts +16 -0
  71. package/packages/client-agent-team/lib/types/client/requests.d.ts.map +1 -0
  72. package/packages/client-agent-team/lib/types/client/requests.js +22 -0
  73. package/packages/client-agent-team/lib/types/client/sidebar-drag.d.ts +35 -0
  74. package/packages/client-agent-team/lib/types/client/sidebar-drag.d.ts.map +1 -0
  75. package/packages/client-agent-team/lib/types/client/sidebar-drag.js +70 -0
  76. package/packages/client-agent-team/lib/types/client/sidebar-order.d.ts +27 -0
  77. package/packages/client-agent-team/lib/types/client/sidebar-order.d.ts.map +1 -0
  78. package/packages/client-agent-team/lib/types/client/sidebar-order.js +170 -0
  79. package/packages/client-agent-team/lib/types/client/slots.d.ts +2 -2
  80. package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
  81. package/packages/client-agent-team/lib/types/client/task-refs.d.ts +25 -0
  82. package/packages/client-agent-team/lib/types/client/task-refs.d.ts.map +1 -0
  83. package/packages/client-agent-team/lib/types/client/task-refs.js +77 -0
  84. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +54 -1
  85. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts.map +1 -1
  86. package/packages/client-agent-team/lib/types/client/team-formatters.js +83 -2
  87. package/packages/client-agent-team/lib/types/client/team-membership.d.ts.map +1 -1
  88. package/packages/client-agent-team/lib/types/client/team-membership.js +2 -1
  89. package/packages/tool-agent-team/lib/index.js +4 -4
  90. package/packages/client-agent-team/lib/types/client/TeamInboxPanel.d.ts +0 -13
  91. package/packages/client-agent-team/lib/types/client/TeamInboxPanel.d.ts.map +0 -1
  92. package/packages/client-agent-team/lib/types/client/TeamInboxPanel.js +0 -90
@@ -0,0 +1,34 @@
1
+ import type { AgentTeamChannel, AgentTeamClientMemberStatus, AgentTeamModelSelection } from '@wowyuarm/dsh-agent-team/types';
2
+ import type { TeamSidebarProps } from './slots.ts';
3
+ /**
4
+ * Shared provider/model dropdown for the create and edit forms. The option
5
+ * list rides the shared Menu primitive (one leading "follow Host default"
6
+ * row, then non-selectable provider headings) with a capped, internally
7
+ * scrolling card so growing model catalogs cannot stretch the dialog.
8
+ */
9
+ export declare function ModelPickerField({ model, onModelChange, loadModels, disabled, t }: {
10
+ readonly model: AgentTeamModelSelection | undefined;
11
+ readonly onModelChange: (choice: AgentTeamModelSelection | undefined) => void;
12
+ readonly loadModels: TeamSidebarProps['loadModels'];
13
+ readonly disabled: boolean;
14
+ readonly t: TeamSidebarProps['t'];
15
+ }): import("react").JSX.Element;
16
+ /**
17
+ * Agent editor: handle, description, and per-Member model selection commit
18
+ * through one durable update; Channel membership below keeps its own
19
+ * immediate add/remove flow.
20
+ */
21
+ export declare function AgentEditorDialog({ status, channels, loadChannels, updateMember, joinChannel, removeChannelMember, loadModels, onCommitted, onClose, t }: {
22
+ readonly status: AgentTeamClientMemberStatus;
23
+ readonly channels: readonly AgentTeamChannel[];
24
+ readonly loadChannels: TeamSidebarProps['loadChannels'];
25
+ readonly updateMember: TeamSidebarProps['updateMember'];
26
+ readonly joinChannel: TeamSidebarProps['joinChannel'];
27
+ readonly removeChannelMember: TeamSidebarProps['removeChannelMember'];
28
+ readonly loadModels: TeamSidebarProps['loadModels'];
29
+ readonly onCommitted: () => Promise<void> | void;
30
+ readonly onClose: () => void;
31
+ readonly t: TeamSidebarProps['t'];
32
+ }): import("react").JSX.Element;
33
+ export declare function sameModel(left: AgentTeamModelSelection | undefined, right: AgentTeamModelSelection | undefined): boolean;
34
+ //# sourceMappingURL=TeamMemberEditor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TeamMemberEditor.d.ts","sourceRoot":"","sources":["../../../src/client/TeamMemberEditor.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAA8B,2BAA2B,EAAE,uBAAuB,EAAgC,MAAM,gCAAgC,CAAA;AACtL,OAAO,KAAK,EAAiD,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAajG;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE;IAClF,QAAQ,CAAC,KAAK,EAAE,uBAAuB,GAAG,SAAS,CAAA;IACnD,QAAQ,CAAC,aAAa,EAAE,CAAC,MAAM,EAAE,uBAAuB,GAAG,SAAS,KAAK,IAAI,CAAA;IAC7E,QAAQ,CAAC,UAAU,EAAE,gBAAgB,CAAC,YAAY,CAAC,CAAA;IACnD,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,CAAC,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAA;CAClC,+BA0GA;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,mBAAmB,EAAE,UAAU,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE;IACzJ,QAAQ,CAAC,MAAM,EAAE,2BAA2B,CAAA;IAC5C,QAAQ,CAAC,QAAQ,EAAE,SAAS,gBAAgB,EAAE,CAAA;IAC9C,QAAQ,CAAC,YAAY,EAAE,gBAAgB,CAAC,cAAc,CAAC,CAAA;IACvD,QAAQ,CAAC,YAAY,EAAE,gBAAgB,CAAC,cAAc,CAAC,CAAA;IACvD,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC,aAAa,CAAC,CAAA;IACrD,QAAQ,CAAC,mBAAmB,EAAE,gBAAgB,CAAC,qBAAqB,CAAC,CAAA;IACrE,QAAQ,CAAC,UAAU,EAAE,gBAAgB,CAAC,YAAY,CAAC,CAAA;IACnD,QAAQ,CAAC,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAChD,QAAQ,CAAC,OAAO,EAAE,MAAM,IAAI,CAAA;IAC5B,QAAQ,CAAC,CAAC,EAAE,gBAAgB,CAAC,GAAG,CAAC,CAAA;CAClC,+BA0HA;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,uBAAuB,GAAG,SAAS,EAAE,KAAK,EAAE,uBAAuB,GAAG,SAAS,GAAG,OAAO,CAIxH"}
@@ -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
+ }
@@ -1,6 +1,7 @@
1
1
  import { type ReactNode } from 'react';
2
- import type { AgentTeamMemberId, AgentTeamMessageAttachment } from '@wowyuarm/dsh-agent-team/types';
2
+ import type { AgentTeamMemberId, AgentTeamMessageAttachment, AgentTeamTaskRef } from '@wowyuarm/dsh-agent-team/types';
3
3
  import type { TeamConversationProps } from './slots.ts';
4
+ import { type ResolvedTaskRef } from './task-refs.ts';
4
5
  export interface TeamMessageProps {
5
6
  readonly senderName: string;
6
7
  readonly memberId: AgentTeamMemberId;
@@ -17,8 +18,12 @@ export interface TeamMessageProps {
17
18
  /** Cache readback for thumbnails; absent on surfaces without the remotes. */
18
19
  readonly loadAttachment?: TeamConversationProps['getAttachment'] | undefined;
19
20
  readonly t?: TeamConversationProps['t'] | undefined;
21
+ /** Resolve a branded ref found in the body; absent surfaces render refs as plain text. */
22
+ readonly onOpenRef?: ((ref: string) => void) | undefined;
23
+ /** Host lookup turning task refs into human-facing numbers; absent keeps raw refs. */
24
+ readonly onResolveTaskRefs?: ((taskRefs: readonly AgentTeamTaskRef[]) => Promise<readonly ResolvedTaskRef[]>) | undefined;
20
25
  readonly children?: ReactNode;
21
26
  }
22
27
  /** One chat message row with identity chrome and sender-appropriate rendering. */
23
- export declare function TeamMessage({ senderName, memberId, human, body, occurredAt, mentionNames, senderTitle, grouped, attachments, loadAttachment, t, children }: TeamMessageProps): import("react").JSX.Element;
28
+ export declare function TeamMessage({ senderName, memberId, human, body, occurredAt, mentionNames, senderTitle, grouped, attachments, loadAttachment, t, onOpenRef, onResolveTaskRefs, children }: TeamMessageProps): import("react").JSX.Element;
24
29
  //# sourceMappingURL=TeamMessage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"TeamMessage.d.ts","sourceRoot":"","sources":["../../../src/client/TeamMessage.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAqD,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAEzF,OAAO,KAAK,EAAgC,iBAAiB,EAAE,0BAA0B,EAAE,MAAM,gCAAgC,CAAA;AACjI,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAKvD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAA;IACpC,wEAAwE;IACxE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,uFAAuF;IACvF,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACzC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,0BAA0B,EAAE,GAAG,SAAS,CAAA;IACxE,6EAA6E;IAC7E,QAAQ,CAAC,cAAc,CAAC,EAAE,qBAAqB,CAAC,eAAe,CAAC,GAAG,SAAS,CAAA;IAC5E,QAAQ,CAAC,CAAC,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC,GAAG,SAAS,CAAA;IACnD,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;CAC9B;AAED,kFAAkF;AAClF,wBAAgB,WAAW,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC,EAAE,QAAQ,EAAE,EAAE,gBAAgB,+BA8C5K"}
1
+ {"version":3,"file":"TeamMessage.d.ts","sourceRoot":"","sources":["../../../src/client/TeamMessage.tsx"],"names":[],"mappings":"AAAA,OAAO,EAA2F,KAAK,SAAS,EAAE,MAAM,OAAO,CAAA;AAE/H,OAAO,KAAK,EAAE,iBAAiB,EAAE,0BAA0B,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAA;AACrH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAEvD,OAAO,EAA4E,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAI/H,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAA;IACpC,wEAAwE;IACxE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,uFAAuF;IACvF,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACzC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,0BAA0B,EAAE,GAAG,SAAS,CAAA;IACxE,6EAA6E;IAC7E,QAAQ,CAAC,cAAc,CAAC,EAAE,qBAAqB,CAAC,eAAe,CAAC,GAAG,SAAS,CAAA;IAC5E,QAAQ,CAAC,CAAC,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC,GAAG,SAAS,CAAA;IACnD,0FAA0F;IAC1F,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,CAAA;IACxD,sFAAsF;IACtF,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC,QAAQ,EAAE,SAAS,gBAAgB,EAAE,KAAK,OAAO,CAAC,SAAS,eAAe,EAAE,CAAC,CAAC,GAAG,SAAS,CAAA;IACzH,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,CAAA;CAC9B;AAED,kFAAkF;AAClF,wBAAgB,WAAW,CAAC,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,CAAC,EAAE,SAAS,EAAE,iBAAiB,EAAE,QAAQ,EAAE,EAAE,gBAAgB,+BA6F1M"}
@@ -1,30 +1,182 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Fragment, useEffect, useState } from 'react';
2
+ import { Fragment, useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
3
3
  import { MarkdownText, MessageText, Modal } from '@deepseek-ai/dsh-client-ui-primitives';
4
4
  import { cachedAttachmentDataUrl, formatByteSize, loadAttachmentDataUrl } from "./attachment-preview.js";
5
- import { formatMessageTime, isPlainTextBody, memberHue, splitMentionNames, stripAttachmentLines } from "./team-formatters.js";
5
+ import { cachedResolvedTaskRef, resolveUnknownTaskRefs, useResolvedTaskRefVersion } from "./task-refs.js";
6
+ import { formatMessageTime, isSingleBrandedRef, memberHue, planMessageBody, splitBrandedRefs, splitMentionNames } from "./team-formatters.js";
6
7
  import css from './conversation.module.css';
7
8
  /** One chat message row with identity chrome and sender-appropriate rendering. */
8
- export function TeamMessage({ senderName, memberId, human, body, occurredAt, mentionNames, senderTitle, grouped, attachments, loadAttachment, t, children }) {
9
+ export function TeamMessage({ senderName, memberId, human, body, occurredAt, mentionNames, senderTitle, grouped, attachments, loadAttachment, t, onOpenRef, onResolveTaskRefs, children }) {
9
10
  const avatarStyle = human ? undefined : { '--team-avatar-hue': memberHue(memberId) };
10
- // Literal bodies carry the chips inline — Human input always, and plain-
11
- // prose Agent bodies where literal rendering loses nothing. Rich Markdown
12
- // Agent bodies fall back to the trailing chip row: the Markdown primitive
13
- // renders block-level documents, so chips cannot interleave mid-paragraph.
11
+ // Literal bodies carry mention chips inline — Human input always, and
12
+ // plain-prose Agent bodies where literal rendering loses nothing. Rich
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
- 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
22
- ? _jsx("div", { className: css.messageText, children: inline.segments.map((segment, index) => segment.mention
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;
21
+ // Resolved refs re-label once the Host lookup lands; the version token
22
+ // refreshes literal links and rendered Markdown prose.
23
+ const refVersion = useResolvedTaskRefVersion();
24
+ const bodyTaskRefs = plan.taskRefs;
25
+ const bodyTaskRefKey = bodyTaskRefs.join(',');
26
+ useEffect(() => {
27
+ if (onResolveTaskRefs === undefined || bodyTaskRefKey === '')
28
+ return;
29
+ void resolveUnknownTaskRefs(bodyTaskRefs, onResolveTaskRefs);
30
+ }, [onResolveTaskRefs, bodyTaskRefKey, refVersion]);
31
+ const taskLabel = useCallback((taskNumber) => t?.('taskLabel', { number: taskNumber }) ?? `Task #${taskNumber}`, [t]);
32
+ const markdownRef = useRef(null);
33
+ useLayoutEffect(() => {
34
+ const root = markdownRef.current;
35
+ if (!richAgentBody || root === null || onOpenRef === undefined)
36
+ return;
37
+ const textNodes = markdownProseTextNodes(root);
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)
41
+ .filter(segment => segment.ref?.startsWith('task:') === true)
42
+ .map(segment => segment.ref)))];
43
+ if (onResolveTaskRefs !== undefined && taskRefs.length > 0)
44
+ void resolveUnknownTaskRefs(taskRefs, onResolveTaskRefs);
45
+ for (const code of styledRefCodes)
46
+ renderResolvedMarkdownCodeRef(code, taskLabel);
47
+ for (const node of textNodes)
48
+ renderResolvedMarkdownText(node, mentionNames ?? [], taskLabel);
49
+ for (const button of root.querySelectorAll('button[data-task-ref]')) {
50
+ const taskRef = button.dataset.taskRef;
51
+ const hit = taskRef === undefined ? undefined : cachedResolvedTaskRef(taskRef);
52
+ if (taskRef !== undefined && hit !== undefined)
53
+ button.textContent = taskLabel(hit.taskNumber);
54
+ }
55
+ }, [richAgentBody, onOpenRef, onResolveTaskRefs, refVersion, taskLabel, mentionNames]);
56
+ useEffect(() => {
57
+ const root = markdownRef.current;
58
+ if (!richAgentBody || root === null || onOpenRef === undefined)
59
+ return;
60
+ const openTask = (event) => {
61
+ const target = event.target;
62
+ if (!(target instanceof Element))
63
+ return;
64
+ const button = target.closest('button[data-task-ref]');
65
+ if (button === null || !root.contains(button))
66
+ return;
67
+ const taskRef = button instanceof HTMLElement ? button.dataset.taskRef : undefined;
68
+ if (taskRef !== undefined)
69
+ onOpenRef(taskRef);
70
+ };
71
+ root.addEventListener('click', openTask);
72
+ return () => { root.removeEventListener('click', openTask); };
73
+ }, [richAgentBody, onOpenRef]);
74
+ return (_jsxs("article", { className: css.messageRow, "data-human": human || undefined, "data-grouped": grouped || undefined, children: [_jsx("div", { className: css.messageIdentity, style: avatarStyle, "aria-hidden": "true", children: senderName.replace('@', '').slice(0, 1).toUpperCase() }), _jsxs("div", { className: css.messageBody, children: [!grouped && (_jsxs("div", { className: css.nameRow, children: [_jsx("strong", { ...(senderTitle === undefined ? {} : { title: senderTitle }), children: senderName }), occurredAt !== undefined && _jsx("span", { className: css.messageTime, children: formatMessageTime(occurredAt) })] })), plan.render === 'inline' && plan.inline !== undefined
75
+ ? _jsx("div", { className: css.messageText, children: plan.inline.segments.map((segment, index) => segment.mention
23
76
  ? _jsx("span", { className: css.mention, children: segment.text }, index)
24
- : _jsx(Fragment, { children: segment.text }, index)) })
25
- : human
26
- ? _jsx("div", { className: css.messageText, children: _jsx(MessageText, { text: displayBody }) })
27
- : _jsx("div", { className: css.messageMarkdown, children: _jsx(MarkdownText, { text: displayBody }) }), fallbackNames.length > 0 && (_jsx("div", { className: css.mentionsRow, children: fallbackNames.map(name => _jsxs("span", { className: css.mention, children: ["@", name] }, name)) })), attachments !== undefined && attachments.length > 0 && _jsx(TeamAttachmentStrip, { attachments: attachments, ...(loadAttachment === undefined ? {} : { loadAttachment }), ...(t === undefined ? {} : { t }) }), children] })] }));
77
+ : _jsx(Fragment, { children: renderRefs(segment.text, onOpenRef, taskLabel) }, index)) })
78
+ : plan.render === 'literal'
79
+ ? _jsx("div", { className: css.messageText, children: onOpenRef === undefined ? _jsx(MessageText, { text: displayBody }) : renderRefs(displayBody, onOpenRef, taskLabel) })
80
+ : _jsx("div", { ref: markdownRef, className: css.messageMarkdown, children: _jsx(MarkdownText, { text: displayBody }, `${displayBody}:${onOpenRef === undefined ? 'literal' : 'refs'}`) }), (plan.fallbackNames.length > 0 || plan.fallbackRefs.length > 0) && (_jsxs("div", { className: css.mentionsRow, children: [plan.fallbackNames.map(name => _jsxs("span", { className: css.mention, children: ["@", name] }, name)), plan.fallbackRefs.map(ref => {
81
+ const resolved = cachedResolvedTaskRef(ref);
82
+ const label = resolved !== undefined && ref.startsWith('task:') ? taskLabel(resolved.taskNumber) : ref;
83
+ return _jsx("button", { type: "button", className: css.refLink, title: ref, onClick: () => { onOpenRef(ref); }, children: label }, ref);
84
+ })] })), attachments !== undefined && attachments.length > 0 && _jsx(TeamAttachmentStrip, { attachments: attachments, ...(loadAttachment === undefined ? {} : { loadAttachment }), ...(t === undefined ? {} : { t }) }), children] })] }));
85
+ }
86
+ /** Text nodes that Markdown rendered as prose rather than code or a link. */
87
+ function markdownProseTextNodes(root) {
88
+ const nodes = [];
89
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
90
+ for (let node = walker.nextNode(); node !== null; node = walker.nextNode()) {
91
+ const parent = node.parentElement;
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);
101
+ }
102
+ return nodes;
103
+ }
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)
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;
140
+ const fragment = document.createDocumentFragment();
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
+ }
155
+ continue;
156
+ }
157
+ const taskRef = refSegment.ref.startsWith('task:') === true ? refSegment.ref : undefined;
158
+ const resolved = taskRef === undefined ? undefined : cachedResolvedTaskRef(taskRef);
159
+ if (resolved === undefined) {
160
+ fragment.append(refSegment.text);
161
+ continue;
162
+ }
163
+ changed = true;
164
+ fragment.append(resolvedTaskRefButton(taskRef, taskLabel(resolved.taskNumber)));
165
+ }
166
+ if (changed)
167
+ node.replaceWith(fragment);
168
+ }
169
+ /** Render one literal text run, linkifying branded refs when navigation is available. */
170
+ function renderRefs(text, onOpenRef, taskLabel) {
171
+ if (onOpenRef === undefined)
172
+ return text;
173
+ return splitBrandedRefs(text).map((segment, index) => {
174
+ if (segment.ref === undefined)
175
+ return _jsx(Fragment, { children: segment.text }, index);
176
+ const resolved = cachedResolvedTaskRef(segment.ref);
177
+ const label = resolved !== undefined && segment.ref.startsWith('task:') ? taskLabel(resolved.taskNumber) : segment.ref;
178
+ return _jsx("button", { type: "button", className: css.refLink, title: segment.ref, onClick: () => { onOpenRef(segment.ref); }, children: label }, index);
179
+ });
28
180
  }
29
181
  /** One message's attachment strip: image thumbnails with a large view, or name chips when bytes are gone. */
30
182
  function TeamAttachmentStrip({ attachments, loadAttachment, t }) {
@@ -18,6 +18,10 @@ interface TeamThreadPageProps {
18
18
  readonly getAttachment: TeamConversationProps['getAttachment'];
19
19
  readonly reply: TeamConversationProps['reply'];
20
20
  readonly changeTask: TeamConversationProps['changeTask'];
21
+ readonly putAttachment: TeamConversationProps['putAttachment'];
22
+ readonly selectChannel: TeamConversationProps['selectChannel'];
23
+ readonly selectThread: TeamConversationProps['selectThread'];
24
+ readonly resolveTaskRefs: TeamConversationProps['resolveTaskRefs'];
21
25
  readonly t: TeamConversationProps['t'];
22
26
  }
23
27
  export declare function TeamThreadPage(props: TeamThreadPageProps): import("react").JSX.Element;
@@ -1 +1 @@
1
- {"version":3,"file":"TeamThreadPage.d.ts","sourceRoot":"","sources":["../../../src/client/TeamThreadPage.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,mBAAmB,EAInB,gBAAgB,EAGhB,kBAAkB,EAEnB,MAAM,gCAAgC,CAAA;AACvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wCAAwC,CAAA;AAEzE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AACvD,OAAO,KAAK,EAAgB,cAAc,EAAE,MAAM,aAAa,CAAA;AAW/D,UAAU,mBAAmB;IAC3B,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAA;IACjC,QAAQ,CAAC,UAAU,CAAC,EAAE,mBAAmB,CAAA;IACzC,QAAQ,CAAC,OAAO,EAAE,gBAAgB,CAAA;IAClC,QAAQ,CAAC,SAAS,EAAE,kBAAkB,CAAA;IACtC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,eAAe,EAAE,qBAAqB,CAAC,iBAAiB,CAAC,CAAA;IAClE,QAAQ,CAAC,YAAY,EAAE,qBAAqB,CAAC,cAAc,CAAC,CAAA;IAC5D,QAAQ,CAAC,UAAU,EAAE,qBAAqB,CAAC,YAAY,CAAC,CAAA;IACxD,QAAQ,CAAC,iBAAiB,EAAE,qBAAqB,CAAC,mBAAmB,CAAC,CAAA;IACtE,QAAQ,CAAC,gBAAgB,EAAE,qBAAqB,CAAC,kBAAkB,CAAC,CAAA;IACpE,QAAQ,CAAC,WAAW,EAAE,qBAAqB,CAAC,aAAa,CAAC,CAAA;IAC1D,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAA;IAC/B,QAAQ,CAAC,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,CAAA;IAC9D,QAAQ,CAAC,KAAK,EAAE,qBAAqB,CAAC,OAAO,CAAC,CAAA;IAC9C,QAAQ,CAAC,UAAU,EAAE,qBAAqB,CAAC,YAAY,CAAC,CAAA;IACxD,QAAQ,CAAC,CAAC,EAAE,qBAAqB,CAAC,GAAG,CAAC,CAAA;CACvC;AA8BD,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,+BAgfxD"}
1
+ {"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"}