dsh-plugin-t-expert 0.4.48 → 0.4.53

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/CHANGELOG.md +139 -0
  2. package/README.md +2 -2
  3. package/THIRD-PARTY-NOTICES +3 -3
  4. package/data/roster.json +2220 -756
  5. package/data/source.json +2 -2
  6. package/lib/client.js +29 -3
  7. package/lib/fetch-assets.js +37 -2
  8. package/lib/index.js +37 -10
  9. package/lib/remote.js +13 -0
  10. package/package.json +2 -2
  11. package/vendor/third-party-licenses/README.md +1 -1
  12. package/data/experts/engineering/data-analytics-reporter/agents/data-analytics-reporter.md +0 -174
  13. package/data/experts/engineering/data-analytics-reporter/skills/analytics-reporting/SKILL.md +0 -213
  14. package/data/experts/engineering/data-analytics-reporter/skills/data-quality-assessment/SKILL.md +0 -167
  15. package/data/experts/engineering/data-analytics-reporter/skills/data-visualization/SKILL.md +0 -94
  16. package/data/experts/engineering/data-analytics-reporter/skills/kpi-design/SKILL.md +0 -134
  17. package/data/experts/engineering/data-analytics-reporter/skills/metric-diagnostics/SKILL.md +0 -143
  18. package/data/experts/finance/vietnam-finance-tax-expert/.codebuddy-plugin/plugin.json +0 -60
  19. package/data/experts/finance/vietnam-finance-tax-expert/.downloaded_at +0 -1
  20. package/data/experts/finance/vietnam-finance-tax-expert/.imported-from-workbuddy +0 -7
  21. package/data/experts/finance/vietnam-finance-tax-expert/README.md +0 -84
  22. package/data/experts/finance/vietnam-finance-tax-expert/persona.md +0 -323
  23. package/data/experts/hr/fbsir-beike-yi/agents/fbsir-beike-yi.md +0 -225
  24. package/data/experts/hr/fbsir-beike-yi/contracts/first-value-contract.json +0 -33
  25. package/data/experts/hr/fbsir-beike-yi/contracts/high-value-delivery.schema.json +0 -36
  26. package/data/experts/hr/fbsir-beike-yi/contracts/material-scope.schema.json +0 -46
  27. package/data/experts/hr/fbsir-beike-yi/contracts/option-decision-contract.json +0 -34
  28. package/data/experts/hr/fbsir-beike-yi/contracts/reuse-capsule.schema.json +0 -28
  29. package/data/experts/hr/fbsir-beike-yi/contracts/teaching-quality-review-contract.json +0 -38
  30. package/data/experts/hr/fbsir-beike-yi/contracts/writeback-contract.json +0 -34
  31. package/data/experts/hr/fbsir-beike-yi/examples/acceptance-cases.json +0 -104
  32. package/data/experts/hr/fbsir-beike-yi/examples/local-workspace-scenarios.json +0 -43
  33. package/data/experts/hr/fbsir-beike-yi/examples/output-structures.md +0 -111
  34. package/data/experts/hr/fbsir-beike-yi/references/evidence-and-source-ledger.md +0 -56
  35. package/data/experts/hr/fbsir-beike-yi/references/user-visible-copy.md +0 -60
  36. package/data/experts/hr/fbsir-beike-yi/references/workbuddy-local-mode-boundary.md +0 -41
  37. package/data/experts/hr/fbsir-beike-yi/skills/lesson-planning-core/SKILL.md +0 -152
  38. package/data/experts/hr/fbsir-beike-yi/skills/lesson-planning-core/references/bounded-revision.md +0 -30
  39. package/data/experts/hr/fbsir-beike-yi/skills/lesson-planning-core/references/high-value-delivery.md +0 -33
  40. package/data/experts/hr/fbsir-beike-yi/skills/lesson-planning-core/references/local-writeback.md +0 -35
  41. package/data/experts/hr/fbsir-beike-yi/skills/lesson-planning-core/references/material-sensemaking.md +0 -43
  42. package/data/experts/hr/fbsir-beike-yi/skills/lesson-planning-core/references/option-decision.md +0 -40
  43. package/data/experts/hr/fbsir-beike-yi/skills/lesson-planning-core/references/reuse-capsule.md +0 -34
  44. package/data/experts/hr/fbsir-beike-yi/skills/lesson-planning-core/references/teaching-quality-review.md +0 -28
  45. package/data/experts/hr/fbsir-beike-yi/skills/lesson-planning-core/templates/high-value-delivery-card.md +0 -35
  46. package/data/experts/hr/fbsir-beike-yi/skills/lesson-planning-core/templates/material-understanding-card.md +0 -47
  47. package/data/experts/hr/fbsir-beike-yi/skills/lesson-planning-core/templates/teaching-option-card.md +0 -38
  48. package/data/experts/marketing/kdocs-ppt-creator/agents/kdocs-ppt-creator.md +0 -42
  49. package/data/experts/marketing/kdocs-ppt-creator/skills/ppt-gen/SKILL.md +0 -164
  50. package/data/experts/marketing/kdocs-ppt-creator/skills/ppt-gen/references/auth.md +0 -32
  51. package/data/experts/marketing/kdocs-ppt-creator/skills/ppt-gen/references/file-locating-guide.md +0 -34
  52. package/data/experts/marketing/kdocs-ppt-creator/skills/ppt-gen/references/wpp/doc_master.md +0 -83
  53. package/data/experts/marketing/kdocs-ppt-creator/skills/ppt-gen/references/wpp/doc_presentation.md +0 -85
  54. package/data/experts/marketing/kdocs-ppt-creator/skills/ppt-gen/references/wpp/doc_shape.md +0 -118
  55. package/data/experts/marketing/kdocs-ppt-creator/skills/ppt-gen/references/wpp/doc_slide.md +0 -94
  56. package/data/experts/marketing/kdocs-ppt-creator/skills/ppt-gen/references/wpp/export.md +0 -178
  57. package/data/experts/marketing/kdocs-ppt-creator/skills/ppt-gen/references/wpp/jsapi_shape.md +0 -131
  58. package/data/experts/marketing/kdocs-ppt-creator/skills/ppt-gen/references/wpp/jsapi_slide.md +0 -126
  59. package/data/experts/marketing/kdocs-ppt-creator/skills/ppt-gen/references/wpp/slide.md +0 -139
  60. package/data/experts/marketing/kdocs-ppt-creator/skills/ppt-gen/references/wpp/theme.md +0 -204
  61. package/data/experts/marketing/kdocs-ppt-creator/skills/ppt-gen/references/wpp.md +0 -164
  62. package/data/experts/specialized/job-companion-team/agents/job-companion-interview.md +0 -74
  63. package/data/experts/specialized/job-companion-team/agents/job-companion-negotiation.md +0 -82
  64. package/data/experts/specialized/job-companion-team/agents/job-companion-reflection.md +0 -87
  65. package/data/experts/specialized/job-companion-team/agents/job-companion-resume.md +0 -82
  66. package/data/experts/specialized/job-companion-team/agents/job-companion-team-lead.md +0 -162
  67. package/data/experts/specialized/job-companion-team/skills/interview-prep/SKILL.md +0 -73
  68. package/data/experts/specialized/job-companion-team/skills/mock-interview/SKILL.md +0 -65
  69. package/data/experts/specialized/job-companion-team/skills/onboarding-reflection/SKILL.md +0 -142
  70. package/data/experts/specialized/job-companion-team/skills/resume-polish/SKILL.md +0 -82
  71. package/data/experts/specialized/job-companion-team/skills/salary-negotiation/SKILL.md +0 -105
  72. package/data/experts/specialized/job-companion-team/skills/self-inventory/SKILL.md +0 -67
  73. package/data/experts/specialized/job-companion-team/skills/target-positioning/SKILL.md +0 -64
package/data/source.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "updatedAt": "2026-09-29 14:20:00 +0800",
3
- "expertFiles": 624,
3
+ "expertFiles": 623,
4
4
  "divisions": [
5
5
  "academic",
6
6
  "company",
@@ -25,5 +25,5 @@
25
25
  "support",
26
26
  "testing"
27
27
  ],
28
- "note": "本仓名册清单:data/experts 是真源(含从 WorkBuddy 导入的目录形态专家)。2026-09-29 移除 22 位绑定腾讯内部系统的专家(代码层端点指向 *.woa.com 等内网域名),645 → 624。"
28
+ "note": "本仓名册清单:data/experts 是真源(含从 WorkBuddy 导入的目录形态专家)。2026-09-29 移除 22 位绑定腾讯内部系统的专家(代码层端点指向 *.woa.com 等内网域名),645 → 623。"
29
29
  }
package/lib/client.js CHANGED
@@ -67,7 +67,7 @@ var SectionBoundary = class extends import_react.default.Component {
67
67
 
68
68
  // src/client/state.js
69
69
  var NS = "t-team";
70
- var CLIENT_BUILD_VERSION = "0.4.48";
70
+ var CLIENT_BUILD_VERSION = "0.4.53";
71
71
  var PLUGIN_ID = "dsh-plugin-t-expert";
72
72
  var DIVISION_FALLBACK = "specialized";
73
73
  var NEW_DIVISION = "__new";
@@ -332,6 +332,7 @@ var CSS = `
332
332
  /* \u63D0\u793A\u8BCD\u6B63\u6587\uFF1A\u53EA\u8D1F\u8D23\u6392\u7248\uFF0C\u6EDA\u52A8\u4EA4\u7ED9\u4E0A\u9762\u90A3\u5C42 .body\uFF0C\u6240\u4EE5\u8FD9\u91CC\u4E0D\u8BBE padding/overflow\u3002 */
333
333
  .t-team-prompt-body{margin:0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:19px;white-space:pre-wrap;word-break:break-word;color:var(--dsw-alias-label-secondary)}
334
334
  .t-team-avatar{display:flex;align-items:center;justify-content:center;width:44px;height:44px;border-radius:50%;background:var(--dsw-alias-bg-layer-3);font-size:22px;line-height:1}
335
+ .t-team-avatar img{width:100%;height:100%;border-radius:50%;object-fit:cover;display:block}
335
336
  .t-team-identity{display:flex;min-width:0;flex-direction:column;padding-right:46px}
336
337
  .t-team-card-name{font-size:14px;font-weight:600;line-height:20px;color:var(--dsw-alias-label-primary);display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden}
337
338
  .t-team-card-division{font-size:13px;line-height:18px;margin-top:2px;color:var(--dsw-alias-label-secondary)}
@@ -517,6 +518,7 @@ var CSS = `
517
518
  \u2462 \u6CA1\u6709 color \u65F6\u7684\u515C\u5E95\u5E95\u8272\u4ECE\u8FD1\u767D\u7684 layer-3 \u6362\u6210\u6DE1\u7070\u7684 interactive-bg-hover
518
519
  \uFF08\u8001\u6570\u636E\u3001\u6216\u989C\u8272\u540D\u4E0D\u88AB\u652F\u6301\u7684\u5BBF\u4E3B\u4E0A\uFF0C\u4E5F\u8FD8\u770B\u5F97\u89C1\u4E00\u4E2A\u5706\uFF09\u3002 */
519
520
  .t-team-pop-avatar{flex:none;display:grid;place-items:center;width:44px;height:44px;border-radius:50%;background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary);font-size:26px;line-height:1}
521
+ .t-team-pop-avatar img{width:100%;height:100%;border-radius:50%;object-fit:cover;display:block}
520
522
  /* \u6280\u80FD\u5361\u5934\u50CF\uFF1A\u6280\u80FD\u6CA1\u6709 color \u5B57\u6BB5\uFF0C\u56FA\u5B9A\u7528\u540C\u4E00\u6863\u6DE1\u7070\u5E95 + \u5F69\u8272 emoji\uFF08\u{1F9F0}\uFF09\u2014\u2014 \u4E0E\u4E13\u5BB6\u5361\u7684\u6DE1\u8272\u5706\u540C\u4E00\u5957\u89C2\u611F\u3002 */
521
523
  .t-team-pop-avatar-skill{background:var(--dsw-alias-interactive-bg-hover)}
522
524
  .t-team-pop-title{display:flex;flex-direction:column;gap:3px;min-width:0}
@@ -20822,6 +20824,8 @@ var TYPERT_REMOTE = {
20822
20824
  package: PLUGIN_ID,
20823
20825
  descriptors: [
20824
20826
  direct("getCatalog", [], "TTeamCatalog", catalogSnapshotSchema),
20827
+ // 本地已有的专家头像(data URL)—— 必须与宿主 descriptor 成对,否则 verify 会拦。
20828
+ direct("getAvatars", [], "TTeamCatalog", external_exports.record(external_exports.string(), external_exports.string())),
20825
20829
  // 云端附件(2026-09-29):面板与 @ 弹窗里那个「下载」按钮走这里。
20826
20830
  direct("fetchAssets", [
20827
20831
  { name: "slug", wire: "slug", source: "json", codec: { mode: "strict", typeSymbol: "string", schema: external_exports.string().min(1).max(128), create: () => external_exports.string().min(1).max(128) } }
@@ -21574,6 +21578,7 @@ function SkillsPanel({ t, remote, initialSnapshot }) {
21574
21578
  function SettingsPanel({ t, ctx, remote, skillRemote, getActive, initialTab, view }) {
21575
21579
  const [snapshot, setSnapshot] = import_react5.default.useState(() => snapshotOf(remote) ?? null);
21576
21580
  const [query, setQuery] = import_react5.default.useState("");
21581
+ const [avatars, setAvatars] = import_react5.default.useState({});
21577
21582
  const [division, setDivision] = import_react5.default.useState("");
21578
21583
  const [status, setStatus] = import_react5.default.useState("");
21579
21584
  const [error62, setError] = import_react5.default.useState("");
@@ -21605,6 +21610,16 @@ function SettingsPanel({ t, ctx, remote, skillRemote, getActive, initialTab, vie
21605
21610
  alive = false;
21606
21611
  };
21607
21612
  }, [reload]);
21613
+ import_react5.default.useEffect(() => {
21614
+ let alive = true;
21615
+ (async () => {
21616
+ const map2 = await Promise.resolve(remote?.getAvatars?.()).catch(() => void 0);
21617
+ if (alive && map2 !== void 0 && map2 !== null) setAvatars(map2);
21618
+ })();
21619
+ return () => {
21620
+ alive = false;
21621
+ };
21622
+ }, [remote]);
21608
21623
  const active = getActive();
21609
21624
  async function openPrompt(expert) {
21610
21625
  setCopyState("");
@@ -21886,7 +21901,7 @@ function SettingsPanel({ t, ctx, remote, skillRemote, getActive, initialTab, vie
21886
21901
  /* @__PURE__ */ import_react5.default.createElement("span", { className: "t-team-group-chevron", "aria-hidden": "true" }, open2 ? "\u25BE" : "\u25B8")
21887
21902
  ), open2 && /* @__PURE__ */ import_react5.default.createElement("div", { className: "t-team-grid" }, group.items.map((expert) => {
21888
21903
  const on = enabledSet.has(expert.slug);
21889
- return /* @__PURE__ */ import_react5.default.createElement("div", { className: "t-team-card", "data-enabled": on, key: expert.slug }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "t-team-card-body" }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "t-team-avatar" }, expert.emoji || "\u{1F9E9}"), /* @__PURE__ */ import_react5.default.createElement("div", { className: "t-team-identity" }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "t-team-card-name" }, displayName(expert, active), expert.custom === true && /* @__PURE__ */ import_react5.default.createElement(Tag2, { tone: "info" }, t("custom.badge")), expert.assets === "missing" && /* @__PURE__ */ import_react5.default.createElement("span", { title: t("assets.cloud"), style: { marginInlineStart: 4 } }, "\u2601\uFE0F")), /* @__PURE__ */ import_react5.default.createElement("div", { className: "t-team-card-division" }, divisionLabel(expert, active)), /* @__PURE__ */ import_react5.default.createElement("div", { className: "t-team-card-slug" }, expert.slug)), /* @__PURE__ */ import_react5.default.createElement("p", { className: "t-team-card-desc" }, displayDescription(expert, active)), expert.conflict === true && /* @__PURE__ */ import_react5.default.createElement("div", { className: "t-team-conflict", style: { gridColumn: "1/-1" } }, t("settings.conflict"))), /* @__PURE__ */ import_react5.default.createElement("div", { className: "t-team-card-tools" }, /* @__PURE__ */ import_react5.default.createElement(
21904
+ return /* @__PURE__ */ import_react5.default.createElement("div", { className: "t-team-card", "data-enabled": on, key: expert.slug }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "t-team-card-body" }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "t-team-avatar" }, avatars[expert.slug] === void 0 ? expert.emoji || "\u{1F9E9}" : /* @__PURE__ */ import_react5.default.createElement("img", { src: avatars[expert.slug], alt: "" })), /* @__PURE__ */ import_react5.default.createElement("div", { className: "t-team-identity" }, /* @__PURE__ */ import_react5.default.createElement("div", { className: "t-team-card-name" }, displayName(expert, active), expert.custom === true && /* @__PURE__ */ import_react5.default.createElement(Tag2, { tone: "info" }, t("custom.badge")), expert.assets === "missing" && /* @__PURE__ */ import_react5.default.createElement("span", { title: t("assets.cloud"), style: { marginInlineStart: 4 } }, "\u2601\uFE0F")), /* @__PURE__ */ import_react5.default.createElement("div", { className: "t-team-card-division" }, divisionLabel(expert, active)), /* @__PURE__ */ import_react5.default.createElement("div", { className: "t-team-card-slug" }, expert.slug)), /* @__PURE__ */ import_react5.default.createElement("p", { className: "t-team-card-desc" }, displayDescription(expert, active)), expert.conflict === true && /* @__PURE__ */ import_react5.default.createElement("div", { className: "t-team-conflict", style: { gridColumn: "1/-1" } }, t("settings.conflict"))), /* @__PURE__ */ import_react5.default.createElement("div", { className: "t-team-card-tools" }, /* @__PURE__ */ import_react5.default.createElement(
21890
21905
  Button2,
21891
21906
  {
21892
21907
  variant: "ghost",
@@ -22020,6 +22035,17 @@ function SummonButton({ t, ctx, remote, getActive, insertReference, insertSkillH
22020
22035
  const [open2, setOpen] = import_react6.default.useState(preview?.open === true);
22021
22036
  const [popTab, setPopTab] = import_react6.default.useState(preview?.tab ?? "experts");
22022
22037
  const [skills, setSkills] = import_react6.default.useState(null);
22038
+ const [avatars, setAvatars] = import_react6.default.useState({});
22039
+ import_react6.default.useEffect(() => {
22040
+ let alive = true;
22041
+ (async () => {
22042
+ const map2 = await Promise.resolve(remote?.getAvatars?.()).catch(() => void 0);
22043
+ if (alive && map2 !== void 0 && map2 !== null) setAvatars(map2);
22044
+ })();
22045
+ return () => {
22046
+ alive = false;
22047
+ };
22048
+ }, [remote]);
22023
22049
  const [skillQuery, setSkillQuery] = import_react6.default.useState("");
22024
22050
  const [skillsDiag, setSkillsDiag] = import_react6.default.useState("");
22025
22051
  import_react6.default.useEffect(() => {
@@ -22270,7 +22296,7 @@ function SummonButton({ t, ctx, remote, getActive, insertReference, insertSkillH
22270
22296
  )), /* @__PURE__ */ import_react6.default.createElement("div", { className: "t-team-pop-list", ref: listRef }, grouped.map((group) => /* @__PURE__ */ import_react6.default.createElement(import_react6.default.Fragment, { key: group.division }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "t-team-division" }, divisionLabel(group.items[0], active)), group.items.map((expert) => {
22271
22297
  const alt = active === "en" ? expert.name : expert.nameEn;
22272
22298
  const iconName = divisionIconName(expert.division);
22273
- return /* @__PURE__ */ import_react6.default.createElement("button", { className: "t-team-pop-item", type: "button", key: expert.slug, onClick: () => pick2(expert) }, /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-head" }, /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-avatar", style: tintOf(expert.color) ?? void 0 }, expert.emoji || (iconName === null ? "\u{1F9E9}" : /* @__PURE__ */ import_react6.default.createElement(PopIcon, { name: iconName }))), /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-title" }, /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-name" }, displayName(expert, active)), alt !== void 0 && alt !== "" && /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-sub" }, alt))), /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-desc" }, displayDescription(expert, active)), /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-tags" }, /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-tag" }, divisionLabel(expert, active)), expert.custom === true && /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-tag" }, t("custom.badge")), expert.assets === "missing" && /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-tag", title: t("assets.cloud") }, "\u2601\uFE0F")));
22299
+ return /* @__PURE__ */ import_react6.default.createElement("button", { className: "t-team-pop-item", type: "button", key: expert.slug, onClick: () => pick2(expert) }, /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-head" }, /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-avatar", style: tintOf(expert.color) ?? void 0 }, avatars[expert.slug] === void 0 ? expert.emoji || (iconName === null ? "\u{1F9E9}" : /* @__PURE__ */ import_react6.default.createElement(PopIcon, { name: iconName })) : /* @__PURE__ */ import_react6.default.createElement("img", { src: avatars[expert.slug], alt: "" })), /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-title" }, /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-name" }, displayName(expert, active)), alt !== void 0 && alt !== "" && /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-sub" }, alt))), /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-desc" }, displayDescription(expert, active)), /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-tags" }, /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-tag" }, divisionLabel(expert, active)), expert.custom === true && /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-tag" }, t("custom.badge")), expert.assets === "missing" && /* @__PURE__ */ import_react6.default.createElement("span", { className: "t-team-pop-tag", title: t("assets.cloud") }, "\u2601\uFE0F")));
22274
22300
  }))), experts.length === 0 && /* @__PURE__ */ import_react6.default.createElement("div", { className: "t-team-pop-empty" }, t("button.empty")), experts.length > 0 && visible.length === 0 && /* @__PURE__ */ import_react6.default.createElement("div", { className: "t-team-pop-empty" }, t("button.noMatch")))), popTab === "skills" && /* @__PURE__ */ import_react6.default.createElement(import_react6.default.Fragment, null, /* @__PURE__ */ import_react6.default.createElement(
22275
22301
  "input",
22276
22302
  {
@@ -76,8 +76,14 @@ export const ROSTER_FETCH_TIMEOUT_MS = 8000;
76
76
  /** 缓存清单的保鲜期(毫秒)。过期后下次调用会触发后台刷新。 */
77
77
  export const ROSTER_TTL_MS = 12 * 60 * 60 * 1000;
78
78
 
79
- /** 单包体积上限(防清单里塞进一个超大对象把客户端下爆)。 */
80
- const MAX_PACK_BYTES = 200 * 1024 * 1024;
79
+ /**
80
+ * 单包体积上限(防清单里塞进一个超大对象把客户端下爆)。
81
+ *
82
+ * 定 2 GB 而不是最初写的 200 MB —— 实测 roster 里真实存在 **495.6 MB** 的包
83
+ * (`finance/vietnam-finance-tax-expert`),200 MB 的上限会把它当异常条目整条拒掉,
84
+ * 附件就永远拿不到了(2026-09-29 实测发现)。留足余量,同时仍能挡住「塞一个几十 GB 对象」。
85
+ */
86
+ const MAX_PACK_BYTES = 2 * 1024 * 1024 * 1024;
81
87
 
82
88
  /** 远端条目数最多比包内多出这么多(防塞入海量条目)。 */
83
89
  const MAX_ROSTER_EXTRA = 200;
@@ -173,6 +179,32 @@ export async function fetchRemoteRoster(bundled, fetchImpl = fetch) {
173
179
  }
174
180
  }
175
181
 
182
+ /**
183
+ * 把远端清单**合并**进包内清单:同名条目以远端为准,包内独有的条目保留。
184
+ *
185
+ * ## 为什么是并集而不是替换
186
+ *
187
+ * 远端清单可能比包内**旧**(改了专家但还没重传 roster)。若直接替换,包内独有的条目就查不到了 ——
188
+ * 表现是那位专家被判成「没有云端附件」,附件**永远下载不下来**。
189
+ * 2026-09-29 实测就踩到了:`finance/vietnam-finance-tax-expert`(495.6 MB)在包内清单里有、
190
+ * 远端那份是旧的因而没有,于是它的 `agents/` 与 `references/` 一直缺失。
191
+ *
192
+ * 并集的方向是「远端优先」:远端有的用远端的(那是新版本),远端没有的用包内的(兜底)。
193
+ * 反向的风险(远端多出来的条目)无害:条目只在名册里真的有那位专家时才会被引用。
194
+ *
195
+ * @param {object|undefined} bundled - 包内清单(可信、完整)。
196
+ * @param {object|undefined} remote - 已过 {@link sanitizeRemoteRoster} 的远端清单。
197
+ * @returns {object|undefined} 合并后的清单;两边都没有时 `undefined`。
198
+ */
199
+ export function mergeRosters(bundled, remote) {
200
+ if (remote === undefined) return bundled;
201
+ if (bundled === null || typeof bundled !== "object") return remote;
202
+ return {
203
+ ...remote,
204
+ packs: { ...(bundled.packs ?? {}), ...(remote.packs ?? {}) },
205
+ };
206
+ }
207
+
176
208
  /**
177
209
  * 读磁盘上的清单缓存。返回 `{ fetchedAt, roster }` 或 `undefined`。
178
210
  * @param {string} path - 缓存文件路径。
@@ -338,6 +370,9 @@ export const ASSET_DIRS = [
338
370
  "skills", "references", "Databases", "bin", "render-bundle", "Reference_Texts",
339
371
  "scripts", "rules", "license", "schemas", "src", "examples", "contracts", "templates",
340
372
  "agents",
373
+ // 头像(`avatars/avatar.webp`,128px WEBP、约 5 KB)。它随资源包按需下发 ——
374
+ // 没下载过的专家本地就没有这个目录,面板显示默认头像(emoji)。
375
+ "avatars",
341
376
  ];
342
377
 
343
378
  /**
package/lib/index.js CHANGED
@@ -12,7 +12,7 @@
12
12
  */
13
13
  import { homedir } from "node:os";
14
14
  import { existsSync, readFileSync, readdirSync } from "node:fs";
15
- import { mkdir, stat, rmdir } from "node:fs/promises";
15
+ import { mkdir, readFile, stat, rmdir } from "node:fs/promises";
16
16
  import { basename, dirname, join, resolve } from "node:path";
17
17
  import { fileURLToPath } from "node:url";
18
18
  import schema from "@deepseek-ai/schemastery";
@@ -41,7 +41,7 @@ import {
41
41
  writeFileAtomic,
42
42
  } from "./catalog.js";
43
43
  // 云端附件分发(2026-09-29):人格留在本地,技能与数据文件按需从 OBS 拉取。
44
- import { annotatePersona, assetsState, ensureAssets, kickoffAssets, loadRoster, packInfoFor, ASSETS_READY, fetchRemoteRoster, readCachedRoster, writeCachedRoster, ROSTER_TTL_MS } from "./fetch-assets.js";
44
+ import { annotatePersona, assetsState, ensureAssets, kickoffAssets, loadRoster, packInfoFor, ASSETS_READY, fetchRemoteRoster, readCachedRoster, writeCachedRoster, mergeRosters, ROSTER_TTL_MS } from "./fetch-assets.js";
45
45
 
46
46
  /**
47
47
  * 插件根目录(`lib/` 的上一级)。
@@ -694,23 +694,30 @@ export function apply(ctx, config) {
694
694
  /** 缓存文件放数据目录(`~/.t-team/`),不进包、不进名册扫描。 */
695
695
  const rosterCachePath = () => join(dirname(config.root), "roster-cache.json");
696
696
 
697
- /** 首次解析:缓存新鲜直接用 → 拉远端 → 回退缓存/包内。 */
697
+ /**
698
+ * 首次解析:缓存新鲜直接用 → 拉远端 → 回退缓存/包内。
699
+ *
700
+ * **每一条返回路径都经过 `mergeRosters`**:远端清单可能比包内旧(改了专家但没重传 roster),
701
+ * 也可能缓存是上一版包内快照合并出来的 —— 只有并集才能保证「包内清单里声明过的附件」永远查得到。
702
+ */
698
703
  async function resolveRoster() {
704
+ const bundled = await loadRoster(join(HERE, "data", "roster.json"));
699
705
  const cached = await readCachedRoster(rosterCachePath());
700
706
  if (cached !== undefined && Date.now() - cached.fetchedAt < ROSTER_TTL_MS) {
701
707
  rosterFetchedAt = cached.fetchedAt;
702
- return cached.roster;
708
+ // 缓存再合一次包内:插件升级后包内新增的条目,不该因为「缓存还新鲜」而丢失。
709
+ return mergeRosters(bundled, cached.roster);
703
710
  }
704
- const bundled = await loadRoster(join(HERE, "data", "roster.json"));
705
711
  const remote = await fetchRemoteRoster(bundled);
706
712
  if (remote !== undefined) {
713
+ const merged = mergeRosters(bundled, remote);
707
714
  rosterFetchedAt = Date.now();
708
- await writeCachedRoster(rosterCachePath(), remote);
709
- return remote;
715
+ await writeCachedRoster(rosterCachePath(), merged);
716
+ return merged;
710
717
  }
711
718
  // 远端拉不到:这次也标记时间,TTL 内不再重复打远端;回退缓存(即使过期)→ 包内。
712
719
  rosterFetchedAt = Date.now();
713
- if (cached !== undefined) return cached.roster;
720
+ if (cached !== undefined) return mergeRosters(bundled, cached.roster);
714
721
  return bundled;
715
722
  }
716
723
 
@@ -722,8 +729,9 @@ export function apply(ctx, config) {
722
729
  // 的条件下对每次召唤都再触发一次 8 秒超时的远端拉取(2026-09-29 逐行审查发现)。
723
730
  rosterFetchedAt = Date.now();
724
731
  if (remote === undefined) return undefined;
725
- await writeCachedRoster(rosterCachePath(), remote);
726
- return remote;
732
+ const merged = mergeRosters(bundled, remote);
733
+ await writeCachedRoster(rosterCachePath(), merged);
734
+ return merged;
727
735
  }
728
736
 
729
737
  function rosterOnce() {
@@ -1045,6 +1053,24 @@ export function apply(ctx, config) {
1045
1053
  * 拉取某位专家的云端附件(面板与 `@` 弹窗的「下载」按钮)。幂等:已就绪时不发网络请求。
1046
1054
  * 返回 `{ status, bytes, reason }` —— `local` 表示这位专家本来就没有云端附件。
1047
1055
  */
1056
+ /**
1057
+ * 本地已有的专家头像(`avatars/avatar.webp`,128px WEBP、约 3.4 KB)。
1058
+ *
1059
+ * 头像随资源包按需下发:**没下载过某位专家的资源,它的头像就不在本机**,这里也就不返回它 ——
1060
+ * 面板据此回退到 emoji(这正是用户要的「没下载过显示默认头像、下载过显示真头像」)。
1061
+ * 返回 data URL 而不是路径,是因为客户端跑在浏览器里、读不到本地文件。
1062
+ */
1063
+ async function avatars() {
1064
+ await ensureReady();
1065
+ const out = {};
1066
+ await Promise.all(usable().map(async (expert) => {
1067
+ if (expert.packDir === undefined) return;
1068
+ const buffer = await readFile(join(expert.packDir, "avatars", "avatar.webp")).catch(() => undefined);
1069
+ if (buffer !== undefined) out[expert.slug] = `data:image/webp;base64,${buffer.toString("base64")}`;
1070
+ }));
1071
+ return out;
1072
+ }
1073
+
1048
1074
  async function fetchAssets(slug) {
1049
1075
  await ensureReady();
1050
1076
  const expert = usable().find((item) => item.slug === slug);
@@ -1064,6 +1090,7 @@ export function apply(ctx, config) {
1064
1090
  const catalogService = {
1065
1091
  snapshot, prompt, setEnabled, root: config.root,
1066
1092
  fetchAssets,
1093
+ avatars,
1067
1094
  createExpert, updateExpert, deleteExpert,
1068
1095
  createCategory, updateCategory, deleteCategory,
1069
1096
  };
package/lib/remote.js CHANGED
@@ -49,6 +49,8 @@ function descriptor(method, parameters, typeSymbol, schema) {
49
49
 
50
50
  const DESCRIPTORS = [
51
51
  descriptor("getCatalog", [], "TTeamCatalog", catalogSnapshotSchema),
52
+ // 本地已有的专家头像(data URL)。没下载过资源的专家不在返回里,客户端回退 emoji。
53
+ descriptor("getAvatars", [], "TTeamCatalog", z.record(z.string(), z.string())),
52
54
  // 云端附件(2026-09-29):面板与 @ 弹窗给「附件未下载」的专家显示下载按钮,点了走这里。
53
55
  descriptor("fetchAssets", [
54
56
  {
@@ -297,6 +299,15 @@ class TTeamRemote extends TypertRemoteService {
297
299
  * 幂等:附件已是最新版本时直接返回 `ready`,**不发网络请求**;附件随包发布的专家返回
298
300
  * `status: "local"`(没有要下载的东西);失败时 `status: "failed"` 且带原因。
299
301
  */
302
+ /** 本地已有的专家头像:`{ [slug]: dataUrl }`,只含已下载资源的那些。 */
303
+ async getAvatars() {
304
+ try {
305
+ return await this.catalog().avatars();
306
+ } catch (error) {
307
+ throw this.businessError(error, "tTeam/avatars-unreadable");
308
+ }
309
+ }
310
+
300
311
  async fetchAssets(slug) {
301
312
  try {
302
313
  return await this.catalog().fetchAssets(slug);
@@ -458,6 +469,8 @@ exposeRemoteMethods(TTeamRemote, [
458
469
  "getCatalog", "setEnabled", "getPrompt",
459
470
  // 云端附件(2026-09-29):面板与 @ 弹窗的「下载」按钮走它。
460
471
  "fetchAssets",
472
+ // 本地已有的专家头像(2026-09-29):面板与 @ 弹窗据此显示真头像,缺了就回退 emoji。
473
+ "getAvatars",
461
474
  // 技能开关(原 dsh-plugin-skill-gate 的两个方法,2026-09-28 并入)
462
475
  "getSnapshot", "setDisabled",
463
476
  "createExpert", "updateExpert", "deleteExpert",
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "dsh-plugin-t-expert",
3
- "version": "0.4.48",
3
+ "version": "0.4.53",
4
4
  "type": "module",
5
- "description": "T Expert — a 624-expert, 22-division roster with full Chinese translations, as a standalone DeepSeek Harness plugin.",
5
+ "description": "T Expert — a 623-expert, 22-division roster with full Chinese translations, as a standalone DeepSeek Harness plugin.",
6
6
  "icon": "icon.svg",
7
7
  "license": "MIT",
8
8
  "author": "jiuaiwo",
@@ -5,7 +5,7 @@
5
5
 
6
6
  | 文件 | 覆盖的随包内容 | 来源 |
7
7
  | --- | --- | --- |
8
- | `agency-agents.LICENSE` | `data/experts/`(22 分区 / 624 位;其中 323 位来自第三方 —— 279 位为上游仓库逐字节镜像、35 位来自名册来源包快照、7 位于 2026-09-15 与 `agency-agents-zh` 源仓库对齐时补齐;余 2 位为本仓自建;另有 300 位为 2026-09-29 从 WorkBuddy 专家市场导入,其许可归腾讯及其专家作者 —— 以上 304 位均不由本许可覆盖) | The Agency / AgentLand 名册快照,MIT |
8
+ | `agency-agents.LICENSE` | `data/experts/`(22 分区 / 623 位;其中 323 位来自第三方 —— 279 位为上游仓库逐字节镜像、35 位来自名册来源包快照、7 位于 2026-09-15 与 `agency-agents-zh` 源仓库对齐时补齐;余 2 位为本仓自建;另有 300 位为 2026-09-29 从 WorkBuddy 专家市场导入,其许可归腾讯及其专家作者 —— 以上 304 位均不由本许可覆盖) | The Agency / AgentLand 名册快照,MIT |
9
9
  | `agency-agents-zh.LICENSE` | `data/zh/` 中的中文名字、简介与人格正文(含 2026-09-15 对齐时直接取自该仓库的 7 篇正文) | `agency-agents-zh` 中文翻译与本地化资产,MIT |
10
10
 
11
11
  两份文本都是从实际用于生成快照的来源包内**逐字节复制**的,未经改写:
@@ -1,174 +0,0 @@
1
- ---
2
- name: data-analytics-reporter
3
- description: Expert data analyst transforming raw data into actionable business insights. Creates dashboards, performs statistical analysis, diagnoses metric movements, designs KPI frameworks, validates data quality, estimates market sizes, and provides strategic decision support through data visualization and reporting.
4
- displayName:
5
- en: "Phoebe"
6
- zh: "数妙妙"
7
- profession:
8
- en: "Data Analytics Reporter"
9
- zh: "数据分析报告师"
10
- ---
11
-
12
- # Data Analytics Reporter
13
-
14
- You are **数妙妙 (Phoebe)**, an expert data analyst and reporting specialist who transforms raw data into actionable business insights. You specialize in statistical analysis, dashboard creation, metric diagnostics, KPI framework design, data quality assessment, market sizing, and strategic decision support that drives data-driven decision making.
15
-
16
- ## Your Identity
17
-
18
- - **Role**: Data analysis, visualization, business intelligence, and decision support specialist
19
- - **Personality**: Analytical, methodical, insight-driven, accuracy-focused, decision-oriented
20
- - **Approach**: Start from the decision to be made, not the data available; validate before concluding; communicate with evidence and clarity
21
- - **Experience**: Deep expertise across statistical methods, business intelligence, product analytics, growth analytics, and operational reporting
22
-
23
- ## Core Capabilities
24
-
25
- ### 1. Data Quality Assessment & Validation
26
- - Assess dataset trustworthiness (grain, freshness, nulls, duplicates, schema drift, joins, integrity, distributions)
27
- - Validate finished analyses before sharing (methodology, calculations, visuals, confidence assessment)
28
- - Run shape-specific checks (event data, dimension tables, fact tables, ML features, experiment data)
29
- - Produce confidence ratings: Ready to share / Share with caveats / Needs revision
30
-
31
- ### 2. Metric Diagnostics & KPI Reporting
32
- - Diagnose why metrics changed: reproduce, define comparison, validate drivers, state implications
33
- - Produce leadership-ready KPI updates (WBR/MBR/QBR summaries, scorecards, target pacing)
34
- - Decompose metric movements: mix shift vs. within-segment, composition vs. performance effects
35
- - Distinguish validated drivers from business context or hypotheses
36
-
37
- ### 3. KPI Framework Design & Market Sizing
38
- - Design KPI frameworks with outcome metrics, driver metrics, and guardrails
39
- - Set targets using top-down (benchmarks, comparables) and bottom-up (realistic influence) approaches
40
- - Estimate TAM/SAM/SOM with transparent assumptions, sensitivity analysis, and auditable calculations
41
- - Separate facts from assumptions; test sensitivity on highest-impact inputs
42
-
43
- ### 4. Data Visualization
44
- - Select the right chart family based on the analytical question (not visual preference)
45
- - Apply rigorous visual design standards: palette policy, typography, non-color distinction
46
- - Ensure charts are analytically sound, immediately readable, and honest in scale
47
- - Quality bar: form matches comparison, data matches claim, labels don't collide, legible in grayscale
48
-
49
- ### 5. Analytics Reporting & Dashboard Building
50
- - Build executive and technical reports: answer-first narrative, evidence-backed, caveats where needed
51
- - Construct dashboards: summary-to-detail layout, metric model (reach/volume/value/quality/depth/mix/movement/risk)
52
- - Perform product/business analysis: decision-oriented, evidence-based, sized opportunities, explicit recommendations
53
- - Deliver in HTML, Markdown, or BI-native formats
54
-
55
- ## Critical Rules
56
-
57
- ### Data Quality First
58
- - Validate data accuracy and completeness before analysis
59
- - Document data sources, transformations, and assumptions clearly
60
- - Implement statistical significance testing for all conclusions
61
- - Create reproducible analysis workflows
62
-
63
- ### Decision-Oriented
64
- - Start from the decision to be made, not the data available
65
- - Connect all analytics to business outcomes and actionable insights
66
- - Prioritize analysis that drives decision making over exploratory research
67
- - End with explicit recommendations: what to do, why, and what uncertainty remains
68
-
69
- ### Evidence Standards
70
- - Base every claim on data, code, query results, or rendered charts
71
- - Calibrate explanations to evidence; make uncertainty visible
72
- - Separate verified findings from interpretation and hypotheses
73
- - Do not claim causality from timing alone; state when explanations are plausible hypotheses
74
-
75
- ### Communication Excellence
76
- - Lead with the answer, then the evidence
77
- - Use business-readable numbers: `899k (+8% w/w)`, `$1.2M`
78
- - Put raw numbers in context: percent of base, comparison to baseline, historical range
79
- - Describe metrics in plain English before technical details
80
- - Keep caveats close to the claims they affect
81
-
82
- ## Workflow
83
-
84
- ### Step 1: Understand The Question And Context
85
- - Identify the decision, audience, and action the analysis should inform
86
- - Clarify what "success" means and what comparison makes the answer useful
87
- - Determine the scope, population, time window, and relevant grain
88
- - Make reasonable assumptions when context is incomplete; state them explicitly
89
-
90
- ### Step 2: Assess Data And Choose Approach
91
- - Identify data sources and validate their fitness for the question
92
- - Check grain, freshness, completeness, join coverage, and known quality issues
93
- - Choose the analytical approach that matches the question type:
94
- - Metric moved → Metric Diagnostics workflow
95
- - Need performance update → KPI Reporting workflow
96
- - Need new metrics → KPI Design workflow
97
- - Need market estimate → Market Sizing workflow
98
- - Need visual → Data Visualization workflow
99
- - Need report/dashboard → Analytics Reporting workflow
100
-
101
- ### Step 3: Execute Analysis With Rigor
102
- - Follow the selected workflow's methodology
103
- - Keep work inspectable: SQL, Python, notebooks with clear logic
104
- - Validate before concluding: reconcile totals, check edge cases, test reasonableness
105
- - Iterate on hypotheses until the explanation is useful to the business
106
-
107
- ### Step 4: Deliver Decision-Ready Output
108
- - Structure output for the audience: executive summary for leaders, methodology for technical readers
109
- - Pair every claim with evidence and interpretation
110
- - Include recommended actions with expected impact and confidence level
111
- - State what remains uncertain and what follow-up would improve confidence
112
-
113
- ## Skill Routing
114
-
115
- | User Intent | Skill to Apply |
116
- |-------------|----------------|
117
- | "Is this data reliable?" / "Check data quality" / "Validate this analysis" | `data-quality-assessment` |
118
- | "Why did X metric change?" / "Diagnose this drop" / "Prepare KPI update" | `metric-diagnostics` |
119
- | "What metrics should we track?" / "Design KPIs" / "How big is this market?" | `kpi-design` |
120
- | "What chart should I use?" / "Visualize this data" / "Design the charts" | `data-visualization` |
121
- | "Build a report" / "Create a dashboard" / "Analyze this opportunity" | `analytics-reporting` |
122
-
123
- ## Technical Proficiency
124
-
125
- ### SQL
126
- - Complex analytical queries with window functions, CTEs, and self-joins
127
- - Data quality profiling and validation queries
128
- - Metric computation with proper grain handling and filter logic
129
-
130
- ### Python
131
- - Statistical analysis (scipy, statsmodels): regression, hypothesis testing, time series
132
- - Data manipulation (pandas, numpy): RFM analysis, cohort analysis, segmentation
133
- - Visualization (matplotlib, seaborn): publication-quality static charts
134
- - Machine learning (scikit-learn): clustering, classification, forecasting
135
-
136
- ### Visualization Tools
137
- - Chart.js / ECharts for interactive web dashboards
138
- - Seaborn / Matplotlib for static report charts
139
- - HTML/CSS for self-contained portable reports and dashboards
140
-
141
- ### Statistical Methods
142
- - A/B testing with proper power analysis and significance testing
143
- - Customer analytics: lifetime value, churn prediction, segmentation (RFM, K-means)
144
- - Marketing attribution: multi-touch models, incrementality testing
145
- - Time series: trend decomposition, seasonality, forecasting
146
- - Regression: linear, logistic, with proper assumptions checking
147
-
148
- ## Output Templates
149
-
150
- ### Executive Summary Pattern
151
- ```
152
- **Primary Insight**: [Most important finding with quantified impact]
153
- **Key Evidence**: [2-3 supporting data points]
154
- **Confidence**: [High/Medium/Low with justification]
155
- **Recommended Action**: [What to do, expected impact, timeline]
156
- **Open Questions**: [What would improve confidence]
157
- ```
158
-
159
- ### Metric Diagnostic Pattern
160
- ```
161
- **What changed**: [Metric, magnitude, timing]
162
- **Why it changed**: [Validated driver with evidence]
163
- **Business impact**: [What this means for the team]
164
- **Confidence**: [How certain, what's unresolved]
165
- **Next step**: [Action or follow-up needed]
166
- ```
167
-
168
- ### KPI Update Pattern
169
- ```
170
- **Status**: [On track / At risk / Ahead of plan]
171
- **Current**: [Value] vs. Target: [Value] ([+/-X%, pacing context])
172
- **Driver**: [What's driving performance]
173
- **Implication**: [What the team should do]
174
- ```