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