@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.
Files changed (127) hide show
  1. package/README.md +5 -3
  2. package/README.zh.md +5 -3
  3. package/cordis.patch.yml +2 -185
  4. package/package.json +52 -46
  5. package/packages/agent-team/README.md +1 -1
  6. package/packages/agent-team/README.zh.md +1 -1
  7. package/packages/agent-team/core-skills/member-memory-manager/SKILL.md +30 -0
  8. package/packages/agent-team/lib/context-continuity-host.js +91 -0
  9. package/packages/agent-team/lib/context-projection.js +266 -489
  10. package/packages/agent-team/lib/context-source.js +17 -106
  11. package/packages/agent-team/lib/human-avatar.js +61 -0
  12. package/packages/agent-team/lib/human-profile.js +46 -0
  13. package/packages/agent-team/lib/human-update-check.js +119 -0
  14. package/packages/agent-team/lib/index.js +408 -264
  15. package/packages/agent-team/lib/ledger.js +120 -15
  16. package/packages/agent-team/lib/member-context.js +9 -8
  17. package/packages/agent-team/lib/member-runtime.js +1 -1
  18. package/packages/agent-team/lib/member-time-context.js +2 -5
  19. package/packages/agent-team/lib/preset-roster.js +15 -0
  20. package/packages/agent-team/lib/pressure-policy.js +9 -6
  21. package/packages/agent-team/lib/session-remediation.js +493 -0
  22. package/packages/agent-team/lib/stored-session-reader.js +2 -1
  23. package/packages/agent-team/lib/typert.host.js +605 -291
  24. package/packages/agent-team/lib/typert.remote-client.d.ts +15 -6
  25. package/packages/agent-team/lib/typert.remote-client.d.ts.map +1 -1
  26. package/packages/agent-team/lib/typert.remote-client.js +367 -244
  27. package/packages/agent-team/lib/types/context-continuity-host.d.ts +84 -0
  28. package/packages/agent-team/lib/types/context-continuity-host.d.ts.map +1 -0
  29. package/packages/agent-team/lib/types/context-projection.d.ts +130 -185
  30. package/packages/agent-team/lib/types/context-projection.d.ts.map +1 -1
  31. package/packages/agent-team/lib/types/context-source.d.ts +17 -81
  32. package/packages/agent-team/lib/types/context-source.d.ts.map +1 -1
  33. package/packages/agent-team/lib/types/human-avatar.d.ts +29 -0
  34. package/packages/agent-team/lib/types/human-avatar.d.ts.map +1 -0
  35. package/packages/agent-team/lib/types/human-profile.d.ts +44 -0
  36. package/packages/agent-team/lib/types/human-profile.d.ts.map +1 -0
  37. package/packages/agent-team/lib/types/human-update-check.d.ts +73 -0
  38. package/packages/agent-team/lib/types/human-update-check.d.ts.map +1 -0
  39. package/packages/agent-team/lib/types/index.d.ts +112 -38
  40. package/packages/agent-team/lib/types/index.d.ts.map +1 -1
  41. package/packages/agent-team/lib/types/ledger.d.ts +31 -2
  42. package/packages/agent-team/lib/types/ledger.d.ts.map +1 -1
  43. package/packages/agent-team/lib/types/member-context.d.ts +0 -14
  44. package/packages/agent-team/lib/types/member-context.d.ts.map +1 -1
  45. package/packages/agent-team/lib/types/member-time-context.d.ts +0 -15
  46. package/packages/agent-team/lib/types/member-time-context.d.ts.map +1 -1
  47. package/packages/agent-team/lib/types/preset-roster.d.ts +5 -0
  48. package/packages/agent-team/lib/types/preset-roster.d.ts.map +1 -0
  49. package/packages/agent-team/lib/types/pressure-policy.d.ts +6 -0
  50. package/packages/agent-team/lib/types/pressure-policy.d.ts.map +1 -1
  51. package/packages/agent-team/lib/types/session-remediation.d.ts +172 -0
  52. package/packages/agent-team/lib/types/session-remediation.d.ts.map +1 -0
  53. package/packages/agent-team/lib/types/stored-session-reader.d.ts +2 -1
  54. package/packages/agent-team/lib/types/stored-session-reader.d.ts.map +1 -1
  55. package/packages/agent-team/lib/types/types/entities.d.ts +20 -3
  56. package/packages/agent-team/lib/types/types/entities.d.ts.map +1 -1
  57. package/packages/agent-team/lib/types/types/requests-results.d.ts +63 -0
  58. package/packages/agent-team/lib/types/types/requests-results.d.ts.map +1 -1
  59. package/packages/agent-team/preset/team-member/agent.cordis.yml +166 -0
  60. package/packages/client-agent-team/lib/client.js +1667 -516
  61. package/packages/client-agent-team/lib/client.js.map +1 -1
  62. package/packages/client-agent-team/lib/types/client/HumanSettingsSection.d.ts +15 -0
  63. package/packages/client-agent-team/lib/types/client/HumanSettingsSection.d.ts.map +1 -0
  64. package/packages/client-agent-team/lib/types/client/HumanSettingsSection.js +115 -0
  65. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.d.ts.map +1 -1
  66. package/packages/client-agent-team/lib/types/client/TeamAgentsPanel.js +12 -8
  67. package/packages/client-agent-team/lib/types/client/TeamAvatarStack.d.ts +22 -1
  68. package/packages/client-agent-team/lib/types/client/TeamAvatarStack.d.ts.map +1 -1
  69. package/packages/client-agent-team/lib/types/client/TeamAvatarStack.js +25 -2
  70. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts +6 -1
  71. package/packages/client-agent-team/lib/types/client/TeamChannelPage.d.ts.map +1 -1
  72. package/packages/client-agent-team/lib/types/client/TeamChannelPage.js +69 -31
  73. package/packages/client-agent-team/lib/types/client/TeamChannelsPanel.js +4 -4
  74. package/packages/client-agent-team/lib/types/client/TeamComposer.js +3 -3
  75. package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts +1 -1
  76. package/packages/client-agent-team/lib/types/client/TeamConversation.d.ts.map +1 -1
  77. package/packages/client-agent-team/lib/types/client/TeamConversation.js +12 -5
  78. package/packages/client-agent-team/lib/types/client/TeamFooterAction.js +2 -2
  79. package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts +4 -1
  80. package/packages/client-agent-team/lib/types/client/TeamInboxPage.d.ts.map +1 -1
  81. package/packages/client-agent-team/lib/types/client/TeamInboxPage.js +22 -7
  82. package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts +11 -1
  83. package/packages/client-agent-team/lib/types/client/TeamMemberEditor.d.ts.map +1 -1
  84. package/packages/client-agent-team/lib/types/client/TeamMemberEditor.js +69 -8
  85. package/packages/client-agent-team/lib/types/client/TeamMembersAction.js +2 -2
  86. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts +18 -3
  87. package/packages/client-agent-team/lib/types/client/TeamMessage.d.ts.map +1 -1
  88. package/packages/client-agent-team/lib/types/client/TeamMessage.js +213 -67
  89. package/packages/client-agent-team/lib/types/client/TeamRowMenu.d.ts.map +1 -1
  90. package/packages/client-agent-team/lib/types/client/TeamRowMenu.js +2 -2
  91. package/packages/client-agent-team/lib/types/client/TeamSidebarSection.js +2 -2
  92. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts +5 -0
  93. package/packages/client-agent-team/lib/types/client/TeamThreadPage.d.ts.map +1 -1
  94. package/packages/client-agent-team/lib/types/client/TeamThreadPage.js +55 -29
  95. package/packages/client-agent-team/lib/types/client/TeamWorkspaceBrowser.js +3 -3
  96. package/packages/client-agent-team/lib/types/client/TeamWorkspaceSelector.js +2 -2
  97. package/packages/client-agent-team/lib/types/client/avatar-image.d.ts +21 -0
  98. package/packages/client-agent-team/lib/types/client/avatar-image.d.ts.map +1 -0
  99. package/packages/client-agent-team/lib/types/client/avatar-image.js +9 -0
  100. package/packages/client-agent-team/lib/types/client/human-identity.d.ts +94 -0
  101. package/packages/client-agent-team/lib/types/client/human-identity.d.ts.map +1 -0
  102. package/packages/client-agent-team/lib/types/client/human-identity.js +117 -0
  103. package/packages/client-agent-team/lib/types/client/index.d.ts.map +1 -1
  104. package/packages/client-agent-team/lib/types/client/index.js +112 -41
  105. package/packages/client-agent-team/lib/types/client/locales.d.ts +72 -2
  106. package/packages/client-agent-team/lib/types/client/locales.d.ts.map +1 -1
  107. package/packages/client-agent-team/lib/types/client/locales.js +78 -2
  108. package/packages/client-agent-team/lib/types/client/multi-menu-field.js +2 -2
  109. package/packages/client-agent-team/lib/types/client/refs.d.ts +71 -0
  110. package/packages/client-agent-team/lib/types/client/refs.d.ts.map +1 -0
  111. package/packages/client-agent-team/lib/types/client/refs.js +181 -0
  112. package/packages/client-agent-team/lib/types/client/slots.d.ts +8 -2
  113. package/packages/client-agent-team/lib/types/client/slots.d.ts.map +1 -1
  114. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts +57 -9
  115. package/packages/client-agent-team/lib/types/client/team-formatters.d.ts.map +1 -1
  116. package/packages/client-agent-team/lib/types/client/team-formatters.js +85 -17
  117. package/packages/tool-agent-team/README.md +1 -1
  118. package/packages/tool-agent-team/README.zh.md +1 -1
  119. package/packages/tool-agent-team/lib/context-tools.js +20 -6
  120. package/packages/tool-agent-team/lib/index.js +2 -2
  121. package/packages/tool-agent-team/lib/types/context-tools.d.ts.map +1 -1
  122. package/packages/agent-team/lib/context-management.js +0 -371
  123. package/packages/agent-team/lib/types/context-management.d.ts +0 -154
  124. package/packages/agent-team/lib/types/context-management.d.ts.map +0 -1
  125. package/packages/client-agent-team/lib/types/client/task-refs.d.ts +0 -31
  126. package/packages/client-agent-team/lib/types/client/task-refs.d.ts.map +0 -1
  127. 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, IconChevronLeftOutlineRegular, IconChecklistOutlineRegular, Modal, 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 { 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 "./task-refs.js";
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 t('human');
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; the open Task's own refs are already on screen, and other Tasks
436
- // are not resolvable from this surface, so they degrade to a no-op.
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 refs do not have a dedicated resolver Remote. The Host view
445
- // query still returns the target's home Channel and Task projection.
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, loadChannels, resolveTaskRefs]);
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
- 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: stableMentionNames(fact.mentions, mentionHandlesMap), onOpenRef: openRef, onResolveTaskRefs: lookupTaskRefs, grouped: grouped, ...(senderStatus === undefined ? {} : { senderTitle: senderStatus.member.description }) }, factKey(fact));
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
- return status?.presence === 'error' ? [{ claim, status }] : [];
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(IconChevronLeftOutlineRegular, {}), 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: [(() => {
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: [t('runtimeRiskDetail', { member: status.member.handle, diagnostic: diagnosticText(status) || t('statusError') }), " \u00B7 ", claim.direction] })] }, claim.claimRef))] }), task !== undefined && thread !== undefined && (() => {
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(IconChecklistOutlineRegular, { 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 => {
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, 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);
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 { IconAgentPresetOutlineRegular, IconListPenOutlineRegular, IconQueueOutlineRegular, Tooltip } from '@deepseek-ai/dsh-client-ui-primitives';
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(IconQueueOutlineRegular, { size: 16 }), unread > 0 && _jsx("span", { className: css.inboxDot, "data-team-inbox-dot": true, "aria-hidden": "true" })] });
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(IconListPenOutlineRegular, { 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(IconAgentPresetOutlineRegular, { size: 16 }) }) })] });
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 { IconChevronDownOutlineRegular, IconFolderOpenRegular, Menu } from '@deepseek-ai/dsh-client-ui-primitives';
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(IconFolderOpenRegular, { size: 16 }) }), _jsx("span", { className: css.workspaceValue, children: selected.title }), _jsx("span", { className: `${css.workspaceChevron} ${open ? css.workspaceChevronOpen : ''}`, "aria-hidden": "true", children: _jsx(IconChevronDownOutlineRegular, {}) })] })) }));
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;AAuCnE,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;AAuBD,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;AAqMD,wBAAsB,KAAK,CAAC,GAAG,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAI7D"}
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"}