bothouse-dsh 0.1.41 → 0.1.43
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/client/client.js +177 -16
- package/collab/dsh-member.mjs +57 -4
- package/collab/handoff-rules.mjs +2 -0
- package/collab/hub.mjs +2 -2
- package/collab/store.mjs +4 -4
- package/collab/team-exit.mjs +146 -59
- package/package.json +1 -1
package/client/client.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
// dsh-client-source-sha256:
|
|
2
|
-
// dsh-style-sha256:
|
|
1
|
+
// dsh-client-source-sha256: 79a9d7898c26fd8a4dbb904f053c9288ca8683a349abf407d1ba53a8ec300f85
|
|
2
|
+
// dsh-style-sha256: 8de64f320b70b31c6dba515e74187a98284317258d9cb57df3dc16f2eb345641
|
|
3
3
|
window.__ModuleLoader__.load({
|
|
4
4
|
id: "bothouse-dsh",
|
|
5
5
|
factory: (require) => {
|
|
6
6
|
var module = { exports: {} };
|
|
7
7
|
var exports = module.exports;
|
|
8
|
-
(function(){var s=document.createElement('style');s.setAttribute('data-dsh-plugin',"bothouse-dsh");s.textContent=".dshc_page {\n width: 100%;\n height: 100%;\n min-height: 0;\n padding-top: var(--dsh-frame-top-clearance, 0px);\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-bg-base);\n grid-template-columns: 220px minmax(0, 1fr) 240px;\n font-size: 14px;\n line-height: 1.5;\n display: grid;\n}\n\n.dshc_notice {\n max-width: 480px;\n color: var(--dsw-alias-label-secondary);\n text-align: center;\n grid-column: 1 / -1;\n place-self: center;\n padding: 24px;\n}\n\n.dshc_rooms, .dshc_members {\n background: var(--dsw-alias-bg-layer-2);\n min-height: 0;\n padding: 0 8px 16px;\n overflow-y: auto;\n}\n\n.dshc_membersHead {\n border-bottom: 1px solid var(--dsw-alias-border-l2);\n flex: none;\n height: 44px;\n margin: 0 0 8px;\n}\n\n.dshc_groupTitle {\n color: var(--dsw-alias-label-tertiary);\n letter-spacing: .02em;\n margin: 12px 8px 6px;\n font-size: 12px;\n font-weight: 600;\n}\n\n.dshc_room {\n border-radius: var(--dsw-radius-sm);\n width: 100%;\n color: var(--dsw-alias-label-secondary);\n font: inherit;\n text-align: left;\n cursor: pointer;\n background: none;\n border: 0;\n align-items: center;\n gap: 6px;\n padding: 6px 8px;\n display: flex;\n}\n\n.dshc_room:hover {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dshc_room:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color);\n}\n\n.dshc_workspace {\n padding-left: 22px;\n font-size: .95em;\n}\n\n.dshc_roomLabel {\n text-overflow: ellipsis;\n white-space: nowrap;\n overflow: hidden;\n}\n\n.dshc_activeCount {\n color: var(--dsw-alias-label-tertiary);\n flex-shrink: 0;\n margin-left: auto;\n font-size: 11px;\n}\n\n.dshc_activeBusy {\n color: var(--dsw-alias-label-primary);\n}\n\n.dshc_roomActive {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dshc_hash {\n color: var(--dsw-alias-label-tertiary);\n font-size: 16px;\n}\n\n.dshc_chat {\n flex-direction: column;\n min-width: 0;\n min-height: 0;\n display: flex;\n}\n\n.dshc_chatHead {\n -webkit-app-region: drag;\n border-bottom: 1px solid var(--dsw-alias-border-l2);\n align-items: center;\n gap: 6px;\n height: 44px;\n padding: 0 16px;\n font-weight: 600;\n display: flex;\n}\n\n.dshc_log {\n flex: 1;\n min-height: 0;\n padding: 8px 0 12px;\n overflow-y: auto;\n}\n\n.dshc_guide {\n border: 1px dashed var(--dsw-alias-border-l2);\n border-radius: var(--dsw-radius-sm);\n color: var(--dsw-alias-label-secondary);\n flex-direction: column;\n align-items: flex-start;\n gap: 10px;\n margin: 16px;\n padding: 14px 16px;\n display: flex;\n}\n\n.dshc_empty {\n color: var(--dsw-alias-label-tertiary);\n padding: 24px 16px;\n}\n\n.dshc_message {\n grid-template-columns: 36px minmax(0, 1fr);\n gap: 12px;\n padding: 6px 16px;\n display: grid;\n}\n\n.dshc_message:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dshc_system .dshc_text {\n color: var(--dsw-alias-label-tertiary);\n font-style: italic;\n}\n\n.dshc_avatar {\n corner-shape: round;\n color: #fff;\n border-radius: 50%;\n place-items: center;\n width: 36px;\n height: 36px;\n font-weight: 700;\n display: grid;\n position: relative;\n}\n\n.dshc_avatarImage {\n object-fit: cover;\n border-radius: 50%;\n width: 100%;\n height: 100%;\n position: absolute;\n inset: 0;\n}\n\n.dshc_avatarSmall {\n width: 28px;\n height: 28px;\n font-size: 12px;\n}\n\n.dshc_avatarAnchor {\n align-self: start;\n position: relative;\n}\n\n.dshc_avatarButton {\n cursor: pointer;\n background: none;\n border: 0;\n border-radius: 50%;\n padding: 0;\n}\n\n.dshc_avatarMenu {\n z-index: 3;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: var(--dsw-radius-md);\n background: var(--dsw-alias-bg-base);\n min-width: 140px;\n box-shadow: var(--dsw-elevation-panel, 0 8px 24px #0000003d);\n flex-direction: column;\n padding: 6px;\n display: flex;\n position: absolute;\n top: calc(100% + 4px);\n left: 0;\n}\n\n.dshc_avatarMenuTitle {\n padding: 4px 8px;\n font-weight: 700;\n}\n\n.dshc_avatarMenuItem {\n border-radius: var(--dsw-radius-sm);\n color: inherit;\n text-align: left;\n cursor: pointer;\n background: none;\n border: 0;\n padding: 6px 8px;\n}\n\n.dshc_avatarMenuItem:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dshc_messageBody {\n min-width: 0;\n}\n\n.dshc_messageHead {\n align-items: baseline;\n gap: 6px;\n display: flex;\n}\n\n.dshc_author {\n font-weight: 600;\n}\n\n.dshc_time {\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n}\n\n.dshc_tag {\n border-radius: var(--dsw-radius-xs);\n color: var(--dsw-alias-label-primary-inverted);\n background: var(--dsw-alias-state-business-primary);\n white-space: nowrap;\n padding: 0 4px;\n font-size: 10px;\n font-weight: 600;\n}\n\n.dshc_edited {\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n}\n\n.dshc_ownActions {\n margin-left: auto;\n margin-right: var(--channels-reply-clearance);\n opacity: 0;\n gap: 8px;\n display: inline-flex;\n}\n\n.dshc_message:hover .dshc_ownActions, .dshc_ownActions:focus-within {\n opacity: 1;\n}\n\n@media (hover: none) {\n .dshc_ownActions {\n opacity: 1;\n }\n}\n\n.dshc_editBox {\n flex-direction: column;\n gap: 6px;\n margin-top: 4px;\n display: flex;\n}\n\n.dshc_editInput {\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: var(--dsw-radius-md);\n background: var(--dsw-alias-bg-base);\n min-height: 60px;\n color: inherit;\n font: inherit;\n resize: vertical;\n padding: 8px;\n}\n\n.dshc_editBar {\n justify-content: flex-end;\n align-items: center;\n gap: 8px;\n display: flex;\n}\n\n.dshc_editBar .dshc_time {\n margin-right: auto;\n}\n\n.dshc_text {\n white-space: normal;\n overflow-wrap: anywhere;\n}\n\n.dshc_card {\n border-left: 4px solid var(--dsw-alias-state-business-primary);\n border-radius: var(--dsw-radius-sm);\n background: var(--dsw-alias-bg-layer-2);\n margin-top: 4px;\n padding: 6px 10px;\n}\n\n.dshc_card[data-state=\"failed\"] {\n border-left-color: var(--dsw-alias-state-error-primary);\n}\n\n.dshc_card[data-state=\"retrying\"] {\n border-left-color: var(--dsw-alias-state-warning-primary, #faa61a);\n}\n\n.dshc_cardButtons {\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n margin-top: 6px;\n display: flex;\n}\n\n.dshc_cardTitle {\n font-size: 13px;\n font-weight: 600;\n}\n\n.dshc_cardHead {\n justify-content: space-between;\n align-items: center;\n gap: 8px;\n display: flex;\n}\n\n.dshc_cardViews {\n gap: 4px;\n display: flex;\n}\n\n.dshc_cardViewButton {\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: var(--dsw-radius-sm);\n color: inherit;\n cursor: pointer;\n background: none;\n padding: 0 6px;\n font-size: 12px;\n}\n\n.dshc_cardViewButton[aria-pressed=\"true\"] {\n background: var(--dsw-alias-bg-base);\n font-weight: 600;\n}\n\n.dshc_permActions {\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n margin-top: 6px;\n display: flex;\n}\n\n.dshc_permScope {\n color: var(--dsw-alias-text-secondary);\n flex-basis: 100%;\n font-size: 12px;\n}\n\n.dshc_permStatus {\n margin-top: 6px;\n font-size: 13px;\n font-weight: 600;\n}\n\n.dshc_message {\n --channels-reply-clearance: 36px;\n position: relative;\n}\n\n.dshc_quote {\n border-left: 2px solid var(--dsw-alias-label-tertiary);\n min-width: 0;\n color: var(--dsw-alias-label-tertiary);\n white-space: nowrap;\n text-overflow: ellipsis;\n gap: 6px;\n margin-bottom: 2px;\n padding-left: 8px;\n font-size: 12px;\n display: flex;\n overflow: hidden;\n}\n\n.dshc_quoteDeleted {\n font-style: italic;\n}\n\n.dshc_quoteAuthor {\n flex: none;\n font-weight: 600;\n}\n\n.dshc_replyButton {\n opacity: 0;\n border-radius: var(--dsw-radius-xs);\n color: var(--dsw-alias-label-tertiary);\n background: var(--dsw-alias-bg-layer-2);\n cursor: pointer;\n border: 0;\n padding: 0 6px;\n position: absolute;\n top: 4px;\n right: 16px;\n}\n\n.dshc_message:hover .dshc_replyButton, .dshc_message:focus-within .dshc_replyButton, .dshc_replyButton:focus-visible {\n opacity: 1;\n}\n\n@media (hover: none) {\n .dshc_replyButton {\n opacity: 1;\n }\n}\n\n.dshc_replyBar {\n border-radius: var(--dsw-radius-sm);\n color: var(--dsw-alias-label-tertiary);\n background: var(--dsw-alias-bg-layer-2);\n align-items: center;\n gap: 8px;\n margin-bottom: 6px;\n padding: 4px 8px;\n font-size: 12px;\n display: flex;\n}\n\n.dshc_replyBarText {\n white-space: nowrap;\n text-overflow: ellipsis;\n flex: 1;\n min-width: 0;\n overflow: hidden;\n}\n\n.dshc_replyCancel {\n color: inherit;\n cursor: pointer;\n background: none;\n border: 0;\n flex: none;\n}\n\n.dshc_working {\n min-height: 20px;\n color: var(--dsw-alias-label-tertiary);\n padding: 0 16px;\n font-size: 12px;\n}\n\n.dshc_composer {\n padding: 0 16px 16px;\n position: relative;\n}\n\n.dshc_composerBox {\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: var(--dsw-radius-xl);\n background: var(--dsw-alias-bg-layer-1, var(--dsw-alias-bg-layer-2));\n box-shadow: var(--dsw-shadow-lv1-blur);\n flex-direction: column;\n display: flex;\n overflow: hidden;\n}\n\n.dshc_composerBox:focus-within {\n border-color: var(--dsw-alias-label-tertiary);\n}\n\n.dshc_composerToolbar {\n flex: none;\n align-items: center;\n gap: 4px;\n padding: 4px 10px 10px;\n display: flex;\n}\n\n.dshc_composerBox > .dshc_replyBar, .dshc_composerBox > .dshc_attachments {\n margin: 10px 14px 0;\n}\n\n.dshc_mentionButton {\n border-radius: var(--dsw-radius-full, 50%);\n width: 32px;\n height: 32px;\n color: var(--dsw-alias-label-secondary);\n font: inherit;\n cursor: pointer;\n background: none;\n border: 0;\n flex: none;\n justify-content: center;\n align-items: center;\n padding: 0;\n font-size: 18px;\n display: inline-flex;\n}\n\n.dshc_mentionButton:hover:not(:disabled) {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dshc_sendButton {\n border-radius: var(--dsw-radius-full, 50%);\n width: 32px;\n height: 32px;\n color: var(--dsw-alias-label-primary-inverted);\n background: var(--dsw-alias-label-primary);\n font: inherit;\n cursor: pointer;\n border: 0;\n flex: none;\n justify-content: center;\n align-items: center;\n margin-left: auto;\n padding: 0;\n display: inline-flex;\n}\n\n.dshc_sendButton:disabled {\n opacity: .45;\n cursor: default;\n}\n\n.dshc_sendButton:focus-visible, .dshc_mentionButton:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color);\n}\n\n.dshc_mentionPopup {\n z-index: 2;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: var(--dsw-radius-md);\n background: var(--dsw-alias-bg-base);\n max-height: 320px;\n box-shadow: var(--dsw-elevation-panel, 0 8px 24px #0000003d);\n flex-direction: column;\n padding: 6px;\n display: flex;\n position: absolute;\n bottom: calc(100% - 8px);\n left: 16px;\n right: 16px;\n}\n\n.dshc_mentionPopup > [role=\"listbox\"] {\n min-height: 0;\n overflow-y: auto;\n}\n\n.dshc_mentionTitle {\n color: var(--dsw-alias-label-tertiary);\n padding: 4px 8px 6px;\n font-size: 12px;\n font-weight: 600;\n}\n\n.dshc_mentionItem {\n border-radius: var(--dsw-radius-sm);\n cursor: pointer;\n align-items: center;\n gap: 10px;\n padding: 6px 8px;\n display: flex;\n}\n\n.dshc_mentionActive {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dshc_mentionName {\n font-weight: 600;\n}\n\n.dshc_mentionMeta {\n color: var(--dsw-alias-label-tertiary);\n margin-left: auto;\n font-size: 12px;\n}\n\n.dshc_mentionHint {\n border-top: 1px solid var(--dsw-alias-border-l2);\n color: var(--dsw-alias-label-tertiary);\n padding: 6px 8px 2px;\n font-size: 11px;\n}\n\n.dshc_sendError {\n color: var(--dsw-alias-state-error-primary);\n margin-bottom: 6px;\n font-size: 12px;\n}\n\n.dshc_input {\n box-sizing: border-box;\n width: 100%;\n min-height: 52px;\n max-height: 40vh;\n color: var(--dsw-alias-label-primary);\n font: inherit;\n resize: none;\n field-sizing: content;\n background: none;\n border: 0;\n border-radius: 0;\n flex: 1;\n min-width: 0;\n padding: 14px 16px 8px;\n line-height: 1.5;\n}\n\n.dshc_input::placeholder {\n white-space: nowrap;\n text-overflow: ellipsis;\n overflow: hidden;\n}\n\n@media (width <= 600px) {\n .dshc_composer {\n padding: 0 8px 8px;\n }\n\n .dshc_composerToolbar {\n padding: 4px 8px 8px;\n }\n\n .dshc_input {\n padding: 12px 14px 8px;\n }\n}\n\n.dshc_input:focus-visible {\n outline: none;\n}\n\n.dshc_member {\n border-radius: var(--dsw-radius-sm);\n min-width: 0;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n background: none;\n border: 0;\n flex: 1;\n align-items: center;\n gap: 10px;\n padding: 5px 8px;\n display: flex;\n}\n\n.dshc_member:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dshc_member:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color);\n}\n\n.dshc_offline {\n opacity: .5;\n}\n\n.dshc_memberAvatar {\n position: relative;\n}\n\n.dshc_presence {\n border: 2px solid var(--dsw-alias-bg-layer-2);\n corner-shape: round;\n background: #23a55a;\n border-radius: 50%;\n width: 10px;\n height: 10px;\n position: absolute;\n bottom: -2px;\n right: -2px;\n}\n\n.dshc_offline .dshc_presence {\n background: var(--dsw-alias-label-tertiary);\n}\n\n.dshc_memberText {\n flex-direction: column;\n min-width: 0;\n display: flex;\n}\n\n.dshc_memberName {\n align-items: center;\n gap: 6px;\n display: flex;\n}\n\n.dshc_memberStatus {\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n}\n\n@media (width <= 900px) {\n .dshc_page {\n grid-template-columns: minmax(0, 1fr);\n }\n\n .dshc_rooms, .dshc_members {\n display: none;\n }\n}\n\n.dshc_groupHead {\n justify-content: space-between;\n align-items: center;\n padding-right: 4px;\n display: flex;\n}\n\n.dshc_iconButton {\n border-radius: var(--dsw-radius-sm);\n width: 22px;\n height: 22px;\n color: var(--dsw-alias-label-tertiary);\n font: inherit;\n cursor: pointer;\n background: none;\n border: 0;\n font-size: 16px;\n line-height: 1;\n}\n\n.dshc_iconButton:hover {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dshc_roomInput, .dshc_fieldInput {\n box-sizing: border-box;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: var(--dsw-radius-sm);\n width: 100%;\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-bg-base);\n font: inherit;\n padding: 6px 8px;\n}\n\n.dshc_roomInput {\n margin: 2px 0 6px;\n}\n\n.dshc_roomInput:focus-visible, .dshc_fieldInput:focus-visible, .dshc_iconButton:focus-visible, .dshc_linkButton:focus-visible, .dshc_addMemberButton:focus-visible, .dshc_primaryButton:focus-visible, .dshc_secondaryButton:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color);\n}\n\n.dshc_addMemberButton {\n box-sizing: border-box;\n border: 1px dashed var(--dsw-alias-border-l2);\n border-radius: var(--dsw-radius-sm);\n width: 100%;\n color: var(--dsw-alias-label-secondary);\n font: inherit;\n cursor: pointer;\n background: none;\n margin: 4px 0;\n padding: 7px 8px;\n}\n\n.dshc_addMemberButton:hover {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dshc_settingsDivider {\n border: 0;\n border-top: 1px solid var(--dsw-alias-border-l1);\n width: 100%;\n margin: 8px 0 4px;\n}\n\n.dshc_memberListDivider {\n border: 0;\n border-top: 1px solid var(--dsw-alias-border-l1);\n width: calc(100% - 16px);\n margin: 12px 8px 2px;\n}\n\n.dshc_settingsButton {\n text-align: left;\n border-style: solid;\n border-color: #0000;\n align-items: center;\n gap: 8px;\n display: flex;\n}\n\n.dshc_settingsButton svg {\n flex: none;\n}\n\n.dshc_memberRow {\n border-radius: var(--dsw-radius-sm);\n align-items: center;\n display: flex;\n position: relative;\n}\n\n.dshc_memberRow:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dshc_memberRow .dshc_member:hover {\n background: none;\n}\n\n.dshc_memberModel {\n color: var(--dsw-alias-label-tertiary);\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 11px;\n overflow: hidden;\n}\n\n.dshc_memberActions {\n color: var(--dsw-alias-label-tertiary);\n opacity: 0;\n flex: none;\n gap: 4px;\n padding-right: 6px;\n font-size: 12px;\n display: flex;\n}\n\n.dshc_memberRow:hover .dshc_memberActions, .dshc_memberRow:focus-within .dshc_memberActions {\n opacity: 1;\n}\n\n.dshc_linkButton {\n border-radius: var(--dsw-radius-xs);\n color: var(--dsw-alias-label-secondary);\n font: inherit;\n cursor: pointer;\n background: none;\n border: 0;\n padding: 2px 4px;\n font-size: 12px;\n}\n\n.dshc_linkButton:hover {\n color: var(--dsw-alias-label-primary);\n text-decoration: underline;\n}\n\n.dshc_dialogBackdrop {\n z-index: 10;\n background: #00000073;\n place-items: center;\n padding: 16px;\n display: grid;\n position: fixed;\n inset: 0;\n}\n\n.dshc_dialog {\n border-radius: var(--dsw-radius-lg);\n width: min(460px, 100%);\n max-height: calc(100vh - 32px);\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-bg-base);\n box-shadow: var(--dsw-elevation-panel, 0 12px 32px #0000004d);\n flex-direction: column;\n gap: 12px;\n padding: 20px;\n display: flex;\n overflow-y: auto;\n}\n\n.dshc_settingsDialog {\n box-sizing: border-box;\n height: min(70vh, 100dvh - 32px);\n max-height: calc(100dvh - 32px);\n overflow: hidden;\n}\n\n.dshc_settingsBody {\n overscroll-behavior: contain;\n flex: auto;\n min-height: 0;\n padding: 2px;\n overflow-y: auto;\n}\n\n.dshc_settingsDialog > :not(.dshc_settingsBody) {\n flex-shrink: 0;\n}\n\n.dshc_settingsFields {\n flex-direction: column;\n gap: 12px;\n display: flex;\n}\n\n.dshc_settingsFeedback {\n overflow-wrap: anywhere;\n max-height: 20%;\n overflow-y: auto;\n}\n\n.dshc_settingsDialog .dshc_dialogActions {\n justify-content: space-between;\n}\n\n@media (width <= 599px) {\n .dshc_settingsDialog {\n height: calc(100dvh - 32px);\n padding: 16px;\n }\n}\n\n.dshc_avatarCropStage {\n background: #000000a6;\n padding: 20px;\n}\n\n.dshc_avatarCropFrame {\n aspect-ratio: 1;\n touch-action: none;\n cursor: grab;\n outline: 2px solid #fff;\n width: min(256px, 100%);\n margin: auto;\n position: relative;\n overflow: hidden;\n}\n\n.dshc_avatarCropFrame:active {\n cursor: grabbing;\n}\n\n.dshc_avatarCropFrame img {\n pointer-events: none;\n user-select: none;\n max-width: none;\n position: absolute;\n}\n\n.dshc_dialogTitle {\n margin: 0;\n font-size: 16px;\n}\n\n.dshc_field {\n color: var(--dsw-alias-label-secondary);\n flex-direction: column;\n gap: 4px;\n font-size: 12px;\n display: flex;\n}\n\n.dshc_field textarea {\n resize: vertical;\n}\n\n.dshc_choiceRow {\n flex-wrap: wrap;\n gap: 12px;\n display: flex;\n}\n\n.dshc_choiceColumn {\n flex-direction: column;\n gap: 12px;\n display: flex;\n}\n\n.dshc_choiceColumn .dshc_choice {\n grid-template-columns: max-content minmax(0, 1fr);\n align-items: start;\n display: grid;\n}\n\n.dshc_choice {\n color: var(--dsw-alias-label-primary, inherit);\n cursor: pointer;\n align-items: center;\n gap: 4px;\n font-size: 13px;\n display: inline-flex;\n}\n\n.dshc_dialogActions {\n justify-content: flex-end;\n gap: 8px;\n display: flex;\n}\n\n.dshc_primaryButton, .dshc_secondaryButton {\n border-radius: var(--dsw-radius-sm);\n font: inherit;\n cursor: pointer;\n padding: 7px 14px;\n}\n\n.dshc_primaryButton {\n color: #fff;\n background: var(--dsw-alias-state-business-primary);\n border: 0;\n}\n\n.dshc_primaryButton:disabled {\n cursor: not-allowed;\n opacity: .5;\n}\n\n.dshc_secondaryButton {\n border: 1px solid var(--dsw-alias-border-l2);\n color: var(--dsw-alias-label-primary);\n background: none;\n}\n\n.dshc_memberFailed .dshc_memberStatus {\n color: var(--dsw-alias-state-error-primary, #ed4245);\n}\n\n.dshc_memberFailed .dshc_presence {\n background: #ed4245;\n}\n\n.dshc_mobileButton, .dshc_drawerBackdrop {\n display: none;\n}\n\n.dshc_panelToggle {\n border-radius: var(--dsw-radius-sm);\n width: 28px;\n height: 28px;\n color: var(--dsw-alias-label-tertiary);\n font: inherit;\n cursor: pointer;\n background: none;\n border: 0;\n justify-content: center;\n align-items: center;\n font-size: 16px;\n line-height: 1;\n display: inline-flex;\n}\n\n.dshc_panelToggle:hover {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dshc_panelToggle:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color);\n}\n\n.dshc_panelToggleEnd {\n margin-left: auto;\n}\n\n.dshc_capabilityMenu {\n z-index: 4;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: var(--dsw-radius-sm);\n background: var(--dsw-alias-bg-base);\n max-height: 260px;\n box-shadow: var(--dsw-elevation-panel, 0 8px 24px #0000003d);\n gap: 2px;\n padding: 5px;\n display: grid;\n position: absolute;\n bottom: calc(100% + 4px);\n left: 0;\n right: 0;\n overflow-y: auto;\n}\n\n.dshc_capabilityItem:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dshc_capabilityHelp {\n color: var(--dsw-alias-label-secondary);\n white-space: normal;\n margin: 6px;\n font-size: 12px;\n}\n\n.dshc_capabilityItem:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color);\n}\n\n.dshc_capabilityItem {\n text-align: left;\n cursor: pointer;\n color: var(--dsw-alias-label-secondary);\n white-space: nowrap;\n background: none;\n border: 0;\n padding: 5px 8px;\n font-size: 12px;\n font-weight: 400;\n}\n\n@media (width >= 901px) {\n .dshc_page.dshc_roomsHidden {\n grid-template-columns: minmax(0, 1fr) 240px;\n }\n\n .dshc_page.dshc_membersHidden {\n grid-template-columns: 220px minmax(0, 1fr);\n }\n\n .dshc_page.dshc_roomsHidden.dshc_membersHidden {\n grid-template-columns: minmax(0, 1fr);\n }\n\n .dshc_roomsHidden > .dshc_rooms, .dshc_membersHidden > .dshc_members {\n display: none;\n }\n}\n\n@media (width <= 900px) {\n .dshc_page {\n position: relative;\n overflow: hidden;\n }\n\n .dshc_mobileButton {\n min-width: 40px;\n min-height: 40px;\n color: inherit;\n font: inherit;\n cursor: pointer;\n background: none;\n border: 0;\n border-radius: 6px;\n justify-content: center;\n align-items: center;\n display: inline-flex;\n }\n\n .dshc_mobileButton:last-child {\n margin-left: auto;\n }\n\n .dshc_panelToggle {\n display: none;\n }\n\n .dshc_drawerBackdrop {\n z-index: 4;\n background: #00000073;\n border: 0;\n display: block;\n position: absolute;\n inset: 0;\n }\n\n .dshc_rooms.dshc_drawerOpen, .dshc_members.dshc_drawerOpen {\n z-index: 5;\n box-sizing: border-box;\n width: min(280px, 85%);\n display: block;\n position: absolute;\n top: 0;\n bottom: 0;\n box-shadow: 0 0 24px #0000004d;\n }\n\n .dshc_rooms.dshc_drawerOpen {\n left: 0;\n }\n\n .dshc_members.dshc_drawerOpen {\n right: 0;\n }\n\n .dshc_memberActions {\n opacity: 1;\n }\n\n .dshc_messageHead {\n flex-wrap: wrap;\n }\n}\n\n.dshc_avatarPicker {\n flex-wrap: wrap;\n gap: 8px;\n display: flex;\n}\n\n.dshc_avatarOption {\n cursor: pointer;\n background: none;\n border: 2px solid #0000;\n border-radius: 50%;\n padding: 2px;\n display: grid;\n}\n\n.dshc_avatarOption[aria-checked=\"true\"] {\n border-color: var(--dsw-alias-accent, #5865f2);\n}\n\n.dshc_taskBoardTrigger {\n box-sizing: border-box;\n border-radius: var(--dsw-radius-sm);\n width: 100%;\n height: 34px;\n color: var(--dsw-alias-label-primary);\n font: inherit;\n text-align: left;\n cursor: pointer;\n background: none;\n border: 0;\n flex: none;\n align-items: center;\n gap: 8px;\n margin: 4px 0;\n padding: 7px 8px;\n font-size: 12px;\n display: flex;\n}\n\n.dshc_taskBoardTrigger:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dshc_taskBoardTrigger:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color);\n}\n\n.dshc_taskBoardSummary {\n color: var(--dsw-alias-label-secondary);\n text-align: right;\n text-overflow: ellipsis;\n white-space: nowrap;\n flex: 1;\n overflow: hidden;\n}\n\n.dshc_taskBoardDialog {\n width: min(920px, 100%);\n height: min(70vh, 100dvh - 32px);\n overflow: hidden;\n}\n\n.dshc_taskBoardBody {\n overscroll-behavior: contain;\n flex: auto;\n min-height: 0;\n overflow-y: auto;\n}\n\n.dshc_taskBoardDialog > :not(.dshc_taskBoardBody) {\n flex-shrink: 0;\n}\n\n.dshc_boardTabs {\n border-bottom: 1px solid var(--dsw-alias-border-l2);\n gap: 4px;\n padding-bottom: 8px;\n display: flex;\n overflow-x: auto;\n}\n\n.dshc_boardTabs button {\n border-radius: var(--dsw-radius-sm);\n color: var(--dsw-alias-label-secondary);\n font: inherit;\n cursor: pointer;\n background: none;\n border: 0;\n flex: none;\n padding: 7px 10px;\n font-size: 13px;\n}\n\n.dshc_boardTabs button:hover, .dshc_boardTabs button[aria-selected=\"true\"] {\n color: var(--dsw-alias-state-business-primary);\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dshc_boardTabs button:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color);\n outline-offset: -2px;\n}\n\n.dshc_boardTabCount {\n font-variant-numeric: tabular-nums;\n margin-left: 4px;\n}\n\n.dshc_boardCardGrid {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 12px;\n margin: 6px 0 16px;\n padding: 2px;\n list-style: none;\n display: grid;\n}\n\n.dshc_boardTaskCard {\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-base);\n border-radius: 12px;\n flex-direction: column;\n gap: 10px;\n min-width: 0;\n padding: 14px;\n transition: transform .14s, box-shadow .14s;\n display: flex;\n}\n\n.dshc_boardTaskCard[data-working=\"true\"] {\n border-color: var(--dsw-alias-state-business-primary);\n}\n\n.dshc_boardTaskCard[data-status=\"done\"] {\n filter: saturate(.25);\n color: var(--dsw-alias-label-secondary);\n}\n\n.dshc_boardTaskCard:hover {\n transform: translateY(-2px);\n box-shadow: 0 4px 12px #00000014;\n}\n\n.dshc_boardTaskHeader {\n align-items: flex-start;\n gap: 8px;\n display: flex;\n}\n\n.dshc_boardTaskTitle {\n overflow-wrap: anywhere;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n flex: 1;\n font-size: 14px;\n font-weight: 600;\n line-height: 1.5;\n display: -webkit-box;\n overflow: hidden;\n}\n\n.dshc_boardTaskHeader .dshc_taskStatus {\n border-radius: 999px;\n padding: 3px 7px;\n font-size: 11px;\n}\n\n.dshc_boardTaskCard .dshc_taskLinks {\n justify-content: flex-end;\n margin-top: auto;\n}\n\n.dshc_boardTabEmpty {\n min-height: 240px;\n color: var(--dsw-alias-label-secondary);\n text-align: center;\n justify-content: center;\n align-items: center;\n padding: 40px 16px;\n display: flex;\n}\n\n@media (width <= 640px) {\n .dshc_boardCardGrid {\n grid-template-columns: minmax(0, 1fr);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .dshc_boardTaskCard {\n transition: none;\n }\n\n .dshc_boardTaskCard:hover {\n transform: none;\n }\n}\n\n.dshc_taskBoardScope {\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n}\n\n.dshc_taskSection + .dshc_taskSection {\n margin-top: 8px;\n}\n\n.dshc_taskSectionTitle {\n color: var(--dsw-alias-label-secondary);\n margin: 0 0 4px;\n padding: 0 8px;\n font-size: 12px;\n font-weight: 600;\n}\n\n.dshc_taskOwnerGroup + .dshc_taskOwnerGroup {\n margin-top: 4px;\n}\n\n.dshc_taskOwnerName {\n color: var(--dsw-alias-label-tertiary);\n padding: 0 8px;\n font-size: 12px;\n}\n\n.dshc_taskList {\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.dshc_taskRow {\n border-radius: var(--dsw-radius-sm);\n padding: 6px 8px;\n}\n\n.dshc_taskRow:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dshc_taskLine {\n align-items: center;\n gap: 6px;\n min-width: 0;\n display: flex;\n}\n\n.dshc_taskTitle {\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 13px;\n overflow: hidden;\n}\n\n.dshc_taskMeta {\n color: var(--dsw-alias-label-tertiary);\n flex-wrap: wrap;\n gap: 0 8px;\n font-size: 12px;\n display: flex;\n}\n\n.dshc_taskLinks {\n gap: 8px;\n display: flex;\n}\n\n.dshc_taskEmpty {\n color: var(--dsw-alias-label-tertiary);\n margin: 0 8px;\n font-size: 12px;\n}\n\n.dshc_taskDone summary {\n cursor: pointer;\n}\n\n.dshc_taskStatus {\n border-radius: var(--dsw-radius-xs);\n color: var(--dsw-alias-label-primary-inverted);\n background: var(--dsw-alias-state-business-primary);\n flex: none;\n padding: 0 4px;\n font-size: 10px;\n font-style: normal;\n font-weight: 600;\n}\n\n.dshc_taskStatus[data-status=\"asked\"], .dshc_taskStatus[data-status=\"escalated\"], .dshc_taskStatus[data-status=\"declined\"], .dshc_taskStatus[data-status=\"rework\"] {\n background: var(--dsw-alias-state-warning-primary, var(--dsw-alias-state-error-primary));\n}\n\n.dshc_taskStatus[data-status=\"done\"] {\n background: var(--dsw-alias-label-tertiary);\n}\n\n.dshc_taskCard {\n margin-top: 4px;\n font-style: normal;\n}\n\n.dshc_taskMembers {\n margin: 4px 0 0;\n padding: 0;\n list-style: none;\n}\n\n.dshc_taskMember {\n align-items: center;\n gap: 8px;\n font-size: 13px;\n display: flex;\n}\n\n.dshc_taskMemberName {\n font-weight: 600;\n}\n\n.dshc_taskSub {\n color: var(--dsw-alias-label-secondary);\n text-overflow: ellipsis;\n white-space: nowrap;\n overflow: hidden;\n}\n\n.dshc_focused {\n background: var(--dsw-alias-interactive-bg-hover);\n outline: 2px solid var(--dsw-alias-state-business-primary);\n}\n\n.dshc_attachments {\n flex-wrap: wrap;\n gap: 6px;\n margin: 4px 0;\n display: flex;\n}\n\n.dshc_attachmentImage {\n border-radius: 4px;\n max-width: min(320px, 100%);\n max-height: 240px;\n display: block;\n}\n\n.dshc_attachmentThumb {\n object-fit: cover;\n vertical-align: middle;\n border-radius: 3px;\n width: 32px;\n height: 32px;\n}\n\n.dshc_attachmentFile {\n background: var(--background-secondary, #7f7f7f26);\n color: inherit;\n border-radius: 4px;\n align-items: center;\n gap: 4px;\n padding: 4px 8px;\n text-decoration: none;\n display: inline-flex;\n}\n\n.dshc_attachmentSize {\n opacity: .6;\n font-size: .85em;\n}\n\n.dshc_attachmentAudio {\n flex-direction: column;\n gap: 2px;\n width: min(320px, 100%);\n display: flex;\n}\n\n.dshc_attachmentAudio audio {\n width: 100%;\n}\n\n.dshc_attachmentAudio a {\n color: inherit;\n text-decoration: none;\n}\n\n.dshc_attachmentFile {\n overflow-wrap: anywhere;\n max-width: 100%;\n}\n\n.dshc_chatHead button, .dshc_chatHead input, .dshc_chatHead select, .dshc_chatHead a, .dshc_chatHead [role=\"menu\"] {\n -webkit-app-region: no-drag;\n}\n\n.dshc_text pre, .dshc_text table {\n max-width: 100%;\n overflow-x: auto;\n}\n";document.head.appendChild(s)})();
|
|
8
|
+
(function(){var s=document.createElement('style');s.setAttribute('data-dsh-plugin',"bothouse-dsh");s.textContent=".dshc_page {\n width: 100%;\n height: 100%;\n min-height: 0;\n padding-top: var(--dsh-frame-top-clearance, 0px);\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-bg-base);\n grid-template-columns: 220px minmax(0, 1fr) 240px;\n font-size: 14px;\n line-height: 1.5;\n display: grid;\n}\n\n.dshc_notice {\n max-width: 480px;\n color: var(--dsw-alias-label-secondary);\n text-align: center;\n grid-column: 1 / -1;\n place-self: center;\n padding: 24px;\n}\n\n.dshc_rooms, .dshc_members {\n background: var(--dsw-alias-bg-layer-2);\n min-height: 0;\n padding: 0 8px 16px;\n overflow-y: auto;\n}\n\n.dshc_membersHead {\n border-bottom: 1px solid var(--dsw-alias-border-l2);\n flex: none;\n height: 44px;\n margin: 0 0 8px;\n}\n\n.dshc_groupTitle {\n color: var(--dsw-alias-label-tertiary);\n letter-spacing: .02em;\n margin: 12px 8px 6px;\n font-size: 12px;\n font-weight: 600;\n}\n\n.dshc_room {\n border-radius: var(--dsw-radius-sm);\n width: 100%;\n color: var(--dsw-alias-label-secondary);\n font: inherit;\n text-align: left;\n cursor: pointer;\n background: none;\n border: 0;\n align-items: center;\n gap: 6px;\n padding: 6px 8px;\n display: flex;\n}\n\n.dshc_room:hover {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dshc_room:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color);\n}\n\n.dshc_workspace {\n padding-left: 22px;\n font-size: .95em;\n}\n\n.dshc_roomLabel {\n text-overflow: ellipsis;\n white-space: nowrap;\n overflow: hidden;\n}\n\n.dshc_activeCount {\n color: var(--dsw-alias-label-tertiary);\n flex-shrink: 0;\n margin-left: auto;\n font-size: 11px;\n}\n\n.dshc_activeBusy {\n color: var(--dsw-alias-label-primary);\n}\n\n.dshc_roomActive {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dshc_hash {\n color: var(--dsw-alias-label-tertiary);\n font-size: 16px;\n}\n\n.dshc_chat {\n flex-direction: column;\n min-width: 0;\n min-height: 0;\n display: flex;\n}\n\n.dshc_chatHead {\n -webkit-app-region: drag;\n border-bottom: 1px solid var(--dsw-alias-border-l2);\n align-items: center;\n gap: 6px;\n height: 44px;\n padding: 0 16px;\n font-weight: 600;\n display: flex;\n}\n\n.dshc_log {\n flex: 1;\n min-height: 0;\n padding: 8px 0 12px;\n overflow-y: auto;\n}\n\n.dshc_guide {\n border: 1px dashed var(--dsw-alias-border-l2);\n border-radius: var(--dsw-radius-sm);\n color: var(--dsw-alias-label-secondary);\n flex-direction: column;\n align-items: flex-start;\n gap: 10px;\n margin: 16px;\n padding: 14px 16px;\n display: flex;\n}\n\n.dshc_empty {\n color: var(--dsw-alias-label-tertiary);\n padding: 24px 16px;\n}\n\n.dshc_message {\n grid-template-columns: 36px minmax(0, 1fr);\n gap: 12px;\n padding: 6px 16px;\n display: grid;\n}\n\n.dshc_message:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dshc_system .dshc_text {\n color: var(--dsw-alias-label-tertiary);\n font-style: italic;\n}\n\n.dshc_avatar {\n corner-shape: round;\n color: #fff;\n border-radius: 50%;\n place-items: center;\n width: 36px;\n height: 36px;\n font-weight: 700;\n display: grid;\n position: relative;\n}\n\n.dshc_avatarImage {\n object-fit: cover;\n border-radius: 50%;\n width: 100%;\n height: 100%;\n position: absolute;\n inset: 0;\n}\n\n.dshc_avatarSmall {\n width: 28px;\n height: 28px;\n font-size: 12px;\n}\n\n.dshc_avatarAnchor {\n align-self: start;\n position: relative;\n}\n\n.dshc_avatarButton {\n cursor: pointer;\n background: none;\n border: 0;\n border-radius: 50%;\n padding: 0;\n}\n\n.dshc_avatarMenu {\n z-index: 3;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: var(--dsw-radius-md);\n background: var(--dsw-alias-bg-base);\n min-width: 140px;\n box-shadow: var(--dsw-elevation-panel, 0 8px 24px #0000003d);\n flex-direction: column;\n padding: 6px;\n display: flex;\n position: absolute;\n top: calc(100% + 4px);\n left: 0;\n}\n\n.dshc_avatarMenuTitle {\n padding: 4px 8px;\n font-weight: 700;\n}\n\n.dshc_avatarMenuItem {\n border-radius: var(--dsw-radius-sm);\n color: inherit;\n text-align: left;\n cursor: pointer;\n background: none;\n border: 0;\n padding: 6px 8px;\n}\n\n.dshc_avatarMenuItem:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dshc_messageBody {\n min-width: 0;\n}\n\n.dshc_messageHead {\n align-items: baseline;\n gap: 6px;\n display: flex;\n}\n\n.dshc_author {\n font-weight: 600;\n}\n\n.dshc_time {\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n}\n\n.dshc_tag {\n border-radius: var(--dsw-radius-xs);\n color: var(--dsw-alias-label-primary-inverted);\n background: var(--dsw-alias-state-business-primary);\n white-space: nowrap;\n padding: 0 4px;\n font-size: 10px;\n font-weight: 600;\n}\n\n.dshc_edited {\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n}\n\n.dshc_ownActions {\n margin-left: auto;\n margin-right: var(--channels-reply-clearance);\n opacity: 0;\n gap: 8px;\n display: inline-flex;\n}\n\n.dshc_message:hover .dshc_ownActions, .dshc_ownActions:focus-within {\n opacity: 1;\n}\n\n@media (hover: none) {\n .dshc_ownActions {\n opacity: 1;\n }\n}\n\n.dshc_editBox {\n flex-direction: column;\n gap: 6px;\n margin-top: 4px;\n display: flex;\n}\n\n.dshc_editInput {\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: var(--dsw-radius-md);\n background: var(--dsw-alias-bg-base);\n min-height: 60px;\n color: inherit;\n font: inherit;\n resize: vertical;\n padding: 8px;\n}\n\n.dshc_editBar {\n justify-content: flex-end;\n align-items: center;\n gap: 8px;\n display: flex;\n}\n\n.dshc_editBar .dshc_time {\n margin-right: auto;\n}\n\n.dshc_text {\n white-space: normal;\n overflow-wrap: anywhere;\n}\n\n.dshc_card {\n border-left: 4px solid var(--dsw-alias-state-business-primary);\n border-radius: var(--dsw-radius-sm);\n background: var(--dsw-alias-bg-layer-2);\n margin-top: 4px;\n padding: 6px 10px;\n}\n\n.dshc_card[data-state=\"failed\"] {\n border-left-color: var(--dsw-alias-state-error-primary);\n}\n\n.dshc_card[data-state=\"retrying\"] {\n border-left-color: var(--dsw-alias-state-warning-primary, #faa61a);\n}\n\n.dshc_cardButtons {\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n margin-top: 6px;\n display: flex;\n}\n\n.dshc_cardTitle {\n font-size: 13px;\n font-weight: 600;\n}\n\n.dshc_cardHead {\n justify-content: space-between;\n align-items: center;\n gap: 8px;\n display: flex;\n}\n\n.dshc_cardViews {\n gap: 4px;\n display: flex;\n}\n\n.dshc_cardViewButton {\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: var(--dsw-radius-sm);\n color: inherit;\n cursor: pointer;\n background: none;\n padding: 0 6px;\n font-size: 12px;\n}\n\n.dshc_cardViewButton[aria-pressed=\"true\"] {\n background: var(--dsw-alias-bg-base);\n font-weight: 600;\n}\n\n.dshc_permActions {\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n margin-top: 6px;\n display: flex;\n}\n\n.dshc_permScope {\n color: var(--dsw-alias-text-secondary);\n flex-basis: 100%;\n font-size: 12px;\n}\n\n.dshc_permStatus {\n margin-top: 6px;\n font-size: 13px;\n font-weight: 600;\n}\n\n.dshc_message {\n --channels-reply-clearance: 36px;\n position: relative;\n}\n\n.dshc_quote {\n border-left: 2px solid var(--dsw-alias-label-tertiary);\n min-width: 0;\n color: var(--dsw-alias-label-tertiary);\n white-space: nowrap;\n text-overflow: ellipsis;\n gap: 6px;\n margin-bottom: 2px;\n padding-left: 8px;\n font-size: 12px;\n display: flex;\n overflow: hidden;\n}\n\n.dshc_quoteDeleted {\n font-style: italic;\n}\n\n.dshc_quoteAuthor {\n flex: none;\n font-weight: 600;\n}\n\n.dshc_replyButton {\n opacity: 0;\n border-radius: var(--dsw-radius-xs);\n color: var(--dsw-alias-label-tertiary);\n background: var(--dsw-alias-bg-layer-2);\n cursor: pointer;\n border: 0;\n padding: 0 6px;\n position: absolute;\n top: 4px;\n right: 16px;\n}\n\n.dshc_message:hover .dshc_replyButton, .dshc_message:focus-within .dshc_replyButton, .dshc_replyButton:focus-visible {\n opacity: 1;\n}\n\n@media (hover: none) {\n .dshc_replyButton {\n opacity: 1;\n }\n}\n\n.dshc_replyBar {\n border-radius: var(--dsw-radius-sm);\n color: var(--dsw-alias-label-tertiary);\n background: var(--dsw-alias-bg-layer-2);\n align-items: center;\n gap: 8px;\n margin-bottom: 6px;\n padding: 4px 8px;\n font-size: 12px;\n display: flex;\n}\n\n.dshc_replyBarText {\n white-space: nowrap;\n text-overflow: ellipsis;\n flex: 1;\n min-width: 0;\n overflow: hidden;\n}\n\n.dshc_replyCancel {\n color: inherit;\n cursor: pointer;\n background: none;\n border: 0;\n flex: none;\n}\n\n.dshc_working {\n min-height: 20px;\n color: var(--dsw-alias-label-tertiary);\n padding: 0 16px;\n font-size: 12px;\n}\n\n.dshc_composer {\n padding: 0 16px 16px;\n position: relative;\n}\n\n.dshc_composerBox {\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: var(--dsw-radius-xl);\n background: var(--dsw-alias-bg-layer-1, var(--dsw-alias-bg-layer-2));\n box-shadow: var(--dsw-shadow-lv1-blur);\n flex-direction: column;\n display: flex;\n overflow: hidden;\n}\n\n.dshc_composerBox:focus-within {\n border-color: var(--dsw-alias-label-tertiary);\n}\n\n.dshc_composerToolbar {\n flex: none;\n align-items: center;\n gap: 4px;\n padding: 4px 10px 10px;\n display: flex;\n}\n\n.dshc_composerBox > .dshc_replyBar, .dshc_composerBox > .dshc_attachments {\n margin: 10px 14px 0;\n}\n\n.dshc_mentionButton {\n border-radius: var(--dsw-radius-full, 50%);\n width: 32px;\n height: 32px;\n color: var(--dsw-alias-label-secondary);\n font: inherit;\n cursor: pointer;\n background: none;\n border: 0;\n flex: none;\n justify-content: center;\n align-items: center;\n padding: 0;\n font-size: 18px;\n display: inline-flex;\n}\n\n.dshc_mentionButton:hover:not(:disabled) {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dshc_sendButton {\n border-radius: var(--dsw-radius-full, 50%);\n width: 32px;\n height: 32px;\n color: var(--dsw-alias-label-primary-inverted);\n background: var(--dsw-alias-label-primary);\n font: inherit;\n cursor: pointer;\n border: 0;\n flex: none;\n justify-content: center;\n align-items: center;\n margin-left: auto;\n padding: 0;\n display: inline-flex;\n}\n\n.dshc_sendButton:disabled {\n opacity: .45;\n cursor: default;\n}\n\n.dshc_sendButton:focus-visible, .dshc_mentionButton:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color);\n}\n\n.dshc_mentionPopup {\n z-index: 2;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: var(--dsw-radius-md);\n background: var(--dsw-alias-bg-base);\n max-height: 320px;\n box-shadow: var(--dsw-elevation-panel, 0 8px 24px #0000003d);\n flex-direction: column;\n padding: 6px;\n display: flex;\n position: absolute;\n bottom: calc(100% - 8px);\n left: 16px;\n right: 16px;\n}\n\n.dshc_mentionPopup > [role=\"listbox\"] {\n min-height: 0;\n overflow-y: auto;\n}\n\n.dshc_mentionTitle {\n color: var(--dsw-alias-label-tertiary);\n padding: 4px 8px 6px;\n font-size: 12px;\n font-weight: 600;\n}\n\n.dshc_mentionItem {\n border-radius: var(--dsw-radius-sm);\n cursor: pointer;\n align-items: center;\n gap: 10px;\n padding: 6px 8px;\n display: flex;\n}\n\n.dshc_mentionActive {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dshc_mentionName {\n font-weight: 600;\n}\n\n.dshc_mentionMeta {\n color: var(--dsw-alias-label-tertiary);\n margin-left: auto;\n font-size: 12px;\n}\n\n.dshc_mentionHint {\n border-top: 1px solid var(--dsw-alias-border-l2);\n color: var(--dsw-alias-label-tertiary);\n padding: 6px 8px 2px;\n font-size: 11px;\n}\n\n.dshc_sendError {\n color: var(--dsw-alias-state-error-primary);\n margin-bottom: 6px;\n font-size: 12px;\n}\n\n.dshc_input {\n box-sizing: border-box;\n width: 100%;\n min-height: 52px;\n max-height: 40vh;\n color: var(--dsw-alias-label-primary);\n font: inherit;\n resize: none;\n field-sizing: content;\n background: none;\n border: 0;\n border-radius: 0;\n flex: 1;\n min-width: 0;\n padding: 14px 16px 8px;\n line-height: 1.5;\n}\n\n.dshc_input::placeholder {\n white-space: nowrap;\n text-overflow: ellipsis;\n overflow: hidden;\n}\n\n@media (width <= 600px) {\n .dshc_composer {\n padding: 0 8px 8px;\n }\n\n .dshc_composerToolbar {\n padding: 4px 8px 8px;\n }\n\n .dshc_input {\n padding: 12px 14px 8px;\n }\n}\n\n.dshc_input:focus-visible {\n outline: none;\n}\n\n.dshc_member {\n border-radius: var(--dsw-radius-sm);\n min-width: 0;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n background: none;\n border: 0;\n flex: 1;\n align-items: center;\n gap: 10px;\n padding: 5px 8px;\n display: flex;\n}\n\n.dshc_member:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dshc_member:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color);\n}\n\n.dshc_offline {\n opacity: .5;\n}\n\n.dshc_memberAvatar {\n position: relative;\n}\n\n.dshc_presence {\n border: 2px solid var(--dsw-alias-bg-layer-2);\n corner-shape: round;\n background: #23a55a;\n border-radius: 50%;\n width: 10px;\n height: 10px;\n position: absolute;\n bottom: -2px;\n right: -2px;\n}\n\n.dshc_offline .dshc_presence {\n background: var(--dsw-alias-label-tertiary);\n}\n\n.dshc_memberText {\n flex-direction: column;\n min-width: 0;\n display: flex;\n}\n\n.dshc_memberName {\n align-items: center;\n gap: 6px;\n display: flex;\n}\n\n.dshc_memberStatus {\n color: var(--dsw-alias-label-tertiary);\n font-size: 12px;\n}\n\n@media (width <= 900px) {\n .dshc_page {\n grid-template-columns: minmax(0, 1fr);\n }\n\n .dshc_rooms, .dshc_members {\n display: none;\n }\n}\n\n.dshc_groupHead {\n justify-content: space-between;\n align-items: center;\n padding-right: 4px;\n display: flex;\n}\n\n.dshc_iconButton {\n border-radius: var(--dsw-radius-sm);\n width: 22px;\n height: 22px;\n color: var(--dsw-alias-label-tertiary);\n font: inherit;\n cursor: pointer;\n background: none;\n border: 0;\n font-size: 16px;\n line-height: 1;\n}\n\n.dshc_iconButton:hover {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dshc_roomInput, .dshc_fieldInput {\n box-sizing: border-box;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: var(--dsw-radius-sm);\n width: 100%;\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-bg-base);\n font: inherit;\n padding: 6px 8px;\n}\n\n.dshc_roomInput {\n margin: 2px 0 6px;\n}\n\n.dshc_roomInput:focus-visible, .dshc_fieldInput:focus-visible, .dshc_iconButton:focus-visible, .dshc_linkButton:focus-visible, .dshc_addMemberButton:focus-visible, .dshc_primaryButton:focus-visible, .dshc_secondaryButton:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color);\n}\n\n.dshc_addMemberButton {\n box-sizing: border-box;\n border: 1px dashed var(--dsw-alias-border-l2);\n border-radius: var(--dsw-radius-sm);\n width: 100%;\n color: var(--dsw-alias-label-secondary);\n font: inherit;\n cursor: pointer;\n background: none;\n margin: 4px 0;\n padding: 7px 8px;\n}\n\n.dshc_addMemberButton:hover {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dshc_settingsDivider {\n border: 0;\n border-top: 1px solid var(--dsw-alias-border-l1);\n width: 100%;\n margin: 8px 0 4px;\n}\n\n.dshc_memberListDivider {\n border: 0;\n border-top: 1px solid var(--dsw-alias-border-l1);\n width: calc(100% - 16px);\n margin: 12px 8px 2px;\n}\n\n.dshc_settingsButton {\n text-align: left;\n border-style: solid;\n border-color: #0000;\n align-items: center;\n gap: 8px;\n display: flex;\n}\n\n.dshc_settingsButton svg {\n flex: none;\n}\n\n.dshc_memberRow {\n border-radius: var(--dsw-radius-sm);\n align-items: center;\n display: flex;\n position: relative;\n}\n\n.dshc_memberRow:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dshc_memberRow .dshc_member:hover {\n background: none;\n}\n\n.dshc_memberModel {\n color: var(--dsw-alias-label-tertiary);\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 11px;\n overflow: hidden;\n}\n\n.dshc_memberActions {\n color: var(--dsw-alias-label-tertiary);\n opacity: 0;\n flex: none;\n gap: 4px;\n padding-right: 6px;\n font-size: 12px;\n display: flex;\n}\n\n.dshc_memberRow:hover .dshc_memberActions, .dshc_memberRow:focus-within .dshc_memberActions {\n opacity: 1;\n}\n\n.dshc_linkButton {\n border-radius: var(--dsw-radius-xs);\n color: var(--dsw-alias-label-secondary);\n font: inherit;\n cursor: pointer;\n background: none;\n border: 0;\n padding: 2px 4px;\n font-size: 12px;\n}\n\n.dshc_linkButton:hover {\n color: var(--dsw-alias-label-primary);\n text-decoration: underline;\n}\n\n.dshc_dialogBackdrop {\n z-index: 10;\n background: #00000073;\n place-items: center;\n padding: 16px;\n display: grid;\n position: fixed;\n inset: 0;\n}\n\n.dshc_dialog {\n border-radius: var(--dsw-radius-lg);\n width: min(460px, 100%);\n max-height: calc(100vh - 32px);\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-bg-base);\n box-shadow: var(--dsw-elevation-panel, 0 12px 32px #0000004d);\n flex-direction: column;\n gap: 12px;\n padding: 20px;\n display: flex;\n overflow-y: auto;\n}\n\n.dshc_settingsDialog {\n box-sizing: border-box;\n height: min(70vh, 100dvh - 32px);\n max-height: calc(100dvh - 32px);\n overflow: hidden;\n}\n\n.dshc_settingsBody {\n overscroll-behavior: contain;\n flex: auto;\n min-height: 0;\n padding: 2px;\n overflow-y: auto;\n}\n\n.dshc_settingsDialog > :not(.dshc_settingsBody) {\n flex-shrink: 0;\n}\n\n.dshc_settingsFields {\n flex-direction: column;\n gap: 12px;\n display: flex;\n}\n\n.dshc_settingsFeedback {\n overflow-wrap: anywhere;\n max-height: 20%;\n overflow-y: auto;\n}\n\n.dshc_settingsDialog .dshc_dialogActions {\n justify-content: space-between;\n}\n\n@media (width <= 599px) {\n .dshc_settingsDialog {\n height: calc(100dvh - 32px);\n padding: 16px;\n }\n}\n\n.dshc_avatarCropStage {\n background: #000000a6;\n padding: 20px;\n}\n\n.dshc_avatarCropFrame {\n aspect-ratio: 1;\n touch-action: none;\n cursor: grab;\n outline: 2px solid #fff;\n width: min(256px, 100%);\n margin: auto;\n position: relative;\n overflow: hidden;\n}\n\n.dshc_avatarCropFrame:active {\n cursor: grabbing;\n}\n\n.dshc_avatarCropFrame img {\n pointer-events: none;\n user-select: none;\n max-width: none;\n position: absolute;\n}\n\n.dshc_dialogTitle {\n margin: 0;\n font-size: 16px;\n}\n\n.dshc_field {\n color: var(--dsw-alias-label-secondary);\n flex-direction: column;\n gap: 4px;\n font-size: 12px;\n display: flex;\n}\n\n.dshc_field textarea {\n resize: vertical;\n}\n\n.dshc_choiceRow {\n flex-wrap: wrap;\n gap: 12px;\n display: flex;\n}\n\n.dshc_choiceColumn {\n flex-direction: column;\n gap: 12px;\n display: flex;\n}\n\n.dshc_choiceColumn .dshc_choice {\n grid-template-columns: max-content minmax(0, 1fr);\n align-items: start;\n display: grid;\n}\n\n.dshc_choice {\n color: var(--dsw-alias-label-primary, inherit);\n cursor: pointer;\n align-items: center;\n gap: 4px;\n font-size: 13px;\n display: inline-flex;\n}\n\n.dshc_dialogActions {\n justify-content: flex-end;\n gap: 8px;\n display: flex;\n}\n\n.dshc_primaryButton, .dshc_secondaryButton {\n border-radius: var(--dsw-radius-sm);\n font: inherit;\n cursor: pointer;\n padding: 7px 14px;\n}\n\n.dshc_primaryButton {\n color: #fff;\n background: var(--dsw-alias-state-business-primary);\n border: 0;\n}\n\n.dshc_primaryButton:disabled {\n cursor: not-allowed;\n opacity: .5;\n}\n\n.dshc_secondaryButton {\n border: 1px solid var(--dsw-alias-border-l2);\n color: var(--dsw-alias-label-primary);\n background: none;\n}\n\n.dshc_memberFailed .dshc_memberStatus {\n color: var(--dsw-alias-state-error-primary, #ed4245);\n}\n\n.dshc_memberFailed .dshc_presence {\n background: #ed4245;\n}\n\n.dshc_mobileButton, .dshc_drawerBackdrop {\n display: none;\n}\n\n.dshc_panelToggle {\n border-radius: var(--dsw-radius-sm);\n width: 28px;\n height: 28px;\n color: var(--dsw-alias-label-tertiary);\n font: inherit;\n cursor: pointer;\n background: none;\n border: 0;\n justify-content: center;\n align-items: center;\n font-size: 16px;\n line-height: 1;\n display: inline-flex;\n}\n\n.dshc_panelToggle:hover {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dshc_panelToggle:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color);\n}\n\n.dshc_panelToggleEnd {\n margin-left: auto;\n}\n\n.dshc_capabilityMenu {\n z-index: 4;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: var(--dsw-radius-sm);\n background: var(--dsw-alias-bg-base);\n max-height: 260px;\n box-shadow: var(--dsw-elevation-panel, 0 8px 24px #0000003d);\n gap: 2px;\n padding: 5px;\n display: grid;\n position: absolute;\n bottom: calc(100% + 4px);\n left: 0;\n right: 0;\n overflow-y: auto;\n}\n\n.dshc_capabilityItem:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dshc_capabilityHelp {\n color: var(--dsw-alias-label-secondary);\n white-space: normal;\n margin: 6px;\n font-size: 12px;\n}\n\n.dshc_capabilityItem:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color);\n}\n\n.dshc_capabilityItem {\n text-align: left;\n cursor: pointer;\n color: var(--dsw-alias-label-secondary);\n white-space: nowrap;\n background: none;\n border: 0;\n padding: 5px 8px;\n font-size: 12px;\n font-weight: 400;\n}\n\n@media (width >= 901px) {\n .dshc_page.dshc_roomsHidden {\n grid-template-columns: minmax(0, 1fr) 240px;\n }\n\n .dshc_page.dshc_membersHidden {\n grid-template-columns: 220px minmax(0, 1fr);\n }\n\n .dshc_page.dshc_roomsHidden.dshc_membersHidden {\n grid-template-columns: minmax(0, 1fr);\n }\n\n .dshc_roomsHidden > .dshc_rooms, .dshc_membersHidden > .dshc_members {\n display: none;\n }\n}\n\n@media (width <= 900px) {\n .dshc_page {\n position: relative;\n overflow: hidden;\n }\n\n .dshc_mobileButton {\n min-width: 40px;\n min-height: 40px;\n color: inherit;\n font: inherit;\n cursor: pointer;\n background: none;\n border: 0;\n border-radius: 6px;\n justify-content: center;\n align-items: center;\n display: inline-flex;\n }\n\n .dshc_mobileButton:last-child {\n margin-left: auto;\n }\n\n .dshc_panelToggle {\n display: none;\n }\n\n .dshc_drawerBackdrop {\n z-index: 4;\n background: #00000073;\n border: 0;\n display: block;\n position: absolute;\n inset: 0;\n }\n\n .dshc_rooms.dshc_drawerOpen, .dshc_members.dshc_drawerOpen {\n z-index: 5;\n box-sizing: border-box;\n width: min(280px, 85%);\n display: block;\n position: absolute;\n top: 0;\n bottom: 0;\n box-shadow: 0 0 24px #0000004d;\n }\n\n .dshc_rooms.dshc_drawerOpen {\n left: 0;\n }\n\n .dshc_members.dshc_drawerOpen {\n right: 0;\n }\n\n .dshc_memberActions {\n opacity: 1;\n }\n\n .dshc_messageHead {\n flex-wrap: wrap;\n }\n}\n\n.dshc_avatarPicker {\n flex-wrap: wrap;\n gap: 8px;\n display: flex;\n}\n\n.dshc_avatarOption {\n cursor: pointer;\n background: none;\n border: 2px solid #0000;\n border-radius: 50%;\n padding: 2px;\n display: grid;\n}\n\n.dshc_avatarOption[aria-checked=\"true\"] {\n border-color: var(--dsw-alias-accent, #5865f2);\n}\n\n.dshc_taskBoardTrigger {\n box-sizing: border-box;\n border-radius: var(--dsw-radius-sm);\n width: 100%;\n height: 34px;\n color: var(--dsw-alias-label-primary);\n font: inherit;\n text-align: left;\n cursor: pointer;\n background: none;\n border: 0;\n flex: none;\n align-items: center;\n gap: 8px;\n margin: 4px 0;\n padding: 7px 8px;\n font-size: 12px;\n display: flex;\n}\n\n.dshc_taskBoardTrigger:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dshc_taskBoardTrigger:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color);\n}\n\n.dshc_taskBoardSummary {\n color: var(--dsw-alias-label-secondary);\n text-align: right;\n text-overflow: ellipsis;\n white-space: nowrap;\n flex: 1;\n overflow: hidden;\n}\n\n.dshc_taskBoardDialog {\n width: min(920px, 100%);\n height: min(70vh, 100dvh - 32px);\n overflow: hidden;\n}\n\n.dshc_taskBoardBody {\n overscroll-behavior: contain;\n flex: auto;\n min-height: 0;\n overflow-y: auto;\n}\n\n.dshc_taskBoardDialog > :not(.dshc_taskBoardBody) {\n flex-shrink: 0;\n}\n\n.dshc_boardTabs {\n border-bottom: 1px solid var(--dsw-alias-border-l2);\n gap: 4px;\n padding-bottom: 8px;\n display: flex;\n overflow-x: auto;\n}\n\n.dshc_boardTabs button {\n border-radius: var(--dsw-radius-sm);\n color: var(--dsw-alias-label-secondary);\n font: inherit;\n cursor: pointer;\n background: none;\n border: 0;\n flex: none;\n padding: 7px 10px;\n font-size: 13px;\n}\n\n.dshc_boardTabs button:hover, .dshc_boardTabs button[aria-selected=\"true\"] {\n color: var(--dsw-alias-state-business-primary);\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dshc_boardTabs button:focus-visible {\n outline: var(--dsw-focus-ring-width, 2px) solid var(--dsw-focus-ring-color);\n outline-offset: -2px;\n}\n\n.dshc_boardTabCount {\n font-variant-numeric: tabular-nums;\n margin-left: 4px;\n}\n\n.dshc_boardCardGrid {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 12px;\n margin: 6px 0 16px;\n padding: 2px;\n list-style: none;\n display: grid;\n}\n\n.dshc_boardTaskCard {\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-base);\n border-radius: 12px;\n flex-direction: column;\n gap: 10px;\n min-width: 0;\n padding: 14px;\n transition: transform .14s, box-shadow .14s;\n display: flex;\n}\n\n.dshc_boardTaskCard[data-working=\"true\"] {\n border-color: var(--dsw-alias-state-business-primary);\n}\n\n.dshc_boardTaskCard[data-status=\"done\"] {\n filter: saturate(.25);\n color: var(--dsw-alias-label-secondary);\n}\n\n.dshc_boardTaskCard:hover {\n transform: translateY(-2px);\n box-shadow: 0 4px 12px #00000014;\n}\n\n.dshc_boardTaskHeader {\n align-items: flex-start;\n gap: 8px;\n display: flex;\n}\n\n.dshc_boardTaskTitle {\n overflow-wrap: anywhere;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n flex: 1;\n font-size: 14px;\n font-weight: 600;\n line-height: 1.5;\n display: -webkit-box;\n overflow: hidden;\n}\n\n.dshc_boardTaskHeader .dshc_taskStatus {\n border-radius: 999px;\n padding: 3px 7px;\n font-size: 11px;\n}\n\n.dshc_boardTaskCard .dshc_taskLinks {\n justify-content: flex-end;\n margin-top: auto;\n}\n\n.dshc_boardTabEmpty {\n min-height: 240px;\n color: var(--dsw-alias-label-secondary);\n text-align: center;\n justify-content: center;\n align-items: center;\n padding: 40px 16px;\n display: flex;\n}\n\n@media (width <= 640px) {\n .dshc_boardCardGrid {\n grid-template-columns: minmax(0, 1fr);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .dshc_boardTaskCard {\n transition: none;\n }\n\n .dshc_boardTaskCard:hover {\n transform: none;\n }\n}\n\n.dshc_taskBoardScope {\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n}\n\n.dshc_taskSection + .dshc_taskSection {\n margin-top: 8px;\n}\n\n.dshc_taskSectionTitle {\n color: var(--dsw-alias-label-secondary);\n margin: 0 0 4px;\n padding: 0 8px;\n font-size: 12px;\n font-weight: 600;\n}\n\n.dshc_taskOwnerGroup + .dshc_taskOwnerGroup {\n margin-top: 4px;\n}\n\n.dshc_taskOwnerName {\n color: var(--dsw-alias-label-tertiary);\n padding: 0 8px;\n font-size: 12px;\n}\n\n.dshc_taskList {\n margin: 0;\n padding: 0;\n list-style: none;\n}\n\n.dshc_taskRow {\n border-radius: var(--dsw-radius-sm);\n padding: 6px 8px;\n}\n\n.dshc_taskRow:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.dshc_taskLine {\n align-items: center;\n gap: 6px;\n min-width: 0;\n display: flex;\n}\n\n.dshc_taskTitle {\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 13px;\n overflow: hidden;\n}\n\n.dshc_taskMeta {\n color: var(--dsw-alias-label-tertiary);\n flex-wrap: wrap;\n gap: 0 8px;\n font-size: 12px;\n display: flex;\n}\n\n.dshc_taskLinks {\n gap: 8px;\n display: flex;\n}\n\n.dshc_taskEmpty {\n color: var(--dsw-alias-label-tertiary);\n margin: 0 8px;\n font-size: 12px;\n}\n\n.dshc_taskDone summary {\n cursor: pointer;\n}\n\n.dshc_taskStatus {\n border-radius: var(--dsw-radius-xs);\n color: var(--dsw-alias-label-primary-inverted);\n background: var(--dsw-alias-state-business-primary);\n flex: none;\n padding: 0 4px;\n font-size: 10px;\n font-style: normal;\n font-weight: 600;\n}\n\n.dshc_taskStatus[data-status=\"asked\"], .dshc_taskStatus[data-status=\"escalated\"], .dshc_taskStatus[data-status=\"declined\"], .dshc_taskStatus[data-status=\"rework\"] {\n background: var(--dsw-alias-state-warning-primary, var(--dsw-alias-state-error-primary));\n}\n\n.dshc_taskStatus[data-status=\"done\"] {\n background: var(--dsw-alias-label-tertiary);\n}\n\n.dshc_taskCard {\n margin-top: 4px;\n font-style: normal;\n}\n\n.dshc_taskMembers {\n margin: 4px 0 0;\n padding: 0;\n list-style: none;\n}\n\n.dshc_taskMember {\n align-items: center;\n gap: 8px;\n font-size: 13px;\n display: flex;\n}\n\n.dshc_taskMemberName {\n font-weight: 600;\n}\n\n.dshc_taskSub {\n color: var(--dsw-alias-label-secondary);\n text-overflow: ellipsis;\n white-space: nowrap;\n overflow: hidden;\n}\n\n.dshc_focused {\n background: var(--dsw-alias-interactive-bg-hover);\n outline: 2px solid var(--dsw-alias-state-business-primary);\n}\n\n.dshc_attachments {\n flex-wrap: wrap;\n gap: 6px;\n margin: 4px 0;\n display: flex;\n}\n\n.dshc_attachmentImage {\n border-radius: 4px;\n max-width: min(320px, 100%);\n max-height: 240px;\n display: block;\n}\n\n.dshc_imageAttachment {\n align-content: start;\n max-width: 100%;\n display: grid;\n}\n\n.dshc_imagePreviewTrigger {\n cursor: zoom-in;\n background: none;\n border: 0;\n border-radius: 4px;\n grid-area: 1 / 1;\n padding: 0;\n}\n\n.dshc_imagePreviewTrigger:focus-visible {\n outline: 2px solid var(--dsw-alias-state-business-primary);\n outline-offset: 2px;\n}\n\n.dshc_imagePreviewDialog {\n box-sizing: border-box;\n width: fit-content;\n max-width: 90vw;\n max-height: 90dvh;\n overflow: auto;\n}\n\n.dshc_imagePreviewHeader {\n flex-shrink: 0;\n justify-content: space-between;\n align-items: center;\n gap: 12px;\n display: flex;\n}\n\n.dshc_imagePreviewHeader span {\n overflow-wrap: anywhere;\n min-width: 0;\n}\n\n.dshc_imagePreviewHeader button {\n flex-shrink: 0;\n}\n\n.dshc_imagePreviewImage {\n object-fit: contain;\n align-self: center;\n max-width: 100%;\n min-height: 0;\n max-height: 65dvh;\n display: block;\n}\n\n.dshc_imagePreviewActions {\n flex-wrap: wrap;\n flex-shrink: 0;\n gap: 8px;\n display: flex;\n}\n\n.dshc_imagePreviewActions a {\n text-decoration: none;\n}\n\n.dshc_imagePreviewActions button:disabled {\n cursor: wait;\n opacity: .5;\n}\n\n.dshc_attachmentActions {\n border-radius: var(--dsw-radius-sm);\n background: var(--dsw-alias-bg-base);\n opacity: 0;\n pointer-events: none;\n flex-wrap: wrap;\n grid-area: 1 / 1;\n place-self: start end;\n gap: 4px;\n margin: 4px;\n padding: 4px;\n display: flex;\n}\n\n.dshc_attachmentActions a {\n text-decoration: none;\n}\n\n.dshc_attachmentActions button:disabled {\n cursor: wait;\n opacity: .5;\n}\n\n.dshc_imageAttachment:hover .dshc_attachmentActions, .dshc_imageAttachment:focus-within .dshc_attachmentActions, .dshc_imageAttachment[data-copy-state=\"error\"] .dshc_attachmentActions, .dshc_imageAttachment[data-copy-state=\"copying\"] .dshc_attachmentActions, .dshc_imageAttachment[data-copy-state=\"copied\"] .dshc_attachmentActions {\n opacity: 1;\n pointer-events: auto;\n}\n\n@media (hover: none), (pointer: coarse), (width <= 768px) {\n .dshc_attachmentActions {\n opacity: 1;\n pointer-events: auto;\n }\n}\n\n.dshc_attachmentThumb {\n object-fit: cover;\n vertical-align: middle;\n border-radius: 3px;\n width: 32px;\n height: 32px;\n}\n\n.dshc_attachmentFile {\n background: var(--background-secondary, #7f7f7f26);\n color: inherit;\n border-radius: 4px;\n align-items: center;\n gap: 4px;\n padding: 4px 8px;\n text-decoration: none;\n display: inline-flex;\n}\n\n.dshc_attachmentSize {\n opacity: .6;\n font-size: .85em;\n}\n\n.dshc_attachmentAudio {\n flex-direction: column;\n gap: 2px;\n width: min(320px, 100%);\n display: flex;\n}\n\n.dshc_attachmentAudio audio {\n width: 100%;\n}\n\n.dshc_attachmentAudio a {\n color: inherit;\n text-decoration: none;\n}\n\n.dshc_attachmentFile {\n overflow-wrap: anywhere;\n max-width: 100%;\n}\n\n.dshc_chatHead button, .dshc_chatHead input, .dshc_chatHead select, .dshc_chatHead a, .dshc_chatHead [role=\"menu\"] {\n -webkit-app-region: no-drag;\n}\n\n.dshc_text pre, .dshc_text table {\n max-width: 100%;\n overflow-x: auto;\n}\n";document.head.appendChild(s)})();
|
|
9
9
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
10
10
|
// \0rolldown/runtime.js
|
|
11
11
|
var __defProp = Object.defineProperty;
|
|
@@ -16413,6 +16413,7 @@ var ChannelsPage_module_default = {
|
|
|
16413
16413
|
"activeBusy": "dshc_activeBusy",
|
|
16414
16414
|
"activeCount": "dshc_activeCount",
|
|
16415
16415
|
"addMemberButton": "dshc_addMemberButton",
|
|
16416
|
+
"attachmentActions": "dshc_attachmentActions",
|
|
16416
16417
|
"attachmentAudio": "dshc_attachmentAudio",
|
|
16417
16418
|
"attachmentFile": "dshc_attachmentFile",
|
|
16418
16419
|
"attachmentImage": "dshc_attachmentImage",
|
|
@@ -16475,6 +16476,12 @@ var ChannelsPage_module_default = {
|
|
|
16475
16476
|
"guide": "dshc_guide",
|
|
16476
16477
|
"hash": "dshc_hash",
|
|
16477
16478
|
"iconButton": "dshc_iconButton",
|
|
16479
|
+
"imageAttachment": "dshc_imageAttachment",
|
|
16480
|
+
"imagePreviewActions": "dshc_imagePreviewActions",
|
|
16481
|
+
"imagePreviewDialog": "dshc_imagePreviewDialog",
|
|
16482
|
+
"imagePreviewHeader": "dshc_imagePreviewHeader",
|
|
16483
|
+
"imagePreviewImage": "dshc_imagePreviewImage",
|
|
16484
|
+
"imagePreviewTrigger": "dshc_imagePreviewTrigger",
|
|
16478
16485
|
"input": "dshc_input",
|
|
16479
16486
|
"linkButton": "dshc_linkButton",
|
|
16480
16487
|
"log": "dshc_log",
|
|
@@ -16570,22 +16577,163 @@ var ChannelsPage_module_default = {
|
|
|
16570
16577
|
const isImage = (mime) => /^image\/(png|jpeg|gif|webp)$/.test(mime);
|
|
16571
16578
|
const isAudio = (mime) => mime.startsWith("audio/");
|
|
16572
16579
|
const size = (bytes) => bytes < 1024 ? `${bytes} B` : bytes < 1048576 ? `${(bytes / 1024).toFixed(1)} KB` : `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
16580
|
+
function ImageAttachment({ attachment: a, t }) {
|
|
16581
|
+
const [opened, setOpened] = (0, react.useState)(false);
|
|
16582
|
+
const trigger = (0, react.useRef)(null);
|
|
16583
|
+
const dialog = (0, react.useRef)(null);
|
|
16584
|
+
const closeButton = (0, react.useRef)(null);
|
|
16585
|
+
(0, react.useEffect)(() => {
|
|
16586
|
+
if (!opened) return;
|
|
16587
|
+
const origin = trigger.current;
|
|
16588
|
+
closeButton.current?.focus();
|
|
16589
|
+
const containFocus = (event) => {
|
|
16590
|
+
if (!dialog.current?.contains(event.target)) closeButton.current?.focus();
|
|
16591
|
+
};
|
|
16592
|
+
document.addEventListener("focusin", containFocus);
|
|
16593
|
+
return () => {
|
|
16594
|
+
document.removeEventListener("focusin", containFocus);
|
|
16595
|
+
origin?.focus();
|
|
16596
|
+
};
|
|
16597
|
+
}, [opened]);
|
|
16598
|
+
const [copyState, setCopyState] = (0, react.useState)("idle");
|
|
16599
|
+
(0, react.useEffect)(() => {
|
|
16600
|
+
if (copyState !== "copied") return;
|
|
16601
|
+
const timer = setTimeout(() => setCopyState("idle"), 2e3);
|
|
16602
|
+
return () => clearTimeout(timer);
|
|
16603
|
+
}, [copyState]);
|
|
16604
|
+
const copy = async () => {
|
|
16605
|
+
if (copyState === "copying") return;
|
|
16606
|
+
setCopyState("copying");
|
|
16607
|
+
try {
|
|
16608
|
+
if (typeof navigator.clipboard?.write !== "function" || typeof ClipboardItem === "undefined") {
|
|
16609
|
+
setCopyState("error");
|
|
16610
|
+
return;
|
|
16611
|
+
}
|
|
16612
|
+
const response = await fetch(attachmentUrl(a.id));
|
|
16613
|
+
if (!response.ok) throw new Error("Image download failed");
|
|
16614
|
+
const blob = await response.blob();
|
|
16615
|
+
await navigator.clipboard.write([new ClipboardItem({ [blob.type]: blob })]);
|
|
16616
|
+
setCopyState("copied");
|
|
16617
|
+
} catch {
|
|
16618
|
+
setCopyState("error");
|
|
16619
|
+
}
|
|
16620
|
+
};
|
|
16621
|
+
const copyLabel = t(copyState === "copied" ? "attachmentCopied" : copyState === "copying" ? "attachmentCopying" : "attachmentCopy");
|
|
16622
|
+
const actions = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
16623
|
+
className: copyState === "error" ? ChannelsPage_module_default.primaryButton : ChannelsPage_module_default.secondaryButton,
|
|
16624
|
+
href: attachmentUrl(a.id),
|
|
16625
|
+
download: a.name,
|
|
16626
|
+
target: "_blank",
|
|
16627
|
+
rel: "noreferrer",
|
|
16628
|
+
"aria-label": t("attachmentDownload"),
|
|
16629
|
+
children: t("attachmentDownload")
|
|
16630
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
16631
|
+
type: "button",
|
|
16632
|
+
className: ChannelsPage_module_default.secondaryButton,
|
|
16633
|
+
onClick: () => {
|
|
16634
|
+
copy();
|
|
16635
|
+
},
|
|
16636
|
+
disabled: copyState === "copying",
|
|
16637
|
+
"aria-label": copyLabel,
|
|
16638
|
+
"aria-live": "polite",
|
|
16639
|
+
children: copyLabel
|
|
16640
|
+
})] });
|
|
16641
|
+
const error = copyState === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
16642
|
+
className: ChannelsPage_module_default.sendError,
|
|
16643
|
+
role: "alert",
|
|
16644
|
+
children: t("attachmentCopyError")
|
|
16645
|
+
});
|
|
16646
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
16647
|
+
className: ChannelsPage_module_default.imageAttachment,
|
|
16648
|
+
"data-copy-state": copyState,
|
|
16649
|
+
children: [
|
|
16650
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
16651
|
+
ref: trigger,
|
|
16652
|
+
type: "button",
|
|
16653
|
+
className: ChannelsPage_module_default.imagePreviewTrigger,
|
|
16654
|
+
title: a.name,
|
|
16655
|
+
"aria-haspopup": "dialog",
|
|
16656
|
+
"aria-expanded": opened,
|
|
16657
|
+
onClick: () => setOpened(true),
|
|
16658
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
16659
|
+
className: ChannelsPage_module_default.attachmentImage,
|
|
16660
|
+
src: attachmentUrl(a.id),
|
|
16661
|
+
alt: a.name,
|
|
16662
|
+
loading: "lazy"
|
|
16663
|
+
})
|
|
16664
|
+
}),
|
|
16665
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
16666
|
+
className: ChannelsPage_module_default.attachmentActions,
|
|
16667
|
+
children: actions
|
|
16668
|
+
}),
|
|
16669
|
+
!opened && error,
|
|
16670
|
+
opened && (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
16671
|
+
className: ChannelsPage_module_default.dialogBackdrop,
|
|
16672
|
+
onClick: (event) => {
|
|
16673
|
+
if (event.target === event.currentTarget) setOpened(false);
|
|
16674
|
+
},
|
|
16675
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
16676
|
+
ref: dialog,
|
|
16677
|
+
className: `${ChannelsPage_module_default.dialog} ${ChannelsPage_module_default.imagePreviewDialog}`,
|
|
16678
|
+
role: "dialog",
|
|
16679
|
+
"aria-modal": "true",
|
|
16680
|
+
"aria-label": a.name,
|
|
16681
|
+
tabIndex: -1,
|
|
16682
|
+
onKeyDown: (event) => {
|
|
16683
|
+
if (event.key === "Escape") {
|
|
16684
|
+
event.preventDefault();
|
|
16685
|
+
event.stopPropagation();
|
|
16686
|
+
setOpened(false);
|
|
16687
|
+
}
|
|
16688
|
+
if (event.key === "Tab") {
|
|
16689
|
+
const controls = Array.from(event.currentTarget.querySelectorAll("button:not(:disabled), a[href]"));
|
|
16690
|
+
const first = controls[0];
|
|
16691
|
+
const last = controls[controls.length - 1];
|
|
16692
|
+
if (event.shiftKey && (document.activeElement === first || document.activeElement === dialog.current)) {
|
|
16693
|
+
event.preventDefault();
|
|
16694
|
+
last?.focus();
|
|
16695
|
+
} else if (!event.shiftKey && (document.activeElement === last || document.activeElement === dialog.current)) {
|
|
16696
|
+
event.preventDefault();
|
|
16697
|
+
first?.focus();
|
|
16698
|
+
}
|
|
16699
|
+
}
|
|
16700
|
+
},
|
|
16701
|
+
children: [
|
|
16702
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
16703
|
+
className: ChannelsPage_module_default.imagePreviewHeader,
|
|
16704
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: a.name }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
16705
|
+
ref: closeButton,
|
|
16706
|
+
type: "button",
|
|
16707
|
+
className: ChannelsPage_module_default.secondaryButton,
|
|
16708
|
+
"aria-label": t("close"),
|
|
16709
|
+
onClick: () => setOpened(false),
|
|
16710
|
+
children: "×"
|
|
16711
|
+
})]
|
|
16712
|
+
}),
|
|
16713
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
16714
|
+
className: ChannelsPage_module_default.imagePreviewImage,
|
|
16715
|
+
src: attachmentUrl(a.id),
|
|
16716
|
+
alt: a.name
|
|
16717
|
+
}),
|
|
16718
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
16719
|
+
className: ChannelsPage_module_default.imagePreviewActions,
|
|
16720
|
+
children: actions
|
|
16721
|
+
}),
|
|
16722
|
+
error
|
|
16723
|
+
]
|
|
16724
|
+
})
|
|
16725
|
+
}), document.body)
|
|
16726
|
+
]
|
|
16727
|
+
});
|
|
16728
|
+
}
|
|
16573
16729
|
/** Thumbnails for images, a download card for other files. */
|
|
16574
|
-
function AttachmentList({ attachments }) {
|
|
16730
|
+
function AttachmentList({ attachments, t }) {
|
|
16575
16731
|
if (attachments === void 0 || attachments.length === 0) return null;
|
|
16576
16732
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
16577
16733
|
className: ChannelsPage_module_default.attachments,
|
|
16578
|
-
children: attachments.map((a) => isImage(a.mime) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
16579
|
-
|
|
16580
|
-
|
|
16581
|
-
rel: "noreferrer",
|
|
16582
|
-
title: a.name,
|
|
16583
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
16584
|
-
className: ChannelsPage_module_default.attachmentImage,
|
|
16585
|
-
src: attachmentUrl(a.id),
|
|
16586
|
-
alt: a.name,
|
|
16587
|
-
loading: "lazy"
|
|
16588
|
-
})
|
|
16734
|
+
children: attachments.map((a) => isImage(a.mime) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ImageAttachment, {
|
|
16735
|
+
attachment: a,
|
|
16736
|
+
t
|
|
16589
16737
|
}, a.id) : isAudio(a.mime) ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
16590
16738
|
className: ChannelsPage_module_default.attachmentAudio,
|
|
16591
16739
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("audio", {
|
|
@@ -18744,7 +18892,10 @@ function MessageRow({ message, member, t, onConfigure, menuOpen, onAvatar, onClo
|
|
|
18744
18892
|
text: markdown,
|
|
18745
18893
|
labels
|
|
18746
18894
|
})
|
|
18747
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AttachmentList, {
|
|
18895
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AttachmentList, {
|
|
18896
|
+
attachments: message.attachments,
|
|
18897
|
+
t
|
|
18898
|
+
})] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
18748
18899
|
className: ChannelsPage_module_default.card,
|
|
18749
18900
|
"data-state": card.state,
|
|
18750
18901
|
children: [
|
|
@@ -19803,6 +19954,11 @@ const NS = "discord-channels";
|
|
|
19803
19954
|
const zh = {
|
|
19804
19955
|
copyCode: "复制代码",
|
|
19805
19956
|
copiedCode: "已复制",
|
|
19957
|
+
attachmentDownload: "下载",
|
|
19958
|
+
attachmentCopy: "复制图片",
|
|
19959
|
+
attachmentCopying: "复制中…",
|
|
19960
|
+
attachmentCopied: "已复制",
|
|
19961
|
+
attachmentCopyError: "复制失败,请下载图片后保存或复制。",
|
|
19806
19962
|
footnotes: "脚注",
|
|
19807
19963
|
panel: "频道",
|
|
19808
19964
|
channelSettings: "频道设置",
|
|
@@ -20008,6 +20164,11 @@ const en = {
|
|
|
20008
20164
|
dispatchMixed: "Mixed — choose models and members for each task",
|
|
20009
20165
|
copyCode: "Copy code",
|
|
20010
20166
|
copiedCode: "Copied",
|
|
20167
|
+
attachmentDownload: "Download",
|
|
20168
|
+
attachmentCopy: "Copy image",
|
|
20169
|
+
attachmentCopying: "Copying…",
|
|
20170
|
+
attachmentCopied: "Copied",
|
|
20171
|
+
attachmentCopyError: "Could not copy. Download the image to save or copy it.",
|
|
20011
20172
|
footnotes: "Footnotes",
|
|
20012
20173
|
taskTabLabel: "Filter tasks by status",
|
|
20013
20174
|
taskTabAll: "All",
|
package/collab/dsh-member.mjs
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
// one that was consumed is settled from the Session log. Nothing is re-sent,
|
|
8
8
|
// so a prompt is executed at most once.
|
|
9
9
|
import { randomUUID } from 'node:crypto';
|
|
10
|
-
import { readFileSync } from 'node:fs';
|
|
10
|
+
import { readFileSync, realpathSync, statSync } from 'node:fs';
|
|
11
|
+
import { basename, isAbsolute, relative, sep } from 'node:path';
|
|
11
12
|
import { exitTarget } from './team-exit.mjs';
|
|
12
13
|
import { DEFAULT_DISPATCH_MODE, MAX_TEXT } from './store.mjs';
|
|
13
14
|
import { ChannelApprovals, DEFAULT_ACTIONS } from './approvals.mjs';
|
|
@@ -62,6 +63,54 @@ import { attachmentBlocks, namedAttachmentBlocks, nativeAttachmentBlocks, undeli
|
|
|
62
63
|
|
|
63
64
|
const CONTEXT_LIMIT = 50;
|
|
64
65
|
const CLOSE_TIMEOUT_MS = 5_000;
|
|
66
|
+
const MAX_LOCAL_IMAGE_BYTES = 8 * 1024 * 1024;
|
|
67
|
+
const MAX_LOCAL_IMAGES = 10;
|
|
68
|
+
const IMAGE_MIME = { png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif', webp: 'image/webp' };
|
|
69
|
+
// Markdown targets may contain spaces; bare paths end at whitespace or punctuation.
|
|
70
|
+
const LOCAL_IMAGE = /!\[([^\]\n]*)\]\((\/[^)\n]+?\.(?:png|jpe?g|gif|webp))\)|(?<![\w/])\/[^\s<>"'`()[\]{},。;,;]+?\.(?:png|jpe?g|gif|webp)(?![\w/])/gi;
|
|
71
|
+
|
|
72
|
+
/** Only files physically inside this room's working directory may leave the machine. */
|
|
73
|
+
export function localImageReply(text, workspacePath, files, logger) {
|
|
74
|
+
if (!files || !workspacePath || !LOCAL_IMAGE.test(text)) return { text, attachments: [] };
|
|
75
|
+
LOCAL_IMAGE.lastIndex = 0;
|
|
76
|
+
let root;
|
|
77
|
+
try { root = realpathSync(workspacePath); } catch { return { text, attachments: [] }; }
|
|
78
|
+
const attachments = [];
|
|
79
|
+
const result = text.replace(LOCAL_IMAGE, (match, alt, markdownPath) => {
|
|
80
|
+
const path = markdownPath ?? match;
|
|
81
|
+
if (!isAbsolute(path)) return match;
|
|
82
|
+
let target;
|
|
83
|
+
try { target = realpathSync(path); } catch (error) {
|
|
84
|
+
logger?.warn('collab: local image skipped (%s): %s', path, error.message);
|
|
85
|
+
return match;
|
|
86
|
+
}
|
|
87
|
+
const within = relative(root, target);
|
|
88
|
+
if (within === '..' || within.startsWith(`..${sep}`) || isAbsolute(within)) return match;
|
|
89
|
+
if (attachments.length >= MAX_LOCAL_IMAGES) {
|
|
90
|
+
logger?.warn('collab: local image skipped (%s): attachment limit %d', path, MAX_LOCAL_IMAGES);
|
|
91
|
+
return match;
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
const size = statSync(target).size;
|
|
95
|
+
if (size > MAX_LOCAL_IMAGE_BYTES) {
|
|
96
|
+
logger?.warn('collab: local image skipped (%s): exceeds %d bytes', path, MAX_LOCAL_IMAGE_BYTES);
|
|
97
|
+
return match;
|
|
98
|
+
}
|
|
99
|
+
const data = readFileSync(target);
|
|
100
|
+
if (data.length > MAX_LOCAL_IMAGE_BYTES) {
|
|
101
|
+
logger?.warn('collab: local image skipped (%s): exceeds %d bytes', path, MAX_LOCAL_IMAGE_BYTES);
|
|
102
|
+
return match;
|
|
103
|
+
}
|
|
104
|
+
const mime = IMAGE_MIME[path.slice(path.lastIndexOf('.') + 1).toLowerCase()];
|
|
105
|
+
attachments.push(...files.save([{ name: basename(path), mime, data: data.toString('base64') }]));
|
|
106
|
+
return alt ?? '';
|
|
107
|
+
} catch (error) {
|
|
108
|
+
logger?.warn('collab: local image skipped (%s): %s', path, error.message);
|
|
109
|
+
return match;
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
return { text: result, attachments };
|
|
113
|
+
}
|
|
65
114
|
// Agent-to-agent relay cap: after this many agent messages in a row without a
|
|
66
115
|
// human message, agent mentions stop starting DSH turns (prevents loops with bots).
|
|
67
116
|
export const DEFAULT_MAX_RELAY = 6;
|
|
@@ -659,8 +708,9 @@ export class DshMembers {
|
|
|
659
708
|
if (!text) { this.fail(job.id, `${member.name} 这一轮没有给出回复(${member.cli} 没有输出文字)`); return; }
|
|
660
709
|
latest = '';
|
|
661
710
|
const reply = this.returnToLead(job, member, text);
|
|
662
|
-
const
|
|
663
|
-
const
|
|
711
|
+
const converted = localImageReply(reply, cwd, this.rt.files, this.rt.logger);
|
|
712
|
+
const committed = draft === undefined ? undefined : this.hub.commit(draft.id, 'role', member.id, converted.text, undefined, converted.attachments);
|
|
713
|
+
const message = committed ?? this.hub.post(job.room_id, { authorKind: 'role', author: member.id, name: member.name, clientKey, text: converted.text, attachments: converted.attachments }).message;
|
|
664
714
|
done = true;
|
|
665
715
|
this.store.finishJob(job.id, 'done', message.id);
|
|
666
716
|
} catch (error) {
|
|
@@ -745,9 +795,12 @@ export class DshMembers {
|
|
|
745
795
|
}
|
|
746
796
|
const kind = reason?.kind;
|
|
747
797
|
const complete = kind === undefined || kind === 'completed' || kind === 'max-tokens';
|
|
798
|
+
const reply = this.returnToLead(job, member, complete ? text : `${text}\n\n(这一轮没有正常结束:${describeReason(reason)})`);
|
|
799
|
+
const cwd = this.store.workspacePath(job.room_id, this.rt.workspacePath);
|
|
800
|
+
const converted = localImageReply(reply, cwd, this.rt.files, this.rt.logger);
|
|
748
801
|
const { message } = this.hub.post(job.room_id, {
|
|
749
802
|
authorKind: 'role', author: member.id, name: member.name, clientKey: `dsh-job-${job.id}`,
|
|
750
|
-
text:
|
|
803
|
+
text: converted.text, attachments: converted.attachments,
|
|
751
804
|
});
|
|
752
805
|
this.store.finishJob(job.id, 'done', message.id);
|
|
753
806
|
return complete;
|
package/collab/handoff-rules.mjs
CHANGED
|
@@ -63,6 +63,8 @@ function run(c, lines, lead, key = 'msg') {
|
|
|
63
63
|
case 'kinds': {
|
|
64
64
|
const found = captures(sections(lines, c.section, lead), new RegExp(c.line_re), lead);
|
|
65
65
|
const got = new Set(found);
|
|
66
|
+
const mx = c.max, n = mx ? found.filter(k => k === mx.kind).length : 0;
|
|
67
|
+
if (mx && n > mx.n) return mx.msg.replace('{n}', String(n));
|
|
66
68
|
const miss = c.need.filter(k => !got.has(k));
|
|
67
69
|
return miss.length ? missing(c, miss) : null;
|
|
68
70
|
}
|
package/collab/hub.mjs
CHANGED
|
@@ -162,10 +162,10 @@ export class Hub {
|
|
|
162
162
|
return r;
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
commit(id, authorKind, author, text, fromTurn) {
|
|
165
|
+
commit(id, authorKind, author, text, fromTurn, attachments) {
|
|
166
166
|
const after = [];
|
|
167
167
|
const r = this.store.transaction(() => {
|
|
168
|
-
const c = this.store.commit(id, authorKind, author, text, fromTurn);
|
|
168
|
+
const c = this.store.commit(id, authorKind, author, text, fromTurn, attachments);
|
|
169
169
|
if (c?.first) for (const plan of this.planners) { const next = plan(c.message); if (next) after.push(next); }
|
|
170
170
|
return c;
|
|
171
171
|
});
|
package/collab/store.mjs
CHANGED
|
@@ -92,7 +92,7 @@ export class Store {
|
|
|
92
92
|
byKey: this.db.prepare('SELECT * FROM messages WHERE client_key = ?'),
|
|
93
93
|
byId: this.db.prepare('SELECT * FROM messages WHERE id = ? AND deleted = 0'),
|
|
94
94
|
edit: this.db.prepare('UPDATE messages SET text = ?, card = ?, changed_at = ? WHERE id = ? AND author_kind = ? AND author = ? AND deleted = 0 RETURNING *'),
|
|
95
|
-
commit: this.db.prepare('UPDATE messages SET text = ?, from_turn = ?, draft = 0, changed_at = ? WHERE id = ? AND author_kind = ? AND author = ? AND deleted = 0 AND card IS NULL AND draft = 1 RETURNING *'),
|
|
95
|
+
commit: this.db.prepare('UPDATE messages SET text = ?, from_turn = ?, attachments = ?, draft = 0, changed_at = ? WHERE id = ? AND author_kind = ? AND author = ? AND deleted = 0 AND card IS NULL AND draft = 1 RETURNING *'),
|
|
96
96
|
editUser: this.db.prepare(`UPDATE messages SET text = ?, edited_at = ? WHERE id = ? AND author_kind = 'user' AND author = ?
|
|
97
97
|
AND deleted = 0 AND draft = 0 AND card IS NULL RETURNING *`),
|
|
98
98
|
del: this.db.prepare('UPDATE messages SET deleted = 1 WHERE id = ? AND author_kind = ? AND author = ? AND deleted = 0 RETURNING *'),
|
|
@@ -664,9 +664,9 @@ export class Store {
|
|
|
664
664
|
* into a reply (the caller fans out once). A row already committed gets the
|
|
665
665
|
* text and first = false. undefined if it is not that author's live text row.
|
|
666
666
|
*/
|
|
667
|
-
commit(id, authorKind, author, text, fromTurn, now = Date.now() / 1000) {
|
|
668
|
-
if (!text.trim() || text.length > MAX_TEXT) return undefined;
|
|
669
|
-
const row = this.q.commit.get(text, fromTurn == null ? null : (fromTurn ? 1 : 0), now, id, authorKind, author);
|
|
667
|
+
commit(id, authorKind, author, text, fromTurn, attachments, now = Date.now() / 1000) {
|
|
668
|
+
if ((!text.trim() && !attachments?.length) || text.length > MAX_TEXT) return undefined;
|
|
669
|
+
const row = this.q.commit.get(text, fromTurn == null ? null : (fromTurn ? 1 : 0), attachments?.length ? JSON.stringify(attachments) : null, now, id, authorKind, author);
|
|
670
670
|
if (row !== undefined) return { message: row, first: true };
|
|
671
671
|
const old = this.message(id);
|
|
672
672
|
if (old === undefined || old.card != null) return undefined;
|
package/collab/team-exit.mjs
CHANGED
|
@@ -1,23 +1,30 @@
|
|
|
1
1
|
// Durable workspace questions; system forwards never enter agent routing.
|
|
2
2
|
import { randomBytes } from 'node:crypto';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
3
4
|
import { withoutLeadingMentions } from './mentions.mjs';
|
|
4
5
|
|
|
5
6
|
export function exitTarget(msg) {
|
|
6
7
|
return /^team-answer:([^:]+):/.exec(msg.client_key ?? '')?.[1];
|
|
7
8
|
}
|
|
8
9
|
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
10
|
+
// The workspace [ask]/[report] rules (first-line mark and #id format, the [ask] self-check, bounce
|
|
11
|
+
// texts) are one file, team-exit-rules.json next to this one; the Go bridge reads the same file
|
|
12
|
+
// (bridge/core/team_exit_rules.go, from its dsh_bundle copy) and both sides run the shared cases in
|
|
13
|
+
// test/team-exit-cases.json. No formats or texts live here. Regexes are written for both RE2 and JS.
|
|
14
|
+
export const RULES = JSON.parse(readFileSync(new URL('./team-exit-rules.json', import.meta.url), 'utf8'));
|
|
15
|
+
const HEADLINE = new RegExp(RULES.headline_re);
|
|
16
|
+
const SELF_CHECK = new RegExp(RULES.ask.self_check_re);
|
|
17
|
+
const CATEGORY = new RegExp(RULES.ask.category_re);
|
|
18
|
+
const EXEMPT = new RegExp(RULES.ask.exempt_re);
|
|
19
|
+
const NO_CHECK = new RegExp(RULES.ask.no_check_re);
|
|
20
|
+
const JARGON_LINE = new RegExp(RULES.jargon.line_re, 'g');
|
|
21
|
+
const JARGON_TABLE = new RegExp(RULES.jargon.table_re);
|
|
22
|
+
const BACKGROUND = new RegExp(RULES.summary.background_re);
|
|
23
|
+
const TODO = new RegExp(RULES.summary.todo_re);
|
|
24
|
+
const DETAIL = new RegExp(RULES.summary.detail_re);
|
|
25
|
+
const ADDED = new RegExp(RULES.summary.added_re);
|
|
19
26
|
// Consecutive bounces that still wake the asking member; later ones are only posted.
|
|
20
|
-
const BOUNCE_WAKE_LIMIT =
|
|
27
|
+
const BOUNCE_WAKE_LIMIT = RULES.bounce.wake_limit;
|
|
21
28
|
|
|
22
29
|
/** What an [ask] body lacks; [] means it may go to the user. */
|
|
23
30
|
export function askEvidenceProblems(body) {
|
|
@@ -32,42 +39,119 @@ export function askEvidenceProblems(body) {
|
|
|
32
39
|
check = rest.trim();
|
|
33
40
|
}
|
|
34
41
|
const problems = [];
|
|
35
|
-
if (!check) problems.push(
|
|
36
|
-
if (!category) problems.push(
|
|
37
|
-
if (check && category && NO_CHECK.test(check) && !EXEMPT.test(category)) problems.push(
|
|
42
|
+
if (!check) problems.push(RULES.ask.missing_self_check);
|
|
43
|
+
if (!category) problems.push(RULES.ask.missing_category);
|
|
44
|
+
if (check && category && NO_CHECK.test(check) && !EXEMPT.test(category)) problems.push(RULES.ask.no_check_not_exempt);
|
|
38
45
|
return problems;
|
|
39
46
|
}
|
|
40
47
|
|
|
41
|
-
|
|
42
|
-
return exitBounceText('ask', problems);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Headline (user 2026-10-28 「做」): the first line is `[report] #<task id> <one or two sentences
|
|
46
|
-
// for the user>`; that sentence is what the chat channel shows first. Same rule in the Go bridge:
|
|
47
|
-
// bridge/core/team_exit_headline.go exitHeadline.
|
|
48
|
-
const HEADLINE = /^#(\d+)[\s\u3000::,,、.。]*(.*)$/;
|
|
49
|
-
export const EXIT_MISSING_ID = '第一行缺任务板编号(#编号)';
|
|
50
|
-
export const EXIT_MISSING_LINE = '第一行缺给用户的一两句话';
|
|
51
|
-
export const EXIT_UNKNOWN_ID = '任务板上没有这个编号';
|
|
52
|
-
|
|
53
|
-
/** { id, line, problems } of a marked body's first line. */
|
|
48
|
+
/** { id, line, problems } of a marked body's first line; line is the part for the user (Go: exitHeadline). */
|
|
54
49
|
export function exitHeadline(body) {
|
|
55
50
|
const first = String(body ?? '').split('\n')[0].trim();
|
|
56
51
|
const m = HEADLINE.exec(first);
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
52
|
+
const { missing_id: noId, missing_line: noLine } = RULES.headline;
|
|
53
|
+
if (!m) return { id: 0, line: '', problems: first === '' || first.startsWith('#') ? [noId, noLine] : [noId] };
|
|
54
|
+
const { summary: line } = exitSplit(body);
|
|
55
|
+
return { id: Number(m[1]), line, problems: line ? [] : [noLine] };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* { summary, detail } of a marked body (Go: exitSplit): after the first line's #id, up to and
|
|
60
|
+
* including the 「我加的:」 line (else the 「要你做的:」 line) is posted for the user; the lines after it go under that post.
|
|
61
|
+
*/
|
|
62
|
+
export function exitSplit(body) {
|
|
63
|
+
let lines = String(body ?? '').replaceAll('\r\n', '\n').split('\n');
|
|
64
|
+
const m = HEADLINE.exec(lines[0].trim());
|
|
65
|
+
if (m) lines[0] = m[2];
|
|
66
|
+
while (lines.length && !lines[0].trim()) lines.shift();
|
|
67
|
+
let end = lines.findIndex(l => TODO.test(l));
|
|
68
|
+
if (end >= 0) {
|
|
69
|
+
const added = lines.findIndex((l, i) => i > end && ADDED.test(l));
|
|
70
|
+
if (added >= 0) end = added;
|
|
71
|
+
}
|
|
72
|
+
if (end < 0) {
|
|
73
|
+
const blank = lines.findIndex(l => !l.trim());
|
|
74
|
+
end = blank < 0 ? lines.length - 1 : blank - 1;
|
|
75
|
+
}
|
|
76
|
+
const summary = lines.slice(0, end + 1).join('\n').trim();
|
|
77
|
+
let rest = lines.slice(end + 1);
|
|
78
|
+
while (rest.length && !rest[0].trim()) rest.shift();
|
|
79
|
+
if (rest.length && DETAIL.test(rest[0].trim())) rest = rest.slice(1);
|
|
80
|
+
return { summary, detail: rest.join('\n').trim() };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** What the part for the user lacks: background, what was done, what the user is to do, what the agent added (Go: summaryProblems). */
|
|
84
|
+
export function summaryProblems(summary) {
|
|
85
|
+
const lines = String(summary).split('\n');
|
|
86
|
+
const problems = [];
|
|
87
|
+
if (!BACKGROUND.test(lines[0].trim())) problems.push(RULES.summary.missing_background);
|
|
88
|
+
const todo = lines.findIndex(l => TODO.test(l));
|
|
89
|
+
let done = false;
|
|
90
|
+
for (let i = 1; i < lines.length && !done; i++) {
|
|
91
|
+
let l = lines[i];
|
|
92
|
+
if (i === todo) l = l.slice(0, TODO.exec(l).index);
|
|
93
|
+
else if (todo >= 0 && i > todo) break;
|
|
94
|
+
done = l.trim() !== '';
|
|
95
|
+
}
|
|
96
|
+
if (!done) problems.push(RULES.summary.missing_done);
|
|
97
|
+
if (todo < 0) problems.push(RULES.summary.missing_todo);
|
|
98
|
+
if (!lines.some(l => ADDED.test(l))) problems.push(RULES.summary.missing_added);
|
|
99
|
+
return problems;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** The part for the user names numbers, workspaces, branches, paths or identifiers, or has a table (Go: jargonProblems). */
|
|
103
|
+
export function jargonProblems(line) {
|
|
104
|
+
const problems = [];
|
|
105
|
+
const hits = [...String(line).matchAll(JARGON_LINE)].map(m => m[0].replace(/^[^~/`#!_\-0-9A-Za-z#!工]+/u, ''));
|
|
106
|
+
if (hits.length) problems.push(RULES.jargon.line.replaceAll('{hits}', hits.join('、')));
|
|
107
|
+
if (JARGON_TABLE.test(line)) problems.push(RULES.jargon.table);
|
|
108
|
+
return problems;
|
|
60
109
|
}
|
|
61
110
|
|
|
62
111
|
/** Why a workspace [ask]/[report] stayed in the workspace and how to resend it (Go: exitBounceText). */
|
|
63
112
|
export function exitBounceText(kind, problems) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return `${t}。`;
|
|
113
|
+
const vars = { kind, problems: problems.join(RULES.bounce.sep), extra: RULES.bounce.extra[kind] ?? '' };
|
|
114
|
+
return RULES.bounce.text.replace(/\{(kind|problems|extra)\}/g, (_, k) => vars[k]);
|
|
67
115
|
}
|
|
68
116
|
|
|
69
|
-
|
|
70
|
-
|
|
117
|
+
/**
|
|
118
|
+
* How a marked body is judged (Go: judgeExit): { id, line, problems, legacy }. known(id): the id is a
|
|
119
|
+
* task; bounces: replies in a row already sent back. problems [] means it goes to the chat channel;
|
|
120
|
+
* legacy: the format is still wrong but bounces stopped waking the member (the bridge's chat lead
|
|
121
|
+
* rewrites it; DSH posts it without the headline).
|
|
122
|
+
*/
|
|
123
|
+
export function judgeExit(kind, body, { known, bounces = 0 }) {
|
|
124
|
+
const problems = kind === 'ask' ? askEvidenceProblems(body) : [];
|
|
125
|
+
let { id, line, problems: hp } = exitHeadline(body);
|
|
126
|
+
if (!hp.length && !known(id)) hp = [RULES.headline.unknown_id.replaceAll('{id}', String(id))];
|
|
127
|
+
else if (!hp.length) hp = [...summaryProblems(line), ...jargonProblems(line)];
|
|
128
|
+
// Format goes back only while bounces still wake the member; after that the chat lead rewrites it.
|
|
129
|
+
if (hp.length && !problems.length && bounces >= BOUNCE_WAKE_LIMIT) return { id, line, problems, legacy: true };
|
|
130
|
+
return { id, line, problems: [...problems, ...hp], legacy: false };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* The task a workspace reply is about (Go: exitTaskPick): the one its first line names when that one
|
|
135
|
+
* is here (bound to this workspace), else the one bound here last; 0 when none is here.
|
|
136
|
+
* items: [{ id, here, bound }], bound compared as given.
|
|
137
|
+
*/
|
|
138
|
+
export function exitTaskPick(named, items) {
|
|
139
|
+
let pick = 0, last;
|
|
140
|
+
for (const it of items) {
|
|
141
|
+
if (!it.here) continue;
|
|
142
|
+
if (named > 0 && it.id === named) return named;
|
|
143
|
+
if (pick === 0 || it.bound > last) { pick = it.id; last = it.bound; }
|
|
144
|
+
}
|
|
145
|
+
return pick;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const escapeRe = s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
149
|
+
const MARKS = RULES.kinds.map(k => escapeRe(RULES.mark.replace('{kind}', k))).join('|');
|
|
150
|
+
const FIRST_MARK = new RegExp(`^(${MARKS})([\\s\\S]*)$`, 'i');
|
|
151
|
+
// A mark with nothing after it is no mark (Go: parseExitKind).
|
|
152
|
+
const firstMark = t => { const m = FIRST_MARK.exec(t); return m?.[2].trim() ? m : null; };
|
|
153
|
+
const markKind = tag => RULES.kinds.find(k => RULES.mark.replace('{kind}', k).toLowerCase() === tag.toLowerCase());
|
|
154
|
+
const LINE_MARK = new RegExp(`^[ \\t]*(${MARKS})`, 'i');
|
|
71
155
|
|
|
72
156
|
/**
|
|
73
157
|
* The [ask] / [report] mark of a role reply: { kind, body } or undefined. Marked on the first
|
|
@@ -80,15 +164,15 @@ const LINE_MARK = /^[ \t]*\[(ask|report)\]/i;
|
|
|
80
164
|
*/
|
|
81
165
|
export function exitMark(text, { lastLine = false, team = [] } = {}) {
|
|
82
166
|
const t = withoutLeadingMentions(String(text ?? ''), team).trim();
|
|
83
|
-
let m =
|
|
167
|
+
let m = firstMark(t);
|
|
84
168
|
if (m === null && lastLine) {
|
|
85
169
|
const lines = t.split('\n');
|
|
86
170
|
for (let i = lines.length - 1; i > 0 && m === null; i--) {
|
|
87
171
|
const line = withoutLeadingMentions(lines[i], team);
|
|
88
|
-
if (LINE_MARK.test(line)) m =
|
|
172
|
+
if (LINE_MARK.test(line)) m = firstMark([line.trimStart(), ...lines.slice(i + 1)].join('\n'));
|
|
89
173
|
}
|
|
90
174
|
}
|
|
91
|
-
return m === null ? undefined : { kind: m[1]
|
|
175
|
+
return m === null ? undefined : { kind: markKind(m[1]), body: m[2].trim() };
|
|
92
176
|
}
|
|
93
177
|
|
|
94
178
|
export class TeamExit {
|
|
@@ -151,16 +235,11 @@ export class TeamExit {
|
|
|
151
235
|
const ask = mark?.kind === 'ask';
|
|
152
236
|
let headline;
|
|
153
237
|
if (mark) {
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
if (
|
|
157
|
-
headline.task = this.hub.tasks?.task?.(headline.id);
|
|
158
|
-
if (!headline.task) headline.problems.push(`${EXIT_UNKNOWN_ID}(#${headline.id})`);
|
|
159
|
-
}
|
|
238
|
+
const bounces = this.bounces.get(`${room.id}:${msg.author}`) ?? 0;
|
|
239
|
+
const v = judgeExit(mark.kind, mark.body, { known: id => Boolean(this.hub.tasks?.task?.(id)), bounces });
|
|
240
|
+
if (v.problems.length) return this.bounce(room, msg, v.problems, mark.kind);
|
|
160
241
|
// Once bounces stop waking the member, a reply still without a headline goes out as before.
|
|
161
|
-
if (
|
|
162
|
-
else problems.push(...headline.problems);
|
|
163
|
-
if (problems.length) return this.bounce(room, msg, problems, mark.kind);
|
|
242
|
+
if (!v.legacy) headline = { ...v, task: this.hub.tasks?.task?.(v.id) };
|
|
164
243
|
}
|
|
165
244
|
// Any reply without a bounced [ask] ends the run of bounces.
|
|
166
245
|
this.bounces.delete(`${room.id}:${msg.author}`);
|
|
@@ -178,12 +257,12 @@ export class TeamExit {
|
|
|
178
257
|
// #597 G35: a DSH member's [ask] keeps its task in work and watched (and its workspace, so the
|
|
179
258
|
// answer reaches the same session); a bridge bot's [ask] parks it as before (#280).
|
|
180
259
|
if (tasks && !this.markOpts(msg).lastLine) {
|
|
181
|
-
const
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const selected =
|
|
186
|
-
if (selected
|
|
260
|
+
const busy = tasks.q.allBusy.all('assigned', 'accepted', 'asked', 'rework');
|
|
261
|
+
const named = headline?.task;
|
|
262
|
+
const items = [...(named ? [named] : []), ...busy]
|
|
263
|
+
.map(t => ({ id: t.id, here: t.room_id === room.id, bound: t.updated_at }));
|
|
264
|
+
const selected = exitTaskPick(named?.id ?? 0, items);
|
|
265
|
+
if (selected) tasks.pauseTask(selected);
|
|
187
266
|
}
|
|
188
267
|
}
|
|
189
268
|
}
|
|
@@ -206,16 +285,24 @@ export class TeamExit {
|
|
|
206
285
|
*/
|
|
207
286
|
forward(room, msg, body, ask, headline) {
|
|
208
287
|
let text = `📬 来自 ${room.name} · ${msg.name}${ask ? ` · ${ask.id}` : ' · 阶段结果'}\n${body}`;
|
|
288
|
+
let detail = '';
|
|
209
289
|
if (headline) {
|
|
210
|
-
// The
|
|
290
|
+
// The three parts for the user, named by its task; DSH has no threads, so the details are a
|
|
291
|
+
// second post marked as such (Go: postWithDetail).
|
|
211
292
|
const name = headline.task?.title ?? '';
|
|
212
|
-
|
|
213
|
-
text =
|
|
214
|
-
|
|
293
|
+
detail = exitSplit(body).detail;
|
|
294
|
+
text = `📬「${name}」${ask ? `要你定\n${headline.line}\n(直接回复这条就能答复,编号 ${ask.id})` : `\n${headline.line}`}`;
|
|
295
|
+
if (detail) detail = `📬 细节「${name}」(来自 ${room.name} · ${msg.name},想看再展开,不用回)\n${detail}`;
|
|
215
296
|
}
|
|
216
|
-
|
|
297
|
+
const { message } = this.hub.post(room.parent_id, {
|
|
217
298
|
authorKind: 'system', author: 'system', name: 'system', clientKey: `team-exit:${msg.id}`, text,
|
|
218
|
-
})
|
|
299
|
+
});
|
|
300
|
+
if (detail) {
|
|
301
|
+
this.hub.post(room.parent_id, {
|
|
302
|
+
authorKind: 'system', author: 'system', name: 'system', clientKey: `team-exit-detail:${msg.id}`, text: detail,
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
return message;
|
|
219
306
|
}
|
|
220
307
|
|
|
221
308
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bothouse-dsh",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.43",
|
|
4
4
|
"description": "BotHouse channels for DeepSeek Harness: Discord-style rooms where DSH models and local CLIs (Claude Code, Codex, Gemini) are members",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./collab/plugin.mjs",
|