@fengye_nobug/dsh-qq-bot 0.2.9 → 0.2.11
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/CHANGELOG.md +8 -0
- package/DESIGN.md +5 -5
- package/README.md +2 -2
- package/index.js +67 -16
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# 更新日志
|
|
2
2
|
|
|
3
|
+
## v0.2.11 — 2026-11-02
|
|
4
|
+
|
|
5
|
+
- **轮数上限会用到人设里的压缩**:`maxRecentTurns`(默认 40,填 0 不限制)在 QQ 叫醒的一轮结束并空闲后,压缩更早的内容。标准人设把压缩放在隔离域里,插件所在的宿主上下文看不见,所以 0.2.9 这次调用直接跳过,会话会一直变长,日志里也没有压缩记录。现在先向这条会话的人设要压缩服务,没有再看会话作用域和宿主。已经超限却找不到,或这次压缩没有改动会话时,记一条警告。仍然等这一轮空闲,并带上未取消的取消信号。不必在宿主配置里再打开压缩。哪一层都没有压缩时不影响收发。
|
|
6
|
+
|
|
7
|
+
## v0.2.10 — 2026-11-02
|
|
8
|
+
|
|
9
|
+
- **用法说明和发消息工具不再用具体表情名当例子**:注入的用法说明,以及 `qq_bridge_send` 的工具说明和参数说明,不再把笑哭、挥手写成推荐写法。模型之前会抓住这两个例子反复使用。现在只说明半角方括号里写 `[表情名]` 或 `[表情数字]`。对得上的表情名仍会变成 QQ 系统表情,名字表还在 `faces.js`。认不出的方括号仍留在文字里。
|
|
10
|
+
|
|
3
11
|
## v0.2.9 — 2026-11-02
|
|
4
12
|
|
|
5
13
|
- **每个会话的轮数上限会真正压缩**:`maxRecentTurns`(默认 40,填 0 不限制)在 QQ 叫醒的一轮结束后,会请宿主把更早的内容压掉。以前这次调用少传了宿主要求的取消信号,压缩还没开始就失败,失败只留在日志里,会话仍会一直变长。现在会等这一轮结束并空闲下来,再按宿主的参数去压缩。宿主没有压缩能力时仍然跳过,不影响收发。
|
package/DESIGN.md
CHANGED
|
@@ -449,7 +449,7 @@ QQ 路径与 `NapCatWinBootHook.dll`;此时 QQ 路径取不到会明确报错
|
|
|
449
449
|
- **字数上限**:出站文本超过 `replyMaxChars` 时**一条都不发**,调用方拿到 `reason: 'too-long'`,
|
|
450
450
|
应按提示缩短后重发;未超长时 `replyWithQuote: true` 让消息带上引用;
|
|
451
451
|
`stripMarkdown: true`(默认)时先去掉 Markdown 标记(QQ 不渲染)。字数按去掉 Markdown 之后、拆表情之前的正文计算。
|
|
452
|
-
- **系统表情**:去掉 Markdown 之后,`sendToQQ` 把正文拆成 OneBot
|
|
452
|
+
- **系统表情**:去掉 Markdown 之后,`sendToQQ` 把正文拆成 OneBot 消息段。半角方括号里的 `[表情名]` 变成 `face` 段,编号取自 `faces.js` 的名字表,NapCat 显示系统表情。也认入站占位符 `[表情数字]`,以及 `[CQ:face,id=数字]`。对不上的方括号(例如 `[没这表情]`、`[图片]`)留在文字段里。标记块和 `qq_bridge_send` 都走这条路。商城表情不走这条路,由 `qq_bridge_sticker` 按登记表发送,见[表情包](#表情包)。
|
|
453
453
|
- **目的地钉在唤醒那一刻**。这一轮的标记块、`qq_bridge_send`、`qq_bridge_sticker` 和 `qq_bridge_listen` 都发回把这一轮叫醒的那个群或私聊,不会被同一时间另一条只记录下来的消息改掉。显式传入 `groupId` 或 `userId` 时发到那里,并且不引用别的会话里的原消息;这个群号必须在 `groupWhitelist` 里,这个 QQ 号必须在 `whitelist` 里,对不上就一条都不发(`reason: "not-allowlisted"`)。白名单留空等于不允许。钉住的目的地不再查一遍,它在入站时已经通过了白名单。同一会话后面没有新唤醒的轮次,仍回落到最后一次非丢弃的入站。
|
|
454
454
|
- **连不上或还没有目的地时丢弃并记日志**,不静默排队堆积。
|
|
455
455
|
- **标记块发出时传输失败**(`reason: 'send-failed'`)同样记进丢弃台账,并把原因回注到这一轮的会话里。模型能看到没发出去,而不是只在日志里留一行。
|
|
@@ -461,7 +461,7 @@ QQ 路径与 `NapCatWinBootHook.dll`;此时 QQ 路径取不到会明确报错
|
|
|
461
461
|
|
|
462
462
|
QQ 的表情、表情包、图片、语音、视频、文件、卡片、合并转发等都会换成可读占位符
|
|
463
463
|
(如 `[表情4]`、`[表情包:[动画表情]]`、`[图片]`、`[文件:a.zip]`),**不会让整条消息丢失**。
|
|
464
|
-
|
|
464
|
+
系统表情的入站占位符带编号,形如 `[表情数字]`。出站时写的 `[表情名]`,或这种占位符,会再变回系统表情,见[出站](#出站三种模式--来源闸门)。
|
|
465
465
|
商城表情若带了可下载的图,描述填回去之后写成 `[表情包#短哈希]`,和普通图片的 `[图片#短哈希]` 分开,见[表情包](#表情包)。
|
|
466
466
|
|
|
467
467
|
### 戳一戳
|
|
@@ -603,7 +603,7 @@ QQ 的“拍一拍”在 OneBot 里走的是 **notice 事件**,不是消息段
|
|
|
603
603
|
|---|---|---|
|
|
604
604
|
| `targetSessionId` | `''` | 接到哪个会话。`single` 时**空 = 不接到任何会话**。`perGroup` 时可以留空(新建会话用宿主当前默认模型);填了则新建会话沿用该会话的模型 |
|
|
605
605
|
| `sessionScope` | `single` | 会话怎么分。`single` = 所有群共用一个会话;`perGroup` = 每个群、每个私聊各记各的,映射写在状态文件的 `sessionByDest`(键为 `group:<群号>` / `private:<QQ号>`)。新建会话带工作目录和「QQ群 / QQ私聊」标题,见 [README 的功能](./README.md#功能) |
|
|
606
|
-
| `maxRecentTurns` | `40` |
|
|
606
|
+
| `maxRecentTurns` | `40` | 每个会话最多留几轮。超出后,等这一轮回到空闲,再调用 `compaction.compactNow(agent, signal)` 尽量压缩。压缩服务先从这条会话的人设读取(`agentPresets.serviceFor(agent, 'compaction')`,标准人设把它放在隔离域里),没有再看会话作用域,最后看宿主。`signal` 是未取消的 AbortSignal,不传命令 id。有投影时看投影轮数。压完没变短就停。返回空也停,并记一条警告。`0` = 不限制。compaction 不在 inject 里。已经超限、三层都没有这项服务时记一条警告并跳过,收发不受影响 |
|
|
607
607
|
| `nicknames` | `['244','小助手']` | 听到这些名字就回应 |
|
|
608
608
|
| `wakeProbability` | `0.05` | 没点名时的回应概率 |
|
|
609
609
|
| `whitelist` / `groupWhitelist` | `[]` | 哪些私聊可以叫醒 / 哪些群可以叫醒(**留空 = 谁都不能叫醒**)。群白名单在卡片里按群号填;连上且查到群名时,群号旁边显示当前群名和头像,查不到或没连上只显示群号,不影响保存 |
|
|
@@ -654,7 +654,7 @@ QQ 的“拍一拍”在 OneBot 里走的是 **notice 事件**,不是消息段
|
|
|
654
654
|
| 入站格式 | 入站消息格式、未唤醒摘要、“[图片#<短哈希>]:<描述>”(就地填回,可能不准)、被戳显示成“[戳一戳](戳的是我)” |
|
|
655
655
|
| 回复方式 | 按 `replyMode` 分叉:标记块怎么写、该调用哪个工具、非 QQ 轮的 2 步确认 |
|
|
656
656
|
| 表情包 | 用 `qq_bridge_sticker` 的 `list` / `send` 发表情包;用 `save` 收藏群友发的图。`[表情包#短哈希]` 是商城表情,发出去是原生表情包;`[图片#短哈希]` 发出去仍是图片 |
|
|
657
|
-
| 群聊礼仪 | 1–2 句话;默认不超过 40 字;不刷屏;不放路径、代码、日志、提交号;不汇报过程。系统表情写 `[
|
|
657
|
+
| 群聊礼仪 | 1–2 句话;默认不超过 40 字;不刷屏;不放路径、代码、日志、提交号;不汇报过程。系统表情写 `[表情名]` 或 `[表情数字]` |
|
|
658
658
|
| 无人值守 | 不碰特权操作,见下一节 |
|
|
659
659
|
| 会话跟随(监听模式) | 何时开窗;默认 5 分钟、上限 60 分钟;只对同一目的地生效;答完主动关 |
|
|
660
660
|
|
|
@@ -698,7 +698,7 @@ QQ 侧没有人能点击“同意”,因此:
|
|
|
698
698
|
| `qq_bridge_status` | `debugTools` | 设置、目标会话是否已加载、连接状态、目的地、缓冲条数、最近日志,以及**最近观测到的 `user/message` 来源**(`recentInboundSources`)。排查“哑火”先看这个工具:`ours: false` = 没有认出那条是插件灌入的 QQ 消息;`ours: true` 但工具仍被拒 = 标记被别的事件清掉了 |
|
|
699
699
|
| `qq_bridge_transport` | `debugTools` | 查看连接状态(含 token 是否解析到),或 `action: 'reconnect'` 强制重连 |
|
|
700
700
|
| `qq_bridge_simulate` | `debugTools` | 模拟一条入站消息走完整策略;可带 `groupName`(群名,用来写 perGroup 会话标题);`dryRun: true` 只看决策,不注入 |
|
|
701
|
-
| `qq_bridge_send` | 始终 | 出站。正文里的 `[
|
|
701
|
+
| `qq_bridge_send` | 始终 | 出站。正文里的 `[表情名]` 会变成系统表情。`replyMode: 'tool'` 时该工具**就是唯一的出站方式**,因此不能藏在调试开关后面 |
|
|
702
702
|
| `qq_bridge_sticker` | 始终 | 表情包:`list` / `save` / `send`。登记项带齐商城表情字段时发 `mface`,否则图片以 base64 出站。与 `qq_bridge_send` 共用来源闸门,见[表情包](#表情包) |
|
|
703
703
|
| `qq_bridge_listen` | 始终 | 会话跟随窗口的开启与关闭。这是产品的使用方式,不是调试工具 |
|
|
704
704
|
| `qq_bridge_napcat` | 始终 | NapCat 自助托管(下载 / 配置 / 启停) |
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Cordis 插件 id 是 `qq-bridge`。npm 包名是 `@fengye_nobug/dsh-qq-bot`。
|
|
|
16
16
|
- 白名单留空时,任何群和私聊都不会叫醒。
|
|
17
17
|
- 被 @、命中昵称、被戳一定叫醒。其余按 `wakeProbability`(默认 `0.05`)抽样,没抽中只记录。
|
|
18
18
|
- 发回 QQ 的内容由 `replyMode` 决定。在 DSH 界面里聊同一个会话时,正文不会进群。
|
|
19
|
-
-
|
|
19
|
+
- 图片先落盘,叫醒时写成文字。表情包可以登记后再发。半角方括号里的 `[表情名]` 会变成 QQ 系统表情。
|
|
20
20
|
- 设置卡片在 **设置 → 插件 → 插件配置 → QQ 桥接**。可以检查更新;有新正式版时一键安装,并重启面板。
|
|
21
21
|
- 可以按需下载、配置、启停 NapCat。加载插件时不会自己下载或执行。
|
|
22
22
|
|
|
@@ -62,7 +62,7 @@ DSH 0.2 的卡片把值写进 `$DSH_HOME/qq-bridge-settings.json`。这份文件
|
|
|
62
62
|
|---|---|---|
|
|
63
63
|
| `targetSessionId` | 空 | `single` 时接到哪个会话。留空 = 不接到任何会话 |
|
|
64
64
|
| `sessionScope` | `single` | `perGroup` 时每个群、每个私聊各一条会话 |
|
|
65
|
-
| `maxRecentTurns` | `40` | 每个会话最多留几轮。QQ
|
|
65
|
+
| `maxRecentTurns` | `40` | 每个会话最多留几轮。QQ 一轮结束并空闲后,更早的内容会尽量压缩掉。用的是这条会话自己的压缩(标准人设里就有)。人设没有时再看会话作用域和宿主。已经超限却找不到压缩,或这次压缩没有改动会话时,记一条警告。`0` = 不限制 |
|
|
66
66
|
| `nicknames` | `244`、`小助手` | 听到这些名字就叫醒 |
|
|
67
67
|
| `wakeProbability` | `0.05` | 没点名时的叫醒概率 |
|
|
68
68
|
| `whitelist` | `[]` | 哪些私聊可以叫醒。留空 = 都不叫醒 |
|
package/index.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// M1:以上全部 + 出站文本采集 + 调试工具。
|
|
12
12
|
// M2:OneBot 传输层(见 onebot.js)——WS 入站映射到 handleInbound(),
|
|
13
13
|
// 出站文本超过 replyMaxChars 就拒绝发送;未超长才发给最后一次入站的目的地。
|
|
14
|
-
// 正文里的 [
|
|
14
|
+
// 正文里的 [表情名] 会拆成 face 段,NapCat 显示系统表情。
|
|
15
15
|
// 传输细节(内置 WebSocket、token 走查询串、退避重连)全部封装在 onebot.js。
|
|
16
16
|
|
|
17
17
|
import { randomUUID } from 'node:crypto'
|
|
@@ -1899,16 +1899,48 @@ export function apply(ctx, config) {
|
|
|
1899
1899
|
}
|
|
1900
1900
|
}
|
|
1901
1901
|
|
|
1902
|
+
/**
|
|
1903
|
+
* 读一份能调用的 compaction。
|
|
1904
|
+
*
|
|
1905
|
+
* 标准人设把 `dsh-compaction-basic` 放在 `isolate: { compaction: true }` 里。
|
|
1906
|
+
* 这份服务只在人设那一组的 realm 里,宿主 `ctx.get('compaction')` 和 `agent.ctx.get`
|
|
1907
|
+
* 都按自己的 realm 查找,读到的是空。`/compact` 能用,是因为 `dsh-command-compact`
|
|
1908
|
+
* 和压缩后端在同一组里,调用的是这一份 `compactNow`。
|
|
1909
|
+
* 宿主侧对应的读法是 `agentPresets.serviceFor(agent, 'compaction')`。
|
|
1910
|
+
*
|
|
1911
|
+
* 人设注册表没有这个方法,或这份人设没挂压缩时,再看会话作用域,最后看宿主。
|
|
1912
|
+
* 宿主自己提供 compaction 的布局因此仍然可用。某一层抛错就换下一层。
|
|
1913
|
+
* @returns {{ service: any, detail: string }}
|
|
1914
|
+
*/
|
|
1915
|
+
function compactionFor(agent) {
|
|
1916
|
+
let detail = ''
|
|
1917
|
+
const take = (read) => {
|
|
1918
|
+
try {
|
|
1919
|
+
const value = read()
|
|
1920
|
+
if (value && typeof value.compactNow === 'function') return value
|
|
1921
|
+
} catch (error) {
|
|
1922
|
+
if (!detail) detail = error?.message ?? String(error)
|
|
1923
|
+
}
|
|
1924
|
+
return undefined
|
|
1925
|
+
}
|
|
1926
|
+
const service =
|
|
1927
|
+
take(() => ctx.get?.('agentPresets')?.serviceFor?.(agent, 'compaction')) ??
|
|
1928
|
+
take(() => agent?.ctx?.get?.('compaction')) ??
|
|
1929
|
+
take(() => (typeof ctx.get === 'function' ? ctx.get('compaction') : undefined))
|
|
1930
|
+
return { service, detail }
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1902
1933
|
/**
|
|
1903
1934
|
* QQ 驱动的一轮结束后,把该会话压到 maxRecentTurns。
|
|
1904
|
-
* 0
|
|
1935
|
+
* 0 时什么都不做。压缩失败只记日志,不挡出站。
|
|
1905
1936
|
*
|
|
1906
|
-
* compaction 不放进 inject
|
|
1907
|
-
*
|
|
1937
|
+
* compaction 不放进 inject。它是可选服务:哪一层都没有时,桥仍要收发 QQ。
|
|
1938
|
+
* 会话已经超过上限、却找不到 compactNow 时记一条 warn,避免静默不压缩。
|
|
1908
1939
|
*
|
|
1909
|
-
*
|
|
1910
|
-
*
|
|
1911
|
-
*
|
|
1940
|
+
* `compactNow(agent, signal, sourceCommandId?)` 会立刻 `signal.throwIfAborted()`。
|
|
1941
|
+
* 这里没有命令,传一个未中止的 AbortSignal,不传 sourceCommandId。
|
|
1942
|
+
* 少传 signal 会在写入 compaction 事件之前抛错。
|
|
1943
|
+
* 返回 null 表示没有可压的区间:记 warn 并停,不再空转。
|
|
1912
1944
|
*
|
|
1913
1945
|
* `turn/end` 写入日志时,驱动还占着 running。这时调用会在 `runMaintenance` 上失败,
|
|
1914
1946
|
* 同样写不出 compaction 事件。先等 `whenIdle`。旧宿主没有这个方法时让出一个宏任务
|
|
@@ -1917,8 +1949,6 @@ export function apply(ctx, config) {
|
|
|
1917
1949
|
async function maybeTrimSession(agent) {
|
|
1918
1950
|
const max = readSettings().maxRecentTurns
|
|
1919
1951
|
if (!max || max <= 0 || !agent) return
|
|
1920
|
-
const compaction = ctx.get('compaction')
|
|
1921
|
-
if (!compaction || typeof compaction.compactNow !== 'function') return
|
|
1922
1952
|
try {
|
|
1923
1953
|
if (typeof agent.whenIdle === 'function') await agent.whenIdle()
|
|
1924
1954
|
else await new Promise((resolve) => setTimeout(resolve, 0))
|
|
@@ -1946,19 +1976,40 @@ export function apply(ctx, config) {
|
|
|
1946
1976
|
return 0
|
|
1947
1977
|
}
|
|
1948
1978
|
}
|
|
1979
|
+
const sessionId = () => agent.session?.id ?? agent.id
|
|
1980
|
+
let compaction
|
|
1981
|
+
let lookupDetail = ''
|
|
1982
|
+
let resolved = false
|
|
1983
|
+
const engine = () => {
|
|
1984
|
+
if (!resolved) {
|
|
1985
|
+
const found = compactionFor(agent)
|
|
1986
|
+
compaction = found.service
|
|
1987
|
+
lookupDetail = found.detail
|
|
1988
|
+
resolved = true
|
|
1989
|
+
}
|
|
1990
|
+
return compaction
|
|
1991
|
+
}
|
|
1949
1992
|
let previous = Infinity
|
|
1950
1993
|
for (let i = 0; i < 6; i++) {
|
|
1951
1994
|
const turns = countTurns()
|
|
1952
1995
|
if (!turns || turns <= max) return
|
|
1953
1996
|
if (turns >= previous) {
|
|
1954
|
-
logger.info(`session ${
|
|
1997
|
+
logger.info(`session ${sessionId()} compact 后轮次没有减少(${turns}),停止`)
|
|
1998
|
+
return
|
|
1999
|
+
}
|
|
2000
|
+
if (!engine()) {
|
|
2001
|
+
const why = lookupDetail ? `(${lookupDetail})` : ''
|
|
2002
|
+
logger.warn(`compact trim: 会话 ${sessionId()} 约 ${turns} 轮,超过上限 ${max},但没有可用的压缩服务${why}`)
|
|
1955
2003
|
return
|
|
1956
2004
|
}
|
|
1957
2005
|
previous = turns
|
|
1958
2006
|
try {
|
|
1959
2007
|
const result = await compaction.compactNow(agent, new AbortController().signal)
|
|
1960
|
-
if (
|
|
1961
|
-
|
|
2008
|
+
if (result == null) {
|
|
2009
|
+
logger.warn(`compact trim: 会话 ${sessionId()} 的 compactNow 返回空(当时约 ${turns} 轮,上限 ${max})`)
|
|
2010
|
+
return
|
|
2011
|
+
}
|
|
2012
|
+
logger.info(`session ${sessionId()} trimmed via compact (turns~${turns} > ${max})`)
|
|
1962
2013
|
} catch (error) {
|
|
1963
2014
|
logger.warn(`compact trim failed: ${error?.message ?? error}`)
|
|
1964
2015
|
return
|
|
@@ -3597,11 +3648,11 @@ export function apply(ctx, config) {
|
|
|
3597
3648
|
'"本轮不是 QQ 发起的,确认要发吗",同一轮里再调一次才真的发出去。' +
|
|
3598
3649
|
'replyMode=tool 时这是唯一的出站方式;marker 模式下它是「直接发这一条」的旁路。' +
|
|
3599
3650
|
'文本超过出站字数上限时**一条都不发**,返回 `reason: "too-long"` 与 `limit`、`length` —— 请缩短后重发。' +
|
|
3600
|
-
'正文里的 [
|
|
3651
|
+
'正文里的 [表情名] 或 [表情数字] 会变成 QQ 系统表情;认不出的方括号原样发出。' +
|
|
3601
3652
|
'显式 groupId / userId 必须在对应白名单里,否则 `reason: "not-allowlisted"`,一条都不发。' +
|
|
3602
3653
|
'发出去的内容会记进 outbox(via: tool)。',
|
|
3603
3654
|
parameters: {
|
|
3604
|
-
text: { type: 'string', required: true, description: '要发送的文本;超过出站字数上限时不会发出。[
|
|
3655
|
+
text: { type: 'string', required: true, description: '要发送的文本;超过出站字数上限时不会发出。[表情名] 或 [表情数字] 会变成系统表情。' },
|
|
3605
3656
|
userId: { type: 'string', description: '私聊目标 QQ 号;与 groupId 二选一,都不给则用这一轮钉住的目的地。' },
|
|
3606
3657
|
groupId: { type: 'string', description: '群号;与 userId 二选一。显式指定时不引用别的群的消息。' },
|
|
3607
3658
|
},
|
|
@@ -4621,7 +4672,7 @@ export function buildUsagePrompt(s = {}) {
|
|
|
4621
4672
|
' QQ 商城表情(带表情 id 的)形如「[表情包#a1b2c3]:<表情名> <描述>」。描述可能不准,别当成绝对事实。',
|
|
4622
4673
|
' 方括号里的 6 位短哈希就是编号:交给 `qq_bridge_sticker` 的 save / send。',
|
|
4623
4674
|
'- 被「戳一戳」会显示成「[戳一戳](戳的是我)」—— 那和被人 @ 一样,是有人在叫你。',
|
|
4624
|
-
'- 系统表情显示成「[
|
|
4675
|
+
'- 系统表情显示成「[表情数字]」这种带编号的占位符。',
|
|
4625
4676
|
'',
|
|
4626
4677
|
'【怎么把话说回 QQ】',
|
|
4627
4678
|
// 出站方式是**开关**(replyMode),提示词必须跟着变 —— 否则会教模型一个在本部署里
|
|
@@ -4662,7 +4713,7 @@ export function buildUsagePrompt(s = {}) {
|
|
|
4662
4713
|
' 标签起不到界定作用,只会跟着正文一起发到 QQ。',
|
|
4663
4714
|
'- 不确定就说不知道,别假装做过某件事。',
|
|
4664
4715
|
`- 出站会自动去掉 Markdown 记号;超过 ${maxChars} 字的回复不会发出,请缩短后重发。`,
|
|
4665
|
-
'- 想发 QQ
|
|
4716
|
+
'- 想发 QQ 自带表情,就写半角方括号里的表情名,形如 [表情名],或入站那种 [表情数字]。',
|
|
4666
4717
|
' 发出去会变成系统表情。认不出的方括号会留在文字里。',
|
|
4667
4718
|
'',
|
|
4668
4719
|
'【无人值守:不要碰需要提权的操作】',
|
package/package.json
CHANGED