@zaofan/dsh-qqbot 1.11.12 → 1.11.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/qqbot-settings.js +8213 -8213
- package/dist/channel-tools.d.ts +4 -0
- package/dist/channel-tools.d.ts.map +1 -1
- package/dist/channel-tools.js +245 -65
- package/dist/channel-tools.js.map +1 -1
- package/dist/session/session-manager.d.ts.map +1 -1
- package/dist/session/session-manager.js +41 -2
- package/dist/session/session-manager.js.map +1 -1
- package/package.json +1 -1
- package/settings-host.js +40 -11
package/dist/channel-tools.d.ts
CHANGED
|
@@ -8,6 +8,8 @@ export interface QQChannel {
|
|
|
8
8
|
}
|
|
9
9
|
export declare const name = "qqbot-channel-tools";
|
|
10
10
|
export declare const inject: string[];
|
|
11
|
+
/** 某实例的标识:优先**人设 id**(config.preset),没配才回落工作区目录名 */
|
|
12
|
+
export declare function instanceTagOfManager(m: unknown): string;
|
|
11
13
|
export declare function setContextlessSmartGlobal(v: boolean): void;
|
|
12
14
|
/** 读登记(session-manager 的 pre-step 用) */
|
|
13
15
|
export declare function readContextPending(dataRoot: string, sessionKey: string): {
|
|
@@ -89,6 +91,8 @@ export declare function syncContextTools(agentCtx: unknown, shouldHave: boolean,
|
|
|
89
91
|
dataRoot?: string;
|
|
90
92
|
sessionKey?: string;
|
|
91
93
|
}, reminderText?: string): void;
|
|
94
|
+
/** 按当前会话的「已领取集合」重设一次剪枝(幂等:deny 名单没变就不动) */
|
|
95
|
+
export declare function applyExtToolVisibility(agentCtx: unknown, selfTag?: string): void;
|
|
92
96
|
export declare function ensureExtToolsGlobal(globalCtx: unknown): void;
|
|
93
97
|
export declare function ensureContextToolsGlobal(globalCtx: unknown): void;
|
|
94
98
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"channel-tools.d.ts","sourceRoot":"","sources":["../src/channel-tools.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAInD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AACnE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AA8FlE,6DAA6D;AAC7D,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,cAAc,CAAC;IACxB,MAAM,EAAE,WAAW,CAAC;CACrB;AAED,eAAO,MAAM,IAAI,wBAAwB,CAAC;AAI1C,eAAO,MAAM,MAAM,UAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"channel-tools.d.ts","sourceRoot":"","sources":["../src/channel-tools.ts"],"names":[],"mappings":"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"}
|
package/dist/channel-tools.js
CHANGED
|
@@ -142,7 +142,7 @@ function workspaceTagOf(cwd) {
|
|
|
142
142
|
return 'ws' + Math.abs(hsh).toString(36).slice(0, 6);
|
|
143
143
|
}
|
|
144
144
|
/** 某实例的标识:优先**人设 id**(config.preset),没配才回落工作区目录名 */
|
|
145
|
-
function instanceTagOfManager(m) {
|
|
145
|
+
export function instanceTagOfManager(m) {
|
|
146
146
|
const cfg = m?.config;
|
|
147
147
|
const p = String(cfg?.preset ?? '').trim();
|
|
148
148
|
return p || workspaceTagOf(m?.cwd);
|
|
@@ -3766,8 +3766,77 @@ export function syncContextTools(agentCtx, shouldHave, env, reminderText) {
|
|
|
3766
3766
|
const CTX_TOOL_NAMES = ['context_memo', 'context_compact', 'context_drop', 'qq_group_admin'];
|
|
3767
3767
|
/** 本轮扫描到的「可领取」扩展工具(full 名 → 条目) */
|
|
3768
3768
|
const extToolIndex = new Map();
|
|
3769
|
-
/** 已领取到某个会话作用域的工具(agentCtx → full → disposer
|
|
3769
|
+
/** 已领取到某个会话作用域的工具(agentCtx → full → disposer)—— metaMode 下用 */
|
|
3770
3770
|
const extGranted = new WeakMap();
|
|
3771
|
+
/**
|
|
3772
|
+
* 「默认全剪枝 + 按需放开」的授予集合(2026-10-28,主人定)。
|
|
3773
|
+
*
|
|
3774
|
+
* 目标形态:`expose:'all'` 的工具**始终全局注册**(注册表不动、不抖),
|
|
3775
|
+
* 但每个会话**默认被 deny 掉全部**(模型清单里连名字都看不到 = 零 token),
|
|
3776
|
+
* 只有 `gain_qq_tools.enable` 过的名字才从 deny 名单里拿掉。
|
|
3777
|
+
*
|
|
3778
|
+
* 复用的是仓库里已有的 `tools.restrict({ deny })` + `liftRestriction`(见 restrictContextTools 注释),
|
|
3779
|
+
* 不新造机制。
|
|
3780
|
+
*/
|
|
3781
|
+
const extGrantSet = new WeakMap();
|
|
3782
|
+
const extRestrictState2 = new WeakMap();
|
|
3783
|
+
/** 见过的会话作用域 → 它的 selfTag(扫描完成后要按新索引**重放一次**剪枝) */
|
|
3784
|
+
const extSeenCtx = new Map();
|
|
3785
|
+
/** 按当前会话的「已领取集合」重设一次剪枝(幂等:deny 名单没变就不动) */
|
|
3786
|
+
export function applyExtToolVisibility(agentCtx, selfTag) {
|
|
3787
|
+
try {
|
|
3788
|
+
const ctx = agentCtx;
|
|
3789
|
+
if (!ctx || typeof ctx !== 'object')
|
|
3790
|
+
return;
|
|
3791
|
+
// ⚠️ 2026-10-28(主人实测:enable 一个 standard 后,自己实例那 4 个被移除):
|
|
3792
|
+
// gain_qq_tools 内部调本函数时**不传 selfTag**,原来会把 ownSuffix 算成空串 ⇒
|
|
3793
|
+
// 连带把本实例自己的那份也 deny 掉。现在:不传就**沿用上次记住的 tag**。
|
|
3794
|
+
const prevTag = extSeenCtx.get(ctx) ?? '';
|
|
3795
|
+
const tag = selfTag !== undefined ? String(selfTag) : prevTag;
|
|
3796
|
+
extSeenCtx.set(ctx, tag);
|
|
3797
|
+
const all = [...extToolIndex.keys()];
|
|
3798
|
+
if (!all.length)
|
|
3799
|
+
return; // 还没扫到东西:先不动(别把不该剪的剪了)
|
|
3800
|
+
// ⚠️ 2026-10-28(主人纠正):开关**不是**"关=不剪枝、全可见"。
|
|
3801
|
+
// 关掉元工具 = "按需取用"这条通道整个关掉 ⇒ 别人实例 expose 的工具**永远不可见**
|
|
3802
|
+
// (没有 gain 入口,也就不该偷偷放出来)。所以无论开关开/关,都照常剪:
|
|
3803
|
+
// · 自己实例那份(xxx__本tag):始终放开
|
|
3804
|
+
// · 别人那份:始终 deny;开关开着时可通过 gain_qq_tools 临时放开
|
|
3805
|
+
// ★ 2026-10-28(主人实测:关了元工具,之前领取的工具还在):开关关掉时
|
|
3806
|
+
// **忽略并清空领取记录** —— 通道一关,别人的工具就该彻底消失,不能靠残留的 granted 续命。
|
|
3807
|
+
const metaOnNow = globalThis.__qqbotExtMetaOn !== false;
|
|
3808
|
+
if (!metaOnNow)
|
|
3809
|
+
extGrantSet.delete(ctx);
|
|
3810
|
+
const granted = metaOnNow ? (extGrantSet.get(ctx) || new Set()) : new Set();
|
|
3811
|
+
// ★ 2026-10-28(主人直觉):**本实例自己的那份默认放开** —— 在 whale-girl 的会话里,
|
|
3812
|
+
// `bing_search__whale-girl` 本来就该看得见;别人的实例、以及自己没暴露的工具才要 gain 领取。
|
|
3813
|
+
const ownSuffix = tag ? ('__' + tag) : '';
|
|
3814
|
+
const deny = all.filter((n) => !granted.has(n) && !(ownSuffix && n.endsWith(ownSuffix)));
|
|
3815
|
+
const sig = (metaOnNow ? '1' : '0') + '|' + deny.join(',');
|
|
3816
|
+
const st = extRestrictState2.get(ctx);
|
|
3817
|
+
if (st && st.sig === sig)
|
|
3818
|
+
return;
|
|
3819
|
+
if (st?.dispose)
|
|
3820
|
+
liftRestriction(st.dispose);
|
|
3821
|
+
const toolsObj = toolsOfScope(ctx);
|
|
3822
|
+
const restrict = toolsObj?.restrict;
|
|
3823
|
+
if (typeof restrict !== 'function') {
|
|
3824
|
+
traceToolReg('[ext-vis] 剪枝失败: 拿不到 tools.restrict');
|
|
3825
|
+
return;
|
|
3826
|
+
}
|
|
3827
|
+
if (!deny.length) {
|
|
3828
|
+
extRestrictState2.set(ctx, { sig });
|
|
3829
|
+
return;
|
|
3830
|
+
}
|
|
3831
|
+
const d = restrict.call(toolsObj, { deny });
|
|
3832
|
+
const dispose = typeof d === 'function' ? d : undefined;
|
|
3833
|
+
extRestrictState2.set(ctx, { sig, dispose });
|
|
3834
|
+
traceToolReg('[ext-vis] deny=' + deny.length + ' 件 已领取=' + granted.size);
|
|
3835
|
+
}
|
|
3836
|
+
catch (e) {
|
|
3837
|
+
traceToolReg('[ext-vis] 异常: ' + String(e?.message ?? e).slice(0, 140));
|
|
3838
|
+
}
|
|
3839
|
+
}
|
|
3771
3840
|
const GAIN_TOOL_NAME = 'gain_qq_tools';
|
|
3772
3841
|
/** 取当前调用方的 agent ctx(enable 的目标作用域) */
|
|
3773
3842
|
function agentCtxOf(exec) {
|
|
@@ -3838,15 +3907,15 @@ function makeGainTool(globalCtx, logger) {
|
|
|
3838
3907
|
description: '按需取用「QQ 实例的扩展工具」:先 list / search 看有哪些(只回名字+一句话),'
|
|
3839
3908
|
+ '再 enable 把某个装进当前会话,之后就能调用它(非 QQ 会话想往 QQ 发消息、读会话时用)。'
|
|
3840
3909
|
+ 'disable 可卸下。',
|
|
3910
|
+
// ⚠️ 2026-10-28(引擎控制台实录):defineTool 的 parameters 要的是**扁平字段表**,
|
|
3911
|
+
// ;写成 JSON Schema(type/properties/required)
|
|
3912
|
+
// 会被判 "unsupported JSON schema: parameters.type must be a value schema object" ⇒ 注册直接失败。
|
|
3841
3913
|
parameters: {
|
|
3842
|
-
type: '
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
tag: { type: 'string', description: '可选:只看某个实例(tag)' },
|
|
3848
|
-
},
|
|
3849
|
-
required: ['action'],
|
|
3914
|
+
action: { type: 'string', required: true, enum: ['list', 'search', 'enable', 'disable'], description: 'list=列出全部 / search=按关键词搜 / enable=装到当前会话 / disable=卸下' },
|
|
3915
|
+
query: { type: 'string', description: 'search 用:关键词(匹配工具名与描述)' },
|
|
3916
|
+
tool: { type: 'string', description: 'enable/disable 用:工具名,可用逗号或空格**批量**(如 qq_send__whale-girl,bing_search__standard;先 list 查)' },
|
|
3917
|
+
tag: { type: 'string', description: '可选:只看某个实例(tag)' },
|
|
3918
|
+
limit: { type: 'integer', description: 'list/search 返回条数 1~50(默认 20)' },
|
|
3850
3919
|
},
|
|
3851
3920
|
output: {
|
|
3852
3921
|
schema: {
|
|
@@ -3858,8 +3927,11 @@ function makeGainTool(globalCtx, logger) {
|
|
|
3858
3927
|
async execute(args, exec) {
|
|
3859
3928
|
const action = String(args?.action ?? 'list');
|
|
3860
3929
|
const q = String(args?.query ?? '').trim().toLowerCase();
|
|
3861
|
-
const
|
|
3930
|
+
const wantTools = String(args?.tool ?? '').trim()
|
|
3931
|
+
.split(/[,\s]+/).map((x) => x.trim()).filter(Boolean);
|
|
3862
3932
|
const wantTag = String(args?.tag ?? '').trim();
|
|
3933
|
+
const limRaw = Number(args?.limit);
|
|
3934
|
+
const lim = Math.max(1, Math.min(50, Number.isFinite(limRaw) && limRaw > 0 ? Math.trunc(limRaw) : 20));
|
|
3863
3935
|
const rows = [...extToolIndex.values()].filter((e) => (!wantTag || e.tag === wantTag));
|
|
3864
3936
|
try {
|
|
3865
3937
|
if (action === 'list' || action === 'search') {
|
|
@@ -3868,41 +3940,88 @@ function makeGainTool(globalCtx, logger) {
|
|
|
3868
3940
|
: rows;
|
|
3869
3941
|
if (!hit.length)
|
|
3870
3942
|
return { ok: true, msg: rows.length ? '没有匹配的工具(换关键词试试)' : '当前没有可领取的扩展工具' };
|
|
3871
|
-
const
|
|
3872
|
-
|
|
3943
|
+
const shown = hit.slice(0, lim);
|
|
3944
|
+
const lines = shown.map((e) => e.full + ' — ' + String(e.description || '').slice(0, 90)).join('\n');
|
|
3945
|
+
const more = hit.length > shown.length
|
|
3946
|
+
? ('\n……(共 ' + hit.length + ' 个,已显示前 ' + shown.length + ' 个;加 limit 或 search 缩小)')
|
|
3947
|
+
: '';
|
|
3948
|
+
return { ok: true, msg: '可领取的扩展工具 ' + hit.length + ' 个(enable 时 tool 写全名,可逗号批量):\n' + lines + more };
|
|
3873
3949
|
}
|
|
3874
3950
|
if (action === 'enable') {
|
|
3875
|
-
if (!
|
|
3951
|
+
if (!wantTools.length)
|
|
3876
3952
|
return { ok: false, msg: 'tool 必填(先 action=list 查名字)' };
|
|
3877
|
-
const ent = extToolIndex.get(wantTool) || rows.find((e) => e.baseName === wantTool) || undefined;
|
|
3878
|
-
if (!ent)
|
|
3879
|
-
return { ok: false, msg: '找不到工具: ' + wantTool + '(先 action=list)' };
|
|
3880
3953
|
const actx = agentCtxOf(exec);
|
|
3881
3954
|
if (!actx)
|
|
3882
3955
|
return { ok: false, msg: '拿不到当前会话作用域(本工具只能在会话内使用)' };
|
|
3883
|
-
let
|
|
3884
|
-
if (!
|
|
3885
|
-
|
|
3886
|
-
|
|
3956
|
+
let gset = extGrantSet.get(actx);
|
|
3957
|
+
if (!gset) {
|
|
3958
|
+
gset = new Set();
|
|
3959
|
+
extGrantSet.set(actx, gset);
|
|
3887
3960
|
}
|
|
3888
|
-
if (m.has(ent.full))
|
|
3889
|
-
return { ok: true, msg: '已经装好了:' + ent.full };
|
|
3890
3961
|
const toolsObj = toolsOfScope(actx);
|
|
3891
3962
|
const regFn = toolsObj?.register;
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3963
|
+
let mm = extGranted.get(actx);
|
|
3964
|
+
if (!mm) {
|
|
3965
|
+
mm = new Map();
|
|
3966
|
+
extGranted.set(actx, mm);
|
|
3967
|
+
}
|
|
3968
|
+
const okList = [];
|
|
3969
|
+
const badList = [];
|
|
3970
|
+
for (const name of wantTools) {
|
|
3971
|
+
const ent = extToolIndex.get(name) || rows.find((e) => e.baseName === name) || undefined;
|
|
3972
|
+
if (!ent) {
|
|
3973
|
+
badList.push(name);
|
|
3974
|
+
continue;
|
|
3975
|
+
}
|
|
3976
|
+
gset.add(ent.full);
|
|
3977
|
+
// 兜底:按需取用模式下工具未全局注册,得真注册一次
|
|
3978
|
+
if (!mm.has(ent.full) && toolsObj && typeof regFn === 'function') {
|
|
3979
|
+
try {
|
|
3980
|
+
const d = regFn.call(toolsObj, buildExtTool(ent, globalCtx, logger));
|
|
3981
|
+
if (typeof d === 'function')
|
|
3982
|
+
mm.set(ent.full, d);
|
|
3983
|
+
}
|
|
3984
|
+
catch { /* 已全局注册时覆盖同名,失败无妨 */ }
|
|
3985
|
+
}
|
|
3986
|
+
okList.push(ent.full);
|
|
3987
|
+
}
|
|
3988
|
+
applyExtToolVisibility(actx);
|
|
3989
|
+
const head = okList.length ? ('已放开 ' + okList.length + ' 个:' + okList.join(', ')) : '没有放开任何工具';
|
|
3990
|
+
const tail = badList.length ? (';找不到:' + badList.join(', ')) : '';
|
|
3991
|
+
return { ok: badList.length === 0, msg: head + tail + '(本会话可见;换会话要重新 enable)' };
|
|
3899
3992
|
}
|
|
3900
3993
|
if (action === 'disable') {
|
|
3901
3994
|
const actx = agentCtxOf(exec);
|
|
3902
|
-
|
|
3995
|
+
if (actx) {
|
|
3996
|
+
const gset = extGrantSet.get(actx);
|
|
3997
|
+
if (!gset || !gset.size)
|
|
3998
|
+
return { ok: true, msg: '当前会话没有领取过扩展工具' };
|
|
3999
|
+
if (!wantTools.length) {
|
|
4000
|
+
gset.clear();
|
|
4001
|
+
applyExtToolVisibility(actx);
|
|
4002
|
+
return { ok: true, msg: '已收回本会话领取的全部扩展工具' };
|
|
4003
|
+
}
|
|
4004
|
+
const done = [];
|
|
4005
|
+
const miss = [];
|
|
4006
|
+
for (const name of wantTools) {
|
|
4007
|
+
const ent = extToolIndex.get(name) || rows.find((e) => e.baseName === name) || undefined;
|
|
4008
|
+
const full = ent ? ent.full : name;
|
|
4009
|
+
if (gset.delete(full))
|
|
4010
|
+
done.push(full);
|
|
4011
|
+
else
|
|
4012
|
+
miss.push(name);
|
|
4013
|
+
}
|
|
4014
|
+
applyExtToolVisibility(actx);
|
|
4015
|
+
return {
|
|
4016
|
+
ok: miss.length === 0,
|
|
4017
|
+
msg: (done.length ? '已收回 ' + done.length + ' 个:' + done.join(', ') : '没有收回任何工具')
|
|
4018
|
+
+ (miss.length ? (';未领取:' + miss.join(', ')) : ''),
|
|
4019
|
+
};
|
|
4020
|
+
}
|
|
4021
|
+
const m = extGranted.get(actx);
|
|
3903
4022
|
if (!m || !m.size)
|
|
3904
4023
|
return { ok: true, msg: '当前会话没有领取过扩展工具' };
|
|
3905
|
-
const target =
|
|
4024
|
+
const target = wantTools[0] ?? '';
|
|
3906
4025
|
if (target) {
|
|
3907
4026
|
const d = m.get(target);
|
|
3908
4027
|
if (!d)
|
|
@@ -3932,10 +4051,23 @@ function makeGainTool(globalCtx, logger) {
|
|
|
3932
4051
|
},
|
|
3933
4052
|
});
|
|
3934
4053
|
}
|
|
3935
|
-
/**
|
|
3936
|
-
function ensureGainToolGlobal(globalCtx, logger) {
|
|
4054
|
+
/** 全局注册/注销元工具(幂等;on=false 时**真注销**,对应设置页的热关闭) */
|
|
4055
|
+
function ensureGainToolGlobal(globalCtx, logger, on = true) {
|
|
3937
4056
|
try {
|
|
3938
4057
|
const g = globalThis;
|
|
4058
|
+
// ★ 2026-10-28(主人 + issue 反馈):**热关闭** —— 开关关掉时元工具必须真的消失,
|
|
4059
|
+
// 否则「非 QQ 会话零 QQ 工具」这条做不到(还会白占几十 token)。
|
|
4060
|
+
if (!on) {
|
|
4061
|
+
if (typeof g.__qqbotGainToolReg === 'function') {
|
|
4062
|
+
try {
|
|
4063
|
+
g.__qqbotGainToolReg();
|
|
4064
|
+
}
|
|
4065
|
+
catch { /* ignore */ }
|
|
4066
|
+
g.__qqbotGainToolReg = undefined;
|
|
4067
|
+
logger?.info?.('[channel-tools] 已注销元工具 ' + GAIN_TOOL_NAME);
|
|
4068
|
+
}
|
|
4069
|
+
return;
|
|
4070
|
+
}
|
|
3939
4071
|
if (g.__qqbotGainToolReg)
|
|
3940
4072
|
return;
|
|
3941
4073
|
const rootCtx = ((globalCtx?.root) ?? globalCtx);
|
|
@@ -3952,7 +4084,14 @@ function ensureGainToolGlobal(globalCtx, logger) {
|
|
|
3952
4084
|
g.__qqbotGainToolReg = d;
|
|
3953
4085
|
gctx.logger?.info?.('[channel-tools] 已注册元工具 ' + GAIN_TOOL_NAME);
|
|
3954
4086
|
}
|
|
3955
|
-
catch {
|
|
4087
|
+
catch (e) {
|
|
4088
|
+
// ⚠️ 2026-10-28:这里原来静默 catch ⇒ "开了开关却连元工具都没有",完全查不出原因。
|
|
4089
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
4090
|
+
try {
|
|
4091
|
+
console.error('[channel-tools] 元工具注册失败: ' + msg);
|
|
4092
|
+
}
|
|
4093
|
+
catch { /* ignore */ }
|
|
4094
|
+
}
|
|
3956
4095
|
}
|
|
3957
4096
|
export function ensureExtToolsGlobal(globalCtx) {
|
|
3958
4097
|
// 节流:本函数挂在 agent/pre-step(**每个 step** 都触发)—— 15 秒内不重复扫盘;
|
|
@@ -3984,6 +4123,45 @@ export function ensureExtToolsGlobal(globalCtx) {
|
|
|
3984
4123
|
extDiag('[extglob] 拿不到 tools 服务, 跳过');
|
|
3985
4124
|
return;
|
|
3986
4125
|
}
|
|
4126
|
+
// 2026-10-28:元工具**先注册**,且不依赖"有没有实例"——
|
|
4127
|
+
// 上一版放在扫描末尾,若中途 early-return 就永远注册不上(实测:开了按需取用却连元工具都没有)。
|
|
4128
|
+
const metaOn = (() => {
|
|
4129
|
+
// ① **文件开关优先**(最可靠,2026-10-28 主人实测:"重启了也读不到勾选"):
|
|
4130
|
+
// settings-host 保存时写 {dataRoot}/.qqbot/ext-meta-tool.json,这里每次扫描现读;
|
|
4131
|
+
// 不依赖 config 快照,也不依赖 globalThis 有没有被写过。
|
|
4132
|
+
try {
|
|
4133
|
+
const dr = String(managersOf()[0]?.dataRoot ?? '').trim();
|
|
4134
|
+
if (dr) {
|
|
4135
|
+
const o = JSON.parse(readFileSync(join(dr, '.qqbot', 'ext-meta-tool.json'), 'utf8'));
|
|
4136
|
+
if (typeof o?.on === 'boolean')
|
|
4137
|
+
return o.on;
|
|
4138
|
+
}
|
|
4139
|
+
}
|
|
4140
|
+
catch { /* 文件不存在 = 还没设过,往下走 */ }
|
|
4141
|
+
// ② 进程级热标记:设置页保存时 settings-host 写入(config 是启动快照,不会自己变)。
|
|
4142
|
+
try {
|
|
4143
|
+
const live = globalThis.__qqbotExtMetaTool;
|
|
4144
|
+
if (typeof live === 'boolean')
|
|
4145
|
+
return live;
|
|
4146
|
+
}
|
|
4147
|
+
catch { /* ignore */ }
|
|
4148
|
+
// ② 回退 config —— ⚠️ 必须**遍历所有实例**,不能只看 managersOf()[0]:
|
|
4149
|
+
// index.ts 的 'qqbot/settings-changed' 监听会 把
|
|
4150
|
+
// 保存值原地合并进当前实例的 config(这就是面板其它开关的热更路子);但只有**被保存的那个
|
|
4151
|
+
// 实例**里有这个字段,第一个 manager 未必是它 ⇒ 只看第 0 个会永远读到 undefined/false。
|
|
4152
|
+
try {
|
|
4153
|
+
for (const m of managersOf()) {
|
|
4154
|
+
const cfgM = m?.config;
|
|
4155
|
+
if (cfgM && cfgM.extMetaTool === true)
|
|
4156
|
+
return true;
|
|
4157
|
+
}
|
|
4158
|
+
}
|
|
4159
|
+
catch { /* ignore */ }
|
|
4160
|
+
return false;
|
|
4161
|
+
})();
|
|
4162
|
+
// ★ 2026-10-28(主人急):**无条件注册元工具** —— 它是个极轻的入口,本来就不该被任何开关挡住。
|
|
4163
|
+
// "开了按需取用却连 gain_qq_tools 都看不到" 这种状态毫无意义:要么两个都有,要么两个都没有。
|
|
4164
|
+
ensureGainToolGlobal(globalCtx, gctx.logger, metaOn);
|
|
3987
4165
|
// 实例清单:每个账号实例注册一份(tag = 人设 id)
|
|
3988
4166
|
const insts = [];
|
|
3989
4167
|
try {
|
|
@@ -4006,15 +4184,15 @@ export function ensureExtToolsGlobal(globalCtx) {
|
|
|
4006
4184
|
g.__qqbotExtToolsGlobal = new Map();
|
|
4007
4185
|
const reg = g.__qqbotExtToolsGlobal;
|
|
4008
4186
|
// 开关(默认开 = 按需取用):关掉后回到"扩展工具全部全局注册"的旧行为
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
}
|
|
4015
|
-
catch { /* ignore */ }
|
|
4187
|
+
// 2026-10-28(主人实测纠正):默认**保持旧行为**(全部 expose:'all' 全局注册)。
|
|
4188
|
+
// 原因:第一版把默认值设成"按需取用",结果元工具没注册上 => 所有会话一个扩展工具都没有
|
|
4189
|
+
// (主人截图:"怎么你改一下代码把自己的扩展工具都给不注册了")。
|
|
4190
|
+
// ⇒ 现在只有显式打开 extMetaTool 才进按需取用,默认一切照旧。
|
|
4191
|
+
const metaMode = metaOn; // 与上面同一个口径(含 live 标记)
|
|
4016
4192
|
extToolIndex.clear();
|
|
4193
|
+
globalThis.__qqbotExtMetaOn = metaMode;
|
|
4017
4194
|
extGlobDiag('metaMode=' + String(metaMode));
|
|
4195
|
+
// ── 阶段 1:扫描,**只填索引**(不注册)────────────────────────────
|
|
4018
4196
|
for (const inst of insts) {
|
|
4019
4197
|
let defs = [];
|
|
4020
4198
|
try {
|
|
@@ -4024,32 +4202,41 @@ export function ensureExtToolsGlobal(globalCtx) {
|
|
|
4024
4202
|
defs = [];
|
|
4025
4203
|
}
|
|
4026
4204
|
const exposed = defs.filter((d) => d.expose === 'all');
|
|
4027
|
-
extGlobDiag('inst=' + inst.tag + ' defs=' + defs.length + ' exposeAll=' + exposed.length
|
|
4028
|
-
if (!exposed.length)
|
|
4029
|
-
continue;
|
|
4205
|
+
extGlobDiag('inst=' + inst.tag + ' defs=' + defs.length + ' exposeAll=' + exposed.length);
|
|
4030
4206
|
for (const def of exposed) {
|
|
4031
|
-
// ⚠️
|
|
4032
|
-
// `@` 不在 LLM API 允许的工具名字符集里(^[a-zA-Z0-9_-]+$),
|
|
4033
|
-
// 一旦注册进去就是"每一轮请求都被拒"(机器人整体瘫痪)。故改用 `__`。
|
|
4207
|
+
// ⚠️ 工具名只允许 [a-zA-Z0-9_-](LLM API 硬约束,2026-10-10 事故)
|
|
4034
4208
|
const full = def.name + '__' + inst.tag;
|
|
4035
|
-
// ⚠️ 兜底闸门:工具名只允许 [a-zA-Z0-9_-](LLM API 硬约束)——
|
|
4036
|
-
// 非法名一旦注册进工具表,**每一轮请求都会被 API 整轮拒绝**(2026-10-10 事故)。
|
|
4037
4209
|
if (!/^[a-zA-Z0-9_-]+$/.test(full)) {
|
|
4038
4210
|
extGlobDiag('SKIP illegal global name: ' + full);
|
|
4039
4211
|
continue;
|
|
4040
4212
|
}
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4213
|
+
extToolIndex.set(full, {
|
|
4214
|
+
full, tag: inst.tag, baseName: def.name,
|
|
4215
|
+
description: String(def.description ?? ''),
|
|
4216
|
+
def: def, inst,
|
|
4217
|
+
});
|
|
4218
|
+
}
|
|
4219
|
+
}
|
|
4220
|
+
// ── 阶段 2:**先剪枝**(此时索引已完整)──────────────────────────
|
|
4221
|
+
// ★ 2026-10-28(主人抓到抖动:"新增 7 个 → 下个 step 又移除 7 个"):
|
|
4222
|
+
// 以前是「循环里边扫描边注册」⇒ 注册发生时 deny 名单还不完整 ⇒ 模型先看到一步、下一步才被剪掉。
|
|
4223
|
+
// 现在顺序固定为 **扫描 → 剪枝 → 注册**:注册落地时 deny 已经就位,模型永远看不到那一步。
|
|
4224
|
+
for (const [c, tg] of extSeenCtx)
|
|
4225
|
+
applyExtToolVisibility(c, tg);
|
|
4226
|
+
// ── 阶段 3:全局注册 —— **始终注册**(注册表 != 可见性;可见由 deny 控制)。
|
|
4227
|
+
// ⚠️ 2026-10-28 修:原来按需取用模式下跳过注册,结果连"自己实例那份"都调不到
|
|
4228
|
+
// (deny 只能隐藏,不能凭空造出工具)。
|
|
4229
|
+
if (true) {
|
|
4230
|
+
for (const ent of extToolIndex.values()) {
|
|
4231
|
+
if (reg.has(ent.full))
|
|
4048
4232
|
continue;
|
|
4233
|
+
const def = ent.def;
|
|
4234
|
+
const inst = ent.inst;
|
|
4235
|
+
const full = ent.full;
|
|
4049
4236
|
try {
|
|
4050
4237
|
const tool = defineTool({
|
|
4051
4238
|
name: full,
|
|
4052
|
-
description: def.description + ' (用户扩展工具 · ' + inst.tag + ')',
|
|
4239
|
+
description: String(def.description ?? '') + ' (用户扩展工具 · ' + inst.tag + ')',
|
|
4053
4240
|
parameters: def.inputSchema,
|
|
4054
4241
|
output: {
|
|
4055
4242
|
schema: {
|
|
@@ -4059,7 +4246,6 @@ export function ensureExtToolsGlobal(globalCtx) {
|
|
|
4059
4246
|
render: (_a, v) => [{ type: 'text', text: v.ok ? v.msg : ('失败: ' + v.msg) }],
|
|
4060
4247
|
},
|
|
4061
4248
|
async execute(args, exec) {
|
|
4062
|
-
// 账号包:按注册名里的 tag 反查(这就是"名字即归属")
|
|
4063
4249
|
let pack;
|
|
4064
4250
|
try {
|
|
4065
4251
|
pack = buildWorkspaceQqPack(inst.tag, inst.dataRoot);
|
|
@@ -4074,7 +4260,6 @@ export function ensureExtToolsGlobal(globalCtx) {
|
|
|
4074
4260
|
logger: gctx.logger,
|
|
4075
4261
|
qq: pack,
|
|
4076
4262
|
};
|
|
4077
|
-
// 若这一次调用恰好来自 QQ 会话,则会话级上下文优先(sender/replyTarget/manager 用当前会话的)
|
|
4078
4263
|
try {
|
|
4079
4264
|
const sess = findSessionRec(channelOf(exec), exec);
|
|
4080
4265
|
if (sess) {
|
|
@@ -4102,17 +4287,12 @@ export function ensureExtToolsGlobal(globalCtx) {
|
|
|
4102
4287
|
if (typeof d === 'function')
|
|
4103
4288
|
reg.set(full, d);
|
|
4104
4289
|
extGlobDiag('OK registered ' + full);
|
|
4105
|
-
extDiag('[extglob] 全局注册 ' + full);
|
|
4106
|
-
gctx.logger?.info?.('[channel-tools] 扩展工具已暴露给全部会话: ' + full);
|
|
4107
4290
|
}
|
|
4108
4291
|
catch (e) {
|
|
4109
4292
|
extGlobDiag('FAIL register ' + full + ' :: ' + ((e instanceof Error ? e.message : String(e)) || '').slice(0, 200));
|
|
4110
|
-
extDiag('[extglob] 注册失败 ' + full + ': ' + ((e instanceof Error ? e.message : String(e)) || '').slice(0, 160));
|
|
4111
4293
|
}
|
|
4112
4294
|
}
|
|
4113
4295
|
}
|
|
4114
|
-
if (metaMode)
|
|
4115
|
-
ensureGainToolGlobal(globalCtx, gctx.logger);
|
|
4116
4296
|
extGlobDiag('index=' + extToolIndex.size + ' 可领取');
|
|
4117
4297
|
}
|
|
4118
4298
|
catch (e) {
|