@yfwu2020/dsh-selection-explain 0.4.1 → 0.7.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/README.md +6 -2
- package/lib/client.js +811 -26
- package/lib/index.js +112 -4
- package/lib/index.js.map +1 -1
- package/lib/types/index.d.ts +18 -0
- package/package.json +3 -2
- package/scripts/gen-star-path.mjs +71 -0
- package/scripts/smoke-panel.mjs +709 -1
- package/scripts/test-client.mjs +12 -2
- package/scripts/test-format.mjs +93 -0
- package/src/client/index.js +811 -26
- package/src/index.ts +117 -4
package/scripts/test-client.mjs
CHANGED
|
@@ -2253,13 +2253,23 @@ assert('尾部上下文变化后仍命中历史(不再重新生成一个)',
|
|
|
2253
2253
|
const pill = Array.from(walk(panel.parentNode)).find((n) => n.className === 'dsh-sel-pill')
|
|
2254
2254
|
assert('右下角有状态胶囊', !!pill, pill ? pill.className : '未找到')
|
|
2255
2255
|
assert(
|
|
2256
|
-
'
|
|
2256
|
+
'胶囊结构和费用胶囊同构:星芒 + 主体 + 次要 + 箭头',
|
|
2257
2257
|
!!pill &&
|
|
2258
|
-
['dsh-sel-
|
|
2258
|
+
['dsh-sel-pillname', 'dsh-sel-pillmeta', 'dsh-sel-pillcaret'].every((c) =>
|
|
2259
2259
|
Array.from(walk(pill)).some((n) => n.className === c),
|
|
2260
|
+
) &&
|
|
2261
|
+
// SVG 元素的 class 是 SVGAnimatedString,客户端用 setAttribute('class', …) 写 —— 桩里也照这个读
|
|
2262
|
+
['dsh-sel-pillicon', 'dsh-sel-pillstar'].every((c) =>
|
|
2263
|
+
Array.from(walk(pill)).some((n) => n.getAttribute && n.getAttribute('class') === c),
|
|
2260
2264
|
),
|
|
2261
2265
|
pill ? walk(pill).next().value.className : '',
|
|
2262
2266
|
)
|
|
2267
|
+
const pillStarEl = pill && Array.from(walk(pill)).find((n) => n.getAttribute && n.getAttribute('class') === 'dsh-sel-pillstar')
|
|
2268
|
+
assert(
|
|
2269
|
+
'球心里的图形初始是"圆点态"那条 path(展开时不许直接画成星)',
|
|
2270
|
+
!!pillStarEl && /^M4 0/.test(String(pillStarEl.getAttribute('d'))),
|
|
2271
|
+
pillStarEl ? String(pillStarEl.getAttribute('d')).slice(0, 24) : '未找到',
|
|
2272
|
+
)
|
|
2263
2273
|
assert('箭头用和费用胶囊同一个字符 ▴ / ▾(不是大三角 ▲ ▼)', textOf(pill).indexOf('▲') < 0 && textOf(pill).indexOf('▼') < 0, textOf(pill))
|
|
2264
2274
|
|
|
2265
2275
|
// 进行中:圆点在呼吸(tone=busy),文本报阶段 + 秒数
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 会话格式契约:**写出去的那几个 shape 必须是当前内核认的**。
|
|
3
|
+
*
|
|
4
|
+
* 为什么单独一个文件:升格(`↗ 升格`)是往会话日志里**写**事件,写完由内核按
|
|
5
|
+
* 会话格式做准入校验 —— 校验发生在**真实内核跑起来的那一刻**,不在构建期,
|
|
6
|
+
* 所以离线测试全绿也照样能在用户机器上炸。v0.7.0 就是这么坏的:
|
|
7
|
+
*
|
|
8
|
+
* `source: { kind: 'plugin', plugin: name }` 是会话格式 v3 的写法;
|
|
9
|
+
* v4 起该包装已退役,准入直接抛
|
|
10
|
+
* format v4 message requires a producer-owned source kind
|
|
11
|
+
* 表现:升格请求返回 `ok: true`(只写进了内存里的会话面)→ 随后那轮 opener
|
|
12
|
+
* 「本轮运行失败」→ 整个 artifact 一个字都没落盘,新会话只剩表头。
|
|
13
|
+
*
|
|
14
|
+
* 这里钉住两件**离线可判**的事实(都是当初真正踩坏的):
|
|
15
|
+
* ① 上下文消息的 source.kind 是生产者自有的(`plugin:<插件名>`:非空、非裸 'plugin'),
|
|
16
|
+
* 且真的被用在 `form: 'snapshot'` 的上下文消息上(不是写了个没人用的常量);
|
|
17
|
+
* ② 构建产物里不再出现退役的 plugin 包装,以及升格那套 turn/step 骨架还在
|
|
18
|
+
* (骨架丢了消息就没有坐标,客户端不会把它显示成历史对话)。
|
|
19
|
+
*
|
|
20
|
+
* 跑法:node scripts/test-format.mjs(已挂进 npm test)
|
|
21
|
+
*/
|
|
22
|
+
import { readFileSync } from 'node:fs'
|
|
23
|
+
import { fileURLToPath } from 'node:url'
|
|
24
|
+
import { dirname, resolve } from 'node:path'
|
|
25
|
+
import { PLUGIN_SOURCE_KIND, name } from '../lib/index.js'
|
|
26
|
+
|
|
27
|
+
const HERE = dirname(fileURLToPath(import.meta.url))
|
|
28
|
+
const BUNDLE = readFileSync(resolve(HERE, '..', 'lib', 'index.js'), 'utf8')
|
|
29
|
+
|
|
30
|
+
let pass = 0
|
|
31
|
+
let fail = 0
|
|
32
|
+
const assert = (label, ok, detail) => {
|
|
33
|
+
if (ok) {
|
|
34
|
+
pass += 1
|
|
35
|
+
console.log(`PASS ${label}${detail ? ` — ${detail}` : ''}`)
|
|
36
|
+
} else {
|
|
37
|
+
fail += 1
|
|
38
|
+
console.log(`FAIL ${label}${detail ? ` — ${detail}` : ''}`)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ───────────────────────── ① 来源归属(v4 准入的那条线) ─────────────────────────
|
|
43
|
+
assert(
|
|
44
|
+
'来源归属:上下文消息的 kind 与「插件名」一致(plugin:<插件名>)',
|
|
45
|
+
PLUGIN_SOURCE_KIND === `plugin:${name}`,
|
|
46
|
+
JSON.stringify({ PLUGIN_SOURCE_KIND, name }),
|
|
47
|
+
)
|
|
48
|
+
assert(
|
|
49
|
+
'来源归属:kind 是生产者自有的(非空、非裸 plugin)',
|
|
50
|
+
typeof PLUGIN_SOURCE_KIND === 'string' && PLUGIN_SOURCE_KIND.length > 'plugin:'.length && PLUGIN_SOURCE_KIND !== 'plugin',
|
|
51
|
+
JSON.stringify(PLUGIN_SOURCE_KIND),
|
|
52
|
+
)
|
|
53
|
+
assert(
|
|
54
|
+
'来源归属:第三方插件带 `plugin:` 命名空间(与内核 v3→v4 迁移的写法一致)',
|
|
55
|
+
PLUGIN_SOURCE_KIND.startsWith('plugin:'),
|
|
56
|
+
JSON.stringify(PLUGIN_SOURCE_KIND),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
// ───────────────────────── ② 真的用上了(不是写了没人用的常量) ─────────────────────────
|
|
60
|
+
const used = BUNDLE.indexOf('kind: PLUGIN_SOURCE_KIND')
|
|
61
|
+
assert('使用点:源码里真的以 PLUGIN_SOURCE_KIND 作为 kind 写出消息', used >= 0, used >= 0 ? `偏移 ${used}` : '构建产物里找不到')
|
|
62
|
+
{
|
|
63
|
+
// 同一个 source 字面量里:kind 之后要跟着 snapshot 呈现(客户端据此折叠成「上下文」行)
|
|
64
|
+
const window = used >= 0 ? BUNDLE.slice(used, used + 240) : ''
|
|
65
|
+
assert('使用点:同一条 source 带 `form: \'snapshot\'`(客户端才折叠成上下文行)', /form:\s*'snapshot'/.test(window), window ? JSON.stringify(window.slice(0, 80)) : '')
|
|
66
|
+
assert('使用点:同一条 source 带 sections(snapshot 的命名分节,模型读的就是这些字)', /sections:\s*\[/.test(window), '')
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ───────────────────────── ③ 退役写法不再出现 ─────────────────────────
|
|
70
|
+
{
|
|
71
|
+
// v3 的 plugin 包装:kind:'plugin' 后面紧跟一个 plugin 字段。
|
|
72
|
+
// 注意这是**文本级**断言(连注释里都别写出那个形状)—— 宁可误报,也别漏报:
|
|
73
|
+
// 这正是本次回归的形态,写进注释里也会把将来的人带沟里。
|
|
74
|
+
const retired = /kind:\s*(["'])plugin\1\s*,\s*plugin\s*:/.exec(BUNDLE)
|
|
75
|
+
assert(
|
|
76
|
+
'退役写法:构建产物里没有 kind: plugin + plugin 字段的包装(v4 会当场拒掉)',
|
|
77
|
+
retired === null,
|
|
78
|
+
retired ? `offset ${retired.index}:${JSON.stringify(retired[0])}` : '',
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ───────────────────────── ④ 升格那套 turn/step 骨架还在 ─────────────────────────
|
|
83
|
+
{
|
|
84
|
+
// 客户端按 turn/step 事件给消息定位;少一个,历史消息就没有坐标、不显示成对话
|
|
85
|
+
const skeleton = ['turn/start', 'step/start', 'user/message', 'assistant/message', 'step/end', 'turn/end']
|
|
86
|
+
const missing = skeleton.filter((type) => !BUNDLE.includes(`'${type}'`))
|
|
87
|
+
assert('升格骨架:turn/step 六个事件都还写着', missing.length === 0, missing.length ? `缺:${missing.join(', ')}` : '')
|
|
88
|
+
assert('升格骨架:turn/end 带 reason(否则那轮不算收尾)', /reason:\s*\{\s*kind:\s*'completed'/.test(BUNDLE), '')
|
|
89
|
+
assert('升格骨架:历史轮号从 1000 起(避开真实第一轮的号段)', /turnNo\s*=\s*1000/.test(BUNDLE), '')
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
console.log(`\n=== 会话格式契约测试结束:${pass} 通过 / ${fail} 失败 ===`)
|
|
93
|
+
process.exit(fail === 0 ? 0 : 1)
|