@wowyuarm/dsh-agent-team 0.1.14-alpha.2 → 0.1.14
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 +5 -3
- package/README.zh.md +5 -3
- package/cordis.patch.yml +2 -185
- package/package.json +52 -46
- package/packages/agent-team/README.md +1 -1
- package/packages/agent-team/README.zh.md +1 -1
- package/packages/agent-team/core-skills/member-memory-manager/SKILL.md +30 -0
- package/packages/agent-team/lib/context-continuity-host.js +91 -0
- package/packages/agent-team/lib/context-projection.js +266 -489
- package/packages/agent-team/lib/context-source.js +17 -106
- package/packages/agent-team/lib/human-avatar.js +61 -0
- package/packages/agent-team/lib/human-profile.js +46 -0
- package/packages/agent-team/lib/human-update-check.js +119 -0
- package/packages/agent-team/lib/index.js +408 -264
- package/packages/agent-team/lib/ledger.js +120 -15
- package/packages/agent-team/lib/member-context.js +9 -8
- package/packages/agent-team/lib/member-runtime.js +1 -1
- package/packages/agent-team/lib/member-time-context.js +2 -5
- package/packages/agent-team/lib/preset-roster.js +15 -0
- package/packages/agent-team/lib/pressure-policy.js +9 -6
- package/packages/agent-team/lib/session-remediation.js +493 -0
- package/packages/agent-team/lib/stored-session-reader.js +2 -1
- package/packages/agent-team/lib/typert.host.js +605 -291
- package/packages/agent-team/lib/typert.remote-client.d.ts +15 -6
- package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
- package/packages/agent-team/lib/typert.remote-client.js +367 -244
- package/packages/agent-team/lib/types/context-continuity-host.d.ts +84 -0
- package/packages/agent-team/lib/types/context-continuity-host.d.ts.map +1 -0
- package/packages/agent-team/lib/types/context-projection.d.ts +130 -185
- package/packages/agent-team/lib/types/context-projection.d.ts.map +1 -1
- package/packages/agent-team/lib/types/context-source.d.ts +17 -81
- package/packages/agent-team/lib/types/context-source.d.ts.map +1 -1
- package/packages/agent-team/lib/types/human-avatar.d.ts +29 -0
- package/packages/agent-team/lib/types/human-avatar.d.ts.map +1 -0
- package/packages/agent-team/lib/types/human-profile.d.ts +44 -0
- package/packages/agent-team/lib/types/human-profile.d.ts.map +1 -0
- package/packages/agent-team/lib/types/human-update-check.d.ts +73 -0
- package/packages/agent-team/lib/types/human-update-check.d.ts.map +1 -0
- package/packages/agent-team/lib/types/index.d.ts +112 -38
- package/packages/agent-team/lib/types/index.d.ts.map +1 -1
- package/packages/agent-team/lib/types/ledger.d.ts +31 -2
- package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
- package/packages/agent-team/lib/types/member-context.d.ts +0 -14
- package/packages/agent-team/lib/types/member-context.d.ts.map +1 -1
- package/packages/agent-team/lib/types/member-time-context.d.ts +0 -15
- package/packages/agent-team/lib/types/member-time-context.d.ts.map +1 -1
- package/packages/agent-team/lib/types/preset-roster.d.ts +5 -0
- package/packages/agent-team/lib/types/preset-roster.d.ts.map +1 -0
- package/packages/agent-team/lib/types/pressure-policy.d.ts +6 -0
- package/packages/agent-team/lib/types/pressure-policy.d.ts.map +1 -1
- package/packages/agent-team/lib/types/session-remediation.d.ts +172 -0
- package/packages/agent-team/lib/types/session-remediation.d.ts.map +1 -0
- package/packages/agent-team/lib/types/stored-session-reader.d.ts +2 -1
- package/packages/agent-team/lib/types/stored-session-reader.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types/entities.d.ts +20 -3
- package/packages/agent-team/lib/types/types/entities.d.ts.map +1 -1
- package/packages/agent-team/lib/types/types/requests-results.d.ts +63 -0
- package/packages/agent-team/lib/types/types/requests-results.d.ts.map +1 -1
- package/packages/agent-team/preset/team-member/agent.cordis.yml +166 -0
- package/packages/client-agent-team/lib/client.js +1667 -516
- package/packages/client-agent-team/lib/client.js.map +1 -1
- package/packages/client-agent-team/lib/types/client/HumanSettingsSection.d.ts +15 -0
- package/packages/client-agent-team/lib/types/client/HumanSettingsSection.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/HumanSettingsSection.js +115 -0
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +12 -8
- package/packages/client-agent-team/lib/types/client/TeamAvatarStack.d.ts +22 -1
- package/packages/client-agent-team/lib/types/client/TeamAvatarStack.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamAvatarStack.js +25 -2
- package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts +6 -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 +69 -31
- package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +4 -4
- package/packages/client-agent-team/lib/types/client/TeamComposer.js +3 -3
- 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 +12 -5
- package/packages/client-agent-team/lib/types/client/TeamFooterAction.js +2 -2
- package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts +4 -1
- package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamInboxPage.js +22 -7
- package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts +11 -1
- package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMemberEditor.js +69 -8
- package/packages/client-agent-team/lib/types/client/TeamMembersAction.js +2 -2
- package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts +18 -3
- package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamMessage.js +213 -67
- package/packages/client-agent-team/lib/types/client/TeamRowMenu.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/TeamRowMenu.js +2 -2
- package/packages/client-agent-team/lib/types/client/TeamSidebarSection.js +2 -2
- package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts +5 -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 +55 -29
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +3 -3
- package/packages/client-agent-team/lib/types/client/TeamWorkspaceSelector.js +2 -2
- package/packages/client-agent-team/lib/types/client/avatar-image.d.ts +21 -0
- package/packages/client-agent-team/lib/types/client/avatar-image.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/avatar-image.js +9 -0
- package/packages/client-agent-team/lib/types/client/human-identity.d.ts +94 -0
- package/packages/client-agent-team/lib/types/client/human-identity.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/human-identity.js +117 -0
- package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/index.js +112 -41
- package/packages/client-agent-team/lib/types/client/locales.d.ts +72 -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 +78 -2
- package/packages/client-agent-team/lib/types/client/multi-menu-field.js +2 -2
- package/packages/client-agent-team/lib/types/client/refs.d.ts +71 -0
- package/packages/client-agent-team/lib/types/client/refs.d.ts.map +1 -0
- package/packages/client-agent-team/lib/types/client/refs.js +181 -0
- package/packages/client-agent-team/lib/types/client/slots.d.ts +8 -2
- package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
- package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +57 -9
- 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 +85 -17
- package/packages/tool-agent-team/README.md +1 -1
- package/packages/tool-agent-team/README.zh.md +1 -1
- package/packages/tool-agent-team/lib/context-tools.js +20 -6
- package/packages/tool-agent-team/lib/index.js +2 -2
- package/packages/tool-agent-team/lib/types/context-tools.d.ts.map +1 -1
- package/packages/agent-team/lib/context-management.js +0 -371
- package/packages/agent-team/lib/types/context-management.d.ts +0 -154
- package/packages/agent-team/lib/types/context-management.d.ts.map +0 -1
- package/packages/client-agent-team/lib/types/client/task-refs.d.ts +0 -31
- package/packages/client-agent-team/lib/types/client/task-refs.d.ts.map +0 -1
- package/packages/client-agent-team/lib/types/client/task-refs.js +0 -99
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
-
import { Button, DisclosureRow,
|
|
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 { diagnosticText, TeamPresenceDot } from "./TeamPresenceDot.js";
|
|
6
6
|
import { TeamMessage } from "./TeamMessage.js";
|
|
7
7
|
import { TeamRunDivider } from "./TeamRunDivider.js";
|
|
8
|
-
import { formatActivity, formatClaimState, formatTaskStatus, formatTaskTitle, mentionNamesOf, mentionedMemberIds, taskStatusDot } from "./team-formatters.js";
|
|
8
|
+
import { firstSentence, formatActivity, formatClaimState, formatRiskClass, formatTaskStatus, formatTaskTitle, mentionNameOf, mentionNamesOf, mentionedMemberIds, taskStatusDot } from "./team-formatters.js";
|
|
9
9
|
import { TeamStateDot } from "./TeamStateDot.js";
|
|
10
10
|
import { mintRequestId, uploadComposerFiles } from "./requests.js";
|
|
11
11
|
import { daySeparatorLabel, isRunGap, timelineDayKey } from "./team-separators.js";
|
|
12
12
|
import { useTimelineScroll } from "./timeline-scroll.js";
|
|
13
|
-
import { hostTaskRefLookup, jumpToTaskThread } from "./
|
|
13
|
+
import { hostTaskRefLookup, jumpToTaskThread } from "./refs.js";
|
|
14
|
+
import { hostThreadRefLookup, jumpToThread } from "./refs.js";
|
|
15
|
+
import { rosterChannelName, rosterMember } from "./refs.js";
|
|
14
16
|
import css from './conversation.module.css';
|
|
15
17
|
import threadCss from './thread.module.css';
|
|
16
18
|
function factKey(fact) {
|
|
@@ -27,10 +29,10 @@ function messageFact(message, mentions = []) {
|
|
|
27
29
|
* what keeps a memoized row from re-rendering on every refresh.
|
|
28
30
|
*/
|
|
29
31
|
const mentionNamesCache = new WeakMap();
|
|
30
|
-
function stableMentionNames(mentions, handles) {
|
|
31
|
-
const names = mentionNamesOf(mentions, handles);
|
|
32
|
+
function stableMentionNames(mentions, handles, humanName) {
|
|
33
|
+
const names = mentionNamesOf(mentions, handles, humanName);
|
|
32
34
|
const cached = mentionNamesCache.get(mentions);
|
|
33
|
-
if (cached !== undefined && cached.length === names.length && cached.every((name, index) => name === names[index]))
|
|
35
|
+
if (cached !== undefined && cached.length === names.length && cached.every((name, index) => mentionNameOf(name) === mentionNameOf(names[index])))
|
|
34
36
|
return cached;
|
|
35
37
|
mentionNamesCache.set(mentions, names);
|
|
36
38
|
return names;
|
|
@@ -49,7 +51,7 @@ function readMeta(facts) {
|
|
|
49
51
|
return new Map(facts.map(fact => [factKey(fact.fact), fact]));
|
|
50
52
|
}
|
|
51
53
|
export function TeamThreadPage(props) {
|
|
52
|
-
const { workspaceId, channelRef, taskRef, threadRef, taskNumber, backToWorkspace, selectChannel, selectThread, resolveTaskRefs, putAttachment, loadChannels, readThread, loadThreadHistory, threadObservations, subscribeChanges, loadMembers, drafts, getAttachment, reply, changeTask, promoteThread, t, } = props;
|
|
54
|
+
const { workspaceId, humanName, humanAvatarUrl, channelRef, taskRef, threadRef, taskNumber, backToWorkspace, selectChannel, selectThread, resolveTaskRefs, resolveThreadRefs, openMemberSession, putAttachment, loadChannels, readThread, loadThreadHistory, threadObservations, subscribeChanges, loadMembers, drafts, getAttachment, reply, changeTask, promoteThread, t, } = props;
|
|
53
55
|
const threadRequest = { threadRef, ...(taskRef === undefined ? {} : { taskRef }) };
|
|
54
56
|
const [projection, setProjection] = useState();
|
|
55
57
|
const [channelView, setChannelView] = useState();
|
|
@@ -425,15 +427,16 @@ export function TeamThreadPage(props) {
|
|
|
425
427
|
}, [activeProjection?.anchor, readFacts, currentFacts, metadata]);
|
|
426
428
|
const memberName = (memberId) => {
|
|
427
429
|
if (memberId === channelView?.humanMemberId)
|
|
428
|
-
return
|
|
430
|
+
return humanName;
|
|
429
431
|
const status = members.find(candidate => candidate.member.memberId === memberId);
|
|
430
432
|
return status === undefined ? t('memberUnknown') : `@${status.member.handle}`;
|
|
431
433
|
};
|
|
432
434
|
const messageSender = (fact) => fact.kind === 'message' ? fact.message.sender : undefined;
|
|
433
435
|
const [pendingFiles, setPendingFiles] = useState([]);
|
|
434
436
|
// Branded-ref navigation for message bodies: channel refs hop to the
|
|
435
|
-
// Channel;
|
|
436
|
-
//
|
|
437
|
+
// Channel; thread and task refs cited here resolve through the Host and
|
|
438
|
+
// jump to their home Channel. Unresolvable refs never become links (see
|
|
439
|
+
// TeamMessage), so this path only fires for refs the Host already confirmed.
|
|
437
440
|
// Identity-stable: a fresh closure per render would defeat TeamMessage's memo.
|
|
438
441
|
const openRef = useCallback((ref) => {
|
|
439
442
|
if (ref.startsWith('channel:') && ref !== channelRef) {
|
|
@@ -441,31 +444,42 @@ export function TeamThreadPage(props) {
|
|
|
441
444
|
return;
|
|
442
445
|
}
|
|
443
446
|
if (ref.startsWith('thread:') && ref !== threadRef) {
|
|
444
|
-
// Thread
|
|
445
|
-
|
|
446
|
-
void loadChannels({ workspaceId, threadRef: ref, includeActivities: false, limit: 1 }).then(result => {
|
|
447
|
-
if (!result.ok)
|
|
448
|
-
return;
|
|
449
|
-
const target = result.value.items[0];
|
|
450
|
-
if (target === undefined)
|
|
451
|
-
return;
|
|
452
|
-
const targetChannel = result.value.channels.find(channel => channel.channelRef === target.message.channelRef);
|
|
453
|
-
if (targetChannel !== undefined)
|
|
454
|
-
selectThread(target.thread.threadRef, targetChannel.channelRef, target.task?.taskRef, target.taskNumber);
|
|
455
|
-
});
|
|
447
|
+
// Another Thread cited here: resolve its home Channel and jump.
|
|
448
|
+
jumpToThread(resolveThreadRefs, workspaceId, ref, selectThread);
|
|
456
449
|
return;
|
|
457
450
|
}
|
|
458
451
|
if (ref.startsWith('task:') && ref !== taskRef) {
|
|
459
452
|
// Another Task cited here: resolve its home Channel and jump.
|
|
460
453
|
jumpToTaskThread(resolveTaskRefs, workspaceId, ref, selectThread);
|
|
461
454
|
}
|
|
462
|
-
}, [channelRef, threadRef, taskRef, workspaceId, selectChannel, selectThread,
|
|
455
|
+
}, [channelRef, threadRef, taskRef, workspaceId, selectChannel, selectThread, resolveTaskRefs, resolveThreadRefs]);
|
|
463
456
|
const lookupTaskRefs = useMemo(() => hostTaskRefLookup(resolveTaskRefs, workspaceId), [resolveTaskRefs, workspaceId]);
|
|
457
|
+
const lookupThreadRefs = useMemo(() => hostThreadRefLookup(resolveThreadRefs, workspaceId), [resolveThreadRefs, workspaceId]);
|
|
458
|
+
// Roster chips resolve synchronously from loaded data: channel names from
|
|
459
|
+
// the Channel view, member facts from the member list. Anything outside the
|
|
460
|
+
// loaded window stays plain text — the same rule unresolvable Task/Thread
|
|
461
|
+
// refs follow. The lookups key on roster content, not array identity: every
|
|
462
|
+
// refresh hands over fresh arrays, and the memoized rows must survive
|
|
463
|
+
// change bursts that leave the roster itself untouched.
|
|
464
|
+
const rosterKey = useMemo(() => [
|
|
465
|
+
(channelView?.channels ?? []).map(channel => `${channel.channelRef}=${channel.name}=${channel.state}`).join(','),
|
|
466
|
+
members.map(status => `${status.member.memberId}=${status.member.handle}=${status.member.sessionId}=${status.availability}`).join(','),
|
|
467
|
+
channelView?.humanMemberId ?? '',
|
|
468
|
+
].join(';'), [channelView, members]);
|
|
469
|
+
const channelNameOf = useMemo(() => {
|
|
470
|
+
const channels = channelView?.channels ?? [];
|
|
471
|
+
return (ref) => rosterChannelName(channels, ref);
|
|
472
|
+
}, [rosterKey]);
|
|
473
|
+
const memberOf = useMemo(() => {
|
|
474
|
+
const humanMemberId = channelView?.humanMemberId;
|
|
475
|
+
return (ref) => rosterMember(members, humanMemberId, humanName, ref);
|
|
476
|
+
}, [rosterKey, humanName]);
|
|
464
477
|
const renderFact = (fact, grouped = false) => {
|
|
465
478
|
if (fact.kind === 'message') {
|
|
466
479
|
const sender = memberName(fact.message.sender);
|
|
467
480
|
const senderStatus = members.find(candidate => candidate.member.memberId === fact.message.sender);
|
|
468
|
-
|
|
481
|
+
const human = fact.message.sender === channelView?.humanMemberId;
|
|
482
|
+
return _jsx(TeamMessage, { senderName: sender, memberId: fact.message.sender, human: human, ...(human && humanAvatarUrl !== undefined ? { avatarUrl: humanAvatarUrl } : {}), body: fact.message.body, attachments: fact.message.attachments, loadAttachment: getAttachment, t: t, occurredAt: fact.message.occurredAt, mentionNames: stableMentionNames(fact.mentions, mentionHandlesMap, humanName), onOpenRef: openRef, onResolveTaskRefs: lookupTaskRefs, onResolveThreadRefs: lookupThreadRefs, channelNameOf: channelNameOf, memberOf: memberOf, onOpenMemberSession: openMemberSession, grouped: grouped, ...(senderStatus === undefined ? {} : { senderTitle: senderStatus.member.description }) }, factKey(fact));
|
|
469
483
|
}
|
|
470
484
|
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));
|
|
471
485
|
};
|
|
@@ -696,14 +710,26 @@ export function TeamThreadPage(props) {
|
|
|
696
710
|
};
|
|
697
711
|
const currentFactsWithAnchor = mergeFacts(activeProjection === undefined ? [] : [messageFact(activeProjection.anchor, activeProjection.anchorMentions)], currentFacts);
|
|
698
712
|
const unreadBoundary = unreadIndex >= 0 ? unreadIndex : undefined;
|
|
713
|
+
// One row per erroring Member holding a live Claim. The row shows the
|
|
714
|
+
// diagnostic's structured class — the localizable axis — beside the first
|
|
715
|
+
// sentence of the Host's own reason; the full English text moves to the
|
|
716
|
+
// row's title.
|
|
699
717
|
const risks = taskClaims.filter(claim => claim.state === 'active').flatMap(claim => {
|
|
700
718
|
const status = members.find(candidate => candidate.member.memberId === claim.owner);
|
|
701
|
-
|
|
719
|
+
if (status?.presence !== 'error')
|
|
720
|
+
return [];
|
|
721
|
+
const risk = formatRiskClass(status, t);
|
|
722
|
+
const detail = diagnosticText(status);
|
|
723
|
+
return [{
|
|
724
|
+
claim, status, ...risk,
|
|
725
|
+
reason: detail === '' ? t('statusError') : firstSentence(detail),
|
|
726
|
+
full: detail === '' ? t('statusError') : detail,
|
|
727
|
+
}];
|
|
702
728
|
});
|
|
703
729
|
// Threads are always entered through a Channel page, so a Channel origin
|
|
704
730
|
// returns to its timeline; a Thread restored without one returns further.
|
|
705
731
|
const backLabel = channelRef === undefined ? t('backToWorkspace') : t('backToChannel');
|
|
706
|
-
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(
|
|
732
|
+
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('threadLabel') : t('taskLabel', { number: resolvedTaskNumber ?? '…' }) }), task !== undefined && _jsxs(Pill, { children: [_jsx(TeamStateDot, { size: 8, state: taskStatusDot(task.status) }), formatTaskStatus(task.status, t)] })] }), taskTitle !== undefined && taskTitle !== '' && _jsx("p", { className: threadCss.taskTitle, title: taskTitle, children: taskTitle })] }), task === undefined && thread !== undefined && _jsx("div", { className: css.headerActions, children: _jsx(Button, { size: "sm", variant: "primary", disabled: pending, onClick: () => { void convertToTask(); }, children: mutating === 'promote' ? t('promotingTask') : t('promoteToTask') }) }), task !== undefined && thread !== undefined && task.resolution !== 'closed' && _jsxs("div", { className: css.headerActions, children: [(() => {
|
|
707
733
|
const activeClaims = projection?.claims.filter(claim => claim.taskRef === task.taskRef && claim.state === 'active') ?? [];
|
|
708
734
|
const earlyAccept = task.resolution === 'open' && task.status === 'in_progress' && activeClaims.length > 0;
|
|
709
735
|
// todo Tasks accept directly: the work finished outside the
|
|
@@ -718,14 +744,14 @@ export function TeamThreadPage(props) {
|
|
|
718
744
|
} }, children: t('acceptTask') });
|
|
719
745
|
})(), task.resolution === 'open'
|
|
720
746
|
? _jsx(Button, { size: "sm", variant: "outline", disabled: pending, onClick: () => { void mutateTask('close'); }, children: t('closeTask') })
|
|
721
|
-
: _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: [
|
|
747
|
+
: _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, label, sentenceKey, reason, full }) => _jsxs("p", { className: threadCss.riskRow, children: [_jsx(TeamPresenceDot, { status: status, t: t }), _jsxs("span", { title: full, children: [_jsx("strong", { className: threadCss.riskClass, children: label }), " \u00B7 ", t(sentenceKey, { member: status.member.handle }), " \u2014 ", reason] })] }, claim.claimRef))] }), task !== undefined && thread !== undefined && (() => {
|
|
722
748
|
// Recomputed here so the confirm list never shows stale rows.
|
|
723
749
|
const activeClaims = projection?.claims.filter(claim => claim.taskRef === task.taskRef && claim.state === 'active') ?? [];
|
|
724
750
|
return _jsxs(Modal, { open: confirmingAccept, onClose: () => { if (!pending)
|
|
725
751
|
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: mutating === 'accept' ? 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))) })] });
|
|
726
|
-
})(), 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(
|
|
752
|
+
})(), 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 => {
|
|
727
753
|
const ownerStatus = members.find(status => status.member.memberId === claim.owner);
|
|
728
|
-
return _jsxs("article", { className: threadCss.claimRow
|
|
754
|
+
return _jsxs("article", { className: `${threadCss.claimRow}${claim.state === 'done' ? ` ${threadCss.claimRowDone}` : ''}`, children: [ownerStatus === undefined ? _jsx("span", {}) : _jsx(TeamPresenceDot, { status: ownerStatus, t: t }), _jsx("strong", { className: threadCss.claimOwner, title: memberName(claim.owner), children: memberName(claim.owner) }), _jsx("span", { className: threadCss.claimDirection, children: claim.direction }), _jsx("small", { className: threadCss.claimState, children: formatClaimState(claim.state, t) })] }, claim.claimRef);
|
|
729
755
|
})] }) }) })] }), _jsx("section", { ref: timeline.ref, onScroll: handleTimelineScroll, 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) }), autoReadExhausted && _jsxs("div", { className: css.timelineAction, role: "alert", children: [_jsx("span", { children: t('autoReadIncomplete') }), _jsx(Button, { size: "sm", onClick: () => { setAutoReadExhausted(false); void drainUnread(); }, disabled: loading, children: t('retry') })] }), newFactsCount > 0 && _jsx("div", { className: threadCss.newUpdates, role: "status", children: _jsx("button", { type: "button", className: threadCss.newUpdatesJump, onClick: () => { setNewFactsCount(0); timeline.scrollToBottom(); }, children: t('newUpdatesJump', { count: newFactsCount }) }) })] }) }), projection !== undefined && thread !== undefined ? (task?.resolution === 'closed'
|
|
730
756
|
? _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') })] })] })
|
|
731
757
|
: _jsx(TeamComposer, { members: channelMembers, followerMemberIds: followerIds, drafts: drafts, draftKey: draftKey, pending: pending, ...(statusMessage === undefined ? {} : { confirmation: statusMessage }), ...(error === undefined ? {} : { error }), onEdit: clearSendState, onSubmit: () => { void sendReply(); }, placeholder: t('replyPlaceholder'), pendingFiles: pendingFiles, onFilesChange: setPendingFiles, t: t }, draftKey)) : _jsx("div", {})] });
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef, useState, useSyncExternalStore } from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { IconAgentPresetOutline16, IconListPenOutline16, IconQueueOutline14, Tooltip } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
4
4
|
import { TeamWorkspaceSelector } from "./TeamWorkspaceSelector.js";
|
|
5
5
|
import { TeamAgentsPanel } from "./TeamAgentsPanel.js";
|
|
6
6
|
import { TeamChannelsPanel } from "./TeamChannelsPanel.js";
|
|
@@ -14,7 +14,7 @@ import css from './sidebar.module.css';
|
|
|
14
14
|
* on the same corner of the same glyph.
|
|
15
15
|
*/
|
|
16
16
|
function InboxMark({ unread }) {
|
|
17
|
-
return _jsxs("span", { className: css.inboxMark, children: [_jsx(
|
|
17
|
+
return _jsxs("span", { className: css.inboxMark, children: [_jsx(IconQueueOutline14, { size: 16 }), unread > 0 && _jsx("span", { className: css.inboxDot, "data-team-inbox-dot": true, "aria-hidden": "true" })] });
|
|
18
18
|
}
|
|
19
19
|
export function TeamWorkspaceBrowser({ wide, expandSidebar, navigation, selectWorkspace, selectChannel, selectInbox, t, useWorkspaces, loadMembers, loadInbox, subscribeChanges, subscribeReads, addMember, loadChannels, createChannel, updateChannel, archiveChannel, updateMember, recoverMember, archiveMember, joinWorkspace, leaveWorkspace, joinChannel, removeChannelMember, loadModels, openMemberSession }) {
|
|
20
20
|
const navigationState = useSyncExternalStore(navigation.subscribe, navigation.getSnapshot, navigation.getSnapshot);
|
|
@@ -95,7 +95,7 @@ export function TeamWorkspaceBrowser({ wide, expandSidebar, navigation, selectWo
|
|
|
95
95
|
queueMicrotask(() => { node?.querySelector('button')?.focus(); });
|
|
96
96
|
}, [wide, pendingSection]);
|
|
97
97
|
if (!wide) {
|
|
98
|
-
return _jsxs("nav", { className: css.railWorkspace, "aria-label": t('workspaceSections'), children: [_jsx(Tooltip, { label: inboxLabel, side: "right", children: _jsx("button", { type: "button", className: css.railButton, "aria-label": inboxLabel, "aria-current": inboxIsCurrent ? 'page' : undefined, onClick: () => { selectInbox(); expandSidebar(); }, children: _jsx(InboxMark, { unread: inboxTotal }) }) }), _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(
|
|
98
|
+
return _jsxs("nav", { className: css.railWorkspace, "aria-label": t('workspaceSections'), children: [_jsx(Tooltip, { label: inboxLabel, side: "right", children: _jsx("button", { type: "button", className: css.railButton, "aria-label": inboxLabel, "aria-current": inboxIsCurrent ? 'page' : undefined, onClick: () => { selectInbox(); expandSidebar(); }, children: _jsx(InboxMark, { unread: inboxTotal }) }) }), _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 }) }) })] });
|
|
99
99
|
}
|
|
100
100
|
return _jsxs("section", { className: css.workspaceBrowser, "aria-label": t('workspaces'), children: [_jsxs("button", { type: "button", className: css.inboxCard, "aria-label": inboxLabel, "aria-current": inboxIsCurrent ? 'page' : undefined, onClick: selectInbox, children: [_jsx(InboxMark, { unread: inboxTotal }), _jsx("span", { className: css.inboxCardLabel, children: t('inboxTitle') })] }), _jsx(TeamWorkspaceSelector, { workspaces: workspaces, selectedId: selectedId, current: overviewIsCurrent, onSelect: selectWorkspace, t: t }), 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, archiveChannel: archiveChannel, 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, addMember: addMember, updateMember: updateMember, recoverMember: recoverMember, archiveMember: archiveMember, joinWorkspace: joinWorkspace, leaveWorkspace: leaveWorkspace, 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) })] })] });
|
|
101
101
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useState } from 'react';
|
|
3
|
-
import {
|
|
3
|
+
import { IconChevronDownOutline14, IconFolderOpen16, Menu } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
4
4
|
import css from './sidebar.module.css';
|
|
5
5
|
/**
|
|
6
6
|
* The single-line Workspace selector that scopes the Team sidebar.
|
|
@@ -54,5 +54,5 @@ export function TeamWorkspaceSelector({ workspaces, selectedId, current, onSelec
|
|
|
54
54
|
id: workspace.workspaceId,
|
|
55
55
|
label: workspace.title,
|
|
56
56
|
}));
|
|
57
|
-
return (_jsx(Menu, { open: open, portal: true, autoFocus: true, items: items, selectedIds: [selected.workspaceId], onSelect: id => { setOpen(false); onSelect(id); }, onClose: () => { setOpen(false); }, anchor: (_jsxs("button", { type: "button", className: css.workspaceTrigger, "data-team-workspace-trigger": true, "aria-label": t('workspaceSelectorWithValue', { title: selected.title }), "aria-haspopup": "menu", "aria-expanded": open, "aria-current": current ? 'page' : undefined, title: `${selected.title} · ${selected.path}`, onClick: () => { setOpen(value => !value); }, children: [_jsx("span", { className: css.workspaceIcon, "aria-hidden": "true", children: _jsx(
|
|
57
|
+
return (_jsx(Menu, { open: open, portal: true, autoFocus: true, items: items, selectedIds: [selected.workspaceId], onSelect: id => { setOpen(false); onSelect(id); }, onClose: () => { setOpen(false); }, anchor: (_jsxs("button", { type: "button", className: css.workspaceTrigger, "data-team-workspace-trigger": true, "aria-label": t('workspaceSelectorWithValue', { title: selected.title }), "aria-haspopup": "menu", "aria-expanded": open, "aria-current": current ? 'page' : undefined, title: `${selected.title} · ${selected.path}`, onClick: () => { setOpen(value => !value); }, children: [_jsx("span", { className: css.workspaceIcon, "aria-hidden": "true", children: _jsx(IconFolderOpen16, { size: 16 }) }), _jsx("span", { className: css.workspaceValue, children: selected.title }), _jsx("span", { className: `${css.workspaceChevron} ${open ? css.workspaceChevronOpen : ''}`, "aria-hidden": "true", children: _jsx(IconChevronDownOutline14, {}) })] })) }));
|
|
58
58
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether one avatar URL actually draws.
|
|
3
|
+
*
|
|
4
|
+
* The Host accepts an avatar by declared media type — `sanitizeMediaType` only
|
|
5
|
+
* checks the shape of `image/…` — so bytes the browser cannot decode are stored
|
|
6
|
+
* and handed back as a data URL that renders nothing: a phone photo in HEIC, or
|
|
7
|
+
* a payload damaged on the way in. The settings copy promises the initial back
|
|
8
|
+
* in that case, so both avatar seats ask this hook instead of testing the URL
|
|
9
|
+
* for `undefined`, which cannot tell a picture from a blank circle.
|
|
10
|
+
*
|
|
11
|
+
* The record is keyed by URL: bytes that already failed keep the fallback, and
|
|
12
|
+
* a new upload retries on its own because its data URL differs.
|
|
13
|
+
*/
|
|
14
|
+
export interface AvatarImage {
|
|
15
|
+
/** The URL to draw, or `undefined` when the seat must fall back to the initial. */
|
|
16
|
+
readonly src: string | undefined;
|
|
17
|
+
/** Attach to the `<img>`'s `onError`: these bytes do not decode. */
|
|
18
|
+
readonly failed: () => void;
|
|
19
|
+
}
|
|
20
|
+
export declare function useAvatarImage(url: string | undefined): AvatarImage;
|
|
21
|
+
//# sourceMappingURL=avatar-image.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"avatar-image.d.ts","sourceRoot":"","sources":["../../../src/client/avatar-image.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,WAAW;IAC1B,mFAAmF;IACnF,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;IAChC,oEAAoE;IACpE,QAAQ,CAAC,MAAM,EAAE,MAAM,IAAI,CAAA;CAC5B;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,WAAW,CAMnE"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
export function useAvatarImage(url) {
|
|
3
|
+
const [broken, setBroken] = useState(undefined);
|
|
4
|
+
return {
|
|
5
|
+
src: url === undefined || url === broken ? undefined : url,
|
|
6
|
+
failed: () => { if (url !== undefined)
|
|
7
|
+
setBroken(url); },
|
|
8
|
+
};
|
|
9
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import type { RemoteResult } from '@deepseek-ai/dsh-typert-protocol';
|
|
2
|
+
import type { AgentTeamHumanProfileResult } from '@wowyuarm/dsh-agent-team/types';
|
|
3
|
+
/**
|
|
4
|
+
* The Client's one projection of the Human identity.
|
|
5
|
+
*
|
|
6
|
+
* The durable facts live in the Host: the display name and the avatar
|
|
7
|
+
* reference in the `agent-team-human` settings section, the avatar bytes in the
|
|
8
|
+
* persistent avatar store. This store is the only place the Team Client reads
|
|
9
|
+
* them, so every seat that names or draws the Human — message rows, member
|
|
10
|
+
* refs, mention chips, the settings page — shows one rename and one face, and a
|
|
11
|
+
* single refresh after a write moves all of them together.
|
|
12
|
+
*
|
|
13
|
+
* Seats render before the first read settles; `name` stays undefined until then
|
|
14
|
+
* and every caller falls back to its own localized name for the Human. Reads
|
|
15
|
+
* are demand-driven: the first subscriber starts the read, so a Client that
|
|
16
|
+
* never opens Team mode or the profile page never calls the Remote.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Host settings namespace that holds the Human profile. The Host declares the
|
|
20
|
+
* same string (`HUMAN_PROFILE_SETTINGS_NAMESPACE`) on its own side of the
|
|
21
|
+
* bundle boundary, and a test pins the two together.
|
|
22
|
+
*/
|
|
23
|
+
export declare const HUMAN_PROFILE_NAMESPACE = "agent-team-human";
|
|
24
|
+
/** One read of the Human identity, replaced wholesale on every change. */
|
|
25
|
+
export interface TeamHumanIdentitySnapshot {
|
|
26
|
+
/**
|
|
27
|
+
* `loading` until the first read settles, `ready` while an accepted value
|
|
28
|
+
* stands (also when a later refresh failed), `unavailable` when no value was
|
|
29
|
+
* ever accepted — the one state that hands the page a retry.
|
|
30
|
+
*/
|
|
31
|
+
readonly status: 'loading' | 'ready' | 'unavailable';
|
|
32
|
+
/** Host-resolved display name; undefined before the first accepted read. */
|
|
33
|
+
readonly name?: string | undefined;
|
|
34
|
+
/** Configured avatar reference; undefined means the identity fallback is the avatar. */
|
|
35
|
+
readonly avatarRef?: string | undefined;
|
|
36
|
+
/** Avatar bytes as a data URL for `<img>`; undefined renders the fallback. */
|
|
37
|
+
readonly avatarUrl?: string | undefined;
|
|
38
|
+
/** Bundle version for the settings footnote. */
|
|
39
|
+
readonly version?: string | undefined;
|
|
40
|
+
/** Repository home the footnote links to. */
|
|
41
|
+
readonly repoUrl?: string | undefined;
|
|
42
|
+
readonly updateAvailable: boolean;
|
|
43
|
+
readonly latestVersion?: string | undefined;
|
|
44
|
+
/** Last failure, kept beside the last accepted value so a page can report it. */
|
|
45
|
+
readonly error?: string | undefined;
|
|
46
|
+
}
|
|
47
|
+
/** Read-side face every consumer binds: the seats' hook and the settings page alike. */
|
|
48
|
+
export interface TeamHumanIdentitySource {
|
|
49
|
+
getSnapshot(): TeamHumanIdentitySnapshot;
|
|
50
|
+
subscribe(listener: () => void): () => void;
|
|
51
|
+
}
|
|
52
|
+
/** The settings page's extra face: re-read after a failed or superseded read. */
|
|
53
|
+
export interface TeamHumanIdentityFace extends TeamHumanIdentitySource {
|
|
54
|
+
refresh(): Promise<void>;
|
|
55
|
+
}
|
|
56
|
+
/** Host calls the store reads through; one loader per Client context. */
|
|
57
|
+
export interface TeamHumanIdentityLoader {
|
|
58
|
+
loadProfile: () => Promise<RemoteResult<AgentTeamHumanProfileResult>>;
|
|
59
|
+
/** Resolve one avatar reference to a displayable URL; null falls back to the initial. */
|
|
60
|
+
loadAvatarUrl: (avatarRef: string) => Promise<string | null>;
|
|
61
|
+
}
|
|
62
|
+
export declare class TeamHumanIdentity implements TeamHumanIdentityFace {
|
|
63
|
+
private snapshot;
|
|
64
|
+
private readonly listeners;
|
|
65
|
+
private reading;
|
|
66
|
+
private readonly loader;
|
|
67
|
+
constructor(loader: TeamHumanIdentityLoader);
|
|
68
|
+
readonly getSnapshot: () => TeamHumanIdentitySnapshot;
|
|
69
|
+
/**
|
|
70
|
+
* Observe the identity, starting the first read when nobody has read yet.
|
|
71
|
+
* @param listener - invoked after every snapshot replacement.
|
|
72
|
+
* @returns the disposer removing this listener.
|
|
73
|
+
*/
|
|
74
|
+
readonly subscribe: (listener: () => void) => (() => void);
|
|
75
|
+
/**
|
|
76
|
+
* Re-read the Host projection. Concurrent callers share one round trip, and a
|
|
77
|
+
* failed refresh keeps the last accepted value beside the reported error —
|
|
78
|
+
* the seats never blank out because a background read failed.
|
|
79
|
+
* @returns settlement of this read (or of the read already in flight).
|
|
80
|
+
*/
|
|
81
|
+
refresh(): Promise<void>;
|
|
82
|
+
dispose(): void;
|
|
83
|
+
private read;
|
|
84
|
+
/**
|
|
85
|
+
* Record one read failure. A value that was already accepted stays on screen
|
|
86
|
+
* (the seats never blank out over a background read), and only an identity
|
|
87
|
+
* that never loaded becomes `unavailable` — the state that offers a retry.
|
|
88
|
+
*/
|
|
89
|
+
private fail;
|
|
90
|
+
private commit;
|
|
91
|
+
}
|
|
92
|
+
/** Subscribe one rendered seat to the identity. */
|
|
93
|
+
export declare function useHumanIdentity(identity: TeamHumanIdentitySource): TeamHumanIdentitySnapshot;
|
|
94
|
+
//# sourceMappingURL=human-identity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"human-identity.d.ts","sourceRoot":"","sources":["../../../src/client/human-identity.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,kCAAkC,CAAA;AACpE,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,gCAAgC,CAAA;AAEjF;;;;;;;;;;;;;;GAcG;AAEH;;;;GAIG;AACH,eAAO,MAAM,uBAAuB,qBAAqB,CAAA;AAEzD,0EAA0E;AAC1E,MAAM,WAAW,yBAAyB;IACxC;;;;OAIG;IACH,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,OAAO,GAAG,aAAa,CAAA;IACpD,4EAA4E;IAC5E,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAClC,wFAAwF;IACxF,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,8EAA8E;IAC9E,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACvC,gDAAgD;IAChD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACrC,6CAA6C;IAC7C,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACrC,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAA;IACjC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3C,iFAAiF;IACjF,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CACpC;AAED,wFAAwF;AACxF,MAAM,WAAW,uBAAuB;IACtC,WAAW,IAAI,yBAAyB,CAAA;IACxC,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAA;CAC5C;AAED,iFAAiF;AACjF,MAAM,WAAW,qBAAsB,SAAQ,uBAAuB;IACpE,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACzB;AAED,yEAAyE;AACzE,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,MAAM,OAAO,CAAC,YAAY,CAAC,2BAA2B,CAAC,CAAC,CAAA;IACrE,yFAAyF;IACzF,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;CAC7D;AAID,qBAAa,iBAAkB,YAAW,qBAAqB;IAC7D,OAAO,CAAC,QAAQ,CAAqC;IACrD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwB;IAClD,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyB;gBAEpC,MAAM,EAAE,uBAAuB;IAI3C,QAAQ,CAAC,WAAW,QAAO,yBAAyB,CAAiB;IAErE;;;;OAIG;IACH,QAAQ,CAAC,SAAS,GAAI,UAAU,MAAM,IAAI,KAAG,CAAC,MAAM,IAAI,CAAC,CAIxD;IAED;;;;;OAKG;IACH,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IASxB,OAAO,IAAI,IAAI;YAID,IAAI;IAkClB;;;;OAIG;IACH,OAAO,CAAC,IAAI;IAOZ,OAAO,CAAC,MAAM;CAIf;AAED,mDAAmD;AACnD,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,uBAAuB,GAAG,yBAAyB,CAE7F"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { useSyncExternalStore } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* The Client's one projection of the Human identity.
|
|
4
|
+
*
|
|
5
|
+
* The durable facts live in the Host: the display name and the avatar
|
|
6
|
+
* reference in the `agent-team-human` settings section, the avatar bytes in the
|
|
7
|
+
* persistent avatar store. This store is the only place the Team Client reads
|
|
8
|
+
* them, so every seat that names or draws the Human — message rows, member
|
|
9
|
+
* refs, mention chips, the settings page — shows one rename and one face, and a
|
|
10
|
+
* single refresh after a write moves all of them together.
|
|
11
|
+
*
|
|
12
|
+
* Seats render before the first read settles; `name` stays undefined until then
|
|
13
|
+
* and every caller falls back to its own localized name for the Human. Reads
|
|
14
|
+
* are demand-driven: the first subscriber starts the read, so a Client that
|
|
15
|
+
* never opens Team mode or the profile page never calls the Remote.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Host settings namespace that holds the Human profile. The Host declares the
|
|
19
|
+
* same string (`HUMAN_PROFILE_SETTINGS_NAMESPACE`) on its own side of the
|
|
20
|
+
* bundle boundary, and a test pins the two together.
|
|
21
|
+
*/
|
|
22
|
+
export const HUMAN_PROFILE_NAMESPACE = 'agent-team-human';
|
|
23
|
+
const INITIAL = { status: 'loading', updateAvailable: false };
|
|
24
|
+
export class TeamHumanIdentity {
|
|
25
|
+
snapshot = INITIAL;
|
|
26
|
+
listeners = new Set();
|
|
27
|
+
reading;
|
|
28
|
+
loader;
|
|
29
|
+
constructor(loader) {
|
|
30
|
+
this.loader = loader;
|
|
31
|
+
}
|
|
32
|
+
getSnapshot = () => this.snapshot;
|
|
33
|
+
/**
|
|
34
|
+
* Observe the identity, starting the first read when nobody has read yet.
|
|
35
|
+
* @param listener - invoked after every snapshot replacement.
|
|
36
|
+
* @returns the disposer removing this listener.
|
|
37
|
+
*/
|
|
38
|
+
subscribe = (listener) => {
|
|
39
|
+
this.listeners.add(listener);
|
|
40
|
+
if (this.snapshot.status === 'loading' && this.reading === undefined)
|
|
41
|
+
void this.refresh();
|
|
42
|
+
return () => { this.listeners.delete(listener); };
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Re-read the Host projection. Concurrent callers share one round trip, and a
|
|
46
|
+
* failed refresh keeps the last accepted value beside the reported error —
|
|
47
|
+
* the seats never blank out because a background read failed.
|
|
48
|
+
* @returns settlement of this read (or of the read already in flight).
|
|
49
|
+
*/
|
|
50
|
+
refresh() {
|
|
51
|
+
if (this.reading !== undefined)
|
|
52
|
+
return this.reading;
|
|
53
|
+
const reading = this.read().finally(() => {
|
|
54
|
+
if (this.reading === reading)
|
|
55
|
+
this.reading = undefined;
|
|
56
|
+
});
|
|
57
|
+
this.reading = reading;
|
|
58
|
+
return reading;
|
|
59
|
+
}
|
|
60
|
+
dispose() {
|
|
61
|
+
this.listeners.clear();
|
|
62
|
+
}
|
|
63
|
+
async read() {
|
|
64
|
+
let profile;
|
|
65
|
+
try {
|
|
66
|
+
const result = await this.loader.loadProfile();
|
|
67
|
+
if (!result.ok) {
|
|
68
|
+
this.fail(result.error.message);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
profile = result.value;
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
// A dropped connection surfaces as a thrown carrier error, not a result:
|
|
75
|
+
// both are read failures and both keep whatever value already stands.
|
|
76
|
+
this.fail(error instanceof Error ? error.message : String(error));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
// Bytes are immutable per reference, so one fetch serves every later
|
|
80
|
+
// refresh that still carries the same avatar.
|
|
81
|
+
const avatarUrl = profile.avatarRef === undefined
|
|
82
|
+
? undefined
|
|
83
|
+
: profile.avatarRef === this.snapshot.avatarRef && this.snapshot.avatarUrl !== undefined
|
|
84
|
+
? this.snapshot.avatarUrl
|
|
85
|
+
: (await this.loader.loadAvatarUrl(profile.avatarRef)) ?? undefined;
|
|
86
|
+
this.commit({
|
|
87
|
+
status: 'ready',
|
|
88
|
+
name: profile.name,
|
|
89
|
+
...(profile.avatarRef === undefined ? {} : { avatarRef: profile.avatarRef }),
|
|
90
|
+
...(avatarUrl === undefined ? {} : { avatarUrl }),
|
|
91
|
+
version: profile.version,
|
|
92
|
+
repoUrl: profile.repoUrl,
|
|
93
|
+
updateAvailable: profile.updateAvailable,
|
|
94
|
+
...(profile.latestVersion === undefined ? {} : { latestVersion: profile.latestVersion }),
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Record one read failure. A value that was already accepted stays on screen
|
|
99
|
+
* (the seats never blank out over a background read), and only an identity
|
|
100
|
+
* that never loaded becomes `unavailable` — the state that offers a retry.
|
|
101
|
+
*/
|
|
102
|
+
fail(message) {
|
|
103
|
+
const held = this.snapshot;
|
|
104
|
+
this.commit(held.name === undefined
|
|
105
|
+
? { ...held, status: 'unavailable', error: message }
|
|
106
|
+
: { ...held, status: 'ready', error: message });
|
|
107
|
+
}
|
|
108
|
+
commit(snapshot) {
|
|
109
|
+
this.snapshot = snapshot;
|
|
110
|
+
for (const listener of this.listeners)
|
|
111
|
+
listener();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/** Subscribe one rendered seat to the identity. */
|
|
115
|
+
export function useHumanIdentity(identity) {
|
|
116
|
+
return useSyncExternalStore(identity.subscribe, identity.getSnapshot, identity.getSnapshot);
|
|
117
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAA;AA6CnE,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEhD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAK5C,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;AAqRD,wBAAsB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAI7D"}
|