@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.
- package/README.md +8 -1
- package/README.zh.md +9 -1
- package/package.json +12 -3
- package/packages/agent-team/README.md +3 -3
- package/packages/agent-team/README.zh.md +3 -3
- package/packages/agent-team/lib/attachments.js +11 -12
- package/packages/agent-team/lib/auto-compaction.js +163 -0
- package/packages/agent-team/lib/index.js +252 -139
- package/packages/agent-team/lib/ledger.js +344 -170
- package/packages/agent-team/lib/recovery.js +30 -35
- package/packages/agent-team/lib/spec.js +27 -11
- package/packages/agent-team/lib/typert.host.js +197 -88
- package/packages/agent-team/lib/typert.remote-client.d.ts +3 -1
- package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
- package/packages/agent-team/lib/typert.remote-client.js +139 -59
- package/packages/agent-team/lib/types/attachments.d.ts +2 -2
- package/packages/agent-team/lib/types/attachments.d.ts.map +1 -1
- package/packages/agent-team/lib/types/auto-compaction.d.ts +38 -0
- package/packages/agent-team/lib/types/auto-compaction.d.ts.map +1 -0
- package/packages/agent-team/lib/types/index.d.ts +52 -19
- package/packages/agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/types/ledger.d.ts +29 -2
- package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
- package/packages/agent-team/lib/types/recovery.d.ts +15 -16
- package/packages/agent-team/lib/types/recovery.d.ts.map +1 -1
- package/packages/agent-team/lib/types/spec.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types.d.ts +69 -30
- package/packages/agent-team/lib/types/types.d.ts.map +1 -1
- package/packages/agent-team/preset/team-member/agent.cordis.yml +7 -2
- package/packages/client-agent-team/README.md +2 -1
- package/packages/client-agent-team/README.zh.md +2 -1
- package/packages/client-agent-team/lib/client.js +1577 -863
- package/packages/client-agent-team/lib/client.js.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +35 -174
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +20 -30
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +3 -2
- package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts +6 -1
- package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamComposer.js +4 -5
- package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts +1 -1
- package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamConversation.js +3 -3
- package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts +34 -0
- package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamMemberEditor.js +169 -0
- package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.d.ts +17 -0
- package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/TeamMemberSessionComposer.js +120 -0
- package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts +4 -1
- package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMessage.js +94 -47
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts +2 -1
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +81 -39
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +2 -1
- package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/index.js +72 -1
- package/packages/client-agent-team/lib/types/client/locales.d.ts +24 -0
- package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/locales.js +24 -0
- package/packages/client-agent-team/lib/types/client/member-session-input.d.ts +22 -0
- package/packages/client-agent-team/lib/types/client/member-session-input.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/member-session-input.js +98 -0
- package/packages/client-agent-team/lib/types/client/navigation.d.ts +1 -1
- package/packages/client-agent-team/lib/types/client/navigation.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/navigation.js +12 -6
- package/packages/client-agent-team/lib/types/client/requests.d.ts +16 -0
- package/packages/client-agent-team/lib/types/client/requests.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/requests.js +22 -0
- package/packages/client-agent-team/lib/types/client/slots.d.ts +2 -1
- package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/task-refs.d.ts +6 -1
- package/packages/client-agent-team/lib/types/client/task-refs.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/task-refs.js +19 -0
- package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +52 -3
- package/packages/client-agent-team/lib/types/client/team-formatters.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/team-formatters.js +74 -4
- package/packages/client-agent-team/lib/types/client/team-membership.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/team-membership.js +2 -1
- package/packages/tool-agent-team/README.md +7 -7
- package/packages/tool-agent-team/README.zh.md +7 -7
- package/packages/tool-agent-team/lib/index.js +38 -28
- package/packages/tool-agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamInboxPanel.d.ts +0 -13
- package/packages/client-agent-team/lib/types/client/TeamInboxPanel.d.ts.map +0 -1
- 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,+
|
|
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,
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
|
97
|
-
|
|
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
|
-
/**
|
|
102
|
-
function
|
|
103
|
-
const
|
|
104
|
-
|
|
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
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
|
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(
|
|
167
|
+
fragment.append(refSegment.text);
|
|
116
168
|
continue;
|
|
117
169
|
}
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
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
|
|
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;
|
|
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"}
|