@kirkchinese/dsh-citeciter 0.4.1 → 0.4.3
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 +12 -12
- package/README.zh.md +12 -12
- package/lib/client.js +49 -15
- package/lib/index.js +94 -13
- package/lib/types/assistant-content.d.ts +7 -0
- package/lib/types/client/answer.d.ts +1 -1
- package/lib/types/client/conversation-dom.d.ts +11 -4
- package/lib/types/observer.d.ts +1 -1
- package/package.json +16 -15
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ Investigate any line in a DSH response—without stopping the Agent or changing
|
|
|
11
11
|
## Highlights
|
|
12
12
|
|
|
13
13
|
- **Exact source binding.** CiteCiter rechecks the visible selection against the committed response before creating a Topic.
|
|
14
|
-
- **No need to wait.** A committed model
|
|
14
|
+
- **No need to wait.** A committed intermediate model call can be investigated while the surrounding Agent turn continues, including its reasoning or a selection spanning reasoning and answer text.
|
|
15
15
|
- **Durable investigations.** Private Topics support natural follow-up questions and reopen after a refresh or restart.
|
|
16
16
|
- **Evidence with no write access.** CiteCiter can inspect committed source events and search or read project files, but cannot change the source Session or workspace.
|
|
17
17
|
- **Inspectable work.** Questions, answers, source reads, and project-file checks remain together in the investigation panel.
|
|
@@ -19,23 +19,23 @@ Investigate any line in a DSH response—without stopping the Agent or changing
|
|
|
19
19
|
|
|
20
20
|
## Use
|
|
21
21
|
|
|
22
|
-
1. Select text inside a committed assistant model call. The surrounding Agent turn may still be running.
|
|
22
|
+
1. Select answer or expanded reasoning text inside a committed assistant model call. The surrounding Agent turn may still be running.
|
|
23
23
|
2. Right-click the selection, enter your first question, and choose `Citer!`.
|
|
24
24
|
3. CiteCiter creates a new Topic in the investigation panel.
|
|
25
25
|
4. Continue asking questions there, or reopen an earlier Topic from CiteCiter's Topic rail.
|
|
26
26
|
5. Change the Topic model, reasoning effort, title, archive state, or panel width without changing the source Session.
|
|
27
27
|
|
|
28
|
-
## Install v0.4.
|
|
28
|
+
## Install v0.4.3
|
|
29
29
|
|
|
30
|
-
CiteCiter 0.4.
|
|
30
|
+
CiteCiter 0.4.3 requires Node.js `^22.19.0 || >=24.0.0` and DSH `>=0.1.1-rc.1 <0.1.1-rc.3`.
|
|
31
31
|
|
|
32
32
|
For DSH Web:
|
|
33
33
|
|
|
34
34
|
```sh
|
|
35
|
-
dsh plugin --profile web add @kirkchinese/dsh-citeciter@0.4.
|
|
35
|
+
dsh plugin --profile web add @kirkchinese/dsh-citeciter@0.4.3
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
Restart DSH Web and refresh the page. Packaged Desktop adaptation is deferred; CiteCiter 0.4.
|
|
38
|
+
Restart DSH Web and refresh the page. Packaged Desktop adaptation is deferred; CiteCiter 0.4.3 does not provide a supported Desktop installation path.
|
|
39
39
|
|
|
40
40
|
## Context modes
|
|
41
41
|
|
|
@@ -47,7 +47,7 @@ Exact Fork is an advanced mode for a source turn that has already ended. `exact-
|
|
|
47
47
|
|
|
48
48
|
| Host | CiteCiter version | Status |
|
|
49
49
|
|---|---|---|
|
|
50
|
-
| DSH Web `0.1.1-rc.2` | `0.4.
|
|
50
|
+
| DSH Web `0.1.1-rc.2` | `0.4.3` | Linux package, regression tests, and real-browser selection checks passed; real-provider acceptance was not run |
|
|
51
51
|
| [DSH Desktop 2.0.2](https://github.com/anywhere-labs/deepseek-harness-desktop) with bundled DSH `0.1.1-rc.2` | Future target | Adaptation and native acceptance are deferred; no packaged-Desktop compatibility claim |
|
|
52
52
|
| [dataelement DSH Desktop](https://github.com/dataelement/dsh-desktop) development shell with DSH `0.1.1-rc.1` | `0.4.0` only | Historical conditional Linux source-shell evidence |
|
|
53
53
|
| DSH Web `0.1.0-rc.7` | `0.3.2` | Previous stable line |
|
|
@@ -57,27 +57,27 @@ The demo uses a deterministic fixture to show the workflow; it is not real-provi
|
|
|
57
57
|
|
|
58
58
|
## Upgrade from an earlier version
|
|
59
59
|
|
|
60
|
-
To install or upgrade v0.4.
|
|
60
|
+
To install or upgrade v0.4.3 in Web:
|
|
61
61
|
|
|
62
62
|
```sh
|
|
63
|
-
dsh plugin --profile web add @kirkchinese/dsh-citeciter@0.4.
|
|
63
|
+
dsh plugin --profile web add @kirkchinese/dsh-citeciter@0.4.3
|
|
64
64
|
dsh plugin --profile web list --depth 0
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
Packaged Desktop adaptation is deferred and is not part of the supported 0.4.
|
|
67
|
+
Packaged Desktop adaptation is deferred and is not part of the supported 0.4.3 upgrade path.
|
|
68
68
|
|
|
69
69
|
Upgrading from v0.3 does not migrate or rewrite existing Topics, settings, or source Sessions. Users remaining on DSH `0.1.0-rc.7` should keep CiteCiter 0.3.2.
|
|
70
70
|
|
|
71
71
|
## Limitations
|
|
72
72
|
|
|
73
|
-
- A selection must include at least one committed assistant model call. User-only ranges, tool-only ranges,
|
|
73
|
+
- A selection must include at least one committed assistant model call. User-only ranges, tool-only ranges, still-streaming fragments, and the generated reasoning disclosure header cannot anchor a Citation.
|
|
74
74
|
- Renderer-generated KaTeX layout and footnote numbers cannot be cited directly because they lack stable source coordinates.
|
|
75
75
|
- Exact Fork cannot start from an open source turn.
|
|
76
76
|
- Source-file access depends on the running DSH filesystem service and remains read-only.
|
|
77
77
|
- Read Frog translated selections are a best-effort compatibility path on DSH rc.1/rc.2 and activate only when its complete private marker set is present; ordinary DSH selection does not depend on Read Frog.
|
|
78
78
|
- DSH does not yet expose a public additive right-dock contribution point. CiteCiter therefore uses a reversible private AppFrame grid adapter whenever the panel and conversation fit; it preserves the layout's details preference but temporarily hides that column while the investigation panel is open, and it must be retested for every supported DSH release.
|
|
79
79
|
- Topics are Host-durable. When Desktop restarts on a different loopback port, CiteCiter falls back to the most recently updated Topic because browser local storage is origin-scoped; configure a fixed Desktop port to restore the exact last-viewed Topic.
|
|
80
|
-
- CiteCiter 0.4.
|
|
80
|
+
- CiteCiter 0.4.3 makes no packaged-Desktop compatibility claim; native Windows and macOS adaptation continues separately.
|
|
81
81
|
- There is currently no TUI interaction adapter.
|
|
82
82
|
- DSH is prerelease software; later DSH API versions may require a CiteCiter update.
|
|
83
83
|
|
package/README.zh.md
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
## 核心能力
|
|
12
12
|
|
|
13
13
|
- **精确绑定来源。** 创建 Topic 前,CiteCiter 会把可见选区与已经提交的回答重新核对。
|
|
14
|
-
- **不用等待主任务。**
|
|
14
|
+
- **不用等待主任务。** 一次中间模型调用提交后即可开始调查,外层 Agent 轮次可以继续运行;思考内容和跨越“思考+回答”的选区也可以使用。
|
|
15
15
|
- **调查可以持续。** 私有 Topic 支持自然续问,刷新或重启后仍能重新打开。
|
|
16
16
|
- **有证据、无写权限。** CiteCiter 能检查来源中已提交的事件,也能搜索和阅读项目文件,但不能改变来源 Session 或工作区。
|
|
17
17
|
- **过程可检查。** 问题、回答、来源读取和项目文件核查都留在调查面板中。
|
|
@@ -19,23 +19,23 @@
|
|
|
19
19
|
|
|
20
20
|
## 使用
|
|
21
21
|
|
|
22
|
-
1.
|
|
22
|
+
1. 在一次已经提交的助手模型调用中选择回答或展开后的思考文字;外层 Agent 轮次可以仍在运行。
|
|
23
23
|
2. 右键选区,输入第一个问题,然后点击 `Citer!`。
|
|
24
24
|
3. CiteCiter 在调查面板中创建一个新 Topic。
|
|
25
25
|
4. 在 Topic 中继续追问,或从 CiteCiter 的 Topic 栏恢复旧讨论。
|
|
26
26
|
5. 独立调整模型、思考强度、标题、归档状态和面板宽度,不改变来源 Session。
|
|
27
27
|
|
|
28
|
-
## 安装 v0.4.
|
|
28
|
+
## 安装 v0.4.3
|
|
29
29
|
|
|
30
|
-
CiteCiter 0.4.
|
|
30
|
+
CiteCiter 0.4.3 需要 Node.js `^22.19.0 || >=24.0.0` 与 DSH `>=0.1.1-rc.1 <0.1.1-rc.3`。
|
|
31
31
|
|
|
32
32
|
DSH Web 使用:
|
|
33
33
|
|
|
34
34
|
```sh
|
|
35
|
-
dsh plugin --profile web add @kirkchinese/dsh-citeciter@0.4.
|
|
35
|
+
dsh plugin --profile web add @kirkchinese/dsh-citeciter@0.4.3
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
安装后重启 DSH Web 并刷新页面。正式 Desktop 适配已经暂缓;CiteCiter 0.4.
|
|
38
|
+
安装后重启 DSH Web 并刷新页面。正式 Desktop 适配已经暂缓;CiteCiter 0.4.3 不提供受支持的 Desktop 安装路径。
|
|
39
39
|
|
|
40
40
|
## 上下文模式
|
|
41
41
|
|
|
@@ -47,7 +47,7 @@ Exact Fork 是面向已结束来源轮次的高级模式。`exact-when-available
|
|
|
47
47
|
|
|
48
48
|
| 宿主 | CiteCiter 版本 | 状态 |
|
|
49
49
|
|---|---|---|
|
|
50
|
-
| DSH Web `0.1.1-rc.2` | `0.4.
|
|
50
|
+
| DSH Web `0.1.1-rc.2` | `0.4.3` | Linux 包、回归测试与真实浏览器选区检查通过;真实提供方验收未运行 |
|
|
51
51
|
| [DSH Desktop 2.0.2](https://github.com/anywhere-labs/deepseek-harness-desktop)(内置 DSH `0.1.1-rc.2`) | 后续目标 | 适配与原生验收暂缓;不声明正式 Desktop 安装包兼容性 |
|
|
52
52
|
| [dataelement DSH Desktop](https://github.com/dataelement/dsh-desktop) 源码开发壳(内置 DSH `0.1.1-rc.1`) | 仅 `0.4.0` | 历史上的有条件 Linux 源码壳证据 |
|
|
53
53
|
| DSH Web `0.1.0-rc.7` | `0.3.2` | 旧版稳定线 |
|
|
@@ -57,27 +57,27 @@ Exact Fork 是面向已结束来源轮次的高级模式。`exact-when-available
|
|
|
57
57
|
|
|
58
58
|
## 从旧版本升级
|
|
59
59
|
|
|
60
|
-
在 Web 安装或升级 v0.4.
|
|
60
|
+
在 Web 安装或升级 v0.4.3:
|
|
61
61
|
|
|
62
62
|
```sh
|
|
63
|
-
dsh plugin --profile web add @kirkchinese/dsh-citeciter@0.4.
|
|
63
|
+
dsh plugin --profile web add @kirkchinese/dsh-citeciter@0.4.3
|
|
64
64
|
dsh plugin --profile web list --depth 0
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
正式 Desktop 适配已经暂缓,不属于 0.4.
|
|
67
|
+
正式 Desktop 适配已经暂缓,不属于 0.4.3 的受支持升级路径。
|
|
68
68
|
|
|
69
69
|
从 v0.3 升级不会迁移或重写现有 Topic、设置或来源 Session。仍在 DSH `0.1.0-rc.7` 上运行的用户应继续使用 CiteCiter 0.3.2。
|
|
70
70
|
|
|
71
71
|
## 已知限制
|
|
72
72
|
|
|
73
|
-
-
|
|
73
|
+
- 选区中必须至少包含一个已经提交的助手模型调用;纯用户消息、纯工具行、仍在流式输出的片段和自动生成的思考折叠标题不能作为引用锚点。
|
|
74
74
|
- 渲染器生成的 KaTeX 排版和脚注编号没有稳定原文坐标,不能直接引用。
|
|
75
75
|
- Exact Fork 不能从仍在运行的来源轮次开始。
|
|
76
76
|
- 来源文件访问依赖当前 DSH 文件系统服务,并始终保持只读。
|
|
77
77
|
- Read Frog 翻译选区是 DSH rc.1/rc.2 上的 best-effort 兼容路径,仅在其完整私有标记同时存在时启用;普通 DSH 选区不依赖 Read Frog。
|
|
78
78
|
- DSH 尚未提供公开的增量右侧 dock 布局扩展点。CiteCiter 因此在面板与主对话能够同时容纳时使用可逆的私有 AppFrame grid 适配器;它保留布局的 details 偏好,但在调查面板打开期间临时隐藏该列,并且每个受支持的 DSH 版本都必须重新验证。
|
|
79
79
|
- Topic 由 Host 持久保存。Desktop 重启到不同 loopback 端口时,浏览器 localStorage 会因 origin 改变而失去精确指针,CiteCiter 将回退到最近更新的 Topic;需要精确恢复“上次查看”时应配置固定 Desktop 端口。
|
|
80
|
-
- CiteCiter 0.4.
|
|
80
|
+
- CiteCiter 0.4.3 不声明正式 Desktop 安装包兼容性;Windows 与 macOS 原生适配将另行继续。
|
|
81
81
|
- 当前没有 TUI 交互适配器。
|
|
82
82
|
- DSH 仍处于预发布阶段,后续 API 版本可能要求同步更新 CiteCiter。
|
|
83
83
|
|
package/lib/client.js
CHANGED
|
@@ -4443,6 +4443,26 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4443
4443
|
}]
|
|
4444
4444
|
};
|
|
4445
4445
|
//#endregion
|
|
4446
|
+
//#region lib/types/assistant-content.js
|
|
4447
|
+
/**
|
|
4448
|
+
* Project committed reasoning and answer blocks in renderer order.
|
|
4449
|
+
*
|
|
4450
|
+
* @param blocks - DSH assistant content blocks.
|
|
4451
|
+
* @returns reasoning and answer text separated by the renderer's paragraph break.
|
|
4452
|
+
*/
|
|
4453
|
+
function projectCitableAssistantContent(blocks) {
|
|
4454
|
+
let text = "";
|
|
4455
|
+
for (const block of blocks) {
|
|
4456
|
+
if (block === null || typeof block !== "object") continue;
|
|
4457
|
+
const candidate = block;
|
|
4458
|
+
const kind = candidate.kind ?? candidate.type;
|
|
4459
|
+
if (typeof candidate.text !== "string" || candidate.text === "") continue;
|
|
4460
|
+
if (kind === "reasoning") text += `${candidate.text}\n\n`;
|
|
4461
|
+
else if (kind === "text") text += candidate.text;
|
|
4462
|
+
}
|
|
4463
|
+
return text;
|
|
4464
|
+
}
|
|
4465
|
+
//#endregion
|
|
4446
4466
|
//#region lib/types/client/answer.js
|
|
4447
4467
|
/**
|
|
4448
4468
|
* Read visible text from one assistant-step payload without retaining its live node.
|
|
@@ -4453,12 +4473,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4453
4473
|
if (data === null || typeof data !== "object") return null;
|
|
4454
4474
|
const record = data;
|
|
4455
4475
|
if (record.status !== "running" && record.status !== "settled" && record.status !== "interrupted") return null;
|
|
4456
|
-
|
|
4457
|
-
for (const block of record.blocks ?? []) {
|
|
4458
|
-
if (typeof block !== "object" || block === null) continue;
|
|
4459
|
-
const candidate = block;
|
|
4460
|
-
if (candidate.kind === "text" && typeof candidate.text === "string") text += candidate.text;
|
|
4461
|
-
}
|
|
4476
|
+
const text = projectCitableAssistantContent(record.blocks ?? []);
|
|
4462
4477
|
return text === "" ? null : {
|
|
4463
4478
|
status: record.status,
|
|
4464
4479
|
text
|
|
@@ -5365,6 +5380,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5365
5380
|
const DSH_FLOW_SELECTOR = "[data-chat-flow-kind]";
|
|
5366
5381
|
const DSH_ASSISTANT_ANCHOR_SELECTOR = "[data-chat-flow-kind=\"assistant-step\"][data-chat-anchor-key]";
|
|
5367
5382
|
const DSH_REASONING_SELECTOR = "[data-variant=\"think\"]";
|
|
5383
|
+
const DSH_REASONING_HEADER_SELECTOR = "[data-disclosure-row]";
|
|
5368
5384
|
const DSH_GENERATED_CONTENT_SELECTOR = "button, .katex, [data-footnotes], sup";
|
|
5369
5385
|
const DSH_CODE_BLOCK_SELECTOR = ".md-code-block";
|
|
5370
5386
|
const READ_FROG_TRANSLATION_SELECTOR = "[data-read-frog-translation-mode]";
|
|
@@ -5423,14 +5439,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5423
5439
|
return anchors;
|
|
5424
5440
|
}
|
|
5425
5441
|
/**
|
|
5426
|
-
* Detect
|
|
5442
|
+
* Detect generated controls, reasoning summaries, and collapsed code chrome.
|
|
5427
5443
|
*
|
|
5428
5444
|
* @param range - current rendered selection range.
|
|
5429
5445
|
* @param flow - assistant flow containing the range.
|
|
5430
5446
|
* @returns whether the range touches content that CiteCiter must ignore.
|
|
5431
5447
|
*/
|
|
5432
5448
|
function dshRangeTouchesExcludedContent(range, flow) {
|
|
5433
|
-
for (const reasoning of flow.querySelectorAll(DSH_REASONING_SELECTOR)) if (range.intersectsNode(
|
|
5449
|
+
for (const reasoning of flow.querySelectorAll(DSH_REASONING_SELECTOR)) for (const header of reasoning.querySelectorAll(DSH_REASONING_HEADER_SELECTOR)) if (range.intersectsNode(header)) return true;
|
|
5434
5450
|
for (const generated of flow.querySelectorAll(DSH_GENERATED_CONTENT_SELECTOR)) if (range.intersectsNode(generated)) return true;
|
|
5435
5451
|
for (const endpoint of [range.startContainer, range.endContainer]) {
|
|
5436
5452
|
const element = elementForNode(endpoint);
|
|
@@ -5449,13 +5465,24 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5449
5465
|
return null;
|
|
5450
5466
|
}
|
|
5451
5467
|
/**
|
|
5452
|
-
* Determine whether a node is
|
|
5468
|
+
* Determine whether a node is generated UI rather than committed citable text.
|
|
5453
5469
|
*
|
|
5454
5470
|
* @param node - rendered node to classify.
|
|
5455
|
-
* @returns whether
|
|
5471
|
+
* @returns whether the node must stay out of the citable projection.
|
|
5456
5472
|
*/
|
|
5457
|
-
function
|
|
5458
|
-
|
|
5473
|
+
function isNonCitableProjection(node) {
|
|
5474
|
+
if (node.nodeType !== Node.ELEMENT_NODE) return false;
|
|
5475
|
+
const element = node;
|
|
5476
|
+
return element.matches(READ_FROG_TRANSLATION_SELECTOR) || element.matches(DSH_REASONING_HEADER_SELECTOR) && element.closest(DSH_REASONING_SELECTOR) !== null;
|
|
5477
|
+
}
|
|
5478
|
+
/**
|
|
5479
|
+
* Determine whether a node owns one DSH reasoning block.
|
|
5480
|
+
*
|
|
5481
|
+
* @param node - rendered node to classify.
|
|
5482
|
+
* @returns whether the node is a reasoning root.
|
|
5483
|
+
*/
|
|
5484
|
+
function isDshReasoningContent(node) {
|
|
5485
|
+
return node.nodeType === Node.ELEMENT_NODE && node.matches(DSH_REASONING_SELECTOR);
|
|
5459
5486
|
}
|
|
5460
5487
|
/**
|
|
5461
5488
|
* Resolve a selection wholly inside one Read Frog translation to its source paragraph.
|
|
@@ -6939,11 +6966,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6939
6966
|
let targetStart;
|
|
6940
6967
|
let targetEnd;
|
|
6941
6968
|
const visit = (node) => {
|
|
6942
|
-
if (
|
|
6969
|
+
if (isNonCitableProjection(node)) return;
|
|
6970
|
+
const start = text.length;
|
|
6943
6971
|
if (node === target) targetStart = text.length;
|
|
6944
6972
|
if (node.nodeType === Node.TEXT_NODE) text += node.textContent ?? "";
|
|
6945
6973
|
else for (const child of node.childNodes) visit(child);
|
|
6946
6974
|
if (node === target) targetEnd = text.length;
|
|
6975
|
+
if (isDshReasoningContent(node) && text.length > start) text += "\n\n";
|
|
6947
6976
|
};
|
|
6948
6977
|
visit(root);
|
|
6949
6978
|
return {
|
|
@@ -6956,7 +6985,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6956
6985
|
let text = "";
|
|
6957
6986
|
let found = false;
|
|
6958
6987
|
const visit = (node) => {
|
|
6959
|
-
if (found ||
|
|
6988
|
+
if (found || isNonCitableProjection(node)) return;
|
|
6960
6989
|
if (node === boundary) {
|
|
6961
6990
|
if (node.nodeType === Node.TEXT_NODE) text += (node.textContent ?? "").slice(0, offset);
|
|
6962
6991
|
else for (let index = 0; index < offset; index++) {
|
|
@@ -6966,8 +6995,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6966
6995
|
found = true;
|
|
6967
6996
|
return;
|
|
6968
6997
|
}
|
|
6998
|
+
const start = text.length;
|
|
6969
6999
|
if (node.nodeType === Node.TEXT_NODE) text += node.textContent ?? "";
|
|
6970
|
-
else for (const child of node.childNodes)
|
|
7000
|
+
else for (const child of node.childNodes) {
|
|
7001
|
+
visit(child);
|
|
7002
|
+
if (found) return;
|
|
7003
|
+
}
|
|
7004
|
+
if (isDshReasoningContent(node) && text.length > start) text += "\n\n";
|
|
6971
7005
|
};
|
|
6972
7006
|
visit(root);
|
|
6973
7007
|
return found ? text : null;
|
package/lib/index.js
CHANGED
|
@@ -33,6 +33,26 @@ var __exportAll = (all, no_symbols) => {
|
|
|
33
33
|
return target;
|
|
34
34
|
};
|
|
35
35
|
//#endregion
|
|
36
|
+
//#region lib/types/assistant-content.js
|
|
37
|
+
/**
|
|
38
|
+
* Project committed reasoning and answer blocks in renderer order.
|
|
39
|
+
*
|
|
40
|
+
* @param blocks - DSH assistant content blocks.
|
|
41
|
+
* @returns reasoning and answer text separated by the renderer's paragraph break.
|
|
42
|
+
*/
|
|
43
|
+
function projectCitableAssistantContent(blocks) {
|
|
44
|
+
let text = "";
|
|
45
|
+
for (const block of blocks) {
|
|
46
|
+
if (block === null || typeof block !== "object") continue;
|
|
47
|
+
const candidate = block;
|
|
48
|
+
const kind = candidate.kind ?? candidate.type;
|
|
49
|
+
if (typeof candidate.text !== "string" || candidate.text === "") continue;
|
|
50
|
+
if (kind === "reasoning") text += `${candidate.text}\n\n`;
|
|
51
|
+
else if (kind === "text") text += candidate.text;
|
|
52
|
+
}
|
|
53
|
+
return text;
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
36
56
|
//#region ../../node_modules/.pnpm/mdast-util-to-string@4.0.0/node_modules/mdast-util-to-string/lib/index.js
|
|
37
57
|
/**
|
|
38
58
|
* @typedef {import('mdast').Nodes} Nodes
|
|
@@ -14369,7 +14389,8 @@ function compactMapped(units) {
|
|
|
14369
14389
|
});
|
|
14370
14390
|
else compact[compact.length - 1] = {
|
|
14371
14391
|
...previous,
|
|
14372
|
-
endOffset: unit.endOffset
|
|
14392
|
+
endOffset: unit.endOffset,
|
|
14393
|
+
...previous.synthetic === true || unit.synthetic === true ? { synthetic: true } : {}
|
|
14373
14394
|
};
|
|
14374
14395
|
}
|
|
14375
14396
|
return compact;
|
|
@@ -14384,7 +14405,8 @@ function joinMapped(parts, separator) {
|
|
|
14384
14405
|
for (const text of separator) joined.push({
|
|
14385
14406
|
text,
|
|
14386
14407
|
startOffset: before,
|
|
14387
|
-
endOffset: after
|
|
14408
|
+
endOffset: after,
|
|
14409
|
+
synthetic: true
|
|
14388
14410
|
});
|
|
14389
14411
|
}
|
|
14390
14412
|
joined.push(...part);
|
|
@@ -14442,6 +14464,47 @@ function candidatesFromProjection(markdown, projection, needle) {
|
|
|
14442
14464
|
}
|
|
14443
14465
|
return candidates;
|
|
14444
14466
|
}
|
|
14467
|
+
function candidatesIgnoringSyntheticWhitespace(markdown, projection, needle) {
|
|
14468
|
+
const rendered = visibleText(projection);
|
|
14469
|
+
const visibleOffsets = [0];
|
|
14470
|
+
for (const unit of projection) visibleOffsets.push(visibleOffsets.at(-1) + unit.text.length);
|
|
14471
|
+
const candidates = [];
|
|
14472
|
+
const seen = /* @__PURE__ */ new Set();
|
|
14473
|
+
for (let start = 0; start < projection.length; start++) {
|
|
14474
|
+
const first = projection[start];
|
|
14475
|
+
if (first.synthetic === true) continue;
|
|
14476
|
+
let unitIndex = start;
|
|
14477
|
+
let needleOffset = 0;
|
|
14478
|
+
let lastIndex = -1;
|
|
14479
|
+
while (needleOffset < needle.length && unitIndex < projection.length) {
|
|
14480
|
+
const unit = projection[unitIndex];
|
|
14481
|
+
if (unit.synthetic === true) {
|
|
14482
|
+
while (projection[unitIndex]?.synthetic === true) unitIndex++;
|
|
14483
|
+
needleOffset += /^\s+/u.exec(needle.slice(needleOffset))?.[0].length ?? 0;
|
|
14484
|
+
continue;
|
|
14485
|
+
}
|
|
14486
|
+
if (!needle.startsWith(unit.text, needleOffset)) break;
|
|
14487
|
+
needleOffset += unit.text.length;
|
|
14488
|
+
lastIndex = unitIndex;
|
|
14489
|
+
unitIndex++;
|
|
14490
|
+
}
|
|
14491
|
+
if (needleOffset !== needle.length || lastIndex < start) continue;
|
|
14492
|
+
const last = projection[lastIndex];
|
|
14493
|
+
const key = `${first.startOffset}:${last.endOffset}`;
|
|
14494
|
+
if (seen.has(key)) continue;
|
|
14495
|
+
seen.add(key);
|
|
14496
|
+
const visibleStart = visibleOffsets[start];
|
|
14497
|
+
const visibleEnd = visibleOffsets[lastIndex + 1];
|
|
14498
|
+
candidates.push({
|
|
14499
|
+
startOffset: first.startOffset,
|
|
14500
|
+
endOffset: last.endOffset,
|
|
14501
|
+
sourceText: markdown.slice(first.startOffset, last.endOffset),
|
|
14502
|
+
displayPrefix: rendered.slice(Math.max(0, visibleStart - 240), visibleStart),
|
|
14503
|
+
displaySuffix: rendered.slice(visibleEnd, visibleEnd + 240)
|
|
14504
|
+
});
|
|
14505
|
+
}
|
|
14506
|
+
return candidates;
|
|
14507
|
+
}
|
|
14445
14508
|
/**
|
|
14446
14509
|
* Locate every GFM source range that renders as one browser-visible selection.
|
|
14447
14510
|
*
|
|
@@ -14458,7 +14521,11 @@ function markdownSourceCandidates(markdown, displayText) {
|
|
|
14458
14521
|
if (needle === "") return [];
|
|
14459
14522
|
const exact = candidatesFromProjection(markdown, projection, needle);
|
|
14460
14523
|
if (exact.length > 0) return exact;
|
|
14461
|
-
|
|
14524
|
+
const compactProjection = compactMapped(projection);
|
|
14525
|
+
const compactNeedle = needle.replace(/\s+/gu, " ");
|
|
14526
|
+
const compact = candidatesFromProjection(markdown, compactProjection, compactNeedle);
|
|
14527
|
+
if (compact.length > 0) return compact;
|
|
14528
|
+
return candidatesIgnoringSyntheticWhitespace(markdown, compactProjection, compactNeedle);
|
|
14462
14529
|
}
|
|
14463
14530
|
//#endregion
|
|
14464
14531
|
//#region lib/types/citation-mapping.js
|
|
@@ -14519,23 +14586,37 @@ function committedAssistantText(source, sourceSessionId, anchorSeq) {
|
|
|
14519
14586
|
if (sourceSessionId !== source.session.id) throw new Error("Citation sourceSessionId does not match the observed source Session");
|
|
14520
14587
|
const anchor = source.events.find((event) => event.seq === anchorSeq);
|
|
14521
14588
|
if (anchor?.type !== "assistant/message") throw new Error("Citation anchorSeq does not identify a committed assistant/message");
|
|
14522
|
-
const
|
|
14523
|
-
|
|
14589
|
+
const citable = projectCitableAssistantContent(anchor.data.message.content);
|
|
14590
|
+
const answer = messageText(anchor.data.message.content);
|
|
14591
|
+
const projections = citable === answer ? [citable] : [citable, answer].filter((text) => text !== "");
|
|
14592
|
+
if (projections[0]?.trim() === "") throw new Error("Citation assistant/message has no citable text");
|
|
14524
14593
|
return {
|
|
14525
14594
|
seq: anchor.seq,
|
|
14526
|
-
|
|
14595
|
+
projections
|
|
14527
14596
|
};
|
|
14528
14597
|
}
|
|
14598
|
+
function resolveProjectedRange(selection, projections) {
|
|
14599
|
+
let failure;
|
|
14600
|
+
for (const text of projections) try {
|
|
14601
|
+
return {
|
|
14602
|
+
range: resolveCitationRange(selection, text),
|
|
14603
|
+
text
|
|
14604
|
+
};
|
|
14605
|
+
} catch (error) {
|
|
14606
|
+
failure ??= error;
|
|
14607
|
+
}
|
|
14608
|
+
throw failure;
|
|
14609
|
+
}
|
|
14529
14610
|
/** Resolve a browser selection claim against the authoritative committed assistant message. */
|
|
14530
14611
|
function resolveObserverCitation(source, rawClaim) {
|
|
14531
14612
|
const claim = citationSelectionClaimSchema.parse(rawClaim);
|
|
14532
14613
|
const anchor = committedAssistantText(source, claim.sourceSessionId, claim.anchorSeq);
|
|
14533
|
-
const range =
|
|
14614
|
+
const { range, text } = resolveProjectedRange({
|
|
14534
14615
|
displayText: claim.displayText,
|
|
14535
14616
|
...claim.sourceHintText === void 0 ? {} : { sourceHintText: claim.sourceHintText },
|
|
14536
14617
|
prefixText: claim.prefixText,
|
|
14537
14618
|
suffixText: claim.suffixText
|
|
14538
|
-
}, anchor.
|
|
14619
|
+
}, anchor.projections);
|
|
14539
14620
|
const identity = {
|
|
14540
14621
|
sourceSessionId: claim.sourceSessionId,
|
|
14541
14622
|
anchorSeq: anchor.seq,
|
|
@@ -14549,20 +14630,20 @@ function resolveObserverCitation(source, rawClaim) {
|
|
|
14549
14630
|
selectionFingerprint
|
|
14550
14631
|
},
|
|
14551
14632
|
assistantMessageSeq: anchor.seq,
|
|
14552
|
-
assistantVisibleText:
|
|
14633
|
+
assistantVisibleText: text,
|
|
14553
14634
|
contentFingerprint: selectionFingerprint
|
|
14554
14635
|
};
|
|
14555
14636
|
}
|
|
14556
14637
|
/**
|
|
14557
|
-
* Validate one Citation against
|
|
14638
|
+
* Validate one Citation against committed reasoning or answer text in the observed source snapshot.
|
|
14558
14639
|
* A matching `assistant/message` is sufficient; its step and turn may remain open.
|
|
14559
14640
|
*/
|
|
14560
14641
|
function validateObserverCitation(source, rawDraft) {
|
|
14561
14642
|
const citation = citationDraftSchema.parse(rawDraft);
|
|
14562
14643
|
const anchor = committedAssistantText(source, citation.sourceSessionId, citation.anchorSeq);
|
|
14563
|
-
|
|
14564
|
-
|
|
14565
|
-
if (visibleText
|
|
14644
|
+
if (anchor.projections.find((text) => citation.endOffset > citation.startOffset && citation.endOffset <= text.length && citation.endOffset - citation.startOffset === citation.sourceText.length && text.slice(citation.startOffset, citation.endOffset) === citation.sourceText) === void 0) throw new Error("Citation UTF-16 offsets and sourceText do not match the assistant/message");
|
|
14645
|
+
const visibleText = anchor.projections.find((text) => citation.endOffset > citation.startOffset && citation.endOffset <= text.length && citation.endOffset - citation.startOffset === citation.sourceText.length && text.slice(citation.startOffset, citation.endOffset) === citation.sourceText && text.slice(Math.max(0, citation.startOffset - citation.prefixText.length), citation.startOffset) === citation.prefixText && text.slice(citation.endOffset, citation.endOffset + citation.suffixText.length) === citation.suffixText);
|
|
14646
|
+
if (visibleText === void 0) throw new Error("Citation surrounding context does not match the assistant/message");
|
|
14566
14647
|
const expectedFingerprint = fingerprintCitationDraft(citation);
|
|
14567
14648
|
if (citation.selectionFingerprint !== expectedFingerprint) throw new Error("Citation content fingerprint does not match its evidence");
|
|
14568
14649
|
return {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Project committed reasoning and answer blocks in renderer order.
|
|
3
|
+
*
|
|
4
|
+
* @param blocks - DSH assistant content blocks.
|
|
5
|
+
* @returns reasoning and answer text separated by the renderer's paragraph break.
|
|
6
|
+
*/
|
|
7
|
+
export declare function projectCitableAssistantContent(blocks: readonly unknown[]): string;
|
|
@@ -41,7 +41,7 @@ export declare function dshAssistantAnchorForTarget(target: EventTarget | null):
|
|
|
41
41
|
*/
|
|
42
42
|
export declare function dshIntersectedAssistantAnchors(range: Range): DshAssistantAnchor[];
|
|
43
43
|
/**
|
|
44
|
-
* Detect
|
|
44
|
+
* Detect generated controls, reasoning summaries, and collapsed code chrome.
|
|
45
45
|
*
|
|
46
46
|
* @param range - current rendered selection range.
|
|
47
47
|
* @param flow - assistant flow containing the range.
|
|
@@ -56,12 +56,19 @@ export declare function dshRangeTouchesExcludedContent(range: Range, flow: HTMLE
|
|
|
56
56
|
*/
|
|
57
57
|
export declare function findDshAssistantAnchor(anchorKey: string): HTMLElement | null;
|
|
58
58
|
/**
|
|
59
|
-
* Determine whether a node is
|
|
59
|
+
* Determine whether a node is generated UI rather than committed citable text.
|
|
60
60
|
*
|
|
61
61
|
* @param node - rendered node to classify.
|
|
62
|
-
* @returns whether
|
|
62
|
+
* @returns whether the node must stay out of the citable projection.
|
|
63
63
|
*/
|
|
64
|
-
export declare function
|
|
64
|
+
export declare function isNonCitableProjection(node: Node): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Determine whether a node owns one DSH reasoning block.
|
|
67
|
+
*
|
|
68
|
+
* @param node - rendered node to classify.
|
|
69
|
+
* @returns whether the node is a reasoning root.
|
|
70
|
+
*/
|
|
71
|
+
export declare function isDshReasoningContent(node: Node): boolean;
|
|
65
72
|
/**
|
|
66
73
|
* Resolve a selection wholly inside one Read Frog translation to its source paragraph.
|
|
67
74
|
*
|
package/lib/types/observer.d.ts
CHANGED
|
@@ -38,7 +38,7 @@ export declare function fingerprintCitationDraft(draft: Omit<CitationDraft, 'sel
|
|
|
38
38
|
/** Resolve a browser selection claim against the authoritative committed assistant message. */
|
|
39
39
|
export declare function resolveObserverCitation(source: ObserverSourceSnapshot, rawClaim: CitationSelectionClaim): ValidatedObserverCitation;
|
|
40
40
|
/**
|
|
41
|
-
* Validate one Citation against
|
|
41
|
+
* Validate one Citation against committed reasoning or answer text in the observed source snapshot.
|
|
42
42
|
* A matching `assistant/message` is sufficient; its step and turn may remain open.
|
|
43
43
|
*/
|
|
44
44
|
export declare function validateObserverCitation(source: ObserverSourceSnapshot, rawDraft: CitationDraft): ValidatedObserverCitation;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kirkchinese/dsh-citeciter",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "Verifiable, source-bound, read-only investigations for DeepSeek Harness responses",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
@@ -80,6 +80,20 @@
|
|
|
80
80
|
"cordis.patch.yml",
|
|
81
81
|
"README.zh.md"
|
|
82
82
|
],
|
|
83
|
+
"scripts": {
|
|
84
|
+
"build": "npm run build:lib && npm run build:bundle",
|
|
85
|
+
"prebuild:lib": "node scripts/clean-types.mjs",
|
|
86
|
+
"build:lib": "tsc -p tsconfig.json",
|
|
87
|
+
"prebuild:bundle": "node scripts/clean-bundle.mjs",
|
|
88
|
+
"build:bundle": "tsdown --env.DSH_BUILD_FACE client",
|
|
89
|
+
"postbuild:bundle": "node scripts/normalize-bundle.mjs",
|
|
90
|
+
"prepack": "npm run build",
|
|
91
|
+
"dev": "npm run build && node dev/watch.mjs",
|
|
92
|
+
"smoke:seed": "node dev/seed-smoke-session.mjs",
|
|
93
|
+
"smoke:hmr": "node dev/hmr-smoke.mjs",
|
|
94
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
95
|
+
"test": "npm run build:lib && node --test tests/*.test.mjs"
|
|
96
|
+
},
|
|
83
97
|
"peerDependencies": {
|
|
84
98
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
85
99
|
"@deepseek-ai/dsh-agent": ">=0.1.1-rc.1 <0.1.1-rc.3",
|
|
@@ -159,18 +173,5 @@
|
|
|
159
173
|
"tsdown": "^0.22.2",
|
|
160
174
|
"typescript": "^6.0.3",
|
|
161
175
|
"zod": "^4.4.3"
|
|
162
|
-
},
|
|
163
|
-
"scripts": {
|
|
164
|
-
"build": "npm run build:lib && npm run build:bundle",
|
|
165
|
-
"prebuild:lib": "node scripts/clean-types.mjs",
|
|
166
|
-
"build:lib": "tsc -p tsconfig.json",
|
|
167
|
-
"prebuild:bundle": "node scripts/clean-bundle.mjs",
|
|
168
|
-
"build:bundle": "tsdown --env.DSH_BUILD_FACE client",
|
|
169
|
-
"postbuild:bundle": "node scripts/normalize-bundle.mjs",
|
|
170
|
-
"dev": "npm run build && node dev/watch.mjs",
|
|
171
|
-
"smoke:seed": "node dev/seed-smoke-session.mjs",
|
|
172
|
-
"smoke:hmr": "node dev/hmr-smoke.mjs",
|
|
173
|
-
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
174
|
-
"test": "npm run build:lib && node --test tests/*.test.mjs"
|
|
175
176
|
}
|
|
176
|
-
}
|
|
177
|
+
}
|