koishi-plugin-image-prompt 2.1.0 → 2.1.1

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/index.js CHANGED
@@ -265,11 +265,11 @@ function buildAgentTools() {
265
265
  type: "function",
266
266
  function: {
267
267
  name: "get_avatar",
268
- description: "\u53D6\u67D0\u4E2A\u4EBA\u7684\u5934\u50CF\u5F53\u53C2\u8003\u56FE\uFF0C\u8FD4\u56DE\u5B83\u7684\u7F16\u53F7\u3002userId \u7559\u7A7A = \u53D6\u53D1\u6307\u4EE4\u7684\u4EBA\u81EA\u5DF1\u7684\u5934\u50CF\uFF1B\u4E5F\u53EF\u4EE5\u586B\u300C\u88AB @ \u7684\u4EBA\u300D\u5217\u8868\u91CC\u7684 id\u3002\u7528\u6237\u8BF4\u300C\u753B\u6211\u300D\u300C\u7528\u6211\u7684\u5934\u50CF\u300D\u300C\u753B @\u67D0\u67D0\u300D\u65F6\u7528\u5B83\u3002",
268
+ description: "\u53D6\u5934\u50CF\u5F53\u53C2\u8003\u56FE\uFF0C\u8FD4\u56DE\u5B83\u7684\u7F16\u53F7\u3002**\u53EA\u80FD\u53D6\u4E24\u79CD\u4EBA\u7684\u5934\u50CF**\uFF1A\u53D1\u6307\u4EE4\u7684\u4EBA\u81EA\u5DF1\uFF08userId \u7559\u7A7A/\u5199 me\uFF09\uFF0C\u6216\u4ED6\u8FD9\u6B21\u6D88\u606F\u91CC\u660E\u786E @ \u5230\u7684\u4EBA\uFF08\u586B\u4E0A\u4E0B\u6587\u300C\u53EF\u53D6\u5934\u50CF\u7684\u4EBA\u300D\u91CC\u5217\u51FA\u7684 id/\u540D\u5B57\uFF09\u3002\u5176\u5B83 id \u4E00\u5F8B\u4F1A\u88AB\u62D2\u7EDD\u3002",
269
269
  parameters: {
270
270
  type: "object",
271
271
  properties: {
272
- userId: { type: "string", description: "\u8981\u53D6\u5934\u50CF\u7684\u7528\u6237 id\uFF08\u53D6\u81EA\u4E0A\u4E0B\u6587\u91CC\u7684\u300C\u88AB @ \u7684\u4EBA\u300D\uFF09\uFF0C\u7559\u7A7A\u6216\u5199 me/self \u8868\u793A\u53D1\u6307\u4EE4\u7684\u4EBA\u81EA\u5DF1" }
272
+ userId: { type: "string", description: "\u7559\u7A7A\u6216\u5199 me/self/\u6211 = \u53D1\u6307\u4EE4\u7684\u4EBA\u81EA\u5DF1\uFF1B\u4E5F\u53EF\u4EE5\u586B\u4E0A\u4E0B\u6587\u91CC\u300C\u53EF\u53D6\u5934\u50CF\u7684\u4EBA\u300D\u5217\u51FA\u7684 id \u6216\u6635\u79F0\uFF08\u5373\u88AB @ \u7684\u4EBA\uFF09\u3002**\u4E0D\u8981\u81EA\u5DF1\u731C id**" }
273
273
  },
274
274
  required: []
275
275
  }
@@ -785,7 +785,9 @@ var DEFAULT_AGENT_INSTRUCTIONS = `\u4F60\u662F QQ \u7FA4\u91CC\u7684\u7ED8\u56FE
785
785
  - gallery_search\uFF1A\u6309\u5173\u952E\u8BCD\u641C\u7D22\u53C2\u8003\u56FE\u5E93\u3002**\u6D89\u53CA\u5177\u4F53\u89D2\u8272/\u7ACB\u7ED8\u65F6\u5FC5\u987B\u5148\u641C\u4E00\u6B21**\u3002
786
786
  \u591A\u4E2A\u5173\u952E\u8BCD\u7528\u7A7A\u683C\u5206\u9694\uFF08\u5982\u300C\u767D\u53D1 \u7ACB\u7ED8\u300D\uFF09\uFF0C\u5168\u90E8\u547D\u4E2D\u7684\u6392\u5728\u6700\u524D\uFF1B\u641C\u4E0D\u5230\u5C31\u6362\u4E2A\u8BCD\u518D\u641C\u3002
787
787
  - ask_user\uFF1A\u628A\u95EE\u9898\u53D1\u7ED9\u7528\u6237**\u5E76\u7B49\u4ED6\u56DE\u590D**\uFF08\u4ED6\u53D1\u7684\u56FE\u4E5F\u4F1A\u4E00\u8D77\u5E26\u56DE\u6765\uFF09\u3002
788
- - get_avatar\uFF1A\u53D6\u67D0\u4E2A\u4EBA\u7684\u5934\u50CF\u5F53\u53C2\u8003\u56FE\u3002userId \u7559\u7A7A\u53D6\u53D1\u6307\u4EE4\u8005\u81EA\u5DF1\uFF1B\u4E5F\u53EF\u4EE5\u53D6\u88AB @ \u7684\u4EBA\u7684\u5934\u50CF\u3002
788
+ - get_avatar\uFF1A\u53D6\u5934\u50CF\u5F53\u53C2\u8003\u56FE\u3002**\u53EA\u80FD\u53D6\u4E24\u79CD\u4EBA**\uFF1A\u53D1\u6307\u4EE4\u7684\u4EBA\u81EA\u5DF1\uFF08userId \u7559\u7A7A\u6216\u5199 me\uFF09\uFF0C
789
+ \u4EE5\u53CA\u4ED6\u8FD9\u6B21\u6D88\u606F\u91CC\u660E\u786E @ \u5230\u7684\u4EBA\uFF08\u586B\u4E0A\u4E0B\u6587\u300C\u53EF\u53D6\u5934\u50CF\u7684\u4EBA\u300D\u91CC\u5217\u51FA\u7684 id \u6216\u6635\u79F0\uFF09\u3002
790
+ \u5176\u5B83\u4EFB\u4F55 id \u90FD\u4F1A\u88AB\u62D2\u7EDD\uFF0C\u4E0D\u8981\u731C\u3002
789
791
  - draw\uFF1A\u771F\u6B63\u5F00\u59CB\u753B\u3002\u628A prompt \u548C\u6311\u4E2D\u7684\u53C2\u8003\u56FE\u7F16\u53F7\u4EA4\u8FDB\u53BB\uFF0C\u753B\u597D\u4F1A\u81EA\u52A8\u53D1\u5230\u7FA4\u91CC\u3002
790
792
 
791
793
  \u5DE5\u4F5C\u89C4\u5219\uFF1A
@@ -807,8 +809,12 @@ var DEFAULT_AGENT_INSTRUCTIONS = `\u4F60\u662F QQ \u7FA4\u91CC\u7684\u7ED8\u56FE
807
809
  8. \u3010\u6CA1\u6307\u5B9A\u53C2\u8003\u56FE\u65F6\u5148\u95EE\u4E00\u53E5\u3011\u7528\u6237\u8FD9\u6B21\u6CA1\u53D1\u56FE\u3001\u6CA1 @ \u4EBA\u3001\u6307\u4EE4\u672C\u8EAB\u4E5F\u6CA1\u6709\u9ED8\u8BA4\u53C2\u8003\u56FE\uFF0C\u4F60\u4E5F\u641C\u4E0D\u5230\u5408\u9002\u7684\u7ACB\u7ED8\u65F6\uFF0C
808
810
  **\u4E0D\u8981\u76F4\u63A5 draw**\u3002\u5148\u7528 ask_user \u95EE\u4E00\u53E5\u8981\u4E0D\u8981\u53C2\u8003\u56FE\uFF0C\u5E76\u7ED9\u51FA\u9009\u9879\uFF08\u4F8B\u5982\u300C\u7528\u4F60\u81EA\u5DF1\u7684\u5934\u50CF / \u7528 @\u67D0\u67D0 \u7684\u5934\u50CF /
809
811
  \u6211\u5E2E\u4F60\u4ECE\u56FE\u5E93\u627E\u4E00\u5F20 / \u4E0D\u7528\u53C2\u8003\u76F4\u63A5\u753B\u300D\uFF09\u3002\u7528\u6237\u8BF4\u300C\u4E0D\u7528 / \u968F\u4FBF / \u76F4\u63A5\u753B\u300D\u5C31\u7ACB\u523B\u5F00\u753B\uFF0C**\u4E0D\u8981\u95EE\u7B2C\u4E8C\u6B21**\u3002
810
- 9. \u3010\u8981\u753B\u300C\u6211\u300D\u5C31\u7528\u5934\u50CF\u3011\u7528\u6237\u8BF4\u300C\u753B\u6211\u300D\u300C\u7528\u6211\u7684\u5F62\u8C61\u300D\u300C\u753B\u6211\u548C xxx\u300D\u5374\u6CA1\u7ED9\u56FE\u65F6\uFF0C\u7528 get_avatar \u53D6\u4ED6\u81EA\u5DF1\u7684\u5934\u50CF\uFF1B
811
- \u8BF4\u300C\u753B @\u67D0\u67D0\u300D\u65F6\u53D6\u90A3\u4E2A\u4EBA\u7684\u5934\u50CF\uFF08\u4E00\u822C\u5DF2\u7ECF\u81EA\u52A8\u5E26\u4E0A\u4E86\uFF0C\u76F4\u63A5\u7528\u90A3\u4E2A\u7F16\u53F7\u5373\u53EF\uFF09\u3002
812
+ 9. \u3010\u5934\u50CF\u53EA\u80FD\u53D6\u300C\u6211\u300D\u548C\u300C\u88AB @ \u7684\u4EBA\u300D\u3011
813
+ - \u7528\u6237\u8BF4\u300C\u753B\u6211\u300D\u300C\u7528\u6211\u7684\u5F62\u8C61\u300D\u300C\u6211\u7684\u5934\u50CF\u300D\u2192 get_avatar \u7684 userId **\u7559\u7A7A**\uFF0C\u53D6\u4ED6\u81EA\u5DF1\u7684\u5934\u50CF\uFF1B
814
+ - \u7528\u6237 @ \u4E86\u8C01 \u2192 \u90A3\u4E2A\u4EBA\u7684\u5934\u50CF\u5DF2\u7ECF\u81EA\u52A8\u5E26\u4E0A\u4E86\uFF08\u4E0A\u4E0B\u6587\u91CC\u4F1A\u5217\u51FA\u7F16\u53F7\uFF09\uFF0C\u76F4\u63A5\u7528\u90A3\u4E2A\u7F16\u53F7\u5373\u53EF\uFF0C
815
+ \u9700\u8981\u65F6\u4E5F\u53EF\u4EE5\u628A\u4ED6\u7684 id \u6216\u6635\u79F0\u586B\u8FDB get_avatar \u518D\u53D6\u4E00\u6B21\uFF1B
816
+ - **\u9664\u6B64\u4E4B\u5916\u6CA1\u6709\u7B2C\u4E09\u79CD\u4EBA**\u3002\u6CA1 @ \u522B\u4EBA\u65F6\u7EDD\u4E0D\u8BB8\u53D6\u522B\u4EBA\u7684\u5934\u50CF\uFF0C\u4E5F\u4E0D\u8981\u81EA\u5DF1\u7F16 userId
817
+ \uFF08\u7F16\u51FA\u6765\u7684 id \u4F1A\u62FF\u5230\u5B8C\u5168\u4E0D\u76F8\u5E72\u7684\u4EBA\u7684\u5934\u50CF\uFF0C\u8FD9\u662F\u4E25\u91CD\u9519\u8BEF\uFF09\u3002
812
818
  10. \u3010\u9ED8\u8BA4\u4E0D\u6DA6\u8272\uFF0C\u7528\u6237\u70B9\u5934\u624D\u6DA6\u8272\u3011\u628A\u7528\u6237\u7684\u8BDD**\u539F\u5C01\u4E0D\u52A8**\u4EA4\u7ED9\u7ED8\u56FE\u6A21\u578B\u5C31\u662F\u6700\u597D\u7684\u9ED8\u8BA4\u503C\uFF1A
813
819
  - **\u4E0D\u8981**\u4E3B\u52A8\u6269\u5199\u3001\u4E0D\u8981\u8865\u753B\u98CE/\u5149\u7EBF/\u6750\u8D28/\u955C\u5934\u3001\u4E0D\u8981\u7FFB\u6210\u82F1\u6587\u3001\u4E0D\u8981\u52A0\u300Cmasterpiece\u300D\u8FD9\u7C7B\u8BCD\uFF1B
814
820
  - \u7528\u6237\u660E\u786E\u8BF4\u300C\u5E2E\u6211\u6DA6\u8272 / \u4F18\u5316\u4E00\u4E0B / \u5199\u8BE6\u7EC6\u70B9\u300D\u65F6\uFF0C\u4F60\u624D\u628A\u63D0\u793A\u8BCD\u5199\u5F97\u66F4\u5B8C\u6574\uFF1B
@@ -820,7 +826,7 @@ var DEFAULT_AGENT_INSTRUCTIONS = `\u4F60\u662F QQ \u7FA4\u91CC\u7684\u7ED8\u56FE
820
826
  \u5982\u679C\u4F60\u6240\u5728\u7684\u63A5\u53E3\u4E0D\u652F\u6301\u51FD\u6570\u8C03\u7528\uFF0C\u5C31**\u53EA\u8F93\u51FA\u4E00\u884C JSON** \u6765\u8C03\u7528\u5DE5\u5177\uFF0C\u4F8B\u5982\uFF1A
821
827
  {"tool":"gallery_search","args":{"keyword":"\u767D\u53D1 \u7ACB\u7ED8"}}
822
828
  {"tool":"ask_user","args":{"question":"\u60F3\u8981\u4EC0\u4E48\u753B\u98CE\uFF1F"}}
823
- {"tool":"get_avatar","args":{"userId":""}}
829
+ {"tool":"get_avatar","args":{"userId":""}}\uFF08\u7559\u7A7A = \u53D1\u6307\u4EE4\u7684\u4EBA\u81EA\u5DF1\uFF1B\u4E5F\u53EF\u4EE5\u586B\u88AB @ \u7684\u4EBA\u7684 id\uFF0C\u4E0D\u8BB8\u81EA\u5DF1\u7F16\uFF09
824
830
  {"tool":"draw","args":{"prompt":"...","references":["ref1","user1"]}}
825
831
  \u4E0D\u60F3\u8C03\u7528\u5DE5\u5177\u65F6\uFF0C\u6B63\u5E38\u8F93\u51FA\u7ED9\u7528\u6237\u770B\u7684\u4E2D\u6587\u5373\u53EF\u3002`;
826
832
  var AGENT_CONFIRM_RULE_ON = `**\u5FC5\u987B\u5148 ask_user**\uFF1A\u7528\u4E00\u53E5\u8BDD\u544A\u8BC9\u7528\u6237\u300C\u8FD9\u6B21\u4F1A\u53C2\u8003\u54EA\u51E0\u5F20\u56FE + \u5927\u81F4\u753B\u6210\u4EC0\u4E48\u6837\u300D\uFF0C
@@ -832,7 +838,7 @@ var AGENT_CONFIRM_RULE_OFF = `\u7528\u6237\u9700\u6C42\u5DF2\u7ECF\u660E\u786E\u
832
838
  var AGENT_JSON_FALLBACK_RULE = `\uFF08\u6CE8\u610F\uFF1A\u5F53\u524D\u63A5\u53E3\u4E0D\u652F\u6301\u51FD\u6570\u8C03\u7528\uFF09\u8BF7\u4F60**\u53EA\u7528\u4E00\u884C JSON** \u6765\u8C03\u7528\u5DE5\u5177\uFF0C\u4E0D\u8981\u8F93\u51FA\u522B\u7684\u5185\u5BB9\uFF1A
833
839
  {"tool":"gallery_search","args":{"keyword":"\u767D\u53D1 \u7ACB\u7ED8"}}
834
840
  {"tool":"ask_user","args":{"question":"\u60F3\u8981\u4EC0\u4E48\u753B\u98CE\uFF1F"}}
835
- {"tool":"get_avatar","args":{"userId":""}}
841
+ {"tool":"get_avatar","args":{"userId":""}}\uFF08\u7559\u7A7A = \u53D1\u6307\u4EE4\u7684\u4EBA\u81EA\u5DF1\uFF1B\u4E5F\u53EF\u4EE5\u586B\u88AB @ \u7684\u4EBA\u7684 id\uFF0C\u4E0D\u8BB8\u81EA\u5DF1\u7F16\uFF09
836
842
  {"tool":"draw","args":{"prompt":"...","references":["ref1","ref2"]}}
837
843
  \u4E0D\u9700\u8981\u8C03\u7528\u5DE5\u5177\u65F6\uFF0C\u6B63\u5E38\u8F93\u51FA\u7ED9\u7528\u6237\u770B\u7684\u4E2D\u6587\u5373\u53EF\u3002`;
838
844
  var defaultCommands = [
@@ -1636,6 +1642,21 @@ function apply(ctx, config) {
1636
1642
  let selfAvatarId = "";
1637
1643
  const atAvatarIds = [];
1638
1644
  const atAvatarById = /* @__PURE__ */ new Map();
1645
+ const avatarAllowed = /* @__PURE__ */ new Map();
1646
+ avatarAllowed.set(session.userId, "\u53D1\u6307\u4EE4\u7684\u4EBA\u81EA\u5DF1");
1647
+ for (const t of atTargets) if (t.id) avatarAllowed.set(t.id, t.name || t.id);
1648
+ const resolveAvatarUser = (raw) => {
1649
+ const text = String(raw || "").trim();
1650
+ if (!text || text === session.userId || /^(me|self|自己|我|本人|我的头像|我自己)$/i.test(text)) {
1651
+ return session.userId;
1652
+ }
1653
+ if (avatarAllowed.has(text)) return text;
1654
+ const lower = text.toLowerCase();
1655
+ for (const [id, name2] of avatarAllowed) {
1656
+ if (name2 && (name2 === text || name2.toLowerCase() === lower)) return id;
1657
+ }
1658
+ return "";
1659
+ };
1639
1660
  if (avatarOn) {
1640
1661
  const selfUrl = await fetchAvatarUrl(session, session.userId);
1641
1662
  if (selfUrl) {
@@ -1665,8 +1686,9 @@ function apply(ctx, config) {
1665
1686
  baseIds,
1666
1687
  atAvatarIds,
1667
1688
  selfAvatarId,
1668
- atTargets
1669
- }, agentCfg) },
1689
+ atTargets,
1690
+ selfUserId: session.userId
1691
+ }, agentCfg, avatarAllowed) },
1670
1692
  ...history,
1671
1693
  { role: "user", content: userInputText ? userInputText : "\uFF08\u7528\u6237\u6CA1\u6709\u9644\u52A0\u8BF4\u660E\uFF0C\u8BF7\u6309\u6307\u4EE4\u81EA\u5E26\u7684\u63D0\u793A\u8BCD\u6765\uFF09" }
1672
1694
  ];
@@ -1765,22 +1787,28 @@ ${output}` }
1765
1787
  async function toolGetAvatar(args) {
1766
1788
  if (!avatarOn) return JSON.stringify({ ok: false, error: "\u5934\u50CF\u53C2\u8003\u56FE\u529F\u80FD\u5DF2\u5173\u95ED" });
1767
1789
  const raw = String(args?.userId ?? args?.id ?? args?.user ?? "").trim();
1768
- if (!raw || /^(me|self|自己|我)$/i.test(raw) || raw === session.userId) {
1769
- if (selfAvatarId) {
1770
- return JSON.stringify({ ok: true, id: selfAvatarId, userId: session.userId, note: "\u53D1\u6307\u4EE4\u7684\u4EBA\u81EA\u5DF1\u7684\u5934\u50CF" });
1771
- }
1772
- const url2 = await fetchAvatarUrl(session, session.userId);
1773
- if (!url2) return JSON.stringify({ ok: false, error: "\u6CA1\u53D6\u5230\u4ED6\u81EA\u5DF1\u7684\u5934\u50CF\uFF08\u8FD9\u4E2A\u5E73\u53F0\u53EF\u80FD\u4E0D\u63D0\u4F9B\u5934\u50CF\u5730\u5740\uFF09" });
1774
- selfAvatarId = register("avatar", url2, "\u53D1\u6307\u4EE4\u7684\u4EBA\u7684\u5934\u50CF", "\u5934\u50CF");
1775
- return JSON.stringify({ ok: true, id: selfAvatarId, userId: session.userId, note: "\u53D1\u6307\u4EE4\u7684\u4EBA\u81EA\u5DF1\u7684\u5934\u50CF" });
1790
+ const userId = resolveAvatarUser(raw);
1791
+ if (!userId) {
1792
+ const options = [...avatarAllowed.entries()].map(([id2, name2]) => ({ userId: id2, who: name2 }));
1793
+ return JSON.stringify({
1794
+ ok: false,
1795
+ error: `\u4E0D\u8BA4\u8BC6\u300C${raw}\u300D\u3002\u5934\u50CF\u53EA\u80FD\u53D6\u300C\u53D1\u6307\u4EE4\u7684\u4EBA\u81EA\u5DF1\u300D\u548C\u300C\u4ED6\u8FD9\u6B21 @ \u5230\u7684\u4EBA\u300D\uFF0C\u4E0D\u80FD\u81EA\u5DF1\u7F16 id`,
1796
+ available: options,
1797
+ note: `\u8981\u753B\u7684\u5982\u679C\u662F\u53D1\u6307\u4EE4\u7684\u4EBA\u81EA\u5DF1\uFF0C\u628A userId \u7559\u7A7A\u518D\u8C03\u4E00\u6B21\uFF1B\u5982\u679C\u662F\u522B\u4EBA\uFF0C\u8BA9\u4ED6 @ \u90A3\u4E2A\u4EBA\u518D\u53D1\u4E00\u6B21\u6307\u4EE4\u3002\u73B0\u5728\u53EF\u53D6\uFF1A${options.map((o) => o.who).join("\u3001")}`
1798
+ });
1799
+ }
1800
+ const isSelf = userId === session.userId;
1801
+ const who = avatarAllowed.get(userId) || (isSelf ? "\u53D1\u6307\u4EE4\u7684\u4EBA\u81EA\u5DF1" : userId);
1802
+ const cached = isSelf ? selfAvatarId : atAvatarById.get(userId);
1803
+ if (cached) return JSON.stringify({ ok: true, id: cached, userId, note: `${who}\u7684\u5934\u50CF` });
1804
+ const url = await fetchAvatarUrl(session, userId);
1805
+ if (!url) {
1806
+ return JSON.stringify({ ok: false, error: `\u6CA1\u53D6\u5230\u300C${who}\u300D\u7684\u5934\u50CF\uFF08\u8FD9\u4E2A\u5E73\u53F0\u53EF\u80FD\u4E0D\u63D0\u4F9B\u5934\u50CF\u5730\u5740\uFF09` });
1776
1807
  }
1777
- const cached = atAvatarById.get(raw);
1778
- if (cached) return JSON.stringify({ ok: true, id: cached, userId: raw, note: "\u88AB @ \u7684\u4EBA\u7684\u5934\u50CF" });
1779
- const url = await fetchAvatarUrl(session, raw);
1780
- if (!url) return JSON.stringify({ ok: false, error: `\u6CA1\u53D6\u5230 ${raw} \u7684\u5934\u50CF` });
1781
- const id = register("avatar", url, `${raw} \u7684\u5934\u50CF`, "\u5934\u50CF");
1782
- atAvatarById.set(raw, id);
1783
- return JSON.stringify({ ok: true, id, userId: raw, note: "\u8FD9\u4E2A\u4EBA\u7684\u5934\u50CF" });
1808
+ const id = register("avatar", url, `${who}\u7684\u5934\u50CF`, "\u5934\u50CF");
1809
+ if (isSelf) selfAvatarId = id;
1810
+ else atAvatarById.set(userId, id);
1811
+ return JSON.stringify({ ok: true, id, userId, note: `${who}\u7684\u5934\u50CF` });
1784
1812
  }
1785
1813
  async function toolAskUser(args) {
1786
1814
  const question = cleanAskMessage(args?.question ?? args?.text ?? args?.message);
@@ -1814,20 +1842,32 @@ ${output}` }
1814
1842
  if (avatarOn) {
1815
1843
  if (avatarCfg.autoAt !== false) {
1816
1844
  for (const target of extractAtIds(String(answer || ""), session.selfId)) {
1845
+ if (!target.id) continue;
1846
+ if (!avatarAllowed.has(target.id)) avatarAllowed.set(target.id, target.name || target.id);
1847
+ const who = avatarAllowed.get(target.id) || target.id;
1817
1848
  const cached = atAvatarById.get(target.id);
1818
1849
  if (cached) {
1819
- if (!avatars.some((a) => a.id === cached)) avatars.push({ id: cached, note: `${target.name || target.id} \u7684\u5934\u50CF` });
1850
+ if (!avatars.some((a) => a.id === cached)) avatars.push({ id: cached, note: `${who} \u7684\u5934\u50CF` });
1820
1851
  continue;
1821
1852
  }
1822
1853
  const url = await fetchAvatarUrl(session, target.id);
1823
1854
  if (!url) continue;
1824
- const id = register("avatar", url, `${target.name || target.id} \u7684\u5934\u50CF`, "\u5934\u50CF");
1855
+ const id = register("avatar", url, `${who} \u7684\u5934\u50CF`, "\u5934\u50CF");
1825
1856
  atAvatarById.set(target.id, id);
1826
- avatars.push({ id, note: `${target.name || target.id} \u7684\u5934\u50CF` });
1857
+ if (!atAvatarIds.includes(id)) atAvatarIds.push(id);
1858
+ avatars.push({ id, note: `${who} \u7684\u5934\u50CF` });
1827
1859
  }
1828
1860
  }
1829
- if (/头像|我的形象|画我|我自己/.test(text) && selfAvatarId) {
1830
- if (!avatars.some((a) => a.id === selfAvatarId)) avatars.push({ id: selfAvatarId, note: "\u53D1\u6307\u4EE4\u7684\u4EBA\u81EA\u5DF1\u7684\u5934\u50CF" });
1861
+ if (/头像|我的形象|画我|我自己/.test(text) && !avatars.some((a) => a.id === selfAvatarId)) {
1862
+ let id = selfAvatarId;
1863
+ if (!id) {
1864
+ const url = await fetchAvatarUrl(session, session.userId);
1865
+ if (url) {
1866
+ id = register("avatar", url, "\u53D1\u6307\u4EE4\u7684\u4EBA\u7684\u5934\u50CF", "\u5934\u50CF");
1867
+ selfAvatarId = id;
1868
+ }
1869
+ }
1870
+ if (id) avatars.push({ id, note: "\u53D1\u6307\u4EE4\u7684\u4EBA\u81EA\u5DF1\u7684\u5934\u50CF" });
1831
1871
  }
1832
1872
  }
1833
1873
  return JSON.stringify({
@@ -1892,7 +1932,7 @@ Negative prompt: ${String(args.negative_prompt).trim()}` : prompt;
1892
1932
  });
1893
1933
  }
1894
1934
  }
1895
- function buildAgentSystemPrompt(cmdConfig, userInputText, refCount, refs, agentCfg) {
1935
+ function buildAgentSystemPrompt(cmdConfig, userInputText, refCount, refs, agentCfg, avatarAllowed) {
1896
1936
  const instructions = renderAgentInstructions(agentCfg.instructions || DEFAULT_AGENT_INSTRUCTIONS, {
1897
1937
  command: cmdConfig.name || "",
1898
1938
  prompt: cmdConfig.prompt || "",
@@ -1911,15 +1951,29 @@ Negative prompt: ${String(args.negative_prompt).trim()}` : prompt;
1911
1951
  `- \u56FE\u5E93\u91CC\u53EF\u4EE5\u641C\u5230\u7684\u53C2\u8003\u56FE\uFF1A${refCount} \u5F20`
1912
1952
  ];
1913
1953
  if (refs.baseIds.length) lines.push(`- \u6307\u4EE4\u9ED8\u8BA4\u53C2\u8003\u56FE\uFF08\u5DF2\u81EA\u52A8\u5E26\u4E0A\uFF0C\u4E0D\u7528\u518D\u586B\uFF09\uFF1A${refs.baseIds.join("\u3001")}`);
1914
- if (refs.selfAvatarId) lines.push(`- \u53D1\u6307\u4EE4\u7684\u4EBA\u81EA\u5DF1\u7684\u5934\u50CF\uFF1A${refs.selfAvatarId}\uFF08\u4ED6\u60F3\u753B\u300C\u6211\u300D\u65F6\u7528\u8FD9\u4E2A\uFF0C\u6216\u7528 get_avatar \u53D6\uFF09`);
1954
+ if (cmdConfig.custom) lines.push("- \u8FD9\u662F\u81EA\u5B9A\u4E49\u6307\u4EE4\uFF1A\u7528\u6237\u6CA1\u8BF4\u6E05\u695A\u8981\u753B\u4EC0\u4E48\u65F6\uFF0C\u5148\u7528 ask_user \u95EE\u4E00\u53E5");
1955
+ const avatarLines = ["", "## \u53EF\u53D6\u5934\u50CF\u7684\u4EBA\uFF08get_avatar \u53EA\u8BA4\u8FD9\u4E9B\uFF09"];
1956
+ if (refs.selfAvatarId) {
1957
+ avatarLines.push(`- \u53D1\u6307\u4EE4\u7684\u4EBA\u81EA\u5DF1\uFF1A\u7F16\u53F7 ${refs.selfAvatarId}\uFF08\u4ED6\u60F3\u753B\u300C\u6211\u300D\u65F6\u7528\u8FD9\u4E2A\u7F16\u53F7\uFF0C\u6216 get_avatar \u7684 userId \u7559\u7A7A\u518D\u53D6\u4E00\u6B21\uFF09`);
1958
+ } else {
1959
+ avatarLines.push("- \u53D1\u6307\u4EE4\u7684\u4EBA\u81EA\u5DF1\uFF1Aget_avatar \u7684 userId **\u7559\u7A7A**\u5373\u53EF\uFF08\u8FD9\u8F6E\u8FD8\u6CA1\u63D0\u524D\u53D6\uFF0C\u53D6\u4E86\u624D\u6709\u7F16\u53F7\uFF09");
1960
+ }
1915
1961
  if (refs.atAvatarIds.length) {
1916
- const who = refs.atTargets.map((t) => `${t.name || t.id}`).join("\u3001");
1917
- lines.push(`- \u88AB @ \u7684\u4EBA\u7684\u5934\u50CF\uFF08\u5DF2\u81EA\u52A8\u5E26\u4E0A\uFF0C\u4E0D\u7528\u518D\u586B\uFF09\uFF1A${refs.atAvatarIds.join("\u3001")}\uFF08\u5206\u522B\u662F ${who} \u7684\u5934\u50CF\uFF09`);
1962
+ const pairs = refs.atTargets.map((t, i) => `${refs.atAvatarIds[i] || "?"}=${t.name || t.id}`).join("\u3001");
1963
+ avatarLines.push(`- \u88AB @ \u7684\u4EBA\uFF08\u5DF2\u81EA\u52A8\u5E26\u4E0A\uFF0C\u4E0D\u7528\u518D\u586B\uFF09\uFF1A${pairs}`);
1918
1964
  } else if (refs.atTargets.length) {
1919
- lines.push(`- \u7528\u6237 @ \u4E86 ${refs.atTargets.map((t) => t.name || t.id).join("\u3001")}\uFF0C\u4F46\u6CA1\u53D6\u5230\u4ED6\u4EEC\u7684\u5934\u50CF\uFF0C\u9700\u8981\u7684\u8BDD\u7528 get_avatar \u518D\u8BD5`);
1965
+ avatarLines.push(`- \u88AB @ \u7684\u4EBA\uFF1A${refs.atTargets.map((t) => `${t.name || t.id}\uFF08id=${t.id}\uFF09`).join("\u3001")}\uFF0C\u8FD8\u6CA1\u53D6\u5230\u5934\u50CF\uFF0C\u9700\u8981\u5C31\u7528 get_avatar \u586B\u4ED6\u7684 id \u518D\u8BD5`);
1920
1966
  }
1921
- if (cmdConfig.custom) lines.push("- \u8FD9\u662F\u81EA\u5B9A\u4E49\u6307\u4EE4\uFF1A\u7528\u6237\u6CA1\u8BF4\u6E05\u695A\u8981\u753B\u4EC0\u4E48\u65F6\uFF0C\u5148\u7528 ask_user \u95EE\u4E00\u53E5");
1922
- return instructions + lines.join("\n");
1967
+ const others = [...(avatarAllowed || /* @__PURE__ */ new Map()).entries()].filter(([id]) => id !== refs.selfUserId && !refs.atTargets.some((t) => t.id === id));
1968
+ if (others.length) {
1969
+ avatarLines.push(`- \u540E\u7EED\u5BF9\u8BDD\u91CC\u53C8 @ \u5230\u7684\uFF1A${others.map(([id, name2]) => `${name2}\uFF08id=${id}\uFF09`).join("\u3001")}`);
1970
+ }
1971
+ if (refs.atTargets.length || others.length) {
1972
+ avatarLines.push("- \u4E0A\u9762\u8FD9\u4E9B\u4EBA\u4E4B\u5916\u7684 id **\u4E00\u5F8B\u4F1A\u88AB\u62D2\u7EDD**\uFF0C\u4E0D\u8981\u4ECE\u56FE\u5E93\u63CF\u8FF0\u3001\u5386\u53F2\u6D88\u606F\u6216\u4EFB\u4F55\u522B\u5904\u6284\u4E00\u4E2A id \u5F53\u5934\u50CF\u7528");
1973
+ } else {
1974
+ avatarLines.push("- \u8FD9\u6B21\u6CA1\u6709 @ \u4EFB\u4F55\u4EBA\uFF0C\u6240\u4EE5**\u53EA\u80FD\u53D6\u53D1\u6307\u4EE4\u8005\u81EA\u5DF1\u7684\u5934\u50CF**\uFF08userId \u7559\u7A7A\uFF09\u3002\u4E0D\u8981\u586B\u4EFB\u4F55 id");
1975
+ }
1976
+ return instructions + lines.join("\n") + avatarLines.join("\n");
1923
1977
  }
1924
1978
  async function requestAgentChat(messages, tools) {
1925
1979
  const agentCfg = config.agent || {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-image-prompt",
3
3
  "description": "多功能 AI 图像处理插件:Agent 模式把「查不查图库 / 要不要追问 / 什么时候开画」全交给模型自己决定,支持手办化、Coser 化、Minecraft 化等 47 种风格与自定义提示词",
4
- "version": "2.1.0",
4
+ "version": "2.1.1",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
package/readme.md CHANGED
@@ -53,7 +53,7 @@
53
53
  模型拿到 4 个工具,自己决定怎么走:
54
54
  ├─ gallery_search(关键词) → 自己想关键词、自己搜图库
55
55
  ├─ ask_user(问题) → 自己开口问、自己等用户回话
56
- ├─ get_avatar(用户id) → 取群友头像当参考图
56
+ ├─ get_avatar(留空/被@的人) → 取头像当参考图(只认自己和被 @ 的人)
57
57
  └─ draw(prompt, references) → 自己写提示词、自己挑参考图、开画
58
58
  ↓
59
59
  模型说最后一句话 → 发给用户
@@ -68,7 +68,7 @@
68
68
  |---|---|
69
69
  | `gallery_search` | 按关键词搜参考图库(关键词由模型自己拟,空格分隔多词)。返回每条的 `id`,写进 `draw` 的 `references` |
70
70
  | `ask_user` | 把一句话发给用户**并等他的下一条消息**(他发的图片也会一起带回来,会拿到新编号) |
71
- | `get_avatar` | 取某个人的头像当参考图。`userId` 留空 = 取发指令的人自己;也可以填被 @ 的人的 id |
71
+ | `get_avatar` | 取头像当参考图。**只能取两种人**:发指令的人自己(`userId` 留空),或他这次明确 @ 到的人(填 id/昵称)。其它 id 一律拒绝 |
72
72
  | `draw` | 真正开画。传最终提示词 + 参考图编号,画好自动发到群里 |
73
73
 
74
74
  ### 工作规则(写进提示词,由模型执行)
@@ -140,6 +140,23 @@
140
140
  > 平台不提供头像地址时,日志里不会有报错,只是这次没有头像参考图。
141
141
  > 想换头像服务就填「自定义头像地址模板」。
142
142
 
143
+ **只有两种人的头像能取:你自己,和你这次明确 @ 到的人。**
144
+
145
+ 头像是按 id 拼 URL 拿的,而 QQ 的头像服务对**随便一个 id** 都会返回一张图片
146
+ (查无此人时给的是默认头像)。也就是说模型只要编一个 id,就能「成功」取到某个
147
+ 完全不相干的人的头像,而你根本看不出来。
148
+
149
+ 所以 `get_avatar` 有白名单,只认:
150
+
151
+ | 能取 | 怎么填 |
152
+ |---|---|
153
+ | 发指令的人自己 | `userId` **留空**(或写 me / 我 / 自己) |
154
+ | 这次消息里 @ 到的人 | 填上下文里列出的 id 或昵称 |
155
+
156
+ 没 @ 任何人时,系统提示词会直接写明「这次只能取发指令者自己的头像」;
157
+ 模型要是硬填一个不在名单里的 id,工具会返回 `不认识「xxx」` 并列出可取的人,
158
+ **不会**去拼那个 id 的地址。想画别人,就在指令里 @ 他。
159
+
143
160
  ### 🖼️ 参考图片组 / 图库
144
161
 
145
162
  参考图以**分组**的方式注册,agent 通过 `gallery_search` 按需搜。
package/src/index.ts CHANGED
@@ -250,11 +250,11 @@ export function buildAgentTools(): any[] {
250
250
  type: 'function',
251
251
  function: {
252
252
  name: 'get_avatar',
253
- description: '取某个人的头像当参考图,返回它的编号。userId 留空 = 取发指令的人自己的头像;也可以填「被 @ 的人」列表里的 id。用户说「画我」「用我的头像」「画 @某某」时用它。',
253
+ description: '取头像当参考图,返回它的编号。**只能取两种人的头像**:发指令的人自己(userId 留空/写 me),或他这次消息里明确 @ 到的人(填上下文「可取头像的人」里列出的 id/名字)。其它 id 一律会被拒绝。',
254
254
  parameters: {
255
255
  type: 'object',
256
256
  properties: {
257
- userId: { type: 'string', description: '要取头像的用户 id(取自上下文里的「被 @ 的人」),留空或写 me/self 表示发指令的人自己' },
257
+ userId: { type: 'string', description: '留空或写 me/self/我 = 发指令的人自己;也可以填上下文里「可取头像的人」列出的 id 或昵称(即被 @ 的人)。**不要自己猜 id**' },
258
258
  },
259
259
  required: [],
260
260
  },
@@ -1161,7 +1161,9 @@ const DEFAULT_AGENT_INSTRUCTIONS = `你是 QQ 群里的绘图助手。用户发
1161
1161
  - gallery_search:按关键词搜索参考图库。**涉及具体角色/立绘时必须先搜一次**。
1162
1162
  多个关键词用空格分隔(如「白发 立绘」),全部命中的排在最前;搜不到就换个词再搜。
1163
1163
  - ask_user:把问题发给用户**并等他回复**(他发的图也会一起带回来)。
1164
- - get_avatar:取某个人的头像当参考图。userId 留空取发指令者自己;也可以取被 @ 的人的头像。
1164
+ - get_avatar:取头像当参考图。**只能取两种人**:发指令的人自己(userId 留空或写 me),
1165
+ 以及他这次消息里明确 @ 到的人(填上下文「可取头像的人」里列出的 id 或昵称)。
1166
+ 其它任何 id 都会被拒绝,不要猜。
1165
1167
  - draw:真正开始画。把 prompt 和挑中的参考图编号交进去,画好会自动发到群里。
1166
1168
 
1167
1169
  工作规则:
@@ -1183,8 +1185,12 @@ const DEFAULT_AGENT_INSTRUCTIONS = `你是 QQ 群里的绘图助手。用户发
1183
1185
  8. 【没指定参考图时先问一句】用户这次没发图、没 @ 人、指令本身也没有默认参考图,你也搜不到合适的立绘时,
1184
1186
  **不要直接 draw**。先用 ask_user 问一句要不要参考图,并给出选项(例如「用你自己的头像 / 用 @某某 的头像 /
1185
1187
  我帮你从图库找一张 / 不用参考直接画」)。用户说「不用 / 随便 / 直接画」就立刻开画,**不要问第二次**。
1186
- 9. 【要画「我」就用头像】用户说「画我」「用我的形象」「画我和 xxx」却没给图时,用 get_avatar 取他自己的头像;
1187
- 说「画 @某某」时取那个人的头像(一般已经自动带上了,直接用那个编号即可)。
1188
+ 9. 【头像只能取「我」和「被 @ 的人」】
1189
+ - 用户说「画我」「用我的形象」「我的头像」→ get_avatar 的 userId **留空**,取他自己的头像;
1190
+ - 用户 @ 了谁 → 那个人的头像已经自动带上了(上下文里会列出编号),直接用那个编号即可,
1191
+ 需要时也可以把他的 id 或昵称填进 get_avatar 再取一次;
1192
+ - **除此之外没有第三种人**。没 @ 别人时绝不许取别人的头像,也不要自己编 userId
1193
+ (编出来的 id 会拿到完全不相干的人的头像,这是严重错误)。
1188
1194
  10. 【默认不润色,用户点头才润色】把用户的话**原封不动**交给绘图模型就是最好的默认值:
1189
1195
  - **不要**主动扩写、不要补画风/光线/材质/镜头、不要翻成英文、不要加「masterpiece」这类词;
1190
1196
  - 用户明确说「帮我润色 / 优化一下 / 写详细点」时,你才把提示词写得更完整;
@@ -1196,7 +1202,7 @@ const DEFAULT_AGENT_INSTRUCTIONS = `你是 QQ 群里的绘图助手。用户发
1196
1202
  如果你所在的接口不支持函数调用,就**只输出一行 JSON** 来调用工具,例如:
1197
1203
  {"tool":"gallery_search","args":{"keyword":"白发 立绘"}}
1198
1204
  {"tool":"ask_user","args":{"question":"想要什么画风?"}}
1199
- {"tool":"get_avatar","args":{"userId":""}}
1205
+ {"tool":"get_avatar","args":{"userId":""}}(留空 = 发指令的人自己;也可以填被 @ 的人的 id,不许自己编)
1200
1206
  {"tool":"draw","args":{"prompt":"...","references":["ref1","user1"]}}
1201
1207
  不想调用工具时,正常输出给用户看的中文即可。`
1202
1208
 
@@ -1212,7 +1218,7 @@ const AGENT_CONFIRM_RULE_OFF = `用户需求已经明确的,直接调用 draw
1212
1218
  const AGENT_JSON_FALLBACK_RULE = `(注意:当前接口不支持函数调用)请你**只用一行 JSON** 来调用工具,不要输出别的内容:
1213
1219
  {"tool":"gallery_search","args":{"keyword":"白发 立绘"}}
1214
1220
  {"tool":"ask_user","args":{"question":"想要什么画风?"}}
1215
- {"tool":"get_avatar","args":{"userId":""}}
1221
+ {"tool":"get_avatar","args":{"userId":""}}(留空 = 发指令的人自己;也可以填被 @ 的人的 id,不许自己编)
1216
1222
  {"tool":"draw","args":{"prompt":"...","references":["ref1","ref2"]}}
1217
1223
  不需要调用工具时,正常输出给用户看的中文即可。`
1218
1224
 
@@ -2144,6 +2150,28 @@ export function apply(ctx: Context, config: CommandConfig) {
2144
2150
  let selfAvatarId = ''
2145
2151
  const atAvatarIds: string[] = []
2146
2152
  const atAvatarById = new Map<string, string>()
2153
+ /**
2154
+ * 本次**允许**取头像的人:只有发指令者自己 + 他明确 @ 到的人。
2155
+ * 为什么必须挡:头像是按 id 拼 URL 的,而 QQ 的头像服务对**任何** id 都会返回一张图
2156
+ * (查无此人时给默认头像),所以模型随手编一个 id 也能「成功」拿到陌生人的头像。
2157
+ * 没有明确 @ 就不是明确指定,一律拒绝。
2158
+ */
2159
+ const avatarAllowed = new Map<string, string>()
2160
+ avatarAllowed.set(session.userId, '发指令的人自己')
2161
+ for (const t of atTargets) if (t.id) avatarAllowed.set(t.id, t.name || t.id)
2162
+ /** 把「我 / me / 空 / 昵称」解析成一个被允许的 userId,解析不出来返回空 */
2163
+ const resolveAvatarUser = (raw: string): string => {
2164
+ const text = String(raw || '').trim()
2165
+ if (!text || text === session.userId || /^(me|self|自己|我|本人|我的头像|我自己)$/i.test(text)) {
2166
+ return session.userId
2167
+ }
2168
+ if (avatarAllowed.has(text)) return text
2169
+ const lower = text.toLowerCase()
2170
+ for (const [id, name] of avatarAllowed) {
2171
+ if (name && (name === text || name.toLowerCase() === lower)) return id
2172
+ }
2173
+ return ''
2174
+ }
2147
2175
  if (avatarOn) {
2148
2176
  const selfUrl = await fetchAvatarUrl(session, session.userId)
2149
2177
  if (selfUrl) {
@@ -2171,8 +2199,8 @@ export function apply(ctx: Context, config: CommandConfig) {
2171
2199
 
2172
2200
  const messages: any[] = [
2173
2201
  { role: 'system', content: buildAgentSystemPrompt(cmdConfig, userInputText, pool.length, {
2174
- userIds, baseIds, atAvatarIds, selfAvatarId, atTargets,
2175
- }, agentCfg) },
2202
+ userIds, baseIds, atAvatarIds, selfAvatarId, atTargets, selfUserId: session.userId,
2203
+ }, agentCfg, avatarAllowed) },
2176
2204
  ...history,
2177
2205
  { role: 'user', content: userInputText ? userInputText : '(用户没有附加说明,请按指令自带的提示词来)' },
2178
2206
  ]
@@ -2279,28 +2307,36 @@ export function apply(ctx: Context, config: CommandConfig) {
2279
2307
  })
2280
2308
  }
2281
2309
 
2282
- /** 取某人头像:留空取发指令的人,也可以取被 @ 的人 */
2310
+ /** 取头像:只能是发指令的人自己,或他这次明确 @ 到的人 */
2283
2311
  async function toolGetAvatar(args: any): Promise<string> {
2284
2312
  if (!avatarOn) return JSON.stringify({ ok: false, error: '头像参考图功能已关闭' })
2285
2313
  const raw = String(args?.userId ?? args?.id ?? args?.user ?? '').trim()
2286
- if (!raw || /^(me|self|自己|我)$/i.test(raw) || raw === session.userId) {
2287
- if (selfAvatarId) {
2288
- return JSON.stringify({ ok: true, id: selfAvatarId, userId: session.userId, note: '发指令的人自己的头像' })
2289
- }
2290
- const url = await fetchAvatarUrl(session, session.userId)
2291
- if (!url) return JSON.stringify({ ok: false, error: '没取到他自己的头像(这个平台可能不提供头像地址)' })
2292
- selfAvatarId = register('avatar', url, '发指令的人的头像', '头像')
2293
- return JSON.stringify({ ok: true, id: selfAvatarId, userId: session.userId, note: '发指令的人自己的头像' })
2314
+ const userId = resolveAvatarUser(raw)
2315
+
2316
+ if (!userId) {
2317
+ const options = [...avatarAllowed.entries()].map(([id, name]) => ({ userId: id, who: name }))
2318
+ return JSON.stringify({
2319
+ ok: false,
2320
+ error: `不认识「${raw}」。头像只能取「发指令的人自己」和「他这次 @ 到的人」,不能自己编 id`,
2321
+ available: options,
2322
+ note: `要画的如果是发指令的人自己,把 userId 留空再调一次;如果是别人,让他 @ 那个人再发一次指令。现在可取:${options.map(o => o.who).join('、')}`,
2323
+ })
2294
2324
  }
2295
2325
 
2296
- const cached = atAvatarById.get(raw)
2297
- if (cached) return JSON.stringify({ ok: true, id: cached, userId: raw, note: '被 @ 的人的头像' })
2326
+ const isSelf = userId === session.userId
2327
+ const who = avatarAllowed.get(userId) || (isSelf ? '发指令的人自己' : userId)
2328
+
2329
+ const cached = isSelf ? selfAvatarId : atAvatarById.get(userId)
2330
+ if (cached) return JSON.stringify({ ok: true, id: cached, userId, note: `${who}的头像` })
2298
2331
 
2299
- const url = await fetchAvatarUrl(session, raw)
2300
- if (!url) return JSON.stringify({ ok: false, error: `没取到 ${raw} 的头像` })
2301
- const id = register('avatar', url, `${raw} 的头像`, '头像')
2302
- atAvatarById.set(raw, id)
2303
- return JSON.stringify({ ok: true, id, userId: raw, note: '这个人的头像' })
2332
+ const url = await fetchAvatarUrl(session, userId)
2333
+ if (!url) {
2334
+ return JSON.stringify({ ok: false, error: `没取到「${who}」的头像(这个平台可能不提供头像地址)` })
2335
+ }
2336
+ const id = register('avatar', url, `${who}的头像`, '头像')
2337
+ if (isSelf) selfAvatarId = id
2338
+ else atAvatarById.set(userId, id)
2339
+ return JSON.stringify({ ok: true, id, userId, note: `${who}的头像` })
2304
2340
  }
2305
2341
 
2306
2342
  /** 提问并等用户的下一条消息(图也会一起带回来) */
@@ -2341,21 +2377,35 @@ export function apply(ctx: Context, config: CommandConfig) {
2341
2377
  if (avatarOn) {
2342
2378
  if (avatarCfg.autoAt !== false) {
2343
2379
  for (const target of extractAtIds(String(answer || ''), session.selfId)) {
2380
+ if (!target.id) continue
2381
+ // 他在回复里明确 @ 了,这个人从此也进入白名单
2382
+ if (!avatarAllowed.has(target.id)) avatarAllowed.set(target.id, target.name || target.id)
2383
+ const who = avatarAllowed.get(target.id) || target.id
2344
2384
  const cached = atAvatarById.get(target.id)
2345
2385
  if (cached) {
2346
- if (!avatars.some(a => a.id === cached)) avatars.push({ id: cached, note: `${target.name || target.id} 的头像` })
2386
+ if (!avatars.some(a => a.id === cached)) avatars.push({ id: cached, note: `${who} 的头像` })
2347
2387
  continue
2348
2388
  }
2349
2389
  const url = await fetchAvatarUrl(session, target.id)
2350
2390
  if (!url) continue
2351
- const id = register('avatar', url, `${target.name || target.id} 的头像`, '头像')
2391
+ const id = register('avatar', url, `${who} 的头像`, '头像')
2352
2392
  atAvatarById.set(target.id, id)
2353
- avatars.push({ id, note: `${target.name || target.id} 的头像` })
2393
+ if (!atAvatarIds.includes(id)) atAvatarIds.push(id)
2394
+ avatars.push({ id, note: `${who} 的头像` })
2354
2395
  }
2355
2396
  }
2356
2397
  // 「用我的头像」「画我」——就算关闭了 autoSelf,用户明确开口了就给他
2357
- if (/头像|我的形象|画我|我自己/.test(text) && selfAvatarId) {
2358
- if (!avatars.some(a => a.id === selfAvatarId)) avatars.push({ id: selfAvatarId, note: '发指令的人自己的头像' })
2398
+ if (/头像|我的形象|画我|我自己/.test(text) && !avatars.some(a => a.id === selfAvatarId)) {
2399
+ // 用户明确开口要自己的头像,没登记过就现取一次
2400
+ let id = selfAvatarId
2401
+ if (!id) {
2402
+ const url = await fetchAvatarUrl(session, session.userId)
2403
+ if (url) {
2404
+ id = register('avatar', url, '发指令的人的头像', '头像')
2405
+ selfAvatarId = id
2406
+ }
2407
+ }
2408
+ if (id) avatars.push({ id, note: '发指令的人自己的头像' })
2359
2409
  }
2360
2410
  }
2361
2411
 
@@ -2454,8 +2504,10 @@ export function apply(ctx: Context, config: CommandConfig) {
2454
2504
  atAvatarIds: string[]
2455
2505
  selfAvatarId: string
2456
2506
  atTargets: { id: string, name: string }[]
2507
+ selfUserId: string
2457
2508
  },
2458
- agentCfg: AgentConfig
2509
+ agentCfg: AgentConfig,
2510
+ avatarAllowed?: Map<string, string>
2459
2511
  ): string {
2460
2512
  const instructions = renderAgentInstructions(agentCfg.instructions || DEFAULT_AGENT_INSTRUCTIONS, {
2461
2513
  command: cmdConfig.name || '',
@@ -2474,16 +2526,33 @@ export function apply(ctx: Context, config: CommandConfig) {
2474
2526
  `- 图库里可以搜到的参考图:${refCount} 张`,
2475
2527
  ]
2476
2528
  if (refs.baseIds.length) lines.push(`- 指令默认参考图(已自动带上,不用再填):${refs.baseIds.join('、')}`)
2477
- if (refs.selfAvatarId) lines.push(`- 发指令的人自己的头像:${refs.selfAvatarId}(他想画「我」时用这个,或用 get_avatar 取)`)
2529
+ if (cmdConfig.custom) lines.push('- 这是自定义指令:用户没说清楚要画什么时,先用 ask_user 问一句')
2530
+
2531
+ // 头像:只列「发指令的人自己」+「被 @ 的人」,并写明其它 id 一律拒绝
2532
+ const avatarLines: string[] = ['', '## 可取头像的人(get_avatar 只认这些)']
2533
+ if (refs.selfAvatarId) {
2534
+ avatarLines.push(`- 发指令的人自己:编号 ${refs.selfAvatarId}(他想画「我」时用这个编号,或 get_avatar 的 userId 留空再取一次)`)
2535
+ } else {
2536
+ avatarLines.push('- 发指令的人自己:get_avatar 的 userId **留空**即可(这轮还没提前取,取了才有编号)')
2537
+ }
2478
2538
  if (refs.atAvatarIds.length) {
2479
- const who = refs.atTargets.map(t => `${t.name || t.id}`).join('、')
2480
- lines.push(`- 被 @ 的人的头像(已自动带上,不用再填):${refs.atAvatarIds.join('、')}(分别是 ${who} 的头像)`)
2539
+ const pairs = refs.atTargets.map((t, i) => `${refs.atAvatarIds[i] || '?'}=${t.name || t.id}`).join('、')
2540
+ avatarLines.push(`- 被 @ 的人(已自动带上,不用再填):${pairs}`)
2481
2541
  } else if (refs.atTargets.length) {
2482
- lines.push(`- 用户 @ 了 ${refs.atTargets.map(t => t.name || t.id).join('、')},但没取到他们的头像,需要的话用 get_avatar 再试`)
2542
+ avatarLines.push(`- 被 @ 的人:${refs.atTargets.map(t => `${t.name || t.id}(id=${t.id})`).join('、')},还没取到头像,需要就用 get_avatar 填他的 id 再试`)
2543
+ }
2544
+ const others = [...(avatarAllowed || new Map<string, string>()).entries()]
2545
+ .filter(([id]) => id !== refs.selfUserId && !refs.atTargets.some(t => t.id === id))
2546
+ if (others.length) {
2547
+ avatarLines.push(`- 后续对话里又 @ 到的:${others.map(([id, name]) => `${name}(id=${id})`).join('、')}`)
2548
+ }
2549
+ if (refs.atTargets.length || others.length) {
2550
+ avatarLines.push('- 上面这些人之外的 id **一律会被拒绝**,不要从图库描述、历史消息或任何别处抄一个 id 当头像用')
2551
+ } else {
2552
+ avatarLines.push('- 这次没有 @ 任何人,所以**只能取发指令者自己的头像**(userId 留空)。不要填任何 id')
2483
2553
  }
2484
- if (cmdConfig.custom) lines.push('- 这是自定义指令:用户没说清楚要画什么时,先用 ask_user 问一句')
2485
2554
 
2486
- return instructions + lines.join('\n')
2555
+ return instructions + lines.join('\n') + avatarLines.join('\n')
2487
2556
  }
2488
2557
 
2489
2558
  /** 发一次 agent 请求,返回助手消息与它想调的工具 */