@wowyuarm/dsh-agent-team 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/README.zh.md +2 -0
- package/package.json +42 -25
- 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 +262 -189
- package/packages/agent-team/lib/ledger.js +140 -109
- package/packages/agent-team/lib/recovery.js +30 -35
- package/packages/agent-team/lib/spec.js +10 -4
- package/packages/agent-team/lib/typert.host.js +68 -71
- package/packages/agent-team/lib/typert.remote-client.d.ts +3 -3
- package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
- package/packages/agent-team/lib/typert.remote-client.js +41 -56
- 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 +49 -24
- package/packages/agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/types/ledger.d.ts +24 -11
- 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 +20 -12
- package/packages/agent-team/lib/types/types.d.ts.map +1 -1
- package/packages/agent-team/preset/team-member/agent.cordis.yml +8 -3
- package/packages/client-agent-team/README.md +3 -2
- package/packages/client-agent-team/README.zh.md +3 -2
- package/packages/client-agent-team/lib/client.js +1507 -525
- package/packages/client-agent-team/lib/client.js.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts +1 -2
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +24 -182
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts +3 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +27 -5
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +19 -4
- package/packages/client-agent-team/lib/types/client/TeamComposer.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamComposer.js +22 -2
- 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 +7 -2
- package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMessage.js +170 -18
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts +4 -0
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +70 -13
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.d.ts +1 -1
- 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 -2
- 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 -2
- package/packages/client-agent-team/lib/types/client/locales.d.ts +8 -2
- package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/locales.js +8 -2
- 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/sidebar-drag.d.ts +35 -0
- package/packages/client-agent-team/lib/types/client/sidebar-drag.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/sidebar-drag.js +70 -0
- package/packages/client-agent-team/lib/types/client/sidebar-order.d.ts +27 -0
- package/packages/client-agent-team/lib/types/client/sidebar-order.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/sidebar-order.js +170 -0
- package/packages/client-agent-team/lib/types/client/slots.d.ts +2 -2
- 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 +25 -0
- package/packages/client-agent-team/lib/types/client/task-refs.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/task-refs.js +77 -0
- package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +54 -1
- 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 +83 -2
- 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 +4 -4
- 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
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Fragment, useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react';
|
|
3
|
-
import { Button, DisclosureRow, IconChevronLeftOutline14, IconChecklistOutline14, Pill } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
3
|
+
import { Button, DisclosureRow, IconChevronLeftOutline14, IconChecklistOutline14, Modal, Pill } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
4
4
|
import { TeamComposer } from "./TeamComposer.js";
|
|
5
5
|
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";
|
|
12
|
+
import { hostTaskRefLookup, jumpToTaskThread } from "./task-refs.js";
|
|
13
|
+
import { bytesToBase64 } from "./attachment-preview.js";
|
|
11
14
|
import css from './conversation.module.css';
|
|
12
15
|
import threadCss from './thread.module.css';
|
|
13
16
|
function factKey(fact) {
|
|
@@ -30,7 +33,7 @@ function readMeta(facts) {
|
|
|
30
33
|
return new Map(facts.map(fact => [factKey(fact.fact), fact]));
|
|
31
34
|
}
|
|
32
35
|
export function TeamThreadPage(props) {
|
|
33
|
-
const { workspaceId, channelRef, taskRef, threadRef, taskNumber, backToWorkspace, loadChannels, readThread, loadThreadHistory, subscribeChanges, loadMembers, drafts, getAttachment, reply, changeTask, t, } = props;
|
|
36
|
+
const { workspaceId, channelRef, taskRef, threadRef, taskNumber, backToWorkspace, selectChannel, selectThread, resolveTaskRefs, putAttachment, loadChannels, readThread, loadThreadHistory, subscribeChanges, loadMembers, drafts, getAttachment, reply, changeTask, t, } = props;
|
|
34
37
|
const [projection, setProjection] = useState();
|
|
35
38
|
const [channelView, setChannelView] = useState();
|
|
36
39
|
const [members, setMembers] = useState([]);
|
|
@@ -46,6 +49,13 @@ export function TeamThreadPage(props) {
|
|
|
46
49
|
const draftKey = `thread:${threadRef}`;
|
|
47
50
|
const { draft, recipients } = useSyncExternalStore(drafts.subscribe, () => drafts.getSnapshot(draftKey));
|
|
48
51
|
const [claimsOpen, setClaimsOpen] = useState(false);
|
|
52
|
+
// Early acceptance: the Human may accept while Claims are still open; the
|
|
53
|
+
// confirm dialog lists exactly what will be completed with the Task.
|
|
54
|
+
const [confirmingAccept, setConfirmingAccept] = useState(false);
|
|
55
|
+
useEffect(() => {
|
|
56
|
+
if (confirmingAccept && projection?.task.resolution === 'accepted')
|
|
57
|
+
setConfirmingAccept(false);
|
|
58
|
+
}, [confirmingAccept, projection?.task.resolution]);
|
|
49
59
|
const [replyRequestId, setReplyRequestId] = useState();
|
|
50
60
|
const [confirmation, setConfirmation] = useState();
|
|
51
61
|
const [statusMessage, setStatusMessage] = useState();
|
|
@@ -55,7 +65,7 @@ export function TeamThreadPage(props) {
|
|
|
55
65
|
const mountedRef = useRef(false);
|
|
56
66
|
const currentFactsRef = useRef([]);
|
|
57
67
|
const sequenceRef = useRef(0);
|
|
58
|
-
const readRequestIdRef = useRef(
|
|
68
|
+
const readRequestIdRef = useRef(mintRequestId());
|
|
59
69
|
const mutationRequests = useRef(new Map());
|
|
60
70
|
const threadLastFact = currentFacts[currentFacts.length - 1];
|
|
61
71
|
const timeline = useTimelineScroll(`${currentFacts.length}:${olderFacts.length}:${threadLastFact === undefined ? '' : factKey(threadLastFact)}`);
|
|
@@ -76,7 +86,7 @@ export function TeamThreadPage(props) {
|
|
|
76
86
|
if (!mountedRef.current)
|
|
77
87
|
return false;
|
|
78
88
|
if (newRequest)
|
|
79
|
-
readRequestIdRef.current =
|
|
89
|
+
readRequestIdRef.current = mintRequestId();
|
|
80
90
|
const sequence = sequenceRef.current + 1;
|
|
81
91
|
sequenceRef.current = sequence;
|
|
82
92
|
setLoading(true);
|
|
@@ -284,7 +294,7 @@ export function TeamThreadPage(props) {
|
|
|
284
294
|
const mentionHandlesMap = useMemo(() => new Map(members.map(status => [status.member.memberId, status.member.handle.replace(/^@/, '')])), [members]);
|
|
285
295
|
const metadata = useMemo(() => readMeta(readFacts), [readFacts]);
|
|
286
296
|
const unreadIndex = useMemo(() => {
|
|
287
|
-
const all = mergeFacts(
|
|
297
|
+
const all = mergeFacts(...(activeProjection === undefined ? [] : [[messageFact(activeProjection.anchor, activeProjection.anchorMentions)]]), readFacts.map(fact => fact.fact), currentFacts);
|
|
288
298
|
return all.findIndex(fact => metadata.get(factKey(fact))?.unread === true);
|
|
289
299
|
}, [activeProjection?.anchor, readFacts, currentFacts, metadata]);
|
|
290
300
|
const memberName = (memberId) => {
|
|
@@ -294,11 +304,24 @@ export function TeamThreadPage(props) {
|
|
|
294
304
|
return status === undefined ? t('memberUnknown') : `@${status.member.handle}`;
|
|
295
305
|
};
|
|
296
306
|
const messageSender = (fact) => fact.kind === 'message' ? fact.message.sender : undefined;
|
|
307
|
+
const [pendingFiles, setPendingFiles] = useState([]);
|
|
308
|
+
// Branded-ref navigation for message bodies: channel refs hop to the
|
|
309
|
+
// Channel; the open Task's own refs are already on screen, and other Tasks
|
|
310
|
+
// are not resolvable from this surface, so they degrade to a no-op.
|
|
311
|
+
const openRef = (ref) => {
|
|
312
|
+
if (ref.startsWith('channel:') && ref !== channelRef)
|
|
313
|
+
selectChannel(ref);
|
|
314
|
+
if (ref.startsWith('task:') && ref !== taskRef) {
|
|
315
|
+
// Another Task cited here: resolve its home Channel and jump.
|
|
316
|
+
jumpToTaskThread(resolveTaskRefs, workspaceId, ref, selectThread);
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
const lookupTaskRefs = hostTaskRefLookup(resolveTaskRefs, workspaceId);
|
|
297
320
|
const renderFact = (fact, grouped = false) => {
|
|
298
321
|
if (fact.kind === 'message') {
|
|
299
322
|
const sender = memberName(fact.message.sender);
|
|
300
323
|
const senderStatus = members.find(candidate => candidate.member.memberId === fact.message.sender);
|
|
301
|
-
return _jsx(TeamMessage, { senderName: sender, memberId: fact.message.sender, human: fact.message.sender === channelView?.humanMemberId, body: fact.message.body, attachments: fact.message.attachments, loadAttachment: getAttachment, t: t, occurredAt: fact.message.occurredAt, mentionNames: mentionNamesOf(fact.mentions, mentionHandlesMap), grouped: grouped, ...(senderStatus === undefined ? {} : { senderTitle: senderStatus.member.description }) }, factKey(fact));
|
|
324
|
+
return _jsx(TeamMessage, { senderName: sender, memberId: fact.message.sender, human: fact.message.sender === channelView?.humanMemberId, body: fact.message.body, attachments: fact.message.attachments, loadAttachment: getAttachment, t: t, occurredAt: fact.message.occurredAt, mentionNames: mentionNamesOf(fact.mentions, mentionHandlesMap), onOpenRef: openRef, onResolveTaskRefs: lookupTaskRefs, grouped: grouped, ...(senderStatus === undefined ? {} : { senderTitle: senderStatus.member.description }) }, factKey(fact));
|
|
302
325
|
}
|
|
303
326
|
return _jsxs("p", { className: threadCss.activityRow, children: [_jsx("span", { className: threadCss.activityMark, "aria-hidden": "true" }), _jsx("span", { className: threadCss.activityText, children: formatActivity(fact.activity, { t, actorName: memberName, claims: taskClaims }) })] }, factKey(fact));
|
|
304
327
|
};
|
|
@@ -354,7 +377,7 @@ export function TeamThreadPage(props) {
|
|
|
354
377
|
setPending(true);
|
|
355
378
|
setError(undefined);
|
|
356
379
|
const key = `task:${action}`;
|
|
357
|
-
const requestId = mutationRequests.current.get(key) ??
|
|
380
|
+
const requestId = mutationRequests.current.get(key) ?? mintRequestId();
|
|
358
381
|
mutationRequests.current.set(key, requestId);
|
|
359
382
|
try {
|
|
360
383
|
const result = await changeTask({ requestId, workspaceId, taskRef: task.taskRef, action, baseRevision: thread.revision });
|
|
@@ -395,12 +418,29 @@ export function TeamThreadPage(props) {
|
|
|
395
418
|
const sendReply = async () => {
|
|
396
419
|
if (pending || task === undefined || thread === undefined || draft.trim() === '')
|
|
397
420
|
return;
|
|
398
|
-
const id = replyRequestId ??
|
|
421
|
+
const id = replyRequestId ?? mintRequestId();
|
|
399
422
|
setReplyRequestId(id);
|
|
400
423
|
setPending(true);
|
|
401
424
|
setError(undefined);
|
|
402
425
|
try {
|
|
403
|
-
|
|
426
|
+
// Upload chosen files first; any failure aborts the reply with the
|
|
427
|
+
// existing error surface and keeps the chips for a retry.
|
|
428
|
+
const attachmentIds = [];
|
|
429
|
+
for (const file of pendingFiles) {
|
|
430
|
+
const uploaded = await putAttachment({
|
|
431
|
+
requestId: mintRequestId(),
|
|
432
|
+
workspaceId,
|
|
433
|
+
name: file.name,
|
|
434
|
+
mediaType: file.type === '' ? undefined : file.type,
|
|
435
|
+
bytesBase64: bytesToBase64(new Uint8Array(await file.arrayBuffer())),
|
|
436
|
+
});
|
|
437
|
+
if (!uploaded.ok) {
|
|
438
|
+
setError(uploaded.error.message);
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
attachmentIds.push(uploaded.value.attachmentId);
|
|
442
|
+
}
|
|
443
|
+
const result = await reply({ requestId: id, workspaceId, taskRef: task.taskRef, body: draft.trim(), baseRevision: thread.revision, recipients: [...recipients].sort(), ...(attachmentIds.length === 0 ? {} : { attachments: attachmentIds }), ...(confirmation === undefined ? {} : { confirmationToken: confirmation }) });
|
|
404
444
|
if (!result.ok) {
|
|
405
445
|
setError(result.error.message);
|
|
406
446
|
return;
|
|
@@ -414,6 +454,7 @@ export function TeamThreadPage(props) {
|
|
|
414
454
|
});
|
|
415
455
|
setProjection(current => current === undefined ? current : { ...current, task: committed.task, thread: committed.thread });
|
|
416
456
|
drafts.clear(draftKey);
|
|
457
|
+
setPendingFiles([]);
|
|
417
458
|
setReplyRequestId(undefined);
|
|
418
459
|
setConfirmation(undefined);
|
|
419
460
|
setStatusMessage(undefined);
|
|
@@ -460,12 +501,28 @@ export function TeamThreadPage(props) {
|
|
|
460
501
|
// Threads are always entered through a Channel page, so a Channel origin
|
|
461
502
|
// returns to its timeline; a Thread restored without one returns further.
|
|
462
503
|
const backLabel = channelRef === undefined ? t('backToWorkspace') : t('backToChannel');
|
|
463
|
-
return _jsxs("main", { className: css.surface, "data-team-thread": threadRef, children: [_jsxs("div", { className: css.surfaceHeader, children: [_jsx("div", { className: css.backRow, children: _jsx(Button, { size: "sm", icon: _jsx(IconChevronLeftOutline14, {}), onClick: backToWorkspace, children: backLabel }) }), _jsxs("header", { className: css.headerRow, children: [_jsxs("div", { className: css.headerCopy, children: [_jsxs("div", { className: threadCss.titleLine, children: [_jsx("h1", { children: task === undefined ? t('taskPending') : t('taskLabel', { number: taskNumber ?? '…' }) }), task !== undefined && _jsx(Pill, { children: formatTaskStatus(task.status, t) })] }), taskTitle !== undefined && taskTitle !== '' && _jsx("p", { className: threadCss.taskTitle, children: taskTitle })] }), task !== undefined && thread !== undefined && task.resolution !== 'closed' && _jsxs("div", { className: css.headerActions, children: [
|
|
504
|
+
return _jsxs("main", { className: css.surface, "data-team-thread": threadRef, children: [_jsxs("div", { className: css.surfaceHeader, children: [_jsx("div", { className: css.backRow, children: _jsx(Button, { size: "sm", icon: _jsx(IconChevronLeftOutline14, {}), onClick: backToWorkspace, children: backLabel }) }), _jsxs("header", { className: css.headerRow, children: [_jsxs("div", { className: css.headerCopy, children: [_jsxs("div", { className: threadCss.titleLine, children: [_jsx("h1", { children: task === undefined ? t('taskPending') : t('taskLabel', { number: taskNumber ?? '…' }) }), task !== undefined && _jsx(Pill, { children: formatTaskStatus(task.status, t) })] }), taskTitle !== undefined && taskTitle !== '' && _jsx("p", { className: threadCss.taskTitle, children: taskTitle })] }), task !== undefined && thread !== undefined && task.resolution !== 'closed' && _jsxs("div", { className: css.headerActions, children: [(() => {
|
|
505
|
+
const activeClaims = projection?.claims.filter(claim => claim.taskRef === task.taskRef && claim.state === 'active') ?? [];
|
|
506
|
+
const earlyAccept = task.resolution === 'open' && task.status === 'in_progress' && activeClaims.length > 0;
|
|
507
|
+
if (!(task.status === 'in_review' || earlyAccept) || task.resolution !== 'open')
|
|
508
|
+
return null;
|
|
509
|
+
return _jsx(Button, { size: "sm", variant: "primary", disabled: pending, onClick: () => { if (earlyAccept) {
|
|
510
|
+
setConfirmingAccept(true);
|
|
511
|
+
}
|
|
512
|
+
else {
|
|
513
|
+
void mutateTask('accept');
|
|
514
|
+
} }, children: t('acceptTask') });
|
|
515
|
+
})(), task.resolution === 'open'
|
|
464
516
|
? _jsx(Button, { size: "sm", variant: "outline", disabled: pending, onClick: () => { void mutateTask('close'); }, children: t('closeTask') })
|
|
465
|
-
: _jsx(Button, { size: "sm", variant: "primary", disabled: pending, onClick: () => { void mutateTask('reopen'); }, children: t('reopenTask') })] })] }), risks.length > 0 && _jsxs("section", { className: threadCss.riskSection, "aria-label": t('runtimeRisk'), children: [_jsx("h2", { children: t('runtimeRisk') }), risks.map(({ claim, status }) => _jsxs("p", { className: threadCss.riskRow, children: [_jsx(TeamPresenceDot, { status: status, t: t }), _jsxs("span", { children: [t('runtimeRiskDetail', { member: status.member.handle, diagnostic: status.diagnostic ?? t('statusError') }), " \u00B7 ", claim.direction] })] }, claim.claimRef))] }), task !== undefined && thread !== undefined &&
|
|
517
|
+
: _jsx(Button, { size: "sm", variant: "primary", disabled: pending, onClick: () => { void mutateTask('reopen'); }, children: t('reopenTask') })] })] }), risks.length > 0 && _jsxs("section", { className: threadCss.riskSection, "aria-label": t('runtimeRisk'), children: [_jsx("h2", { children: t('runtimeRisk') }), risks.map(({ claim, status }) => _jsxs("p", { className: threadCss.riskRow, children: [_jsx(TeamPresenceDot, { status: status, t: t }), _jsxs("span", { children: [t('runtimeRiskDetail', { member: status.member.handle, diagnostic: status.diagnostic ?? t('statusError') }), " \u00B7 ", claim.direction] })] }, claim.claimRef))] }), task !== undefined && thread !== undefined && (() => {
|
|
518
|
+
// Recomputed here so the confirm list never shows stale rows.
|
|
519
|
+
const activeClaims = projection?.claims.filter(claim => claim.taskRef === task.taskRef && claim.state === 'active') ?? [];
|
|
520
|
+
return _jsxs(Modal, { open: confirmingAccept, onClose: () => { if (!pending)
|
|
521
|
+
setConfirmingAccept(false); }, title: t('acceptEarlyTitle'), closeLabel: t('cancel'), footer: _jsxs(_Fragment, { children: [_jsx(Button, { variant: "outline", disabled: pending, onClick: () => { setConfirmingAccept(false); }, children: t('cancel') }), _jsx(Button, { variant: "primary", disabled: pending, onClick: () => { void mutateTask('accept'); }, children: pending ? t('acceptingTask') : t('acceptTask') })] }), children: [_jsx("p", { className: css.confirmBody, children: t('acceptEarlyBody', { count: activeClaims.length }) }), _jsx("ul", { className: css.confirmList, children: activeClaims.map(claim => (_jsxs("li", { children: [memberName(claim.owner), " \u00B7 ", claim.direction] }, claim.claimRef))) })] });
|
|
522
|
+
})(), task !== undefined && thread !== undefined && _jsx("section", { className: threadCss.workSection, "aria-label": t('claims'), children: _jsx(DisclosureRow, { expandOnRowClick: true, expandable: true, open: claimsOpen, onToggle: () => { setClaimsOpen(current => !current); }, icon: _jsx(IconChecklistOutline14, { size: 14 }), title: `${t('claims')} · ${taskClaims.length}`, children: _jsxs("div", { className: threadCss.claimList, children: [taskClaims.length === 0 && _jsx("p", { className: threadCss.emptyClaims, children: t('noClaims') }), taskClaims.map(claim => {
|
|
466
523
|
const ownerStatus = members.find(status => status.member.memberId === claim.owner);
|
|
467
524
|
return _jsxs("article", { className: threadCss.claimRow, children: [ownerStatus === undefined ? _jsx("span", {}) : _jsx(TeamPresenceDot, { status: ownerStatus, t: t }), _jsx("strong", { className: threadCss.claimOwner, children: memberName(claim.owner) }), _jsx("span", { className: threadCss.claimDirection, children: claim.direction }), _jsx("small", { className: threadCss.claimState, children: formatClaimState(claim.state, t) })] }, claim.claimRef);
|
|
468
525
|
})] }) }) })] }), _jsx("section", { ref: timeline.ref, onScroll: timeline.onScroll, className: css.timeline, "aria-label": t('timelineLabel'), children: _jsxs("div", { className: css.timelineContent, children: [loading && projection === undefined && error === undefined && _jsx("div", { className: css.emptySurface, children: _jsxs("p", { className: css.loadingState, children: [_jsx("span", { className: css.loadingMark, "aria-hidden": "true" }), t('loadingThread')] }) }), projection === undefined && error !== undefined && _jsxs("div", { className: css.errorState, role: "alert", children: [_jsx("span", { children: error }), _jsx(Button, { size: "sm", variant: "outline", onClick: () => { void readCurrent(); }, children: t('retry') })] }), olderFacts.length > 0 && _jsxs("section", { className: threadCss.historySection, "aria-label": t('olderHistory'), children: [_jsx("h2", { children: t('olderHistory') }), renderFactBlocks(olderFacts, undefined)] }), historyHasMore && _jsx("div", { className: css.timelineAction, children: _jsx(Button, { size: "sm", onClick: () => { void loadOlder(); }, children: t('loadOlder') }) }), currentFactsWithAnchor.length > 0 && _jsx("section", { className: threadCss.publicSection, children: renderFactBlocks(currentFactsWithAnchor, unreadBoundary) }), newFactsCount > 0 && _jsxs("div", { className: threadCss.newUpdates, role: "status", children: [_jsx("span", { children: t('readNewUpdates', { count: newFactsCount }) }), _jsx(Button, { size: "sm", variant: "outline", disabled: loading, onClick: () => { timeline.jumpToLatest(); void readCurrent(true); }, children: t('markRead') })] }), remainingUnreadCount > 0 && _jsxs("div", { className: css.timelineAction, role: "status", children: [_jsx("span", { children: t('remainingUnread', { count: remainingUnreadCount }) }), _jsx(Button, { size: "sm", onClick: () => { timeline.jumpToBoundary(); void readCurrent(true); }, disabled: loading, children: t('continueReading') })] })] }) }), projection !== undefined && task !== undefined && thread !== undefined ? (task.resolution === 'closed'
|
|
469
526
|
? _jsxs("div", { className: threadCss.closedBar, "data-team-closed": true, children: [error !== undefined && _jsx("p", { className: css.error, role: "alert", children: error }), _jsxs("div", { className: threadCss.closedNotice, children: [_jsx("span", { children: t('taskClosedNotice') }), _jsx(Button, { size: "sm", variant: "outline", disabled: pending, onClick: () => { void mutateTask('reopen'); }, children: t('reopenTask') })] })] })
|
|
470
|
-
: _jsx(TeamComposer, { members: channelMembers, recipients: recipients, draft: draft, pending: pending, ...(statusMessage === undefined ? {} : { confirmation: statusMessage }), ...(error === undefined ? {} : { error }), onDraftChange: next => { drafts.writeDraft(draftKey, next); setConfirmation(undefined); setReplyRequestId(undefined); setStatusMessage(undefined); }, onRecipientsChange: next => { drafts.writeRecipients(draftKey, next); setConfirmation(undefined); setReplyRequestId(undefined); setStatusMessage(undefined); }, onSubmit: () => { void sendReply(); }, t: t }, draftKey)) : _jsx("div", {})] });
|
|
527
|
+
: _jsx(TeamComposer, { members: channelMembers, recipients: recipients, draft: draft, pending: pending, ...(statusMessage === undefined ? {} : { confirmation: statusMessage }), ...(error === undefined ? {} : { error }), onDraftChange: next => { drafts.writeDraft(draftKey, next); setConfirmation(undefined); setReplyRequestId(undefined); setStatusMessage(undefined); }, onRecipientsChange: next => { drafts.writeRecipients(draftKey, next); setConfirmation(undefined); setReplyRequestId(undefined); setStatusMessage(undefined); }, onSubmit: () => { void sendReply(); }, pendingFiles: pendingFiles, onFilesChange: setPendingFiles, t: t }, draftKey)) : _jsx("div", {})] });
|
|
471
528
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { TeamSidebarProps } from './slots.ts';
|
|
2
|
-
export declare function TeamWorkspaceBrowser({ wide, expandSidebar, navigation, selectWorkspace, selectChannel, t, useWorkspaces, loadMembers, subscribeChanges, addMember, loadChannels, createChannel, updateChannel, updateMember, recoverMember,
|
|
2
|
+
export declare function TeamWorkspaceBrowser({ wide, expandSidebar, navigation, selectWorkspace, selectChannel, t, useWorkspaces, loadMembers, subscribeChanges, addMember, loadChannels, createChannel, updateChannel, updateMember, recoverMember, joinChannel, removeChannelMember, loadModels, openMemberSession }: TeamSidebarProps): import("react").JSX.Element;
|
|
3
3
|
//# sourceMappingURL=TeamWorkspaceBrowser.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TeamWorkspaceBrowser.d.ts","sourceRoot":"","sources":["../../../src/client/TeamWorkspaceBrowser.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAQlD,wBAAgB,oBAAoB,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE,aAAa,EAAE,CAAC,EAAE,aAAa,EAAE,WAAW,EAAE,gBAAgB,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,EAAE,
|
|
1
|
+
{"version":3,"file":"TeamWorkspaceBrowser.d.ts","sourceRoot":"","sources":["../../../src/client/TeamWorkspaceBrowser.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAQlD,wBAAgB,oBAAoB,CAAC,EAAE,IAAI,EAAE,aAAa,EAAE,UAAU,EAAE,eAAe,EAAE,aAAa,EAAE,CAAC,EAAE,aAAa,EAAE,WAAW,EAAE,gBAAgB,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,mBAAmB,EAAE,UAAU,EAAE,iBAAiB,EAAE,EAAE,gBAAgB,+BA4D/T"}
|
|
@@ -5,7 +5,7 @@ import { TeamWorkspaceRow } from "./TeamWorkspaceRow.js";
|
|
|
5
5
|
import { TeamAgentsPanel } from "./TeamAgentsPanel.js";
|
|
6
6
|
import { TeamChannelsPanel } from "./TeamChannelsPanel.js";
|
|
7
7
|
import css from './sidebar.module.css';
|
|
8
|
-
export function TeamWorkspaceBrowser({ wide, expandSidebar, navigation, selectWorkspace, selectChannel, t, useWorkspaces, loadMembers, subscribeChanges, addMember, loadChannels, createChannel, updateChannel, updateMember, recoverMember,
|
|
8
|
+
export function TeamWorkspaceBrowser({ wide, expandSidebar, navigation, selectWorkspace, selectChannel, t, useWorkspaces, loadMembers, subscribeChanges, addMember, loadChannels, createChannel, updateChannel, updateMember, recoverMember, joinChannel, removeChannelMember, loadModels, openMemberSession }) {
|
|
9
9
|
const navigationState = useSyncExternalStore(navigation.subscribe, navigation.getSnapshot, navigation.getSnapshot);
|
|
10
10
|
const workspaces = useWorkspaces(state => state.items);
|
|
11
11
|
const selected = navigationState.workspaceId;
|
|
@@ -36,5 +36,5 @@ export function TeamWorkspaceBrowser({ wide, expandSidebar, navigation, selectWo
|
|
|
36
36
|
if (!wide) {
|
|
37
37
|
return _jsxs("nav", { className: css.railWorkspace, "aria-label": t('workspaceSections'), children: [_jsx(Tooltip, { label: t('channels'), side: "right", children: _jsx("button", { type: "button", className: css.railButton, "aria-label": t('channels'), onClick: () => { setPendingSection('channels'); expandSidebar(); }, children: _jsx(IconListPenOutline16, { size: 16 }) }) }), _jsx(Tooltip, { label: t('agents'), side: "right", children: _jsx("button", { type: "button", className: css.railButton, "aria-label": t('agents'), onClick: () => { setPendingSection('agents'); expandSidebar(); }, children: _jsx(IconAgentPresetOutline16, { size: 16 }) }) })] });
|
|
38
38
|
}
|
|
39
|
-
return _jsxs("section", { className: css.workspaceBrowser, "aria-label": t('workspaces'), children: [_jsx("div", { className: css.workspaceHeader, children: t('workspaces') }), _jsxs("div", { className: css.workspaceList, children: [workspaces.map(workspace => _jsx(TeamWorkspaceRow, { workspaceId: workspace.workspaceId, title: workspace.title, path: workspace.path, selected: workspace.workspaceId === selectedId, current: workspace.workspaceId === selectedId && overviewIsCurrent, onSelect: selectWorkspace }, workspace.workspaceId)), workspaces.length === 0 && _jsx("p", { className: css.emptyState, children: t('empty') })] }), selectedId !== undefined && _jsxs("div", { className: css.workspaceSection, children: [_jsx("div", { ref: channelsRef, children: _jsx(TeamChannelsPanel, { workspaceId: selectedId, loadMembers: loadMembers, loadChannels: loadChannels, subscribeChanges: subscribeChanges, createChannel: createChannel, updateChannel: updateChannel, joinChannel: joinChannel, removeChannelMember: removeChannelMember, creatingAgents: creatingAgents.filter(request => request.workspaceId === selectedId), ...(navigationState.memberSessionId !== undefined || navigationState.channelRef === undefined ? {} : { selectedChannelRef: navigationState.channelRef }), selectChannel: selectChannel, t: t }, selectedId) }), _jsx("div", { ref: agentsRef, children: _jsx(TeamAgentsPanel, { workspaceId: selectedId, loadMembers: loadMembers, subscribeChanges: subscribeChanges, loadChannels: loadChannels, addMember: addMember, updateMember: updateMember, recoverMember: recoverMember,
|
|
39
|
+
return _jsxs("section", { className: css.workspaceBrowser, "aria-label": t('workspaces'), children: [_jsx("div", { className: css.workspaceHeader, children: t('workspaces') }), _jsxs("div", { className: css.workspaceList, children: [workspaces.map(workspace => _jsx(TeamWorkspaceRow, { workspaceId: workspace.workspaceId, title: workspace.title, path: workspace.path, selected: workspace.workspaceId === selectedId, current: workspace.workspaceId === selectedId && overviewIsCurrent, onSelect: selectWorkspace }, workspace.workspaceId)), workspaces.length === 0 && _jsx("p", { className: css.emptyState, children: t('empty') })] }), selectedId !== undefined && _jsxs("div", { className: css.workspaceSection, children: [_jsx("div", { ref: channelsRef, children: _jsx(TeamChannelsPanel, { workspaceId: selectedId, loadMembers: loadMembers, loadChannels: loadChannels, subscribeChanges: subscribeChanges, createChannel: createChannel, updateChannel: updateChannel, joinChannel: joinChannel, removeChannelMember: removeChannelMember, creatingAgents: creatingAgents.filter(request => request.workspaceId === selectedId), ...(navigationState.memberSessionId !== undefined || navigationState.channelRef === undefined ? {} : { selectedChannelRef: navigationState.channelRef }), selectChannel: selectChannel, t: t }, selectedId) }), _jsx("div", { ref: agentsRef, children: _jsx(TeamAgentsPanel, { workspaceId: selectedId, loadMembers: loadMembers, subscribeChanges: subscribeChanges, loadChannels: loadChannels, addMember: addMember, updateMember: updateMember, recoverMember: recoverMember, joinChannel: joinChannel, removeChannelMember: removeChannelMember, loadModels: loadModels, ...(navigationState.memberSessionId === undefined ? {} : { memberSessionId: navigationState.memberSessionId }), openMemberSession: openMemberSession, onCreatingChange: (request, creating) => { setCreatingAgents(current => creating ? [...current.filter(item => item.requestId !== request.requestId), request] : current.filter(item => item.requestId !== request.requestId)); }, t: t }, selectedId) })] })] });
|
|
40
40
|
}
|
|
@@ -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.
|
|
@@ -24,7 +26,6 @@ function registerModeShadow(ctx, navigation, changes, drafts, name, component, e
|
|
|
24
26
|
updateChannel: (request) => ctx.remote.agentTeam.updateChannel(request),
|
|
25
27
|
updateMember: (request) => ctx.remote.agentTeam.updateMember(request),
|
|
26
28
|
recoverMember: (request) => ctx.remote.agentTeam.recoverMember(request),
|
|
27
|
-
restartMember: (request) => ctx.remote.agentTeam.restartMember(request),
|
|
28
29
|
// The Host-scoped catalog needs no live Member, so suspended ones stay editable too.
|
|
29
30
|
loadModels: async () => {
|
|
30
31
|
const connection = ctx.get('connection');
|
|
@@ -71,6 +72,7 @@ function registerModeShadow(ctx, navigation, changes, drafts, name, component, e
|
|
|
71
72
|
getAttachment: (request) => ctx.remote.agentTeam.getAttachment(request),
|
|
72
73
|
reply: (request) => ctx.remote.agentTeam.reply(request),
|
|
73
74
|
changeTask: (request) => ctx.remote.agentTeam.changeTask(request),
|
|
75
|
+
resolveTaskRefs: (request) => ctx.remote.agentTeam.resolveTaskRefs(request),
|
|
74
76
|
} : {}),
|
|
75
77
|
...(name === 'sidebar.workspaces' ? {
|
|
76
78
|
addMember: (request) => ctx.remote.agentTeam.addMember(request),
|
|
@@ -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) {
|
|
@@ -37,7 +37,6 @@ export declare const zh: {
|
|
|
37
37
|
readonly editChannel: "编辑频道";
|
|
38
38
|
readonly editAgent: "编辑 Agent";
|
|
39
39
|
readonly resumeAgent: "恢复";
|
|
40
|
-
readonly restartAgent: "重启会话";
|
|
41
40
|
readonly optionalSuffix: "(可选)";
|
|
42
41
|
readonly agentDescriptionPlaceholder: "留空则暂无描述";
|
|
43
42
|
readonly channelsPickerEmpty: "选择初始频道";
|
|
@@ -115,6 +114,10 @@ export declare const zh: {
|
|
|
115
114
|
readonly activityClaimReleased: "{actor} 释放了「{direction}」";
|
|
116
115
|
readonly activityClaimsReleased: "{actor} 因成员权限变化释放了 {count} 个 Claim";
|
|
117
116
|
readonly activityAccepted: "{actor} 验收了此 Task";
|
|
117
|
+
readonly activityAcceptedWithClaims: "{actor} 验收了此 Task,并一并完成了 {count} 个未完成 Claim";
|
|
118
|
+
readonly acceptEarlyTitle: "提前验收任务";
|
|
119
|
+
readonly acceptEarlyBody: "将验收本 Task,并把以下 {count} 个未完成的 Claim 一并标记为完成:";
|
|
120
|
+
readonly acceptingTask: "验收中…";
|
|
118
121
|
readonly activityClosed: "{actor} 关闭了此 Task";
|
|
119
122
|
readonly activityReopened: "{actor} 重新打开了此 Task";
|
|
120
123
|
readonly mentionConfirmation: "提及未关注成员需要确认,再次发送以继续";
|
|
@@ -162,7 +165,6 @@ export declare const en: {
|
|
|
162
165
|
readonly editChannel: "Edit Channel";
|
|
163
166
|
readonly editAgent: "Edit Agent";
|
|
164
167
|
readonly resumeAgent: "Resume";
|
|
165
|
-
readonly restartAgent: "Restart session";
|
|
166
168
|
readonly optionalSuffix: " (optional)";
|
|
167
169
|
readonly agentDescriptionPlaceholder: "Leave empty if not needed";
|
|
168
170
|
readonly channelsPickerEmpty: "Choose initial channels";
|
|
@@ -240,6 +242,10 @@ export declare const en: {
|
|
|
240
242
|
readonly activityClaimReleased: "{actor} released “{direction}”";
|
|
241
243
|
readonly activityClaimsReleased: "{actor} released {count} Claim(s) after a membership change";
|
|
242
244
|
readonly activityAccepted: "{actor} accepted this Task";
|
|
245
|
+
readonly activityAcceptedWithClaims: "{actor} accepted this Task and completed {count} unfinished Claims with it";
|
|
246
|
+
readonly acceptEarlyTitle: "Accept Task early";
|
|
247
|
+
readonly acceptEarlyBody: "This accepts the Task and marks the following {count} unfinished Claims as done:";
|
|
248
|
+
readonly acceptingTask: "Accepting…";
|
|
243
249
|
readonly activityClosed: "{actor} closed this Task";
|
|
244
250
|
readonly activityReopened: "{actor} reopened this Task";
|
|
245
251
|
readonly mentionConfirmation: "Mentioning an unfollowed member requires confirmation; send again to continue";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,EAAE
|
|
1
|
+
{"version":3,"file":"locales.d.ts","sourceRoot":"","sources":["../../../src/client/locales.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+HL,CAAA;AAEV,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+HL,CAAA;AAEV,MAAM,MAAM,OAAO,GAAG,MAAM,OAAO,EAAE,CAAA"}
|
|
@@ -37,7 +37,6 @@ export const zh = {
|
|
|
37
37
|
editChannel: '编辑频道',
|
|
38
38
|
editAgent: '编辑 Agent',
|
|
39
39
|
resumeAgent: '恢复',
|
|
40
|
-
restartAgent: '重启会话',
|
|
41
40
|
optionalSuffix: '(可选)',
|
|
42
41
|
agentDescriptionPlaceholder: '留空则暂无描述',
|
|
43
42
|
channelsPickerEmpty: '选择初始频道',
|
|
@@ -115,6 +114,10 @@ export const zh = {
|
|
|
115
114
|
activityClaimReleased: '{actor} 释放了「{direction}」',
|
|
116
115
|
activityClaimsReleased: '{actor} 因成员权限变化释放了 {count} 个 Claim',
|
|
117
116
|
activityAccepted: '{actor} 验收了此 Task',
|
|
117
|
+
activityAcceptedWithClaims: '{actor} 验收了此 Task,并一并完成了 {count} 个未完成 Claim',
|
|
118
|
+
acceptEarlyTitle: '提前验收任务',
|
|
119
|
+
acceptEarlyBody: '将验收本 Task,并把以下 {count} 个未完成的 Claim 一并标记为完成:',
|
|
120
|
+
acceptingTask: '验收中…',
|
|
118
121
|
activityClosed: '{actor} 关闭了此 Task',
|
|
119
122
|
activityReopened: '{actor} 重新打开了此 Task',
|
|
120
123
|
mentionConfirmation: '提及未关注成员需要确认,再次发送以继续',
|
|
@@ -162,7 +165,6 @@ export const en = {
|
|
|
162
165
|
editChannel: 'Edit Channel',
|
|
163
166
|
editAgent: 'Edit Agent',
|
|
164
167
|
resumeAgent: 'Resume',
|
|
165
|
-
restartAgent: 'Restart session',
|
|
166
168
|
optionalSuffix: ' (optional)',
|
|
167
169
|
agentDescriptionPlaceholder: 'Leave empty if not needed',
|
|
168
170
|
channelsPickerEmpty: 'Choose initial channels',
|
|
@@ -240,6 +242,10 @@ export const en = {
|
|
|
240
242
|
activityClaimReleased: '{actor} released “{direction}”',
|
|
241
243
|
activityClaimsReleased: '{actor} released {count} Claim(s) after a membership change',
|
|
242
244
|
activityAccepted: '{actor} accepted this Task',
|
|
245
|
+
activityAcceptedWithClaims: '{actor} accepted this Task and completed {count} unfinished Claims with it',
|
|
246
|
+
acceptEarlyTitle: 'Accept Task early',
|
|
247
|
+
acceptEarlyBody: 'This accepts the Task and marks the following {count} unfinished Claims as done:',
|
|
248
|
+
acceptingTask: 'Accepting…',
|
|
243
249
|
activityClosed: '{actor} closed this Task',
|
|
244
250
|
activityReopened: '{actor} reopened this Task',
|
|
245
251
|
mentionConfirmation: 'Mentioning an unfollowed member requires confirmation; send again to continue',
|
|
@@ -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"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
export const TEAM_COMMAND_SOURCE = 'agent-team-command';
|
|
2
|
+
export const TEAM_MEMBER_SOURCE = 'agent-team-member';
|
|
3
|
+
/** Preserve the public Session command admission contract, not handler outcome. */
|
|
4
|
+
export async function admitTeamCompact(session) {
|
|
5
|
+
const result = await session.command('/compact');
|
|
6
|
+
if (!result.ok)
|
|
7
|
+
throw new Error(`session.command failed: ${result.error.message}`);
|
|
8
|
+
return result.value.matched ? { kind: 'success' } : { kind: 'error', text: 'unknown command: /compact' };
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The two Team-only sources live in the shared public registry, but their
|
|
12
|
+
* candidate plane is inert for every ordinary Session. The member source owns
|
|
13
|
+
* its stable ref→label cache so serialization never falls back to guessing a
|
|
14
|
+
* handle from plain text.
|
|
15
|
+
*/
|
|
16
|
+
export function createTeamMemberSessionSources(options) {
|
|
17
|
+
const labels = new Map();
|
|
18
|
+
const command = {
|
|
19
|
+
trigger: '/',
|
|
20
|
+
name: TEAM_COMMAND_SOURCE,
|
|
21
|
+
showGroupTitle: false,
|
|
22
|
+
async candidates(session, { query }) {
|
|
23
|
+
if (!options.isEmbeddedMemberSession(session.sessionId) || !'compact'.startsWith(query.toLocaleLowerCase()))
|
|
24
|
+
return [];
|
|
25
|
+
return [{ name: '/compact', description: '压缩当前 Session 上下文', value: 'compact' }];
|
|
26
|
+
},
|
|
27
|
+
onPick({ candidate, session }) {
|
|
28
|
+
if (candidate.value !== 'compact' || !options.isEmbeddedMemberSession(session.sessionId))
|
|
29
|
+
return undefined;
|
|
30
|
+
const claim = {
|
|
31
|
+
token: '/compact',
|
|
32
|
+
submit: async () => options.executeCompact(session.sessionId),
|
|
33
|
+
};
|
|
34
|
+
return { claim };
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
const member = {
|
|
38
|
+
trigger: '@',
|
|
39
|
+
name: TEAM_MEMBER_SOURCE,
|
|
40
|
+
showGroupTitle: false,
|
|
41
|
+
async candidates(session, { query, signal }) {
|
|
42
|
+
if (!options.isEmbeddedMemberSession(session.sessionId))
|
|
43
|
+
return [];
|
|
44
|
+
const normalized = query.toLocaleLowerCase();
|
|
45
|
+
const members = await options.members();
|
|
46
|
+
if (signal.aborted)
|
|
47
|
+
return [];
|
|
48
|
+
return members
|
|
49
|
+
.filter(status => status.member.sessionId !== session.sessionId && status.member.state !== 'inactive')
|
|
50
|
+
.filter(status => status.member.handle.toLocaleLowerCase().startsWith(normalized))
|
|
51
|
+
.map(status => {
|
|
52
|
+
labels.set(status.member.memberId, `@${status.member.handle}`);
|
|
53
|
+
return {
|
|
54
|
+
name: `@${status.member.handle}`,
|
|
55
|
+
description: '引用成员 · 不会通知',
|
|
56
|
+
value: String(status.member.memberId),
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
onPick({ candidate, session }) {
|
|
61
|
+
if (!options.isEmbeddedMemberSession(session.sessionId) || candidate.value === undefined)
|
|
62
|
+
return undefined;
|
|
63
|
+
const ref = candidate.value;
|
|
64
|
+
const label = labels.get(ref);
|
|
65
|
+
if (label === undefined)
|
|
66
|
+
return undefined;
|
|
67
|
+
const insert = {
|
|
68
|
+
source: TEAM_MEMBER_SOURCE,
|
|
69
|
+
// The input machine retains the typed trigger; label must therefore
|
|
70
|
+
// omit `@` or the draft would become `@@handle` after insertion.
|
|
71
|
+
label: label.slice(1),
|
|
72
|
+
ref,
|
|
73
|
+
clipboardText: label,
|
|
74
|
+
};
|
|
75
|
+
return { insert };
|
|
76
|
+
},
|
|
77
|
+
codec: {
|
|
78
|
+
clipboardText: ref => labels.get(ref) ?? `@${String(ref)}`,
|
|
79
|
+
serialize: async (ref) => {
|
|
80
|
+
const stable = String(ref);
|
|
81
|
+
const label = labels.get(ref) ?? '';
|
|
82
|
+
return `<team-member ref="${escapeAttribute(stable)}">${escapeText(label)}</team-member>`;
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
return [command, member];
|
|
87
|
+
}
|
|
88
|
+
/** Unknown leading slash must never fall into the shipped global palette. */
|
|
89
|
+
export function allowsMemberSessionSubmit(draft, claimToken) {
|
|
90
|
+
const trimmed = draft.trimStart();
|
|
91
|
+
return !trimmed.startsWith('/') || claimToken === '/compact';
|
|
92
|
+
}
|
|
93
|
+
function escapeText(value) {
|
|
94
|
+
return value.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>');
|
|
95
|
+
}
|
|
96
|
+
function escapeAttribute(value) {
|
|
97
|
+
return escapeText(value).replaceAll('"', '"').replaceAll("'", ''');
|
|
98
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { RemoteResult } from '@deepseek-ai/dsh-typert-protocol';
|
|
2
|
+
import type { AgentTeamAttachmentId, AgentTeamPutAttachmentRequest, AgentTeamPutAttachmentResult, AgentTeamRequestId } from '@wowyuarm/dsh-agent-team/types';
|
|
3
|
+
/** Fresh idempotency identity for one Client-initiated durable request. */
|
|
4
|
+
export declare const mintRequestId: () => AgentTeamRequestId;
|
|
5
|
+
/**
|
|
6
|
+
* Upload composer files in order. One failure stops with the error text; the
|
|
7
|
+
* caller keeps its chips so a retry uploads only the still-pending files.
|
|
8
|
+
*/
|
|
9
|
+
export declare const uploadComposerFiles: (putAttachment: (request: AgentTeamPutAttachmentRequest) => Promise<RemoteResult<AgentTeamPutAttachmentResult>>, workspaceId: AgentTeamPutAttachmentRequest["workspaceId"], files: readonly File[]) => Promise<{
|
|
10
|
+
ok: true;
|
|
11
|
+
attachmentIds: readonly AgentTeamAttachmentId[];
|
|
12
|
+
} | {
|
|
13
|
+
ok: false;
|
|
14
|
+
error: string;
|
|
15
|
+
}>;
|
|
16
|
+
//# sourceMappingURL=requests.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"requests.d.ts","sourceRoot":"","sources":["../../../src/client/requests.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AACpE,OAAO,KAAK,EACV,qBAAqB,EAAE,6BAA6B,EAAE,4BAA4B,EAAE,kBAAkB,EACvG,MAAM,gCAAgC,CAAA;AAGvC,2EAA2E;AAC3E,eAAO,MAAM,aAAa,QAAO,kBAA+D,CAAA;AAEhG;;;GAGG;AACH,eAAO,MAAM,mBAAmB,GAC9B,eAAe,CAAC,OAAO,EAAE,6BAA6B,KAAK,OAAO,CAAC,YAAY,CAAC,4BAA4B,CAAC,CAAC,EAC9G,aAAa,6BAA6B,CAAC,aAAa,CAAC,EACzD,OAAO,SAAS,IAAI,EAAE,KACrB,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,aAAa,EAAE,SAAS,qBAAqB,EAAE,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAatG,CAAA"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { bytesToBase64 } from "./attachment-preview.js";
|
|
2
|
+
/** Fresh idempotency identity for one Client-initiated durable request. */
|
|
3
|
+
export const mintRequestId = () => crypto.randomUUID();
|
|
4
|
+
/**
|
|
5
|
+
* Upload composer files in order. One failure stops with the error text; the
|
|
6
|
+
* caller keeps its chips so a retry uploads only the still-pending files.
|
|
7
|
+
*/
|
|
8
|
+
export const uploadComposerFiles = async (putAttachment, workspaceId, files) => {
|
|
9
|
+
const attachmentIds = [];
|
|
10
|
+
for (const file of files) {
|
|
11
|
+
const uploaded = await putAttachment({
|
|
12
|
+
requestId: mintRequestId(), workspaceId,
|
|
13
|
+
name: file.name,
|
|
14
|
+
mediaType: file.type === '' ? undefined : file.type,
|
|
15
|
+
bytesBase64: bytesToBase64(new Uint8Array(await file.arrayBuffer())),
|
|
16
|
+
});
|
|
17
|
+
if (!uploaded.ok)
|
|
18
|
+
return { ok: false, error: uploaded.error.message };
|
|
19
|
+
attachmentIds.push(uploaded.value.attachmentId);
|
|
20
|
+
}
|
|
21
|
+
return { ok: true, attachmentIds };
|
|
22
|
+
};
|