dsh-plugin-t-expert 0.3.28 → 0.3.40

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/lib/client.js CHANGED
@@ -19874,11 +19874,14 @@ var enabledStateSchema = external_exports.object({
19874
19874
  });
19875
19875
  var uiPrefsSchema = external_exports.object({
19876
19876
  showTaskBadge: external_exports.boolean(),
19877
- showScheduleButton: external_exports.boolean()
19877
+ showScheduleButton: external_exports.boolean(),
19878
+ // 窗口右上角「活跃指示」是否显示。宿主**总是**返回布尔(缺省按 true 算),所以这里必填。
19879
+ showActivePulse: external_exports.boolean()
19878
19880
  });
19879
19881
  var uiPrefsPatchSchema = external_exports.object({
19880
19882
  showTaskBadge: external_exports.boolean().optional(),
19881
- showScheduleButton: external_exports.boolean().optional()
19883
+ showScheduleButton: external_exports.boolean().optional(),
19884
+ showActivePulse: external_exports.boolean().optional()
19882
19885
  });
19883
19886
  var promptSchema = external_exports.object({ prompt: external_exports.string() });
19884
19887
  var squadMemberSchema = external_exports.object({
@@ -20015,8 +20018,9 @@ var scheduleWorkspaceSchema = external_exports.object({
20015
20018
 
20016
20019
  // src/client.jsx
20017
20020
  var NS = "t-team";
20018
- var CLIENT_BUILD_VERSION = "0.3.28";
20019
- var uiPrefs = { showTaskBadge: true, showScheduleButton: true };
20021
+ var CLIENT_BUILD_VERSION = "0.3.40";
20022
+ var uiPrefs = { showTaskBadge: true, showScheduleButton: true, showActivePulse: true };
20023
+ var UI_PREF_KEYS = Object.keys(uiPrefs);
20020
20024
  var uiPrefsListeners = /* @__PURE__ */ new Set();
20021
20025
  function setUiPrefs(next) {
20022
20026
  let changed = false;
@@ -20052,6 +20056,9 @@ function useShowTaskBadge() {
20052
20056
  function useShowScheduleButton() {
20053
20057
  return useUiPref("showScheduleButton", true);
20054
20058
  }
20059
+ function useShowActivePulse() {
20060
+ return useUiPref("showActivePulse", true);
20061
+ }
20055
20062
  var PLUGIN_ID = "dsh-plugin-t-expert";
20056
20063
  var DIVISION_FALLBACK = "specialized";
20057
20064
  var NEW_DIVISION = "__new";
@@ -20060,6 +20067,15 @@ var zh = {
20060
20067
  "settings.showTaskBadge": "\u663E\u793A\u4EFB\u52A1\u5FBD\u7AE0",
20061
20068
  "settings.showTaskBadgeHint": "\u5173\u95ED\u540E\u4E0D\u518D\u81EA\u52A8\u51FA\u73B0\u53F3\u4E0A\u89D2\u7684\u4EFB\u52A1\u5FBD\u7AE0\uFF08\u8F93\u5165\u533A\u6D6E\u5C42\u91CC\u7684\u300C\u4EFB\u52A1\u300D\u9875\u4ECD\u7136\u53EF\u7528\uFF09",
20062
20069
  "settings.showTaskBadgeUnreadable": "\u8BFB\u53D6\u8BE5\u8BBE\u7F6E\u5931\u8D25",
20070
+ "settings.uiPrefsWriteFailed": "\u4FDD\u5B58\u8BE5\u8BBE\u7F6E\u5931\u8D25",
20071
+ "settings.showActivePulse": "\u663E\u793A\u6D3B\u8DC3\u6307\u793A",
20072
+ "settings.showActivePulseHint": "\u4F1A\u8BDD\u6807\u9898\u680F\u91CC\u90A3\u679A\u5FC3\u7535\u56FE\u56FE\u6807\uFF1A\u6709\u4F1A\u8BDD\u6B63\u5728\u8DD1\u3001\u6216\u6709\u4E8B\u7B49\u4F60\u67E5\u770B\u65F6\u663E\u793A\u5F69\u8272\uFF0C\u5168\u90E8\u7A7A\u95F2\u4E14\u65E0\u5F85\u770B\u65F6\u663E\u793A\u7070\u8272\u3002\u70B9\u5B83\u53EF\u4EE5\u5217\u51FA\u9700\u8981\u6CE8\u610F\u7684\u4F1A\u8BDD\uFF0C\u70B9\u5176\u4E2D\u4E00\u6761\u5C31\u76F4\u63A5\u63A5\u4E0A\u90A3\u4E2A\u4F1A\u8BDD",
20073
+ "pulse.on": "\u6709\u4F1A\u8BDD\u6B63\u5728\u6D3B\u52A8\uFF0C\u6216\u6709\u4E8B\u7B49\u4F60\u67E5\u770B",
20074
+ "pulse.off": "\u5168\u90E8\u7A7A\u95F2\uFF0C\u4E14\u6CA1\u6709\u5F85\u4F60\u67E5\u770B\u7684",
20075
+ "pulse.title": "\u9700\u8981\u6CE8\u610F\u7684\u4F1A\u8BDD",
20076
+ "pulse.empty": "\u73B0\u5728\u6CA1\u6709\u9700\u8981\u6CE8\u610F\u7684\u4F1A\u8BDD",
20077
+ "pulse.running": "\u8FD0\u884C\u4E2D",
20078
+ "pulse.pending": "\u5F85\u67E5\u770B",
20063
20079
  "settings.loading": "\u6B63\u5728\u8BFB\u53D6\u540D\u518C\u2026",
20064
20080
  "settings.retry": "\u91CD\u8BD5",
20065
20081
  "settings.search": "\u641C\u7D22\u4E13\u5BB6\u540D\u6216\u7B80\u4ECB",
@@ -20330,7 +20346,16 @@ var en = {
20330
20346
  "settings.title": "T Expert",
20331
20347
  "settings.showTaskBadge": "Show task badge",
20332
20348
  "settings.showTaskBadgeHint": "When off, the top-right task badge no longer appears (the Tasks tab in the input floater still works)",
20349
+ "settings.showActivePulse": "Show activity indicator",
20350
+ "settings.showActivePulseHint": "The ECG icon in the session header: coloured when a session is running or something awaits your review, grey when everything is idle. Click it to list the sessions that need attention and jump straight into one",
20351
+ "pulse.on": "A session is running, or something awaits your review",
20352
+ "pulse.off": "Everything is idle and nothing awaits you",
20353
+ "pulse.title": "Sessions needing attention",
20354
+ "pulse.empty": "Nothing needs your attention right now",
20355
+ "pulse.running": "Running",
20356
+ "pulse.pending": "Unread",
20333
20357
  "settings.showTaskBadgeUnreadable": "Failed to read this setting",
20358
+ "settings.uiPrefsWriteFailed": "Failed to save this setting",
20334
20359
  "settings.loading": "Loading roster\u2026",
20335
20360
  "settings.retry": "Retry",
20336
20361
  "settings.search": "Search expert name or description",
@@ -20664,6 +20689,28 @@ var CSS = `
20664
20689
  .t-team-badge{font-size:11px;border-radius:999px;padding:1px 8px;background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-secondary)}
20665
20690
  .t-team-badge[data-tone="warn"]{color:var(--dsw-alias-state-warn-primary)}
20666
20691
  .t-team-badge[data-tone="error"]{color:var(--dsw-alias-state-error-primary)}
20692
+ /* \u6D3B\u8DC3\u6307\u793A\uFF1A\u4F1A\u8BDD\u6807\u9898\u680F\u91CC\u7684\u4E00\u679A\u5FC3\u7535\u56FE\u56FE\u6807 \u2014\u2014 \u7D27\u8D34 agent preset \u80F6\u56CA\u5DE6\u4FA7\uFF08order \u6BD4\u5B83\u5C0F\uFF09\u3002
20693
+ \u5F69\u8272 = \u6709\u6D3B\u52A8\u6216\u6709\u5F85\u770B\uFF0C\u7070 = \u5168\u7A7A\u95F2\u3002pointer-events:none\uFF1A\u5B83\u53EA\u662F\u6307\u793A\uFF0C\u4E0D\u8BE5\u6321\u4F4F\u70B9\u51FB\u3002 */
20694
+ .t-team-pulse{display:inline-flex;align-items:center;justify-content:center;flex:none;width:24px;height:24px;margin-right:2px;padding:0;border:0;font:inherit;border-radius:7px;cursor:pointer;color:var(--dsw-alias-state-success-primary);background:color-mix(in srgb,var(--dsw-alias-state-success-primary) 14%,transparent)}
20695
+ .t-team-pulse[data-on="false"]{color:var(--dsw-alias-label-caption);background:color-mix(in srgb,var(--dsw-alias-label-caption) 12%,transparent)}
20696
+ .t-team-pulse:hover{background:color-mix(in srgb,var(--dsw-alias-label-primary) 12%,transparent)}
20697
+ /* \u70B9\u5FBD\u7AE0\u5F39\u51FA\u7684\u300C\u9700\u8981\u6CE8\u610F\u7684\u4F1A\u8BDD\u300D\u6E05\u5355\uFF1A\u8D34\u5FBD\u7AE0\u53F3\u7F18\u3001\u56FE\u6807\u4E0B\u65B9\u3002 */
20698
+ .t-team-pulse-pop{position:fixed;z-index:70;width:300px;max-height:min(420px,60vh);overflow:auto;box-sizing:border-box;display:flex;flex-direction:column;gap:2px;padding:8px;border-radius:12px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-specific-menu,var(--dsw-alias-bg-layer-3));box-shadow:var(--dsw-shadow-lv3);color:var(--dsw-alias-label-primary)}
20699
+ .t-team-pulse-pop-head{font-size:12px;font-weight:600;color:var(--dsw-alias-label-secondary);padding:4px 6px 6px}
20700
+ .t-team-pulse-pop-empty{font-size:12px;color:var(--dsw-alias-label-caption);padding:8px 6px}
20701
+ .t-team-pulse-pop-row{display:flex;align-items:center;gap:8px;width:100%;box-sizing:border-box;border:0;background:transparent;color:inherit;font:inherit;font-size:13px;text-align:left;padding:7px 8px;border-radius:8px;cursor:pointer}
20702
+ .t-team-pulse-pop-row:hover{background:var(--dsw-alias-interactive-bg-hover)}
20703
+ .t-team-pulse-pop-dot{flex:none;width:6px;height:6px;border-radius:50%;background:var(--dsw-alias-state-success-primary)}
20704
+ .t-team-pulse-pop-dot[data-state="pending"]{background:var(--dsw-alias-state-warn-primary)}
20705
+ .t-team-pulse-pop-title{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
20706
+ .t-team-pulse-pop-tag{flex:none;font-size:11px;color:var(--dsw-alias-label-caption)}
20707
+ /* \u6D3B\u52A8\u6001\u52A8\u6548\uFF1A\u4E00\u6BB5\u9AD8\u4EAE\u6CBF\u5FC3\u7535\u56FE\u8DEF\u5F84\u6EDA\u52A8\uFF08\u8D70\u7EB8\uFF0F\u76D1\u62A4\u4EEA\u626B\u8FC7\u90A3\u4E00\u4E0B\uFF09\uFF0C\u53EA\u5BF9\u6709\u6D3B\u52A8\u6216\u6709\u5F85\u770B\u65F6\u751F\u6548\uFF1B
20708
+ pathLength=100 \u8BA9\u8FD9\u91CC\u7684 100 \u5C31\u662F"\u6574\u6761\u8DEF\u5F84"\uFF0C\u4E0D\u5FC5\u77E5\u9053\u771F\u5B9E\u957F\u5EA6\u3002\u7A7A\u95F2\uFF08\u7070\uFF09\u65F6\u662F\u9759\u6B62\u5B9E\u7EBF\u3002
20709
+ \u53C2\u6570\uFF08\u7528\u6237 2026-09-15 \u8C03\u8FC7\u4E24\u8F6E\uFF09\uFF1A\u9AD8\u4EAE\u7EA6\u5360\u4E00\u534A\u8DEF\u5F84\u30011.2s \u8DD1\u5B8C\u4E00\u5708 \u2014\u2014 \u60F3\u518D\u8C03\u5C31\u6539\u8FD9\u4E24\u4E2A\u6570\u3002
20710
+ \u6CE8\u610F\u522B\u628A\u9AD8\u4EAE\u8C03\u5230\u63A5\u8FD1 100\uFF1A\u6697\u6BB5\u592A\u77ED\u5C31\u770B\u4E0D\u51FA"\u5728\u6EDA\u52A8"\u4E86\u3002 */
20711
+ @keyframes t-team-pulse-scan{from{stroke-dashoffset:100}to{stroke-dashoffset:0}}
20712
+ .t-team-pulse[data-on="true"] path{stroke-dasharray:48 52;animation:t-team-pulse-scan 1.2s linear infinite}
20713
+ @media (prefers-reduced-motion:reduce){.t-team-pulse[data-on="true"] path{animation:none;stroke-dasharray:none}}
20667
20714
  .t-team-team-meta{font-size:12px;color:var(--dsw-alias-label-caption);display:flex;gap:10px;flex-wrap:wrap}
20668
20715
  .t-team-note{font-size:12px;color:var(--dsw-alias-label-caption)}
20669
20716
  .t-team-ok{font-size:12px;color:var(--dsw-alias-state-success-primary)}
@@ -21872,6 +21919,171 @@ function ScheduleGlyph({ size = 16 }) {
21872
21919
  /* @__PURE__ */ import_react.default.createElement("path", { d: "M6.1 9.7l1.3 1.3 2.5-2.7" })
21873
21920
  );
21874
21921
  }
21922
+ var attentionCache = { key: "", list: [] };
21923
+ function useAttentionSessions(ctx) {
21924
+ const subscribe = import_react.default.useCallback((onChange) => {
21925
+ const list = ctx?.sessions?.list;
21926
+ if (typeof list?.subscribe !== "function") return () => {
21927
+ };
21928
+ return list.subscribe(onChange);
21929
+ }, [ctx]);
21930
+ const read = import_react.default.useCallback(() => {
21931
+ const byId = ctx?.sessions?.list?.getSnapshot?.()?.byId ?? {};
21932
+ const runningSubagents = /* @__PURE__ */ new Map();
21933
+ for (const id of Object.keys(byId)) {
21934
+ const child = byId[id];
21935
+ if (child?.origin !== "subagent" || child.running !== true) continue;
21936
+ const seen = /* @__PURE__ */ new Set();
21937
+ let parent = child.parentId;
21938
+ while (typeof parent === "string" && parent !== "" && !seen.has(parent)) {
21939
+ seen.add(parent);
21940
+ runningSubagents.set(parent, (runningSubagents.get(parent) ?? 0) + 1);
21941
+ parent = byId[parent]?.parentId;
21942
+ }
21943
+ }
21944
+ const rows = [];
21945
+ for (const id of Object.keys(byId)) {
21946
+ const session = byId[id];
21947
+ if (session === void 0 || session === null) continue;
21948
+ if (session.origin === "subagent" || session.blank === true) continue;
21949
+ const running = session.running === true || (runningSubagents.get(id) ?? 0) > 0;
21950
+ const pending = session.completed === true;
21951
+ if (!running && !pending) continue;
21952
+ rows.push({
21953
+ id,
21954
+ title: session.displayTitle || session.title || id,
21955
+ running,
21956
+ pending,
21957
+ at: typeof session.updatedAt === "number" ? session.updatedAt : 0
21958
+ });
21959
+ }
21960
+ rows.sort((a, b) => a.running === b.running ? b.at - a.at : a.running ? -1 : 1);
21961
+ const key = rows.map((row) => `${row.id}:${row.running ? 1 : 0}${row.pending ? 1 : 0}:${row.title}`).join("|");
21962
+ if (key !== attentionCache.key) {
21963
+ attentionCache.key = key;
21964
+ attentionCache.list = rows;
21965
+ }
21966
+ return attentionCache.list;
21967
+ }, [ctx]);
21968
+ return import_react.default.useSyncExternalStore(subscribe, read, () => attentionCache.list);
21969
+ }
21970
+ var expandGroupFailureReported = false;
21971
+ function reportExpandFailure(cause) {
21972
+ if (expandGroupFailureReported) return;
21973
+ expandGroupFailureReported = true;
21974
+ console.warn("[t-team] \u5C55\u5F00\u5DE5\u4F5C\u533A\u5206\u7EC4\u5931\u8D25\uFF08\u53EA\u63D0\u793A\u4E00\u6B21\uFF0C\u4E0D\u5F71\u54CD\u8DF3\u8F6C\uFF09\uFF1A", cause);
21975
+ }
21976
+ function owningWorkspaceKey(items, sessionId) {
21977
+ return items.find((item) => Array.isArray(item?.sessionIds) && item.sessionIds.includes(sessionId))?.workspaceId ?? "";
21978
+ }
21979
+ function expandOwningWorkspaceGroup(ctx, sessionId) {
21980
+ try {
21981
+ if (typeof sessionId !== "string" || sessionId === "") return;
21982
+ const items = ctx?.get?.("workspaces")?.list?.getSnapshot?.()?.items;
21983
+ if (!Array.isArray(items) || items.length === 0) return;
21984
+ const entries = ctx?.slots?.entries?.("sidebar.workspaces");
21985
+ if (!Array.isArray(entries) || entries.length === 0) return;
21986
+ const entry = entries.find((row) => row?.store?.spec?.actions?.setGroupExpanded !== void 0);
21987
+ if (entry === void 0) {
21988
+ reportExpandFailure("\u4FA7\u680F\u69FD\u91CC\u627E\u4E0D\u5230\u5E26 setGroupExpanded \u7684 store \u58F0\u660E\uFF08\u4E0A\u6E38\u63A5\u53E3\u53EF\u80FD\u6539\u4E86\uFF09");
21989
+ return;
21990
+ }
21991
+ const store = ctx?.slots?.hostFace?.()?.storeOf?.(entry);
21992
+ if (store?.actions?.setGroupExpanded === void 0) {
21993
+ reportExpandFailure("\u62FF\u5230\u7684\u4FA7\u680F store \u5B9E\u4F8B\u6CA1\u6709 setGroupExpanded\uFF08\u4E0A\u6E38\u63A5\u53E3\u53EF\u80FD\u6539\u4E86\uFF09");
21994
+ return;
21995
+ }
21996
+ const key = owningWorkspaceKey(items, sessionId);
21997
+ if (store.getSnapshot?.()?.groupExpansion?.[key] === true) return;
21998
+ store.actions.setGroupExpanded(key, true);
21999
+ } catch (error62) {
22000
+ reportExpandFailure(error62);
22001
+ }
22002
+ }
22003
+ function AttentionPanel({ t, sessions, anchor: anchor2, onClose, onOpen }) {
22004
+ import_react.default.useEffect(() => {
22005
+ const onKey = (event) => {
22006
+ if (event.key === "Escape") onClose();
22007
+ };
22008
+ const onDown = (event) => {
22009
+ const target = event.target;
22010
+ if (target instanceof Element && target.closest(".t-team-pulse-pop, .t-team-pulse") !== null) return;
22011
+ onClose();
22012
+ };
22013
+ window.addEventListener("keydown", onKey);
22014
+ window.addEventListener("mousedown", onDown, true);
22015
+ return () => {
22016
+ window.removeEventListener("keydown", onKey);
22017
+ window.removeEventListener("mousedown", onDown, true);
22018
+ };
22019
+ }, [onClose]);
22020
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-pulse-pop", style: { right: anchor2.right, top: anchor2.top }, role: "dialog", "aria-label": t("pulse.title") }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-pulse-pop-head" }, t("pulse.title")), sessions.length === 0 && /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-pulse-pop-empty" }, t("pulse.empty")), sessions.map((row) => /* @__PURE__ */ import_react.default.createElement("button", { key: row.id, type: "button", className: "t-team-pulse-pop-row", onClick: () => onOpen(row.id), title: row.title }, /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-pulse-pop-dot", "data-state": row.running ? "running" : "pending", "aria-hidden": "true" }), /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-pulse-pop-title" }, row.title), /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-pulse-pop-tag" }, t(row.running ? "pulse.running" : "pulse.pending")))));
22021
+ }
22022
+ function ActivePulse({ ctx, t }) {
22023
+ const show = useShowActivePulse();
22024
+ const sessions = useAttentionSessions(ctx);
22025
+ const [anchor2, setAnchor] = import_react.default.useState(null);
22026
+ import_react.default.useEffect(() => subscribePopoverClaims((owner) => {
22027
+ if (owner !== "pulse") setAnchor(null);
22028
+ }), []);
22029
+ const close = () => {
22030
+ setAnchor(null);
22031
+ releasePopover("pulse");
22032
+ };
22033
+ const attention = sessions.length > 0;
22034
+ if (!show) return null;
22035
+ const label = t(attention ? "pulse.on" : "pulse.off");
22036
+ const toggle = (event) => {
22037
+ if (anchor2 !== null) {
22038
+ close();
22039
+ return;
22040
+ }
22041
+ const rect = event.currentTarget.getBoundingClientRect();
22042
+ claimPopover("pulse");
22043
+ setAnchor({ right: Math.max(8, window.innerWidth - rect.right - 24), top: Math.round(rect.bottom) + 6 });
22044
+ };
22045
+ return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement(
22046
+ "button",
22047
+ {
22048
+ type: "button",
22049
+ className: "t-team-pulse",
22050
+ "data-on": attention,
22051
+ onClick: toggle,
22052
+ "aria-label": label,
22053
+ "aria-expanded": anchor2 !== null,
22054
+ title: label
22055
+ },
22056
+ /* @__PURE__ */ import_react.default.createElement(
22057
+ "svg",
22058
+ {
22059
+ width: "16",
22060
+ height: "16",
22061
+ viewBox: "0 0 16 16",
22062
+ fill: "none",
22063
+ stroke: "currentColor",
22064
+ strokeWidth: "1.5",
22065
+ strokeLinecap: "round",
22066
+ strokeLinejoin: "round",
22067
+ "aria-hidden": "true",
22068
+ focusable: "false"
22069
+ },
22070
+ /* @__PURE__ */ import_react.default.createElement("path", { pathLength: "100", d: "M1 8h3.2l1.4-4.2 2.6 8.4 1.5-5.2 1 1H15" })
22071
+ )
22072
+ ), anchor2 !== null && /* @__PURE__ */ import_react.default.createElement(
22073
+ AttentionPanel,
22074
+ {
22075
+ t,
22076
+ sessions,
22077
+ anchor: anchor2,
22078
+ onClose: close,
22079
+ onOpen: (id) => {
22080
+ expandOwningWorkspaceGroup(ctx, id);
22081
+ ctx?.sessions?.open?.(id);
22082
+ close();
22083
+ }
22084
+ }
22085
+ ));
22086
+ }
21875
22087
  function SettingsPanel({ t, ctx, remote, getActive, refreshCatalog, initialTab, squadsPreview, teamsPreview, draftPreview }) {
21876
22088
  const [snapshot, setSnapshot] = import_react.default.useState(() => snapshotOf(remote) ?? null);
21877
22089
  const [query, setQuery] = import_react.default.useState("");
@@ -22080,26 +22292,27 @@ function SettingsPanel({ t, ctx, remote, getActive, refreshCatalog, initialTab,
22080
22292
  setBusy(false);
22081
22293
  }
22082
22294
  }
22083
- if (snapshot === null) {
22084
- return /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-settings" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-title" }, t("settings.title")), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-meta" }, t("settings.loading")), error62 !== "" && /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-error" }, t("settings.loadFailed", { detail: error62 })), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-row" }, /* @__PURE__ */ import_react.default.createElement("button", { className: "t-team-chip", type: "button", onClick: () => void reload(true) }, t("settings.retry")))));
22085
- }
22086
22295
  const [showTaskBadge, setShowTaskBadgeState] = import_react.default.useState(true);
22087
22296
  const [showScheduleButton, setShowScheduleButtonState] = import_react.default.useState(true);
22297
+ const [showActivePulse, setShowActivePulseState] = import_react.default.useState(true);
22088
22298
  const [uiPrefsError, setUiPrefsError] = import_react.default.useState("");
22299
+ const [uiPrefsWriteError, setUiPrefsWriteError] = import_react.default.useState("");
22300
+ const uiPrefQueue = import_react.default.useRef(Promise.resolve());
22089
22301
  import_react.default.useEffect(() => {
22090
22302
  let alive = true;
22091
22303
  void (async () => {
22092
22304
  try {
22093
22305
  const data = unwrap(await remote.getUiPrefs(), "getUiPrefs");
22094
22306
  if (!alive) return;
22095
- if (typeof data?.showTaskBadge !== "boolean" || typeof data?.showScheduleButton !== "boolean") {
22307
+ if (typeof data?.showTaskBadge !== "boolean" || typeof data?.showScheduleButton !== "boolean" || typeof data?.showActivePulse !== "boolean") {
22096
22308
  setUiPrefsError(t("settings.showTaskBadgeUnreadable"));
22097
22309
  return;
22098
22310
  }
22099
22311
  setUiPrefsError("");
22100
22312
  setShowTaskBadgeState(data.showTaskBadge);
22101
22313
  setShowScheduleButtonState(data.showScheduleButton);
22102
- setUiPrefs({ showTaskBadge: data.showTaskBadge, showScheduleButton: data.showScheduleButton });
22314
+ setShowActivePulseState(data.showActivePulse);
22315
+ setUiPrefs({ showTaskBadge: data.showTaskBadge, showScheduleButton: data.showScheduleButton, showActivePulse: data.showActivePulse });
22103
22316
  } catch (cause) {
22104
22317
  console.error("[t-team] \u754C\u9762\u504F\u597D\u8BFB\u53D6\u5931\u8D25\uFF0C\u6682\u6309\u9ED8\u8BA4\uFF08\u663E\u793A\u5FBD\u7AE0\uFF09\u5904\u7406\uFF1A", cause);
22105
22318
  if (alive) setUiPrefsError(cause instanceof Error ? cause.message : String(cause));
@@ -22109,19 +22322,30 @@ function SettingsPanel({ t, ctx, remote, getActive, refreshCatalog, initialTab,
22109
22322
  alive = false;
22110
22323
  };
22111
22324
  }, [remote]);
22325
+ if (snapshot === null) {
22326
+ return /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-settings" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-title" }, t("settings.title")), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-meta" }, t("settings.loading")), error62 !== "" && /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-error" }, t("settings.loadFailed", { detail: error62 })), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-row" }, /* @__PURE__ */ import_react.default.createElement("button", { className: "t-team-chip", type: "button", onClick: () => void reload(true) }, t("settings.retry")))));
22327
+ }
22112
22328
  async function writeUiPref(patch, rollback) {
22113
22329
  setUiPrefs(patch);
22114
- try {
22115
- const next = unwrap(await remote.setUiPrefs(patch), "setUiPrefs");
22116
- setUiPrefs(next);
22117
- setShowTaskBadgeState(next.showTaskBadge);
22118
- setShowScheduleButtonState(next.showScheduleButton);
22119
- } catch (cause) {
22120
- setUiPrefs(rollback);
22121
- setShowTaskBadgeState(rollback.showTaskBadge ?? showTaskBadge);
22122
- setShowScheduleButtonState(rollback.showScheduleButton ?? showScheduleButton);
22123
- setError(cause instanceof Error ? cause.message : String(cause));
22124
- }
22330
+ const run = uiPrefQueue.current.then(async () => {
22331
+ try {
22332
+ const next = unwrap(await remote.setUiPrefs(patch), "setUiPrefs");
22333
+ setUiPrefs(next);
22334
+ setShowTaskBadgeState(next.showTaskBadge);
22335
+ setShowScheduleButtonState(next.showScheduleButton);
22336
+ setShowActivePulseState(next.showActivePulse);
22337
+ setUiPrefsWriteError("");
22338
+ } catch (cause) {
22339
+ setUiPrefs(rollback);
22340
+ setShowTaskBadgeState(rollback.showTaskBadge ?? showTaskBadge);
22341
+ setShowScheduleButtonState(rollback.showScheduleButton ?? showScheduleButton);
22342
+ setShowActivePulseState(rollback.showActivePulse ?? showActivePulse);
22343
+ setUiPrefsWriteError(cause instanceof Error ? cause.message : String(cause));
22344
+ }
22345
+ });
22346
+ uiPrefQueue.current = run.catch(() => {
22347
+ });
22348
+ await run;
22125
22349
  }
22126
22350
  async function setShowTaskBadge(next) {
22127
22351
  setShowTaskBadgeState(next);
@@ -22131,6 +22355,10 @@ function SettingsPanel({ t, ctx, remote, getActive, refreshCatalog, initialTab,
22131
22355
  setShowScheduleButtonState(next);
22132
22356
  await writeUiPref({ showScheduleButton: next }, { showScheduleButton: !next });
22133
22357
  }
22358
+ async function setShowActivePulse(next) {
22359
+ setShowActivePulseState(next);
22360
+ await writeUiPref({ showActivePulse: next }, { showActivePulse: !next });
22361
+ }
22134
22362
  const enabledCount = snapshot.enabled.length;
22135
22363
  return /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-settings" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-head" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-title" }, t("settings.title")), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-meta" }, t("settings.enabledCount", { enabled: enabledCount, total: experts.length }), " \xB7 v", CLIENT_BUILD_VERSION)), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-setting-row" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-setting-head" }, /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-setting-name" }, t("settings.showTaskBadge")), /* @__PURE__ */ import_react.default.createElement(
22136
22364
  "button",
@@ -22143,7 +22371,7 @@ function SettingsPanel({ t, ctx, remote, getActive, refreshCatalog, initialTab,
22143
22371
  onClick: () => void setShowTaskBadge(!showTaskBadge)
22144
22372
  },
22145
22373
  /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-switch-knob" })
22146
- )), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-setting-hint" }, t("settings.showTaskBadgeHint")), uiPrefsError !== "" && /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-error" }, t("settings.showTaskBadgeUnreadable"), "\uFF1A", uiPrefsError)), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-setting-row" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-setting-head" }, /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-setting-name" }, t("settings.showScheduleButton")), /* @__PURE__ */ import_react.default.createElement(
22374
+ )), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-setting-hint" }, t("settings.showTaskBadgeHint")), uiPrefsError !== "" && /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-error" }, t("settings.showTaskBadgeUnreadable"), "\uFF1A", uiPrefsError), uiPrefsWriteError !== "" && /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-error" }, t("settings.uiPrefsWriteFailed"), "\uFF1A", uiPrefsWriteError)), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-setting-row" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-setting-head" }, /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-setting-name" }, t("settings.showScheduleButton")), /* @__PURE__ */ import_react.default.createElement(
22147
22375
  "button",
22148
22376
  {
22149
22377
  className: "t-team-switch",
@@ -22154,7 +22382,18 @@ function SettingsPanel({ t, ctx, remote, getActive, refreshCatalog, initialTab,
22154
22382
  onClick: () => void setShowScheduleButton(!showScheduleButton)
22155
22383
  },
22156
22384
  /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-switch-knob" })
22157
- )), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-setting-hint" }, t("settings.showScheduleButtonHint"))), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-tabs", role: "tablist" }, [["experts", "tab.experts"], ["categories", "tab.categories"], ["roster", "tab.roster"], ["teams", "tab.teams"]].map(([key, label]) => /* @__PURE__ */ import_react.default.createElement(
22385
+ )), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-setting-hint" }, t("settings.showScheduleButtonHint"))), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-setting-row" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-setting-head" }, /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-setting-name" }, t("settings.showActivePulse")), /* @__PURE__ */ import_react.default.createElement(
22386
+ "button",
22387
+ {
22388
+ className: "t-team-switch",
22389
+ type: "button",
22390
+ role: "switch",
22391
+ "aria-checked": showActivePulse,
22392
+ "data-on": showActivePulse,
22393
+ onClick: () => void setShowActivePulse(!showActivePulse)
22394
+ },
22395
+ /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-switch-knob" })
22396
+ )), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-setting-hint" }, t("settings.showActivePulseHint"))), /* @__PURE__ */ import_react.default.createElement("div", { className: "t-team-tabs", role: "tablist" }, [["experts", "tab.experts"], ["categories", "tab.categories"], ["roster", "tab.roster"], ["teams", "tab.teams"]].map(([key, label]) => /* @__PURE__ */ import_react.default.createElement(
22158
22397
  "button",
22159
22398
  {
22160
22399
  className: "t-team-tab",
@@ -22617,6 +22856,21 @@ function useDockUi() {
22617
22856
  () => dockUi
22618
22857
  );
22619
22858
  }
22859
+ var popoverClaim = { owner: "", listeners: /* @__PURE__ */ new Set() };
22860
+ function claimPopover(id) {
22861
+ if (popoverClaim.owner === id) return;
22862
+ popoverClaim.owner = id;
22863
+ for (const listener of [...popoverClaim.listeners]) listener(popoverClaim.owner);
22864
+ }
22865
+ function releasePopover(id) {
22866
+ if (popoverClaim.owner !== id) return;
22867
+ popoverClaim.owner = "";
22868
+ for (const listener of [...popoverClaim.listeners]) listener(popoverClaim.owner);
22869
+ }
22870
+ function subscribePopoverClaims(listener) {
22871
+ popoverClaim.listeners.add(listener);
22872
+ return () => popoverClaim.listeners.delete(listener);
22873
+ }
22620
22874
  function jobDockPerfReset() {
22621
22875
  try {
22622
22876
  performance.clearMarks("t-team-jobs-open");
@@ -22640,6 +22894,9 @@ function JobsBadge({ t, ctx }) {
22640
22894
  import_react.default.useEffect(() => {
22641
22895
  if (!showBadge) setDockOpen(false);
22642
22896
  }, [showBadge]);
22897
+ import_react.default.useEffect(() => subscribePopoverClaims((owner) => {
22898
+ if (owner !== "dock") setDockOpen(false);
22899
+ }), []);
22643
22900
  if (!showBadge) return null;
22644
22901
  if (rows.length === 0 && subagents.length === 0) return null;
22645
22902
  const anyLive = liveCount > 0 || subagentsRunning > 0;
@@ -22659,7 +22916,10 @@ function JobsBadge({ t, ctx }) {
22659
22916
  onClick: () => {
22660
22917
  const next = !ui.open;
22661
22918
  setDockOpen(next);
22662
- if (next) jobDockPerfReset();
22919
+ if (next) {
22920
+ claimPopover("dock");
22921
+ jobDockPerfReset();
22922
+ }
22663
22923
  try {
22664
22924
  if (next) window.localStorage.removeItem(JOB_DOCK_CLOSED_KEY);
22665
22925
  else window.localStorage.setItem(JOB_DOCK_CLOSED_KEY, "1");
@@ -23403,7 +23663,9 @@ async function apply(ctx) {
23403
23663
  void (async () => {
23404
23664
  try {
23405
23665
  const data = unwrap(await remote.getUiPrefs(), "getUiPrefs");
23406
- setUiPrefs({ showTaskBadge: data?.showTaskBadge, showScheduleButton: data?.showScheduleButton });
23666
+ const patch = {};
23667
+ for (const key of UI_PREF_KEYS) if (typeof data?.[key] === "boolean") patch[key] = data[key];
23668
+ setUiPrefs(patch);
23407
23669
  } catch (cause) {
23408
23670
  console.warn("[t-team] \u754C\u9762\u504F\u597D\u8BFB\u53D6\u5931\u8D25\uFF0C\u6682\u6309\u9ED8\u8BA4\uFF08\u663E\u793A\u5FBD\u7AE0\uFF09\u5904\u7406\uFF1A", cause);
23409
23671
  }
@@ -23472,6 +23734,17 @@ async function apply(ctx) {
23472
23734
  t: ctx.locale.bind(NS),
23473
23735
  ctx: props?.ctx ?? ctx
23474
23736
  })));
23737
+ ctx.slots.inject("conversation.session.header.utilities", () => ctx.slots.register({
23738
+ name: "conversation.session.header.utilities",
23739
+ id: `${NS}-pulse`,
23740
+ order: 1,
23741
+ label: () => ctx.locale.bind(NS)("pulse.on"),
23742
+ locale: NS
23743
+ }, (props) => import_react.default.createElement(ActivePulse, {
23744
+ ...props,
23745
+ t: ctx.locale.bind(NS),
23746
+ ctx: props?.ctx ?? ctx
23747
+ })));
23475
23748
  ctx.slots.inject("shell.overlay", () => ctx.slots.register({
23476
23749
  name: "shell.overlay",
23477
23750
  id: `${NS}-jobs`,
package/lib/index.js CHANGED
@@ -167,6 +167,14 @@ const settingsSchema = schema.object({
167
167
  * (见 apply 里的 applyScheduleArmed)。任务与执行记录完整保留,重新打开即恢复调度。
168
168
  */
169
169
  showScheduleButton: schema.boolean().default(true),
170
+ /**
171
+ * 是否显示窗口右上角的「活跃指示」(心电图图标,界面偏好)。
172
+ *
173
+ * 彩色 = 有会话正在活动(自己在跑、或子代理在跑)**或**有待查看的完成提醒;
174
+ * 灰色 = 全部空闲且没有待看。与任务徽章的差别:它是**全局**的(挂 shell.overlay),
175
+ * 不受"当前打开哪个会话"影响。
176
+ */
177
+ showActivePulse: schema.boolean().default(true),
170
178
  });
171
179
 
172
180
  /**
@@ -394,7 +402,7 @@ export function apply(ctx, config) {
394
402
  return;
395
403
  }
396
404
  scope = settingsService.register(SETTINGS_NAMESPACE, settingsSchema, {
397
- base: { enabled: [], showTaskBadge: true, showScheduleButton: true },
405
+ base: { enabled: [], showTaskBadge: true, showScheduleButton: true, showActivePulse: true },
398
406
  applies: "live",
399
407
  validate: (value) => {
400
408
  const list = value?.enabled;
@@ -902,6 +910,7 @@ export function apply(ctx, config) {
902
910
  return {
903
911
  showTaskBadge: current?.showTaskBadge !== false,
904
912
  showScheduleButton: current?.showScheduleButton !== false,
913
+ showActivePulse: current?.showActivePulse !== false,
905
914
  };
906
915
  }
907
916
 
@@ -911,6 +920,7 @@ export function apply(ctx, config) {
911
920
  return {
912
921
  ...(typeof source.showTaskBadge === "boolean" ? { showTaskBadge: source.showTaskBadge } : {}),
913
922
  ...(typeof source.showScheduleButton === "boolean" ? { showScheduleButton: source.showScheduleButton } : {}),
923
+ ...(typeof source.showActivePulse === "boolean" ? { showActivePulse: source.showActivePulse } : {}),
914
924
  };
915
925
  }
916
926
 
@@ -927,6 +937,7 @@ export function apply(ctx, config) {
927
937
  [
928
938
  { op: "set", path: ["showTaskBadge"], value: next.showTaskBadge },
929
939
  { op: "set", path: ["showScheduleButton"], value: next.showScheduleButton },
940
+ { op: "set", path: ["showActivePulse"], value: next.showActivePulse },
930
941
  ],
931
942
  revision(),
932
943
  );
@@ -82,12 +82,15 @@ export const enabledStateSchema = z.object({
82
82
  export const uiPrefsSchema = z.object({
83
83
  showTaskBadge: z.boolean(),
84
84
  showScheduleButton: z.boolean(),
85
+ // 窗口右上角「活跃指示」是否显示。宿主**总是**返回布尔(缺省按 true 算),所以这里必填。
86
+ showActivePulse: z.boolean(),
85
87
  });
86
88
 
87
89
  /** `setUiPrefs` 的入参:想改哪个传哪个(未传的键保持原值)。 */
88
90
  export const uiPrefsPatchSchema = z.object({
89
91
  showTaskBadge: z.boolean().optional(),
90
92
  showScheduleButton: z.boolean().optional(),
93
+ showActivePulse: z.boolean().optional(),
91
94
  });
92
95
 
93
96
  export const promptSchema = z.object({ prompt: z.string() });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-t-expert",
3
- "version": "0.3.28",
3
+ "version": "0.3.40",
4
4
  "type": "module",
5
5
  "description": "T Expert — a 323-expert, 22-division roster with full Chinese translations, as a standalone DeepSeek Harness plugin, with a built-in multi-agent team engine (T Team).",
6
6
  "license": "MIT",