dsh-vscode-mode 0.23.4 → 0.23.5
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 +4 -4
- package/lib/client.js +1 -0
- package/lib/client.js.map +2 -2
- package/lib/index.js +60 -16
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/state/diffSummary.ts +4 -0
- package/src/pending/decide.ts +9 -2
- package/src/pending/store.ts +13 -9
- package/src/pendingCompat.ts +7 -1
- package/src/pendingRpc.ts +5 -2
- package/src/reviewDb.ts +25 -0
- package/src/reviewRpc.ts +7 -3
package/README.md
CHANGED
|
@@ -533,13 +533,13 @@ profile 请替换为实际目标,不要把示例目录当成当前运行 profi
|
|
|
533
533
|
|
|
534
534
|
```bash
|
|
535
535
|
# ① Git 安装(clone + prepare 构建;推荐打固定 tag)
|
|
536
|
-
dsh plugin --profile web add github:Lenonss/DSH_VsCodeMode#v0.23.
|
|
536
|
+
dsh plugin --profile web add github:Lenonss/DSH_VsCodeMode#v0.23.5
|
|
537
537
|
|
|
538
538
|
# ② npm 注册表(发布到 npm 后)
|
|
539
|
-
dsh plugin --profile web add dsh-vscode-mode@0.23.
|
|
539
|
+
dsh plugin --profile web add dsh-vscode-mode@0.23.5
|
|
540
540
|
|
|
541
541
|
# ③ GitHub Release tgz 直装(Release asset 可用后)
|
|
542
|
-
dsh plugin --profile web add https://github.com/Lenonss/DSH_VsCodeMode/releases/download/v0.23.
|
|
542
|
+
dsh plugin --profile web add https://github.com/Lenonss/DSH_VsCodeMode/releases/download/v0.23.5/dsh-vscode-mode-0.23.5.tgz
|
|
543
543
|
```
|
|
544
544
|
|
|
545
545
|
> **DSH 版本政策(0.17.0 起)**:六项 DSH peer 仍统一声明为 `*`,另以
|
|
@@ -880,7 +880,7 @@ Keep All / Undo All 却是亮的。修复:单文件 Keep / Undo 覆盖冲突
|
|
|
880
880
|
完整变更见 [GitHub Releases](https://github.com/Lenonss/DSH_VsCodeMode/releases)。
|
|
881
881
|
近期关键版本:
|
|
882
882
|
|
|
883
|
-
- **v0.23.4**:修复桌面端点计划卡「查看全文」会先闪开一页官方计划预览、随即自动关闭。根因:官方卡片走 `ctx.sidebarRight.openResource(planAddress(callId))`,而本插件以 extension 优先级认领了 `dsh-resource://plan/**` 与 `dsh-resource://plan-review/**`,于是侧栏先按本插件类型建出一个页签(正文「正在打开计划预览…」),挂载后才把计划转进「文件编辑」、再把这个页签关掉——用户看到的就是这次建而即关。现由新增 `src/client/planDirect.ts` 在装配期包装 `sidebarRight.openResource`:只接管「2 段计划地址 + 会话为在屏会话」,正文依次取回顾卡片的 `params.planReview`、插件侧地址→正文缓存、可选 `resources.source()` 资源流(上限 1.5s),拿到正文后经 `requestOpen(..., { preview: true })` 直通本插件编辑器的只读计划页签,不再创建侧栏页签;非计划地址、跨会话与 subagent 5 段地址、正文不可得、编辑器 5s 未回执、以及服务对象只读导致包装赋值失败,一律回落原生 `openResource`,原「认领 + 挂载后转移」链路保留为后备。`openReceipt` 新增可选 `timeoutMs`,`planState` 新增有界(64 条)地址→正文缓存并由 live `exit_plan_mode` 写入。验证:typecheck 0 error;`tests/planDirect.test.ts`(新增 9 例)、`tests/planBridge.test.ts`(13)、`tests/planState.test.ts`(10)、`tests/openReceipt.test.ts`(9)四套 41/41;`npm run build` 双 bundle 通过(含客户端产物完整性校验);浏览器确认新 bundle 已加载且 Console 无「计划直通装配失败」;GUI 点按由用户在本机 Web 端手动复验后发布。见 [发布说明](docs/release-0.23.4.md)。
|
|
883
|
+
- **v0.23.5**:修复差异栏把同一文件算成两条(差异文件数偏大)。待审条目存的是写入时的相对路径,官方导航用绝对身份,而 `mergedPage()` 与 `diffSummary` 的去重都只按 path 建覆盖集,相对路径的待审行压不掉绝对身份的官方行,于是同一文件同时留下待审行与官方汇总行;现两侧覆盖集都补入条目身份(与官方 identity 同源)。另修复本会话新建的文件被渲染成一堆修改块:累计基线保留「最早内容」,新建文件首次采集时磁盘已有会话自己的中间产物,基线因此非空,「新增 516 行」被切成若干 `-`/`+` 混合块,与官方整文件新增(`before=false`、`oldLines=0`)口径相反;现按每个文件在该会话内最早的官方记录判定整文件新增,命中时待审侧基线置空,列表与详情只出一块全加差异、增删计数等于整文件行数,`addedFiles()` 查询失败则回退旧口径。验证:typecheck 0 error;全量单测通过;`npm run build` 通过;本版未做 GUI 点按验收(缺口保留)。见 [0.23.5 发布说明](docs/release-0.23.5.md)。- **v0.23.4**:修复桌面端点计划卡「查看全文」会先闪开一页官方计划预览、随即自动关闭。根因:官方卡片走 `ctx.sidebarRight.openResource(planAddress(callId))`,而本插件以 extension 优先级认领了 `dsh-resource://plan/**` 与 `dsh-resource://plan-review/**`,于是侧栏先按本插件类型建出一个页签(正文「正在打开计划预览…」),挂载后才把计划转进「文件编辑」、再把这个页签关掉——用户看到的就是这次建而即关。现由新增 `src/client/planDirect.ts` 在装配期包装 `sidebarRight.openResource`:只接管「2 段计划地址 + 会话为在屏会话」,正文依次取回顾卡片的 `params.planReview`、插件侧地址→正文缓存、可选 `resources.source()` 资源流(上限 1.5s),拿到正文后经 `requestOpen(..., { preview: true })` 直通本插件编辑器的只读计划页签,不再创建侧栏页签;非计划地址、跨会话与 subagent 5 段地址、正文不可得、编辑器 5s 未回执、以及服务对象只读导致包装赋值失败,一律回落原生 `openResource`,原「认领 + 挂载后转移」链路保留为后备。`openReceipt` 新增可选 `timeoutMs`,`planState` 新增有界(64 条)地址→正文缓存并由 live `exit_plan_mode` 写入。验证:typecheck 0 error;`tests/planDirect.test.ts`(新增 9 例)、`tests/planBridge.test.ts`(13)、`tests/planState.test.ts`(10)、`tests/openReceipt.test.ts`(9)四套 41/41;`npm run build` 双 bundle 通过(含客户端产物完整性校验);浏览器确认新 bundle 已加载且 Console 无「计划直通装配失败」;GUI 点按由用户在本机 Web 端手动复验后发布。见 [发布说明](docs/release-0.23.4.md)。
|
|
884
884
|
- **v0.23.3**:修复 issue #13(已删除文件的差异条目把人卡死)与 #12(重启后成串「采集不完整」)。①文件在磁盘上被删掉时,客户端申请整文件删除预览被「已有官方审查记录」拒绝,而拒绝只是一条普通 `Error`,于是落到通用加载失败分支——「文件加载失败:…」+ 进度钉在 0%、重试同错、标签页关不掉;`edrv.review.detail` 还把 Undo 不可用解释成「缺少可验证的原始字节」,而真实原因是文件已不在。现缺失文件预览的可预期拒绝带稳定码(`official-covered` / `official-decided` / `no-records` / `no-evidence` / `too-large`),客户端据此显示「文件已在磁盘删除(· 有官方待审改动)」终态:标题 + 原因 + 重试 / 关闭标签页,不再画 0% 进度条;官方待审记录在磁盘已删除时给出「Keep 按删除结案、Undo 需要读写该文件」的准确原因(Keep 语义不变,仍只写共享审查库),记录都已结案时提示可直接关闭;`!review` 的磁盘状态抖动改为最多重读 2 次后给终态,消除缺失↔重现的无限递归。v0.22.0 起 `hasOfficial()` 已用 `typeof path !== 'string'` 守卫堵掉把不透明 FsTarget 绑进 SQL 导致的 `Unknown named parameter 'targetKey'`,本版不再复现。②官方 `workspace/changes` 的 summary 只存在宿主内存里,插件启动补采原先无条件把历史事件全部入队,重启后每一轮都必然抛「summary 已失效或尚不可用」并写成不可修复的 `gap`(面板成串「轮次 N 采集不完整」);现补采先 `summary()` 探测再入队,失效事件只记一条汇总日志。验证:typecheck 与基线同为 37 个既有错误(`fflate/browser` 依赖缺失)、零新增;定向单测 111/111(含新增 `tests/deletedFileReview.test.ts` 5 例与 `tests/officialBackfill.test.ts` 2 例)、EditorView 相关 82/82;`npm run build` 四个 bundle 通过。见 [发布说明](docs/release-0.23.3.md)。
|
|
885
885
|
- **v0.23.2**:修复 Keep 在非活跃端(桌面端)点了没反应,两处成因。①`edrv.review.decide` 原先一律要求本端活跃会话,而能力位只按审查库推导,会话在另一实例时按钮亮着、点击后抛错被兜底成 HTTP 500。现按写入副作用区分:官方记录 Keep 只写共享审查库(`BEGIN IMMEDIATE` + 幂等键 + 版本 CAS),冷端放行;官方 Undo(校验证据并写回源文件)与待审投影条目(本机 JSON 无跨进程锁)仍限活跃端,返回可读跨端原因而非 500;详情新增 `capability.live`,冷端禁用 Undo 并说明、Keep 提示补「写入共享审查库,仍可执行」。②已结算待审投影的 `capability.keep` 恒为假,却因排在 `items[0]`、同路径官方待审行被历史过滤,把官方记录挤出差异栏(表现为「待审 0」+Keep 禁用+点击无反应);现 `pendingPageOf()` 剔除已结算投影,官方记录重新成为审查目标。另详情未加载时点 Keep 不再静默失败;打开文件时若某条官方记录的改动已全部不在当前文件中(无可定位差异且缓冲区干净),自动按 Keep 结案(只标记不写盘),差异栏不再显示「本记录 N 处改动均已不在当前文件中」;官方元数据首次载入期间差异栏显示 `—` 而非按历史记录报数,重启瞬间不再闪出几十条假待审。验证:typecheck 0 error、目标单测 57/57、build 通过、真实审查库探针确认首条回到官方记录且 `capability={keep:true,undo:true,live:true}`;桌面端 GUI 点按验收因重启后恢复的是另一个会话未完成,缺口保留。发布不自动部署,升级后重启 DSH 并刷新页面。见 [发布说明](docs/release-0.23.2.md)。
|
|
886
886
|
- **v0.23.1**:修复 Keep All / Undo All 在待审超过一页时必定中止——批量按钮原先复用分页接口并把每页的展示用计数当全量总数校验,官方待审 150 条时先报「待审记录在分页期间变化」或最终报「待审记录数量不完整」,实际 0 项被决策。现改为一次性完整快照(`edrv.review.list` 新增 `all: true`,服务端循环取全并与历史改动投影去重,`nextCursor: null`、`pending` 为真实总数),客户端单次取快照、校验唯一且仍待审后逐项决策;超过 2000 条明确拒绝并提示分批,途中变化仍整批中止不部分写入。差异栏浏览仍走原分页接口。本版未在重启后的 GUI 点按验证,缺口保留;发布不自动部署,升级后重启 DSH 并刷新页面。见 [发布说明](docs/release-0.23.1.md)。
|
package/lib/client.js
CHANGED
|
@@ -4117,6 +4117,7 @@ window.__ModuleLoader__.load({
|
|
|
4117
4117
|
if (!item.id.startsWith("pending:")) continue;
|
|
4118
4118
|
const path = item.currentPath || item.path;
|
|
4119
4119
|
covered.add(diffPathKey(path));
|
|
4120
|
+
covered.add(diffPathKey(item.id.slice(8)));
|
|
4120
4121
|
rows.push({
|
|
4121
4122
|
path,
|
|
4122
4123
|
label: item.display || path,
|
package/lib/client.js.map
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
[diffend] Oversized file quarantined before diffing.
|
|
2
2
|
name: package/lib/client.js.map
|
|
3
|
-
size:
|
|
4
|
-
sha256:
|
|
3
|
+
size: 35694514 bytes
|
|
4
|
+
sha256: 7fa559b003ee01e2e53a8698796d4a4e567f8e63d0b6d00548272a0057026c6d
|
package/lib/index.js
CHANGED
|
@@ -7652,6 +7652,27 @@ function reviewPage(cwd, cursor = 0, history = false, path) {
|
|
|
7652
7652
|
/**
|
|
7653
7653
|
* @public
|
|
7654
7654
|
* @author ddj 2026年10月31号
|
|
7655
|
+
* @description: 该会话里由官方记录判为「整文件新增」的文件身份集合。待审条目命中时基线要置空:
|
|
7656
|
+
* 文件是本会话新建的,累计基线只是会话自己的中间产物,拿它做旧侧会把新增文件渲染成一堆修改块。
|
|
7657
|
+
* @param cwd 迁移后的工作区
|
|
7658
|
+
* @param sessionId 会话
|
|
7659
|
+
* @returns 整文件新增的文件身份集合
|
|
7660
|
+
*/
|
|
7661
|
+
function addedFiles(cwd, sessionId) {
|
|
7662
|
+
try {
|
|
7663
|
+
const db = dbFor(cwd);
|
|
7664
|
+
if (!db.prepare("SELECT 1 FROM sqlite_master WHERE name='review_files' AND type='table'").get()) return /* @__PURE__ */ new Set();
|
|
7665
|
+
const rows = db.prepare(`SELECT identity FROM (SELECT identity,
|
|
7666
|
+
json_extract(diff,'$.before') AS before, ROW_NUMBER() OVER (PARTITION BY identity ORDER BY turn, seq) AS rank
|
|
7667
|
+
FROM review_files WHERE session_id=?) WHERE rank=1 AND before=0`).all(sessionId);
|
|
7668
|
+
return new Set(rows.map((row) => String(row.identity)));
|
|
7669
|
+
} catch {
|
|
7670
|
+
return /* @__PURE__ */ new Set();
|
|
7671
|
+
}
|
|
7672
|
+
}
|
|
7673
|
+
/**
|
|
7674
|
+
* @public
|
|
7675
|
+
* @author ddj 2026年10月31号
|
|
7655
7676
|
* @description: 该文件是否还有未处理的官方待审改动。缺失文件预览据此区分「需要去差异栏 Keep 结案」
|
|
7656
7677
|
* 与「记录都已处理、可直接关闭标签页」,避免已结案的删除文件继续提示待审。
|
|
7657
7678
|
* @param cwd Migrated workspace
|
|
@@ -9297,10 +9318,22 @@ function capabilityOf(entry, flags) {
|
|
|
9297
9318
|
* @author ddj 2026年10月29号
|
|
9298
9319
|
* @description: 供详情接口复用的差异构造。
|
|
9299
9320
|
* @param entry 待审条目
|
|
9321
|
+
* @param baselineEmpty 基线置空:文件由本次会话新建,旧基线只是会话自己的中间产物
|
|
9300
9322
|
* @returns 官方差异
|
|
9301
9323
|
*/
|
|
9302
|
-
function diffOf(entry) {
|
|
9303
|
-
return textDiff(entry.path, entry.path, entry.oldText, entry.newText);
|
|
9324
|
+
function diffOf(entry, baselineEmpty = false) {
|
|
9325
|
+
if (!baselineEmpty) return textDiff(entry.path, entry.path, entry.oldText, entry.newText);
|
|
9326
|
+
if (entry.newText === "") return textDiff(entry.path, entry.path, "", "");
|
|
9327
|
+
const diff = textDiff(entry.path, entry.path, "", entry.newText);
|
|
9328
|
+
if (diff.kind !== "text") return diff;
|
|
9329
|
+
return {
|
|
9330
|
+
...diff,
|
|
9331
|
+
hunks: diff.hunks.map((hunk) => ({
|
|
9332
|
+
...hunk,
|
|
9333
|
+
oldLines: 0,
|
|
9334
|
+
lines: hunk.lines.filter((line) => line !== "-")
|
|
9335
|
+
}))
|
|
9336
|
+
};
|
|
9304
9337
|
}
|
|
9305
9338
|
//#endregion
|
|
9306
9339
|
//#region src/pending/persist.ts
|
|
@@ -9560,10 +9593,11 @@ function flagsOfRow(row) {
|
|
|
9560
9593
|
* @author ddj 2026年10月29号
|
|
9561
9594
|
* @description: 统计一次差异的增删行数;非文本差异统一记 0/0。
|
|
9562
9595
|
* @param entry 待审条目
|
|
9596
|
+
* @param baselineEmpty 基线置空(新建文件按全量新增计)
|
|
9563
9597
|
* @returns 增删行数
|
|
9564
9598
|
*/
|
|
9565
|
-
function countsOf(entry) {
|
|
9566
|
-
const diff = diffOf(entry);
|
|
9599
|
+
function countsOf(entry, baselineEmpty = false) {
|
|
9600
|
+
const diff = diffOf(entry, baselineEmpty);
|
|
9567
9601
|
if (diff.kind !== "text") return {
|
|
9568
9602
|
added: 0,
|
|
9569
9603
|
deleted: 0
|
|
@@ -9633,11 +9667,12 @@ function foldWrite(stored, write) {
|
|
|
9633
9667
|
* @author ddj 2026年10月29号
|
|
9634
9668
|
* @description: 条目转列表投影,已解决条目仍保留但不可再决定。
|
|
9635
9669
|
* @param stored 条目与标记
|
|
9670
|
+
* @param baselineEmpty 基线置空(新建文件按全量新增计)
|
|
9636
9671
|
* @returns 列表项
|
|
9637
9672
|
*/
|
|
9638
|
-
function itemOf(stored) {
|
|
9673
|
+
function itemOf(stored, baselineEmpty = false) {
|
|
9639
9674
|
const { entry, flags } = stored;
|
|
9640
|
-
const counts = countsOf(entry);
|
|
9675
|
+
const counts = countsOf(entry, baselineEmpty);
|
|
9641
9676
|
const item = {
|
|
9642
9677
|
id: entry.id,
|
|
9643
9678
|
path: entry.path,
|
|
@@ -9662,12 +9697,13 @@ function itemOf(stored) {
|
|
|
9662
9697
|
* @description: 读一页待审条目,按更新时间升序;会话过滤走血缘集合。
|
|
9663
9698
|
* @param cwd 工作区
|
|
9664
9699
|
* @param sessionId 当前会话
|
|
9700
|
+
* @param wholeAdd 本次会话新建的文件身份集合,命中时基线置空
|
|
9665
9701
|
* @returns 列表
|
|
9666
9702
|
*/
|
|
9667
|
-
function loadPage(cwd, sessionId) {
|
|
9703
|
+
function loadPage(cwd, sessionId, wholeAdd) {
|
|
9668
9704
|
const all = loadEntries(cwd);
|
|
9669
9705
|
const visible = sessionId ? all.filter((stored) => stored.entry.sessionIds.includes(sessionId)) : all;
|
|
9670
|
-
const items = visible.map(itemOf);
|
|
9706
|
+
const items = visible.map((stored) => itemOf(stored, wholeAdd?.has(stored.entry.id) === true));
|
|
9671
9707
|
const gaps = [];
|
|
9672
9708
|
for (const stored of visible) if (stored.flags.oversized) gaps.push(`${stored.entry.path}: 文本超出上限,仅保留标记`);
|
|
9673
9709
|
return {
|
|
@@ -9682,14 +9718,15 @@ function loadPage(cwd, sessionId) {
|
|
|
9682
9718
|
* @description: 读单条详情,包含可交给现有面板渲染的差异与块决定。
|
|
9683
9719
|
* @param cwd 工作区
|
|
9684
9720
|
* @param id 条目身份
|
|
9721
|
+
* @param baselineEmpty 基线置空(新建文件按全量新增渲染)
|
|
9685
9722
|
* @returns 详情,条目不存在时 null
|
|
9686
9723
|
*/
|
|
9687
|
-
function loadDetail(cwd, id) {
|
|
9724
|
+
function loadDetail(cwd, id, baselineEmpty = false) {
|
|
9688
9725
|
const stored = loadEntries(cwd).find((item) => item.entry.id === id);
|
|
9689
9726
|
if (!stored) return null;
|
|
9690
|
-
const diff = diffOf(stored.entry);
|
|
9727
|
+
const diff = diffOf(stored.entry, baselineEmpty);
|
|
9691
9728
|
return {
|
|
9692
|
-
item: itemOf(stored),
|
|
9729
|
+
item: itemOf(stored, baselineEmpty),
|
|
9693
9730
|
diff,
|
|
9694
9731
|
blocks: blockStatesOf(stored.entry),
|
|
9695
9732
|
capability: capabilityOf(stored.entry, stored.flags)
|
|
@@ -10249,7 +10286,11 @@ function pendingCovers(cwd, path) {
|
|
|
10249
10286
|
* @returns 合并后的页
|
|
10250
10287
|
*/
|
|
10251
10288
|
function mergedPage(official, pending, cwd) {
|
|
10252
|
-
const covered = new Set(
|
|
10289
|
+
const covered = /* @__PURE__ */ new Set();
|
|
10290
|
+
for (const item of pending.items) {
|
|
10291
|
+
covered.add(pathKey$1(item.path));
|
|
10292
|
+
covered.add(pathKey$1(item.id));
|
|
10293
|
+
}
|
|
10253
10294
|
const kept = official.items.filter((item) => !covered.has(pathKey$1(item.currentPath)) && !covered.has(pathKey$1(item.path)));
|
|
10254
10295
|
return {
|
|
10255
10296
|
items: [...pending.items.map((item) => pendingItemOf(item, cwd)), ...kept],
|
|
@@ -16154,7 +16195,7 @@ function reviewHandlers(ctx, registry) {
|
|
|
16154
16195
|
*/
|
|
16155
16196
|
function pendingPageOf(cwd, sessionId) {
|
|
16156
16197
|
try {
|
|
16157
|
-
const page = loadPage(cwd, sessionId);
|
|
16198
|
+
const page = loadPage(cwd, sessionId, addedFiles(cwd, sessionId));
|
|
16158
16199
|
return page.items.some((item) => item.settled === true) ? {
|
|
16159
16200
|
...page,
|
|
16160
16201
|
items: page.items.filter((item) => item.settled !== true)
|
|
@@ -16201,7 +16242,8 @@ function reviewHandlers(ctx, registry) {
|
|
|
16201
16242
|
const scope = await scopeOf(args.sessionId);
|
|
16202
16243
|
const cwd = scope.cwd;
|
|
16203
16244
|
if (args.id.startsWith("pending:")) {
|
|
16204
|
-
const
|
|
16245
|
+
const entryId = args.id.slice(8);
|
|
16246
|
+
const detail = loadDetail(cwd, entryId, addedFiles(cwd, args.sessionId).has(entryId));
|
|
16205
16247
|
if (!detail) return {
|
|
16206
16248
|
ok: false,
|
|
16207
16249
|
error: "待审条目不存在"
|
|
@@ -16350,7 +16392,7 @@ function readHandlers(kit) {
|
|
|
16350
16392
|
return {
|
|
16351
16393
|
"edrv.pending.list": async (args) => {
|
|
16352
16394
|
const cwd = await kit.read(args.sessionId);
|
|
16353
|
-
const page = loadPage(cwd, args.sessionId);
|
|
16395
|
+
const page = loadPage(cwd, args.sessionId, addedFiles(cwd, args.sessionId));
|
|
16354
16396
|
for (const item of page.items) trackPendingCover(cwd, item.path, true);
|
|
16355
16397
|
return {
|
|
16356
16398
|
ok: true,
|
|
@@ -16358,7 +16400,9 @@ function readHandlers(kit) {
|
|
|
16358
16400
|
};
|
|
16359
16401
|
},
|
|
16360
16402
|
"edrv.pending.detail": async (args) => {
|
|
16361
|
-
const
|
|
16403
|
+
const cwd = await kit.read(args.sessionId);
|
|
16404
|
+
const rawId = pendingRawId(args.id);
|
|
16405
|
+
const detail = loadDetail(cwd, rawId, addedFiles(cwd, args.sessionId).has(rawId));
|
|
16362
16406
|
return detail ? {
|
|
16363
16407
|
ok: true,
|
|
16364
16408
|
detail
|