dsh-plugin-t-expert 0.3.26 → 0.3.39

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.26";
20019
- var uiPrefs = { showTaskBadge: true, showScheduleButton: true };
20021
+ var CLIENT_BUILD_VERSION = "0.3.39";
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,163 @@ 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
+ const attention = sessions.length > 0;
22027
+ if (!show) return null;
22028
+ const label = t(attention ? "pulse.on" : "pulse.off");
22029
+ const toggle = (event) => {
22030
+ if (anchor2 !== null) {
22031
+ setAnchor(null);
22032
+ return;
22033
+ }
22034
+ const rect = event.currentTarget.getBoundingClientRect();
22035
+ setAnchor({ right: Math.max(8, window.innerWidth - rect.right - 24), top: Math.round(rect.bottom) + 6 });
22036
+ };
22037
+ return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement(
22038
+ "button",
22039
+ {
22040
+ type: "button",
22041
+ className: "t-team-pulse",
22042
+ "data-on": attention,
22043
+ onClick: toggle,
22044
+ "aria-label": label,
22045
+ "aria-expanded": anchor2 !== null,
22046
+ title: label
22047
+ },
22048
+ /* @__PURE__ */ import_react.default.createElement(
22049
+ "svg",
22050
+ {
22051
+ width: "16",
22052
+ height: "16",
22053
+ viewBox: "0 0 16 16",
22054
+ fill: "none",
22055
+ stroke: "currentColor",
22056
+ strokeWidth: "1.5",
22057
+ strokeLinecap: "round",
22058
+ strokeLinejoin: "round",
22059
+ "aria-hidden": "true",
22060
+ focusable: "false"
22061
+ },
22062
+ /* @__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" })
22063
+ )
22064
+ ), anchor2 !== null && /* @__PURE__ */ import_react.default.createElement(
22065
+ AttentionPanel,
22066
+ {
22067
+ t,
22068
+ sessions,
22069
+ anchor: anchor2,
22070
+ onClose: () => setAnchor(null),
22071
+ onOpen: (id) => {
22072
+ expandOwningWorkspaceGroup(ctx, id);
22073
+ ctx?.sessions?.open?.(id);
22074
+ setAnchor(null);
22075
+ }
22076
+ }
22077
+ ));
22078
+ }
21875
22079
  function SettingsPanel({ t, ctx, remote, getActive, refreshCatalog, initialTab, squadsPreview, teamsPreview, draftPreview }) {
21876
22080
  const [snapshot, setSnapshot] = import_react.default.useState(() => snapshotOf(remote) ?? null);
21877
22081
  const [query, setQuery] = import_react.default.useState("");
@@ -22080,26 +22284,27 @@ function SettingsPanel({ t, ctx, remote, getActive, refreshCatalog, initialTab,
22080
22284
  setBusy(false);
22081
22285
  }
22082
22286
  }
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
22287
  const [showTaskBadge, setShowTaskBadgeState] = import_react.default.useState(true);
22087
22288
  const [showScheduleButton, setShowScheduleButtonState] = import_react.default.useState(true);
22289
+ const [showActivePulse, setShowActivePulseState] = import_react.default.useState(true);
22088
22290
  const [uiPrefsError, setUiPrefsError] = import_react.default.useState("");
22291
+ const [uiPrefsWriteError, setUiPrefsWriteError] = import_react.default.useState("");
22292
+ const uiPrefQueue = import_react.default.useRef(Promise.resolve());
22089
22293
  import_react.default.useEffect(() => {
22090
22294
  let alive = true;
22091
22295
  void (async () => {
22092
22296
  try {
22093
22297
  const data = unwrap(await remote.getUiPrefs(), "getUiPrefs");
22094
22298
  if (!alive) return;
22095
- if (typeof data?.showTaskBadge !== "boolean" || typeof data?.showScheduleButton !== "boolean") {
22299
+ if (typeof data?.showTaskBadge !== "boolean" || typeof data?.showScheduleButton !== "boolean" || typeof data?.showActivePulse !== "boolean") {
22096
22300
  setUiPrefsError(t("settings.showTaskBadgeUnreadable"));
22097
22301
  return;
22098
22302
  }
22099
22303
  setUiPrefsError("");
22100
22304
  setShowTaskBadgeState(data.showTaskBadge);
22101
22305
  setShowScheduleButtonState(data.showScheduleButton);
22102
- setUiPrefs({ showTaskBadge: data.showTaskBadge, showScheduleButton: data.showScheduleButton });
22306
+ setShowActivePulseState(data.showActivePulse);
22307
+ setUiPrefs({ showTaskBadge: data.showTaskBadge, showScheduleButton: data.showScheduleButton, showActivePulse: data.showActivePulse });
22103
22308
  } catch (cause) {
22104
22309
  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
22310
  if (alive) setUiPrefsError(cause instanceof Error ? cause.message : String(cause));
@@ -22109,19 +22314,30 @@ function SettingsPanel({ t, ctx, remote, getActive, refreshCatalog, initialTab,
22109
22314
  alive = false;
22110
22315
  };
22111
22316
  }, [remote]);
22317
+ if (snapshot === null) {
22318
+ 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")))));
22319
+ }
22112
22320
  async function writeUiPref(patch, rollback) {
22113
22321
  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
- }
22322
+ const run = uiPrefQueue.current.then(async () => {
22323
+ try {
22324
+ const next = unwrap(await remote.setUiPrefs(patch), "setUiPrefs");
22325
+ setUiPrefs(next);
22326
+ setShowTaskBadgeState(next.showTaskBadge);
22327
+ setShowScheduleButtonState(next.showScheduleButton);
22328
+ setShowActivePulseState(next.showActivePulse);
22329
+ setUiPrefsWriteError("");
22330
+ } catch (cause) {
22331
+ setUiPrefs(rollback);
22332
+ setShowTaskBadgeState(rollback.showTaskBadge ?? showTaskBadge);
22333
+ setShowScheduleButtonState(rollback.showScheduleButton ?? showScheduleButton);
22334
+ setShowActivePulseState(rollback.showActivePulse ?? showActivePulse);
22335
+ setUiPrefsWriteError(cause instanceof Error ? cause.message : String(cause));
22336
+ }
22337
+ });
22338
+ uiPrefQueue.current = run.catch(() => {
22339
+ });
22340
+ await run;
22125
22341
  }
22126
22342
  async function setShowTaskBadge(next) {
22127
22343
  setShowTaskBadgeState(next);
@@ -22131,6 +22347,10 @@ function SettingsPanel({ t, ctx, remote, getActive, refreshCatalog, initialTab,
22131
22347
  setShowScheduleButtonState(next);
22132
22348
  await writeUiPref({ showScheduleButton: next }, { showScheduleButton: !next });
22133
22349
  }
22350
+ async function setShowActivePulse(next) {
22351
+ setShowActivePulseState(next);
22352
+ await writeUiPref({ showActivePulse: next }, { showActivePulse: !next });
22353
+ }
22134
22354
  const enabledCount = snapshot.enabled.length;
22135
22355
  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
22356
  "button",
@@ -22143,7 +22363,7 @@ function SettingsPanel({ t, ctx, remote, getActive, refreshCatalog, initialTab,
22143
22363
  onClick: () => void setShowTaskBadge(!showTaskBadge)
22144
22364
  },
22145
22365
  /* @__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(
22366
+ )), /* @__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
22367
  "button",
22148
22368
  {
22149
22369
  className: "t-team-switch",
@@ -22154,7 +22374,18 @@ function SettingsPanel({ t, ctx, remote, getActive, refreshCatalog, initialTab,
22154
22374
  onClick: () => void setShowScheduleButton(!showScheduleButton)
22155
22375
  },
22156
22376
  /* @__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(
22377
+ )), /* @__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(
22378
+ "button",
22379
+ {
22380
+ className: "t-team-switch",
22381
+ type: "button",
22382
+ role: "switch",
22383
+ "aria-checked": showActivePulse,
22384
+ "data-on": showActivePulse,
22385
+ onClick: () => void setShowActivePulse(!showActivePulse)
22386
+ },
22387
+ /* @__PURE__ */ import_react.default.createElement("span", { className: "t-team-switch-knob" })
22388
+ )), /* @__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
22389
  "button",
22159
22390
  {
22160
22391
  className: "t-team-tab",
@@ -23403,7 +23634,9 @@ async function apply(ctx) {
23403
23634
  void (async () => {
23404
23635
  try {
23405
23636
  const data = unwrap(await remote.getUiPrefs(), "getUiPrefs");
23406
- setUiPrefs({ showTaskBadge: data?.showTaskBadge, showScheduleButton: data?.showScheduleButton });
23637
+ const patch = {};
23638
+ for (const key of UI_PREF_KEYS) if (typeof data?.[key] === "boolean") patch[key] = data[key];
23639
+ setUiPrefs(patch);
23407
23640
  } catch (cause) {
23408
23641
  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
23642
  }
@@ -23472,6 +23705,17 @@ async function apply(ctx) {
23472
23705
  t: ctx.locale.bind(NS),
23473
23706
  ctx: props?.ctx ?? ctx
23474
23707
  })));
23708
+ ctx.slots.inject("conversation.session.header.utilities", () => ctx.slots.register({
23709
+ name: "conversation.session.header.utilities",
23710
+ id: `${NS}-pulse`,
23711
+ order: 1,
23712
+ label: () => ctx.locale.bind(NS)("pulse.on"),
23713
+ locale: NS
23714
+ }, (props) => import_react.default.createElement(ActivePulse, {
23715
+ ...props,
23716
+ t: ctx.locale.bind(NS),
23717
+ ctx: props?.ctx ?? ctx
23718
+ })));
23475
23719
  ctx.slots.inject("shell.overlay", () => ctx.slots.register({
23476
23720
  name: "shell.overlay",
23477
23721
  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.26",
3
+ "version": "0.3.39",
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",