dsh-deepmoyu 2.3.0 → 2.4.2
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/AGENTS.md +93 -153
- package/CHANGELOG.md +36 -0
- package/CONTRIBUTING.md +51 -0
- package/CONTRIBUTORS.md +3 -7
- package/NOTICE.md +3 -3
- package/README.md +143 -81
- package/SECURITY.md +21 -0
- package/assets/screenshots/memory.png +0 -0
- package/assets/screenshots/menu.png +0 -0
- package/assets/screenshots/workspace.png +0 -0
- package/client.js +1404 -441
- package/cordis.patch.yml +1 -1
- package/docs/ASSETS.md +25 -0
- package/docs/DEPLOYMENT.md +102 -0
- package/docs/PRIVACY.md +32 -0
- package/docs/RELEASE_AUDIT.md +70 -0
- package/docs/RELEASING.md +94 -0
- package/docs/TEST_MATRIX.md +77 -0
- package/docs/assets-permissions.json +20 -0
- package/docs/tool-evidence.json +196 -0
- package/index.js +1095 -285
- package/lib/asr.py +41 -35
- package/lib/background-output.mjs +64 -17
- package/lib/background-tools.mjs +1 -0
- package/lib/companion.mjs +43 -0
- package/lib/config-policy.mjs +20 -0
- package/lib/favops.py +9 -2
- package/lib/ids.mjs +41 -0
- package/lib/login-session.mjs +34 -0
- package/lib/login.mjs +5 -4
- package/lib/mcp.mjs +3 -2
- package/lib/memory-center.mjs +74 -0
- package/lib/mode.mjs +6 -7
- package/lib/model-relay.mjs +5 -4
- package/lib/open-directory.mjs +93 -0
- package/lib/pace.mjs +92 -12
- package/lib/persona.mjs +46 -0
- package/lib/product-status.mjs +123 -0
- package/lib/prompts.mjs +40 -5
- package/lib/pv-production.mjs +280 -0
- package/lib/pv-session.mjs +316 -0
- package/lib/pv.mjs +398 -306
- package/lib/pv.py +84 -22
- package/lib/pv_cover.py +108 -26
- package/lib/pv_editorial.py +336 -0
- package/lib/pv_verify.py +267 -0
- package/lib/python-env.mjs +61 -0
- package/lib/reply.mjs +10 -3
- package/lib/run-settings.mjs +6 -0
- package/lib/sticker-automation.mjs +5 -4
- package/lib/sticker.mjs +135 -25
- package/lib/tool-scope.mjs +65 -0
- package/lib/update.mjs +265 -0
- package/lib/uvfetch.mjs +165 -153
- package/lib/verify.mjs +111 -127
- package/marketplace/pyyxzi__dsh-deepmoyu.yml +7 -0
- package/package.json +23 -12
- package/presets/deepmoyu.patch.yml +1 -1
- package/prompts/duty.md +4 -3
- package/screenshots.json +5 -0
- package/selfcheck.mjs +21 -6
- package/vendor/bilibili-mcp/requirements.in +9 -0
- package/vendor/bilibili-mcp/requirements.txt +1936 -11
- package/vendor/bilibili-mcp/runtime_guard.py +35 -10
- package/lib/__pycache__/pv.cpython-310.pyc +0 -0
- package/lib/__pycache__/pv_cover.cpython-310.pyc +0 -0
- package/tests/automation-routes.test.mjs +0 -183
- package/tests/automation.test.mjs +0 -79
- package/tests/background-tools.test.mjs +0 -182
- package/tests/background-window.test.mjs +0 -75
- package/tests/dup-cross-channel.test.mjs +0 -158
- package/tests/manual-browsing.test.mjs +0 -671
- package/tests/mcp-restart.test.mjs +0 -131
- package/tests/memory-chain.test.mjs +0 -413
- package/tests/model-relay.test.mjs +0 -116
- package/tests/persona.test.mjs +0 -67
- package/tests/pet-run-routes.test.mjs +0 -127
- package/tests/pet-run-settings.test.mjs +0 -38
- package/tests/pet-session.test.mjs +0 -125
- package/tests/pv-stage1.test.mjs +0 -203
- package/tests/sticker-routes.test.mjs +0 -141
- package/tests/sticker-upload-routes.test.mjs +0 -97
- package/tests/sticker-upload.test.mjs +0 -122
- package/tests/sticker.test.mjs +0 -169
- package/tests/test_guard_cross_channel.py +0 -141
- package/tests/test_pv_cover.py +0 -95
- package/tests/test_pv_stage1.py +0 -250
- package/tests/test_reply_pictures.py +0 -255
- package/tests/token-ledger.test.mjs +0 -78
- package/tests/verify_pv_sample.py +0 -91
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* tests/dup-cross-channel.test.mjs —— 「同一条评论只回一次」的回归测试
|
|
3
|
-
* ============================================================================
|
|
4
|
-
* 2026-10-25 使用者报:「有的时候回复动态或者评论,会回一个人一条评论两条消息,修复一下,
|
|
5
|
-
* 不要重复回,一条回一个就行」。
|
|
6
|
-
*
|
|
7
|
-
* 真机根因(state.json + runtime-guard.sqlite3 实测):同一条评论能从**两条通道**各回一次 ——
|
|
8
|
-
* · bili_reply(bvid, rpid) 记 `video:<rpid>`
|
|
9
|
-
* · bili_reply_opus(opus_id, rpid) 记 `opus-reply:<opusId>:<rpid>`
|
|
10
|
-
* 两个键各记各的、谁都不认谁,模型换个工具看到同一条评论就又回一遍。
|
|
11
|
-
* 账本里共有 34 个 rpid 这样重复过,最近一例相隔 4 分 24 秒(rpid 318036124737)。
|
|
12
|
-
*
|
|
13
|
-
* 这个文件钉住两件事:
|
|
14
|
-
* 1. 跨通道能认出来(不再重复回);
|
|
15
|
-
* 2. 动态楼层在**别的动态**里同号仍然不拦(别把 2026-10-20 那个静默漏回的坑走回去)。
|
|
16
|
-
*
|
|
17
|
-
* 跑:node tests/dup-cross-channel.test.mjs
|
|
18
|
-
* 纯函数、不碰网络和磁盘,随时可跑。
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import assert from 'node:assert/strict'
|
|
22
|
-
import {
|
|
23
|
-
dupLookupVideoComment, dupLookupOpusComment,
|
|
24
|
-
dupLookup, dupRemember, keyVideoReply, keyOpusReply,
|
|
25
|
-
} from '../lib/dup.mjs'
|
|
26
|
-
|
|
27
|
-
let n = 0
|
|
28
|
-
const t = (name, fn) => {
|
|
29
|
-
try { fn(); n++; console.log(` ok ${name}`) } catch (err) { console.error(` FAIL ${name}\n ${err.message}`); process.exitCode = 1 }
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
console.log('dup-cross-channel')
|
|
33
|
-
|
|
34
|
-
/* ── 1. 视频通道 ───────────────────────────────────────────────────────────── */
|
|
35
|
-
|
|
36
|
-
t('视频通道:没回过就是没回过', () => {
|
|
37
|
-
assert.equal(dupLookupVideoComment({}, '318036124737').hit, false)
|
|
38
|
-
assert.equal(dupLookupVideoComment(null, '318036124737').hit, false)
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
t('视频通道:自己记过的认得', () => {
|
|
42
|
-
const book = dupRemember({}, keyVideoReply('318036124737'), { at: '2026-10-24T11:01:50.178Z' })
|
|
43
|
-
const hit = dupLookupVideoComment(book, '318036124737')
|
|
44
|
-
assert.equal(hit.hit, true)
|
|
45
|
-
assert.equal(hit.at, '2026-10-24T11:01:50.178Z')
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
t('视频通道:老账本里裸 rpid 的照样认', () => {
|
|
49
|
-
const book = { 318036124737: { bvid: 'BV14AHP6VExR', rpid: '318036124737' } }
|
|
50
|
-
assert.equal(dupLookupVideoComment(book, '318036124737').hit, true)
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
t('🔁 视频通道:动态通道回过的同号 rpid 也认(就是这次的 bug)', () => {
|
|
54
|
-
// 真机账本原文:同一条评论先在视频通道回过,4 分钟后动态通道又记了一次
|
|
55
|
-
const book = {
|
|
56
|
-
'video:318036124737': { bvid: 'BV14AHP6VExR', rpid: '318036124737', at: '2026-10-24T11:01:50.178Z' },
|
|
57
|
-
'opus-reply:1262700971822153744:318036124737': {
|
|
58
|
-
opus_id: '1262700971822153744', rpid: '318036124737', at: '2026-10-24T11:06:14.771Z',
|
|
59
|
-
},
|
|
60
|
-
}
|
|
61
|
-
// 只保留动态那条(模拟"先回动态、再回视频"的顺序)
|
|
62
|
-
const onlyOpus = { 'opus-reply:1262700971822153744:318036124737': book['opus-reply:1262700971822153744:318036124737'] }
|
|
63
|
-
assert.equal(dupLookupVideoComment(onlyOpus, '318036124737').hit, true)
|
|
64
|
-
assert.equal(dupLookupVideoComment(onlyOpus, '318036124737').at, '2026-10-24T11:06:14.771Z')
|
|
65
|
-
assert.equal(dupLookupVideoComment(onlyOpus, '318036124737').key, 'opus-reply:1262700971822153744:318036124737')
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
t('视频通道:别的 rpid 不受影响', () => {
|
|
69
|
-
const book = { 'opus-reply:1262700971822153744:318036124737': { rpid: '318036124737' } }
|
|
70
|
-
assert.equal(dupLookupVideoComment(book, '318036124738').hit, false)
|
|
71
|
-
// 后缀必须整段相等,不能被前半段糊弄
|
|
72
|
-
assert.equal(dupLookupVideoComment(book, '360124737').hit, false)
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
/* ── 2. 动态通道 ───────────────────────────────────────────────────────────── */
|
|
76
|
-
|
|
77
|
-
t('动态通道:没回过就是没回过', () => {
|
|
78
|
-
assert.equal(dupLookupOpusComment({}, '318036124737', '0', '1262700971822153744').hit, false)
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
t('动态通道:同一层楼回过的认得', () => {
|
|
82
|
-
const book = dupRemember({}, keyOpusReply('318036124737', '0', '1262700971822153744'), {
|
|
83
|
-
opus_id: '1262700971822153744', rpid: '318036124737', at: '2026-10-24T11:06:14.771Z',
|
|
84
|
-
})
|
|
85
|
-
assert.equal(dupLookupOpusComment(book, '318036124737', '0', '1262700971822153744').hit, true)
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
t('🔁 动态通道:视频通道回过的同号 rpid 也认(反过来的那一半)', () => {
|
|
89
|
-
const book = { 'video:318036124737': { bvid: 'BV14AHP6VExR', rpid: '318036124737', at: '2026-10-24T11:01:50.178Z' } }
|
|
90
|
-
const hit = dupLookupOpusComment(book, '318036124737', '0', '1262700971822153744')
|
|
91
|
-
assert.equal(hit.hit, true)
|
|
92
|
-
assert.equal(hit.key, 'video:318036124737')
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
t('🔁 动态通道:老账本裸 rpid(视频通道)也认', () => {
|
|
96
|
-
const book = { 318036124737: { bvid: 'BV14AHP6VExR' } }
|
|
97
|
-
assert.equal(dupLookupOpusComment(book, '318036124737', '0', '1262700971822153744').hit, true)
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
t('⚠️ 动态通道:**别的动态**下同号楼层仍然不拦(别走回 2026-10-20 的坑)', () => {
|
|
101
|
-
const book = { 'opus-reply:1262700971822153744:318036124737': { rpid: '318036124737' } }
|
|
102
|
-
// 同在 B 动态下回过的同号楼层 —— 原来就是这么判的(同一把钥匙),行为不能变
|
|
103
|
-
assert.equal(dupLookupOpusComment(book, '318036124737', '0', '1262700971822153744').hit, true)
|
|
104
|
-
// 换一条动态,同号楼层不受牵连(新层楼照样能回)
|
|
105
|
-
assert.equal(dupLookupOpusComment(book, '318036124737', '0', '1262712387822157856').hit, false)
|
|
106
|
-
})
|
|
107
|
-
|
|
108
|
-
t('楼中楼:同一层楼里的**另一条**评论不算重复(root 只是排版,不是身份)', () => {
|
|
109
|
-
const book = dupRemember({}, keyOpusReply('318036124799', '318036124737', '1262700971822153744'), {
|
|
110
|
-
opus_id: '1262700971822153744', rpid: '318036124799',
|
|
111
|
-
})
|
|
112
|
-
// 老楼层键 `opus:<root>` 只在记录没写 rpid 时才算保守命中;写了 rpid 就要对得上
|
|
113
|
-
assert.equal(dupLookupOpusComment(book, '318036124800', '318036124737', '1262700971822153744').hit, false)
|
|
114
|
-
// 老格式、没写 rpid 的那条(没法自证是哪层)⇒ 保守算回过,宁可不回也别重复刷
|
|
115
|
-
assert.equal(dupLookupOpusComment({ '318036124737': {} }, '318036124800', '318036124737', '1262700971822153744').hit, true)
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
/* ── 3. 边界:坏输入不能变成"拦一切"或"放一切" ─────────────────────────────── */
|
|
119
|
-
|
|
120
|
-
t('坏 ID:一律当没回过(和 old 的 dupLookup 一致),不抛异常', () => {
|
|
121
|
-
const book = { 'video:318036124737': { rpid: '318036124737' } }
|
|
122
|
-
for (const bad of ['', '0', 'abc', null, undefined, '-1']) {
|
|
123
|
-
assert.equal(dupLookupVideoComment(book, bad).hit, false, `video ${JSON.stringify(bad)}`)
|
|
124
|
-
assert.equal(dupLookupOpusComment(book, bad, '0', '1262700971822153744').hit, false, `opus ${JSON.stringify(bad)}`)
|
|
125
|
-
}
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
t('账本不是对象时也安全', () => {
|
|
129
|
-
for (const junk of [null, undefined, [], 'x', 42]) {
|
|
130
|
-
assert.equal(dupLookupVideoComment(junk, '318036124737').hit, false)
|
|
131
|
-
assert.equal(dupLookupOpusComment(junk, '318036124737', '0', '1262700971822153744').hit, false)
|
|
132
|
-
}
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
t('大数 ID 原样当字符串过(不许转成数字掉尾数)', () => {
|
|
136
|
-
const big = '3180361247371234567'
|
|
137
|
-
const book = { [`opus-reply:1262700971822153744:${big}`]: { rpid: big } }
|
|
138
|
-
assert.equal(dupLookupVideoComment(book, big).hit, true)
|
|
139
|
-
// 少一位就认不出来(不能被 JS number 的精度糊过去)
|
|
140
|
-
assert.equal(dupLookupVideoComment(book, '3180361247371234560').hit, false)
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
/* ── 4. 旧行为没被改坏 ────────────────────────────────────────────────────── */
|
|
144
|
-
|
|
145
|
-
t('dupLookup 本体行为不变', () => {
|
|
146
|
-
const book = dupRemember({}, 'video:1', { at: 'now' })
|
|
147
|
-
assert.equal(dupLookup(book, 'video:1').hit, true)
|
|
148
|
-
assert.equal(dupLookup(book, 'video:2').hit, false)
|
|
149
|
-
// 传 legacyKeys 时 key 本身必须有值(空 key 直接查不了账)
|
|
150
|
-
assert.equal(dupLookup(book, 'video:2', ['video:1']).hit, true)
|
|
151
|
-
assert.equal(dupLookup(book, '', ['video:1']).hit, false)
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
if (process.exitCode) {
|
|
155
|
-
console.error(`\n${n} passed, some FAILED`)
|
|
156
|
-
} else {
|
|
157
|
-
console.log(`\n${n}/${n} passed`)
|
|
158
|
-
}
|