dsh-instruction-memory 1.3.0 → 1.3.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/CHANGELOG.md +16 -0
- package/lib/client.js +15 -2
- package/lib/index.js +36 -30
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,22 @@
|
|
|
7
7
|
> 1.0.13 起由 CI 经 OIDC Trusted Publishing 发布(首个带 provenance 的版本)。
|
|
8
8
|
> 这些历史 tag **不应回填**——npm 上的版本号已被占用。
|
|
9
9
|
|
|
10
|
+
## [1.3.1] — 2026-10-15
|
|
11
|
+
|
|
12
|
+
1.3.0 发布当天的评审跟进:一个披露计数回归与三个展示层小修正。
|
|
13
|
+
|
|
14
|
+
### 修复
|
|
15
|
+
|
|
16
|
+
- **ui-test 去竞态**:F7 真实定时器冒烟中「暂停后立刻断言注入已静音」与 50ms 定时器存在调度竞态
|
|
17
|
+
(高负载 runner 上定时器可能已过期),改为确定性地断言到期时刻已写入存储文件,并把到期等待
|
|
18
|
+
从 300ms 放宽到 1s。仅测试基建,不影响已发布的包内容。
|
|
19
|
+
- **导入回退去重后的计数披露**:「新增指令 N 条 / 新增用户形象 N 个」按实际落库数(staged)
|
|
20
|
+
统计,不再包含被回退去重跳过的项;截断披露同样只统计实际落库项。80f94f5 引入的口径回归,
|
|
21
|
+
contract-test 新增断言钉住「2 通过 − 1 回退重复 = 新增指令 1 条」。
|
|
22
|
+
- **暂停倒计时与复制路径的三个小修正**:新起暂停时先取当前时刻再起秒表(否则长开面板的
|
|
23
|
+
首帧剩余时间可能偏大最多 1 秒,下一秒自愈);他窗发起暂停时复位暂停菜单展开态;
|
|
24
|
+
剪贴板 execCommand 抛错路径也移除游离的辅助 textarea。均为 1.3.0 评审发现,仅展示层。
|
|
25
|
+
|
|
10
26
|
## [1.3.0] — 2026-10-15
|
|
11
27
|
|
|
12
28
|
两个审查修复与两项体验增强(F12 / F7)。
|
package/lib/client.js
CHANGED
|
@@ -1600,6 +1600,13 @@ window.__ModuleLoader__.load({
|
|
|
1600
1600
|
&& view.injection.muted === true
|
|
1601
1601
|
React.useEffect(() => {
|
|
1602
1602
|
if (!mutedNow) return undefined
|
|
1603
|
+
// A pause started from ANOTHER window replaces the menu row; the open
|
|
1604
|
+
// flag must not resurface when the row returns after expiry.
|
|
1605
|
+
setPauseMenuOpen(false)
|
|
1606
|
+
// Snapshot the clock the moment the pause lands: nowTick may be up to
|
|
1607
|
+
// a second stale from a PREVIOUS pause, which would render the first
|
|
1608
|
+
// frame with an inflated remaining time.
|
|
1609
|
+
setNowTick(Date.now())
|
|
1603
1610
|
const tick = setInterval(() => setNowTick(Date.now()), 1000)
|
|
1604
1611
|
return () => clearInterval(tick)
|
|
1605
1612
|
}, [mutedNow])
|
|
@@ -1984,8 +1991,14 @@ window.__ModuleLoader__.load({
|
|
|
1984
1991
|
const host = document.body || document.head
|
|
1985
1992
|
if (host && typeof host.appendChild === 'function') host.appendChild(area)
|
|
1986
1993
|
if (typeof area.select === 'function') area.select()
|
|
1987
|
-
|
|
1988
|
-
|
|
1994
|
+
// execCommand may THROW (not merely return false); the detached
|
|
1995
|
+
// helper must leave the DOM on every path.
|
|
1996
|
+
let ok = false
|
|
1997
|
+
try {
|
|
1998
|
+
ok = typeof document.execCommand === 'function' && document.execCommand('copy') === true
|
|
1999
|
+
} finally {
|
|
2000
|
+
if (host && typeof host.removeChild === 'function' && area.parentNode === host) host.removeChild(area)
|
|
2001
|
+
}
|
|
1989
2002
|
if (ok) {
|
|
1990
2003
|
setNotice(t('diag.copied'))
|
|
1991
2004
|
return
|
package/lib/index.js
CHANGED
|
@@ -3372,8 +3372,6 @@ export function apply(ctx) {
|
|
|
3372
3372
|
const seenIds = new Set(existing.map((entry) => entry.setId + '\u0000' + entry.id))
|
|
3373
3373
|
const seenBodies = new Set(existing.map((entry) => bodyKey(entry.setId, entry.title, entry.content)))
|
|
3374
3374
|
const accepted = []
|
|
3375
|
-
const cutFields = new Set()
|
|
3376
|
-
let truncatedCount = 0
|
|
3377
3375
|
let skipped = 0
|
|
3378
3376
|
for (const candidate of (rawEntries || [])) {
|
|
3379
3377
|
const entry = sanitizeEntry(candidate)
|
|
@@ -3390,17 +3388,12 @@ export function apply(ctx) {
|
|
|
3390
3388
|
skipped += 1
|
|
3391
3389
|
continue
|
|
3392
3390
|
}
|
|
3391
|
+
// Truncation travels per item and is only REPORTED for items that are
|
|
3392
|
+
// actually staged: an item later skipped as a fallback duplicate must
|
|
3393
|
+
// not inflate the truncation disclosure (or the inserted-count).
|
|
3393
3394
|
const cut = truncatedFields(candidate)
|
|
3394
|
-
if (cut.length > 0) {
|
|
3395
|
-
// sanitizeEntry enforces the limits silently, which is fine for the
|
|
3396
|
-
// editor (its inputs carry the same maxLength) but wrong here: an
|
|
3397
|
-
// imported 7,000-character instruction would come back as 6,000 with
|
|
3398
|
-
// nothing said about the loss.
|
|
3399
|
-
truncatedCount += 1
|
|
3400
|
-
for (const field of cut) cutFields.add(field)
|
|
3401
|
-
}
|
|
3402
3395
|
entry.updatedAt = Date.now()
|
|
3403
|
-
accepted.push({ entry, wanted })
|
|
3396
|
+
accepted.push({ entry, wanted, cut })
|
|
3404
3397
|
seenIds.add(wanted + '\u0000' + entry.id)
|
|
3405
3398
|
seenBodies.add(bodyKey(wanted, entry.title, entry.content))
|
|
3406
3399
|
}
|
|
@@ -3411,8 +3404,6 @@ export function apply(ctx) {
|
|
|
3411
3404
|
const seenProfileIds = new Set(existingProfiles.map((profile) => profile.setId + '\u0000' + profile.id))
|
|
3412
3405
|
const seenProfileBodies = new Set(existingProfiles.map((profile) => profile.setId + '\u0000' + profile.name + '\u0000' + profile.content))
|
|
3413
3406
|
const acceptedProfiles = []
|
|
3414
|
-
const cutProfileFields = new Set()
|
|
3415
|
-
let truncatedProfileCount = 0
|
|
3416
3407
|
let skippedProfiles = 0
|
|
3417
3408
|
for (const candidate of (rawProfiles || [])) {
|
|
3418
3409
|
const profile = sanitizeProfile(candidate)
|
|
@@ -3431,12 +3422,8 @@ export function apply(ctx) {
|
|
|
3431
3422
|
continue
|
|
3432
3423
|
}
|
|
3433
3424
|
const cut = truncatedProfileFields(candidate)
|
|
3434
|
-
if (cut.length > 0) {
|
|
3435
|
-
truncatedProfileCount += 1
|
|
3436
|
-
for (const field of cut) cutProfileFields.add(field)
|
|
3437
|
-
}
|
|
3438
3425
|
profile.updatedAt = Date.now()
|
|
3439
|
-
acceptedProfiles.push({ profile, wanted })
|
|
3426
|
+
acceptedProfiles.push({ profile, wanted, cut })
|
|
3440
3427
|
seenProfileIds.add(wanted + '\u0000' + profile.id)
|
|
3441
3428
|
seenProfileBodies.add(wanted + '\u0000' + profile.name + '\u0000' + profile.content)
|
|
3442
3429
|
}
|
|
@@ -3499,7 +3486,7 @@ export function apply(ctx) {
|
|
|
3499
3486
|
finalSeenBodies.add(bodyKey(finalId, item.entry.title, item.entry.content))
|
|
3500
3487
|
}
|
|
3501
3488
|
item.entry.setId = finalId
|
|
3502
|
-
stagedEntries.push(item
|
|
3489
|
+
stagedEntries.push(item)
|
|
3503
3490
|
}
|
|
3504
3491
|
const finalSeenProfileIds = new Set(existingProfiles.map((profile) => profile.setId + '\u0000' + profile.id))
|
|
3505
3492
|
const finalSeenProfileBodies = new Set(existingProfiles.map((profile) => profile.setId + '\u0000' + profile.name + '\u0000' + profile.content))
|
|
@@ -3522,7 +3509,7 @@ export function apply(ctx) {
|
|
|
3522
3509
|
finalSeenProfileBodies.add(finalId + '\u0000' + item.profile.name + '\u0000' + item.profile.content)
|
|
3523
3510
|
}
|
|
3524
3511
|
item.profile.setId = finalId
|
|
3525
|
-
stagedProfiles.push(item
|
|
3512
|
+
stagedProfiles.push(item)
|
|
3526
3513
|
}
|
|
3527
3514
|
}
|
|
3528
3515
|
if (stagedEntries.length === 0 && stagedProfiles.length === 0) {
|
|
@@ -3538,8 +3525,8 @@ export function apply(ctx) {
|
|
|
3538
3525
|
}
|
|
3539
3526
|
const saved = await commit(() => {
|
|
3540
3527
|
state.data.sets.push(...createdSets)
|
|
3541
|
-
for (const
|
|
3542
|
-
for (const
|
|
3528
|
+
for (const item of stagedEntries) state.data.entries.push(item.entry)
|
|
3529
|
+
for (const item of stagedProfiles) state.data.profiles.push(item.profile)
|
|
3543
3530
|
})
|
|
3544
3531
|
const parts = []
|
|
3545
3532
|
if (createdSets.length > 0) {
|
|
@@ -3551,19 +3538,38 @@ export function apply(ctx) {
|
|
|
3551
3538
|
if (dedupedOnFallback > 0) {
|
|
3552
3539
|
parts.push('另有 ' + dedupedOnFallback + ' 项因回退到当前配置集后与已有内容重复被跳过')
|
|
3553
3540
|
}
|
|
3554
|
-
if (
|
|
3555
|
-
|
|
3556
|
-
|
|
3541
|
+
if (stagedEntries.length > 0) {
|
|
3542
|
+
// Counts quote what actually LANDED: staged ⊆ accepted, and the gap is
|
|
3543
|
+
// the fallback duplicates already disclosed on their own line — an
|
|
3544
|
+
// accepted-based count would claim inserts that were skipped.
|
|
3545
|
+
const stagedCut = new Set()
|
|
3546
|
+
let stagedTruncated = 0
|
|
3547
|
+
for (const item of stagedEntries) {
|
|
3548
|
+
if (item.cut.length > 0) {
|
|
3549
|
+
stagedTruncated += 1
|
|
3550
|
+
for (const field of item.cut) stagedCut.add(field)
|
|
3551
|
+
}
|
|
3552
|
+
}
|
|
3553
|
+
const truncated = stagedTruncated > 0
|
|
3554
|
+
? ';其中 ' + stagedTruncated + ' 条超过长度上限,' + [...stagedCut].join('、') + '已被截断'
|
|
3557
3555
|
: ''
|
|
3558
|
-
parts.push('新增指令 ' +
|
|
3556
|
+
parts.push('新增指令 ' + stagedEntries.length + ' 条,跳过 ' + skipped + ' 条' + truncated)
|
|
3559
3557
|
} else if (skipped > 0) {
|
|
3560
3558
|
parts.push('指令跳过 ' + skipped + ' 条(已存在或无效)')
|
|
3561
3559
|
}
|
|
3562
|
-
if (
|
|
3563
|
-
const
|
|
3564
|
-
|
|
3560
|
+
if (stagedProfiles.length > 0) {
|
|
3561
|
+
const stagedCut = new Set()
|
|
3562
|
+
let stagedTruncated = 0
|
|
3563
|
+
for (const item of stagedProfiles) {
|
|
3564
|
+
if (item.cut.length > 0) {
|
|
3565
|
+
stagedTruncated += 1
|
|
3566
|
+
for (const field of item.cut) stagedCut.add(field)
|
|
3567
|
+
}
|
|
3568
|
+
}
|
|
3569
|
+
const truncated = stagedTruncated > 0
|
|
3570
|
+
? ';其中 ' + stagedTruncated + ' 个超过长度上限,' + [...stagedCut].join('、') + '已被截断'
|
|
3565
3571
|
: ''
|
|
3566
|
-
parts.push('新增用户形象 ' +
|
|
3572
|
+
parts.push('新增用户形象 ' + stagedProfiles.length + ' 个,跳过 ' + skippedProfiles + ' 个' + truncated)
|
|
3567
3573
|
} else if (skippedProfiles > 0) {
|
|
3568
3574
|
parts.push('用户形象跳过 ' + skippedProfiles + ' 个(已存在或无效)')
|
|
3569
3575
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-instruction-memory",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "User-only instruction memory for DSH: long-term instructions maintained in the settings UI and auto-injected into every conversation — the model has no write access. 指令记忆:只由用户在设置页维护的长期指令,自动注入此后每轮对话;模型没有任何写入口。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|