@goodandready/dsh-dsml-artifact-guard 0.1.2 → 0.1.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 CHANGED
@@ -12,7 +12,7 @@
12
12
  </p>
13
13
 
14
14
  <p align="center">
15
- <a href="https://goodandready.app/"><img src="https://img.shields.io/badge/All_Author_Projects-goodandready.app-ff4500.svg?style=for-the-badge&logo=rocket&logoColor=white&labelColor=1a1a2e" alt="GoodAndReady Showcase"></a>
15
+ <a href="https://goodandready.app/"><img src="https://img.shields.io/badge/🌐_DSH_Hub-goodandready.app-ff4500.svg?style=for-the-badge&labelColor=1a1a2e" alt="GoodAndReady Showcase"></a>
16
16
  </p>
17
17
 
18
18
  <p align="center">
@@ -21,6 +21,16 @@
21
21
  <a href="docs/README.zh.md"><b>🇨🇳 中文说明</b></a>
22
22
  </p>
23
23
 
24
+ <table align="center">
25
+ <tr>
26
+ <td align="center">
27
+ ⭐ <strong>If you like this plugin, please star it on GitHub</strong> — it shows me that the plugin is useful to you and motivates me to keep developing it.
28
+ <br><br>
29
+ 🐛 <strong>If you find a bug or would like to request a feature</strong>, open a GitHub issue in any language — I will review your proposal and implement useful suggestions in a future plugin version.
30
+ </td>
31
+ </tr>
32
+ </table>
33
+
24
34
  </div>
25
35
 
26
36
  ---
@@ -64,15 +74,15 @@ graph TD
64
74
  Audit["Audit Mode<br/>(Emits ctx.logger warning only)"]
65
75
  end
66
76
 
67
- Turn -->|ctx.on('llm/stream')| Hook
77
+ Turn -->|llm/stream hook| Hook
68
78
  Hook --> ScopeCheck
69
79
  ScopeCheck -->|No| PassThrough
70
80
  ScopeCheck -->|Yes| Buffer
71
81
  PassThrough --> ChatUI
72
82
  Buffer --> Detector
73
83
  Detector -->|No Artifact| ChatUI
74
- Detector -->|Artifact Found & sanitize| Sanitize --> ChatUI
75
- Detector -->|Artifact Found & audit| Audit --> ChatUI
84
+ Detector -->|Artifact detected: sanitize| Sanitize --> ChatUI
85
+ Detector -->|Artifact detected: audit| Audit --> ChatUI
76
86
  ```
77
87
 
78
88
  ---
@@ -150,3 +160,19 @@ npm run check
150
160
  ## 📄 License
151
161
 
152
162
  MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
163
+
164
+
165
+ ## Changed in v0.1.3
166
+
167
+ #8: wrap `llm/stream` in `ctx.effect` so unload unsubscribes.
168
+ #9: `cordis.patch.yml` uses `config: {}` — schema defaults (`mode: audit`, provider/model ids) apply unless overridden in host config.
169
+
170
+ ## Changed in v0.1.4
171
+
172
+ #11: schema `mode` default set to `sanitize` for out-of-the-box protection.
173
+ #11: robust open/close tag balancing in `sanitizeDsmlArtifacts` to handle prior closed blocks in prose.
174
+ #11: repo hygiene with tracked `package-lock.json` and design contract.
175
+
176
+ ## Changed in v0.1.5
177
+
178
+ #15: fix Mermaid diagram edge label syntax on GitHub and add mandatory repository support block.
package/cordis.patch.yml CHANGED
@@ -1,8 +1,5 @@
1
- # Host-only DSH bundle.
1
+ # Host-only DSH bundle. Defaults live in Config schema (mode=sanitize).
2
2
  - insert:
3
3
  - id: dsh-dsml-artifact-guard
4
4
  name: '@goodandready/dsh-dsml-artifact-guard'
5
- config:
6
- mode: sanitize
7
- providerId: opencode-go
8
- modelId: deepseek-v4-flash
5
+ config: {}
package/docs/README.ru.md CHANGED
@@ -12,7 +12,7 @@
12
12
  </p>
13
13
 
14
14
  <p align="center">
15
- <a href="https://goodandready.app/"><img src="https://img.shields.io/badge/Все_проекты_автора-goodandready.app-ff4500.svg?style=for-the-badge&logo=rocket&logoColor=white&labelColor=1a1a2e" alt="Все проекты автора"></a>
15
+ <a href="https://goodandready.app/"><img src="https://img.shields.io/badge/🌐_DSH_Hub-goodandready.app-ff4500.svg?style=for-the-badge&labelColor=1a1a2e" alt="GoodAndReady Showcase"></a>
16
16
  </p>
17
17
 
18
18
  <p align="center">
@@ -21,6 +21,16 @@
21
21
  <a href="README.zh.md"><b>🇨🇳 中文说明</b></a>
22
22
  </p>
23
23
 
24
+ <table align="center">
25
+ <tr>
26
+ <td align="center">
27
+ ⭐ <strong>Если вам нравится этот плагин, поставьте ему звезду на GitHub</strong> — это покажет мне, что плагин вам полезен, и будет мотивировать меня развивать его дальше.
28
+ <br><br>
29
+ 🐛 <strong>Если вы нашли баг или хотите предложить новый функционал</strong>, создайте issue на GitHub на любом языке — я рассмотрю ваше предложение и реализую полезные идеи в одной из следующих версий плагина.
30
+ </td>
31
+ </tr>
32
+ </table>
33
+
24
34
  </div>
25
35
 
26
36
  ---
@@ -64,15 +74,15 @@ graph TD
64
74
  Audit["Режим Audit<br/>(Логирование через ctx.logger)"]
65
75
  end
66
76
 
67
- Turn -->|ctx.on('llm/stream')| Hook
77
+ Turn -->|Хук llm/stream| Hook
68
78
  Hook --> ScopeCheck
69
79
  ScopeCheck -->|Нет| PassThrough
70
80
  ScopeCheck -->|Да| Buffer
71
81
  PassThrough --> ChatUI
72
82
  Buffer --> Detector
73
83
  Detector -->|Нет артефакта| ChatUI
74
- Detector -->|Артефакт найден & sanitize| Sanitize --> ChatUI
75
- Detector -->|Артефакт найден & audit| Audit --> ChatUI
84
+ Detector -->|Артефакт найден: sanitize| Sanitize --> ChatUI
85
+ Detector -->|Артефакт найден: audit| Audit --> ChatUI
76
86
  ```
77
87
 
78
88
  ---
@@ -150,3 +160,19 @@ npm run check
150
160
  ## 📄 Лицензия
151
161
 
152
162
  MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
163
+
164
+
165
+ ## Changed in v0.1.3
166
+
167
+ #8: wrap `llm/stream` in `ctx.effect` so unload unsubscribes.
168
+ #9: `cordis.patch.yml` uses `config: {}` — schema defaults (`mode: audit`, provider/model ids) apply unless overridden in host config.
169
+
170
+ ## Изменения в v0.1.4
171
+
172
+ #11: значение `mode` по умолчанию в схеме переведено на `sanitize` для защиты «из коробки».
173
+ #11: балансировка открывающих и закрывающих тегов в `sanitizeDsmlArtifacts` при наличии закрытых блоков в прозе.
174
+ #11: гигиена репозитория с отслеживаемым `package-lock.json` и дизайн-контрактом.
175
+
176
+ ## Изменения в v0.1.5
177
+
178
+ #15: исправлен синтаксис стрелок диаграммы Mermaid на GitHub и добавлен обязательный блок поддержки репозитория.
package/docs/README.zh.md CHANGED
@@ -21,6 +21,16 @@
21
21
  <a href="README.zh.md"><b>🇨🇳 中文说明</b></a>
22
22
  </p>
23
23
 
24
+ <table align="center">
25
+ <tr>
26
+ <td align="center">
27
+ ⭐ <strong>如果您喜欢这个插件,请在 GitHub 上为它点亮 Star</strong> — 这能让我知道插件对您有用,并鼓励我继续开发和维护它。
28
+ <br><br>
29
+ 🐛 <strong>如果您发现 Bug 或希望增加功能</strong>,请使用任意语言在 GitHub 上提交 Issue — 我会评估您的建议,并在后续版本中实现有价值的改进。
30
+ </td>
31
+ </tr>
32
+ </table>
33
+
24
34
  </div>
25
35
 
26
36
  ---
@@ -64,15 +74,15 @@ graph TD
64
74
  Audit["审计模式 (Audit)<br/>(仅记录日志不改动文本)"]
65
75
  end
66
76
 
67
- Turn -->|ctx.on('llm/stream')| Hook
77
+ Turn -->|llm/stream 拦截| Hook
68
78
  Hook --> ScopeCheck
69
79
  ScopeCheck -->|未命中| PassThrough
70
80
  ScopeCheck -->|命中| Buffer
71
81
  PassThrough --> ChatUI
72
82
  Buffer --> Detector
73
83
  Detector -->|无异常| ChatUI
74
- Detector -->|检出残留 & sanitize| Sanitize --> ChatUI
75
- Detector -->|检出残留 & audit| Audit --> ChatUI
84
+ Detector -->|检出残留: sanitize| Sanitize --> ChatUI
85
+ Detector -->|检出残留: audit| Audit --> ChatUI
76
86
  ```
77
87
 
78
88
  ---
@@ -150,3 +160,19 @@ npm run check
150
160
  ## 📄 开源许可证
151
161
 
152
162
  MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
163
+
164
+
165
+ ## Changed in v0.1.3
166
+
167
+ #8: wrap `llm/stream` in `ctx.effect` so unload unsubscribes.
168
+ #9: `cordis.patch.yml` uses `config: {}` — schema defaults (`mode: audit`, provider/model ids) apply unless overridden in host config.
169
+
170
+ ## v0.1.4 变更说明
171
+
172
+ #11: 将 schema 中 `mode` 默认值设置为 `sanitize`,实现开箱即用的清理保护。
173
+ #11: 在 `sanitizeDsmlArtifacts` 中实现严格的开闭标签深度平衡,精准处理正文中已闭合标签与末尾泄漏标签。
174
+ #11: 纳入 `package-lock.json` 跟踪并完善设计契约。
175
+
176
+ ## v0.1.5 变更说明
177
+
178
+ #15: 修复 GitHub 上 Mermaid 流程图边标签语法解析错误,并补全标准项目支持引导区。
@@ -0,0 +1,36 @@
1
+ # Design Contract: dsh-dsml-artifact-guard
2
+
3
+ ## 1. Product Purpose & User Scenarios
4
+ - **Purpose**: Host-only streaming guard for DeepSeek Harness that intercepts assistant stream chunks and strips leaked DSML closing tags (`</|DSML|parameter>`, `</|DSML|invoke>`, `</|DSML|tool_calls>`) emitted by upstream providers before text reaches the user interface.
5
+ - **Primary Scenarios**:
6
+ - Assistant responds through an upstream gateway exhibiting DSML leakage (e.g. `opencode-go` + `deepseek-v4-flash`). Leaked protocol closing tags at the end of the response are stripped automatically.
7
+ - User and assistant discuss DSML syntax or generate markdown/code examples with DSML tags. Legitimate open tags and their matching closing tags are preserved intact (fail-open).
8
+ - Out-of-scope providers/models pass through with zero buffering or regex evaluation.
9
+
10
+ ## 2. User Surfaces
11
+ - **Web UI**: UI отсутствует на текущем этапе (host-only middleware plugin).
12
+ - **DSH Configuration**:
13
+ - `mode`: `"sanitize"` (default, strips tags), `"audit"` (logs to `ctx.logger.info`), `"disabled"` (bypasses).
14
+ - `providerId`: target provider (default `"opencode-go"`).
15
+ - `modelId`: target model (default `"deepseek-v4-flash"`).
16
+ - **Documentation**:
17
+ - Full trilingual documentation: `README.md` (EN), `docs/README.ru.md` (RU), `docs/README.zh.md` (ZH).
18
+
19
+ ## 3. Streaming & Buffering Contract
20
+ - **Synchronous Hook Contract**: The `llm/stream` listener returns an `AsyncIterable` synchronously. Returning a `Promise` breaks the Cordis event dispatcher.
21
+ - **Sliding Buffer (KEEP = 96)**: Preserves the terminal window across chunk splits.
22
+ - **Tag Balancing**: Tracks opening and closing DSML tags across the stream so that only orphaned closing tags at the terminal tail are sanitized, leaving balanced code blocks untouched.
23
+
24
+ ## 4. Do / Don't
25
+ - **Do**:
26
+ - Preserve fail-open guarantees for all user prose.
27
+ - Dispose hook registration with `ctx.effect`.
28
+ - Pass non-target provider/model streams directly through.
29
+ - **Don't**:
30
+ - Do not make the stream hook async.
31
+ - Do not drop legitimate DSML discussion in chat.
32
+ - Do not add heavy client-side UI for simple host middleware.
33
+
34
+ ## 5. Decisions Log
35
+ - **2026-09-06**: Added `ctx.effect` lifecycle wrapper and switched patch to schema-driven defaults (`#8, #9`).
36
+ - **2026-09-12**: Changed schema default mode to `sanitize` for out-of-the-box protection, implemented robust open/close tag balancing across streaming chunks, and tracked `package-lock.json` (`#11`).
package/lib/index.js CHANGED
@@ -1 +1,105 @@
1
- import Schema from '@deepseek-ai/schemastery'; const TAIL=/(?:<\/|DSML|parameter>\s*)?(?:<\/|DSML|invoke>\s*)?<\/|DSML|tool_calls>\s*$/u,OPEN=/<|DSML|(?:tool_calls|invoke|parameter)>/u,KEEP=96; export const name='@goodandready/dsh-dsml-artifact-guard',inject=['llm'],Config=Schema.object({mode:Schema.string().default('audit'),providerId:Schema.string().default('opencode-go'),modelId:Schema.string().default('deepseek-v4-flash')}); export function matchesScope(o,c){return (o?.providerId??o?.provider?.id??o?.provider)==c.providerId&&(o?.modelId??o?.model?.id??o?.model?.name??o?.model)==c.modelId} export async function* sanitizeDsmlArtifacts(s,{mode,onArtifact}={}){let p='',i,open=false;const flush=async function*(end){if(!p)return;let text=p;if(end&&!open&&TAIL.test(text)){onArtifact?.({mode,removed:1});if(mode==='sanitize')text=text.replace(TAIL,'')}if(text)yield{type:'text-delta',index:i,text};p='';i=undefined};for await(const c of s){if(c?.type==='text-delta'&&typeof c.text==='string'){if(p&&i!==c.index)yield*flush(false);i=c.index;p+=c.text;open||=OPEN.test(c.text);if(p.length>KEEP){const n=p.length-KEEP;yield{type:'text-delta',index:i,text:p.slice(0,n)};p=p.slice(n)}continue}if(c?.type==='finish'){yield*flush(true);yield c;continue}yield*flush(false);yield c}yield*flush(true)} export function apply(ctx,c={}){const e=Config(c)??{};ctx.on('llm/stream',(o,next)=>{const s=next(o);return e.mode==='disabled'||!matchesScope(o,e)?s:sanitizeDsmlArtifacts(s,{mode:e.mode,onArtifact:x=>ctx.logger?.info?.('DSML closing-tag artifact detected',x)})})}
1
+ import Schema from '@deepseek-ai/schemastery';
2
+
3
+ const TAIL = /(?:<\/|DSML|parameter>\s*)?(?:<\/|DSML|invoke>\s*)?<\/|DSML|tool_calls>\s*$/u;
4
+ const OPEN_TAG = /<|DSML|(?:tool_calls|invoke|parameter)[^>]*>/gu;
5
+ const CLOSE_TAG = /<\/|DSML|(?:tool_calls|invoke|parameter)>/gu;
6
+ const KEEP = 96;
7
+ const SCAN_BUFFER_SAFE = 64;
8
+
9
+ export const name = '@goodandready/dsh-dsml-artifact-guard';
10
+ export const inject = ['llm'];
11
+
12
+ export const Config = Schema.object({
13
+ mode: Schema.string().default('sanitize'),
14
+ providerId: Schema.string().default('opencode-go'),
15
+ modelId: Schema.string().default('deepseek-v4-flash'),
16
+ });
17
+
18
+ export function matchesScope(o, c) {
19
+ return (o?.providerId ?? o?.provider?.id ?? o?.provider) == c.providerId
20
+ && (o?.modelId ?? o?.model?.id ?? o?.model?.name ?? o?.model) == c.modelId;
21
+ }
22
+
23
+ export async function* sanitizeDsmlArtifacts(s, { mode = 'sanitize', onArtifact } = {}) {
24
+ let p = '';
25
+ let i;
26
+ let scanBuffer = '';
27
+ let totalOpen = 0;
28
+ let totalClose = 0;
29
+
30
+ const countTags = (str) => {
31
+ if (!str) return;
32
+ const opens = str.match(OPEN_TAG);
33
+ if (opens) totalOpen += opens.length;
34
+ const closes = str.match(CLOSE_TAG);
35
+ if (closes) totalClose += closes.length;
36
+ };
37
+
38
+ const flush = async function* (end) {
39
+ if (!p) return;
40
+ let text = p;
41
+ if (end) {
42
+ if (scanBuffer) {
43
+ countTags(scanBuffer);
44
+ scanBuffer = '';
45
+ }
46
+ if (TAIL.test(text)) {
47
+ const tailMatch = text.match(TAIL);
48
+ const tailCloseCount = tailMatch ? (tailMatch[0].match(CLOSE_TAG) || []).length : 0;
49
+ const closedBeforeTail = totalClose - tailCloseCount;
50
+ const unclosedBeforeTail = totalOpen - closedBeforeTail;
51
+ if (unclosedBeforeTail <= 0) {
52
+ onArtifact?.({ mode, removed: 1 });
53
+ if (mode === 'sanitize') text = text.replace(TAIL, '');
54
+ }
55
+ }
56
+ }
57
+ if (text) yield { type: 'text-delta', index: i, text };
58
+ p = '';
59
+ i = undefined;
60
+ };
61
+
62
+ for await (const c of s) {
63
+ if (c?.type === 'text-delta' && typeof c.text === 'string') {
64
+ if (p && i !== c.index) yield* flush(false);
65
+ i = c.index;
66
+ p += c.text;
67
+
68
+ scanBuffer += c.text;
69
+ if (scanBuffer.length > SCAN_BUFFER_SAFE) {
70
+ const safe = scanBuffer.slice(0, scanBuffer.length - SCAN_BUFFER_SAFE);
71
+ scanBuffer = scanBuffer.slice(scanBuffer.length - SCAN_BUFFER_SAFE);
72
+ countTags(safe);
73
+ }
74
+
75
+ if (p.length > KEEP) {
76
+ const n = p.length - KEEP;
77
+ yield { type: 'text-delta', index: i, text: p.slice(0, n) };
78
+ p = p.slice(n);
79
+ }
80
+ continue;
81
+ }
82
+ if (c?.type === 'finish') {
83
+ yield* flush(true);
84
+ yield c;
85
+ continue;
86
+ }
87
+ yield* flush(false);
88
+ yield c;
89
+ }
90
+ yield* flush(true);
91
+ }
92
+
93
+ export function apply(ctx, c = {}) {
94
+ const e = Config(c) ?? {};
95
+ // #8: dispose with ctx.effect so unload unsubscribes llm/stream
96
+ ctx.effect(() => ctx.on('llm/stream', (o, next) => {
97
+ const s = next(o);
98
+ return e.mode === 'disabled' || !matchesScope(o, e)
99
+ ? s
100
+ : sanitizeDsmlArtifacts(s, {
101
+ mode: e.mode,
102
+ onArtifact: (x) => ctx.logger?.info?.('DSML closing-tag artifact detected', x),
103
+ });
104
+ }));
105
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-dsml-artifact-guard",
3
- "version": "0.1.2",
3
+ "version": "0.1.5",
4
4
  "description": "Sanitizes leaked DeepSeek DSML closing tags from model text streams",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",