dsh-strata-sync 0.8.1 → 0.10.0

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
@@ -6,28 +6,46 @@ A plugin that aligns **DeepSeek Harness (DSH)** with the model actually loaded b
6
6
 
7
7
  Open **Settings → dsh-strata-sync** to inspect differences, synchronize an existing Strata provider, or enable automatic synchronization when DSH starts. The interface and README are available in English, Simplified Chinese and Traditional Chinese. The interface automatically follows DSH’s language, including live changes. DSH `zh` uses Simplified Chinese; `zh-Hant`, `zh-TW`, `zh-HK` and `zh-MO` use Traditional Chinese. Other languages fall back to English. There is no separate language preference. Unknown backend diagnostics retain their original text for troubleshooting.
8
8
 
9
- Version 0.8.1 calculates a dynamic length target before each supported Strata summary and appends **one sentence** to DSH's native summary prompt. The original prompt, output cap and other settings remain unchanged. The settings page explains the formula and distinguishes this writing target from the hard output limit. Existing synchronization safeguards, startup behavior, capability verification and backups are retained.
9
+ Version 0.10.0 simplifies the summary settings and makes the supplemental prompt editable. Keep the default template or customize it with the dynamic `{target}` placeholder; DSH's native prompt and output cap remain unchanged.
10
10
 
11
11
  ## What it does
12
12
 
13
13
  ### Dynamic summary guidance
14
14
 
15
- For supported Strata routes, the plugin calculates a fresh writing target for each native DSH summary request:
15
+ The compact **Dynamic summary target** card contains the enable switch, context divisor and editable **Supplemental summary prompt**. Expand **Calculation and limits** for the summary-output and source-length divisors, formula and limitations. Guidance is enabled by default; turning it off preserves DSH's native summary and automatic compaction.
16
+
17
+ The editor shows the actual template appended to the native prompt. Its default is:
16
18
 
17
19
  ```text
18
- context = min(verified Strata context, existing DSH model context)
19
- target = floor(min(context / 16,
20
- actual summary maxTokens / 2,
21
- selected-content estimated tokens / 2))
20
+ Keep the entire checkpoint, including all required headings, within a target of {target} tokens by retaining only information essential to continue the remaining work, consolidating prior summaries, and removing redundant or obsolete details.
22
21
  ```
23
22
 
24
- The selected-content estimate covers only the conversation span being replaced, including any prior checkpoint in that span. It excludes the system prompt and tool schemas. Before each summary, read-only metadata queries verify Strata's loaded model and context; the current summary request supplies its actual output cap. The calculation uses the smaller of verified server context and the existing DSH model context, so a larger server window never silently expands a smaller client budget. The target is calculated at summary time, not fixed when DSH starts or when settings are synchronized.
23
+ Keep the literal `{target}` placeholder in your template; each occurrence is replaced with the target calculated for that summary. Templates may contain up to 2,048 characters. Replacement is plain text, not executable code. **Restore default prompt** restores the original English template in the editor; click **Save preferences** to apply edits or the restored default. Changing the interface language does not translate or overwrite the saved template. This edits only the supplement, never DSH's original summary instructions.
24
+
25
+ | Plugin preference | Default | Meaning |
26
+ |---|---:|---|
27
+ | `summaryGuidance` | `true` | Append guidance to supported summary requests. |
28
+ | `summaryContextDivisor` | `16` | Divide the verified context by this value. |
29
+ | `summaryOutputDivisor` | `2` | Divide the actual summary output cap by this value. |
30
+ | `summarySourceDivisor` | `2` | Divide the estimated source length by this value. |
31
+ | `summaryPromptTemplate` | English template above | Supplement appended after substituting `{target}`. |
32
+
33
+ All three divisors accept finite numbers of at least 1, including decimals such as 1.5 or 2.5. A larger divisor makes its bound smaller. The final target is the smallest of the three bounds, rounded down; these controls do not change the actual output hard limit.
34
+
35
+ For each supported summary:
36
+
37
+ ```text
38
+ context = min(verified Strata context, existing DSH model context)
39
+ target = floor(min(context / summaryContextDivisor,
40
+ actual summary maxTokens / summaryOutputDivisor,
41
+ selected-content estimated tokens / summarySourceDivisor))
42
+ ```
25
43
 
26
- With a 4,096-token summary output cap and enough source content, 32K context gives a target of **2,048 tokens**, and 64K gives **2,048 tokens**. Shorter content lowers the target. **Settings → dsh-strata-sync** shows the formula and the maximum target for a preview; it cannot know the exact target of a future summary.
44
+ The source estimate covers the conversation span being replaced, including prior checkpoints in that span, but excludes system prompts and tool schemas. Read-only metadata queries verify the loaded model and context; the current request supplies its actual output cap. With 32K context, a 4,096-token summary cap and enough content, context divisors 32, 16 and 8 produce targets of 1,024, 2,048 and 2,048 tokens. The UI preview is a ceiling, not the actual target of a future request.
27
45
 
28
- Exactly one supplemental English sentence asks the model to meet that target, retain information needed to continue the remaining work, consolidate prior summaries and remove redundant or obsolete details. The native instructions, eight-section format and existing request options are preserved. The target is a heuristic writing instruction, **not a new hard output limit, an optimal value, or a guarantee against truncation**. No automatic retry loop or extra inference request is added.
46
+ Saving these controls changes only plugin preferences; it does not synchronize model budgets or change Strata Server settings. DSH's native prompt, eight-section format and request options remain intact. The default supplement is one sentence; a custom template is appended as entered. The target is a writing instruction, **not a new hard output limit or a guarantee against truncation**. No retry loop or extra inference request is added.
29
47
 
30
- If the route, required metadata or native summary format cannot be verified, or image/file attachment pricing cannot be estimated safely, the original DSH request is passed through unchanged. A cancelled request or disabled plugin also preserves native handling. Calculated targets below 256 tokens leave the native prompt unchanged, rather than imposing an impractically small target for eight sections; the plugin does not cancel the summary or replace content with an empty checkpoint. Disabling or removing this plugin removes the supplemental guidance; it does not rewrite saved conversation history.
48
+ Unverified routes/metadata, unknown native prompts, unpriced attachments, invalid templates, cancellation, disabled guidance and targets below 256 tokens leave the native request unchanged. Removing the plugin stops the supplement without rewriting saved conversation history.
31
49
 
32
50
  ### Automatic model capabilities (0.4.0)
33
51
 
@@ -77,7 +95,7 @@ dsh plugin --profile desktop add dsh-strata-sync --registry=https://registry.npm
77
95
 
78
96
  Reopen DSH after installation. The bundle is automatically selected on its first installation; an already installed but disabled bundle must be enabled in DSH's plugin manager.
79
97
 
80
- To install **0.8.1 with dynamic summary guidance**, build from this source with Node.js 22 or newer, finish active tasks and fully quit DSH Desktop before the final installation command:
98
+ To install **0.10.0 with the editable summary prompt** from source, use Node.js 22 or newer, finish active tasks and fully quit DSH Desktop before the final installation command:
81
99
 
82
100
  ```sh
83
101
  git clone https://github.com/ethan741205/dsh-strata-sync.git
@@ -85,7 +103,7 @@ cd dsh-strata-sync
85
103
  npm ci
86
104
  npm test
87
105
  npm pack
88
- dsh plugin --profile desktop add ./dsh-strata-sync-0.8.1.tgz
106
+ dsh plugin --profile desktop add ./dsh-strata-sync-0.10.0.tgz
89
107
  ```
90
108
 
91
109
  Use an absolute tarball path if running the last command elsewhere. DSH's plugin installer selects the included bundle; it can require a desktop reload to mount the new browser entry. Finish any active task before restarting DSH.
@@ -98,8 +116,22 @@ Then open **Settings → dsh-strata-sync**:
98
116
  4. Optionally enable **Automatically sync when DSH starts** and save. It defaults to off and takes effect on the next start. Existing `autoSync: true` preferences now use startup behavior.
99
117
  5. Start a new conversation to use the new Agent preset revision.
100
118
 
119
+ The **Dynamic summary target** card is independent of model synchronization. Leave guidance enabled, keep the default template, and use divisors 16 / 2 / 2 to retain the previous behavior, or change these preferences and save. Turning this switch off does not disable DSH's own compaction.
120
+
101
121
  No API key needs to be copied into this plugin. Set up or repair credentials in DSH Models settings.
102
122
 
123
+ ## Changes in 0.10.0
124
+
125
+ - Makes the actual supplemental prompt editable, with a dynamic `{target}` placeholder and Restore default prompt action.
126
+ - Simplifies the summary card and moves detailed formula explanations into expandable help in all three languages. Adds advanced output/source divisors and decimal support for all three divisors.
127
+ - Validates and saves prompt edits through existing backup and revision safeguards without changing native instructions or other budgets.
128
+
129
+ ## Changes in 0.9.0
130
+
131
+ - Adds a default-on switch for the supplemental summary guidance and an adjustable context divisor, defaulting to 16.
132
+ - Saves both controls through the existing preferences and revision checks, with matching English, Simplified Chinese and Traditional Chinese UI and documentation.
133
+ - Keeps native summary instructions, hard output limits, other budgets and server settings unchanged when saving these controls.
134
+
103
135
  ## Changes in 0.5.0
104
136
 
105
137
  - Follows DSH’s native locale service; removes the independent language selector and product disclaimer.
package/dist/client.js CHANGED
@@ -87,17 +87,77 @@ window.__ModuleLoader__.load({
87
87
  "zh-Hant": "動態摘要目標",
88
88
  "zh-Hans": "动态摘要目标"
89
89
  },
90
+ "Supplemental summary prompt": {
91
+ "zh-Hant": "摘要補充提示詞",
92
+ "zh-Hans": "摘要补充提示词"
93
+ },
94
+ "Restore default prompt": {
95
+ "zh-Hant": "還原預設提示詞",
96
+ "zh-Hans": "恢复默认提示词"
97
+ },
98
+ "Appended to DSH's native prompt; {target} becomes the calculated token target. Save preferences to apply.": {
99
+ "zh-Hant": "附加於 DSH 原生提示詞;{target} 會替換為計算出的 token 目標。儲存偏好後套用。",
100
+ "zh-Hans": "附加于 DSH 原生提示词;{target} 会替换为计算出的 token 目标。保存偏好后应用。"
101
+ },
102
+ "Use 1–2,048 characters and include {target}.": {
103
+ "zh-Hant": "請輸入 1–2,048 個字元,並包含 {target}。",
104
+ "zh-Hans": "请输入 1–2,048 个字符,并包含 {target}。"
105
+ },
106
+ "Calculation and limits": {
107
+ "zh-Hant": "計算方式與限制",
108
+ "zh-Hans": "计算方式与限制"
109
+ },
90
110
  "How the target is calculated": {
91
111
  "zh-Hant": "目標如何計算",
92
112
  "zh-Hans": "目标如何计算"
93
113
  },
94
- "Before each supported Strata summary, calculates a length target and appends one sentence to DSH's native summary prompt.": {
95
- "zh-Hant": "每次支援的 Strata 摘要請求前,計算長度目標,並在 DSH 原生摘要提示詞後補充一句。",
96
- "zh-Hans": "每次支持的 Strata 摘要请求前,计算长度目标,并在 DSH 原生摘要提示词后补充一句。"
114
+ "When enabled, before each supported Strata summary, calculates a length target and appends one sentence to DSH's native summary prompt.": {
115
+ "zh-Hant": "啟用後,在每次支援的 Strata 摘要請求前,計算長度目標,並在 DSH 原生摘要提示詞後補充一句。",
116
+ "zh-Hans": "启用后,在每次支持的 Strata 摘要请求前,计算长度目标,并在 DSH 原生摘要提示词后补充一句。"
117
+ },
118
+ "Target = floor(min(context / N, summary output limit / O, selected content estimate / S)).": {
119
+ "zh-Hant": "目標 = floor(min(上下文 / N, 摘要輸出上限 / O, 待壓縮內容估算 / S))。",
120
+ "zh-Hans": "目标 = floor(min(上下文 / N, 摘要输出上限 / O, 待压缩内容估算 / S))。"
121
+ },
122
+ "Enable summary guidance": {
123
+ "zh-Hant": "啟用摘要提示補充",
124
+ "zh-Hans": "启用摘要提示补充"
125
+ },
126
+ "Enabled by default. Turning this off keeps DSH's native summary and automatic compaction behavior.": {
127
+ "zh-Hant": "預設開啟。關閉後保留 DSH 原生摘要與自動壓縮行為。",
128
+ "zh-Hans": "默认开启。关闭后保留 DSH 原生摘要与自动压缩行为。"
97
129
  },
98
- "Target = floor(min(context / 16, summary output limit / 2, selected content estimate / 2)).": {
99
- "zh-Hant": "目標 = floor(min(上下文 / 16, 摘要輸出上限 / 2, 待壓縮內容估算 / 2))。",
100
- "zh-Hans": "目标 = floor(min(上下文 / 16, 摘要输出上限 / 2, 待压缩内容估算 / 2))。"
130
+ "Context divisor (context ÷ N)": {
131
+ "zh-Hant": "上下文除數(上下文 ÷ N)",
132
+ "zh-Hans": "上下文除数(上下文 ÷ N)"
133
+ },
134
+ "Summary output divisor (output limit ÷ O)": {
135
+ "zh-Hant": "摘要輸出除數(輸出上限 ÷ O)",
136
+ "zh-Hans": "摘要输出除数(输出上限 ÷ O)"
137
+ },
138
+ "Source length divisor (source estimate ÷ S)": {
139
+ "zh-Hant": "原文長度除數(原文估算 ÷ S)",
140
+ "zh-Hans": "原文长度除数(原文估算 ÷ S)"
141
+ },
142
+ "Defaults: N = 16, O = 2, S = 2. Smaller divisors allow longer targets; the smallest of the three bounds wins. Decimals are supported.": {
143
+ "zh-Hant": "預設 N = 16、O = 2、S = 2。除數越小,目標可越長;最後取三項限制的最小值。支援小數。",
144
+ "zh-Hans": "默认 N = 16、O = 2、S = 2。除数越小,目标可越长;最后取三项限制的最小值。支持小数。"
145
+ },
146
+ "Enter a finite divisor of at least 1; decimals are allowed.": {
147
+ "zh-Hant": "請輸入大於或等於 1 的有限除數,可使用小數。",
148
+ "zh-Hans": "请输入大于或等于 1 的有限除数,可使用小数。"
149
+ },
150
+ "This preview is below the 256-token minimum. The native summary prompt remains unchanged.": {
151
+ "zh-Hant": "本次預覽低於 256 tokens 的最低目標,將保留原生摘要提示詞。",
152
+ "zh-Hans": "本次预览低于 256 tokens 的最低目标,将保留原生摘要提示词。"
153
+ },
154
+ "Summary guidance is off. No extra sentence is added to the native prompt.": {
155
+ "zh-Hant": "摘要提示補充已關閉,不會在原生提示詞中加上額外句子。",
156
+ "zh-Hans": "摘要提示补充已关闭,不会在原生提示词中添加额外句子。"
157
+ },
158
+ "Save preferences to apply these controls to subsequent summary requests; other model and compaction settings stay unchanged.": {
159
+ "zh-Hant": "儲存偏好後,這些控制項會套用至後續摘要請求;其他模型與壓縮設定保持不變。",
160
+ "zh-Hans": "保存偏好后,这些控制项将应用于后续摘要请求;其他模型与压缩设置保持不变。"
101
161
  },
102
162
  "Context uses the smaller of the verified Strata window and the existing DSH model window. The preview ceiling assumes the proposed budgets; existing conversation settings can make the actual target lower.": {
103
163
  "zh-Hant": "上下文採用已確認的 Strata 視窗與 DSH 既有模型視窗的較小值。預覽上限依提議預算計算,既有對話設定可能讓實際目標更低。",
@@ -339,7 +399,7 @@ window.__ModuleLoader__.load({
339
399
  model: 'Model', modelId: 'Model', defaultModel: 'Default model',
340
400
  thresholdRatio: 'Compaction ratio ceiling', retainRatio: 'Recent conversation retention ratio',
341
401
  };
342
- const css = `.strata-sync{max-width:880px;color:inherit;font:inherit;line-height:1.6}.strata-sync *{box-sizing:border-box}.strata-sync header{border-bottom:1px solid #8884;padding-bottom:16px;margin-bottom:20px}.strata-sync h2{margin:0;font-size:22px}.strata-sync .ss-muted{opacity:.72;font-size:13px}.strata-sync .ss-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:14px}.strata-sync label{display:flex;flex-direction:column;gap:5px;font-size:14px}.strata-sync select,.strata-sync input[type=number]{padding:9px;border:1px solid #8886;border-radius:7px;background:transparent;color:inherit;width:100%}.strata-sync option{color:#17252b;background:#fff}.strata-sync button{padding:9px 15px;border:1px solid #8885;border-radius:7px;background:transparent;color:inherit;cursor:pointer}.strata-sync button.primary{background:#126c65;border-color:#126c65;color:#fff}.strata-sync button:disabled{opacity:.45;cursor:default}.strata-sync .ss-actions{display:flex;gap:9px;flex-wrap:wrap;margin:18px 0}.strata-sync .ss-check{display:flex;flex-direction:row;align-items:center;margin-top:13px}.strata-sync .ss-card{padding:16px;border:1px solid #8884;border-radius:9px;margin:16px 0}.strata-sync .ss-status{padding:10px 13px;border-left:3px solid #21867b;background:#21867b12;margin:13px 0;overflow-wrap:anywhere}.strata-sync .ss-error{border-color:#ce7140;background:#ce714012}.strata-sync table{width:100%;border-collapse:collapse;font-size:14px}.strata-sync td,.strata-sync th{padding:9px 8px;text-align:left;border-bottom:1px solid #8883;overflow-wrap:anywhere}.strata-sync code{font-size:12px;overflow-wrap:anywhere}.strata-sync .ss-scroll{overflow-x:auto;container-type:inline-size;border:1px solid #8883;border-radius:8px;margin-top:16px}.strata-sync table{table-layout:fixed}.strata-sync th,.strata-sync td{vertical-align:top;padding:12px;line-height:1.5}.strata-sync thead th{font-size:12px;font-weight:600;background:#8881}.strata-sync th:first-child{width:34%}.strata-sync thead th:not(:first-child){width:33%;text-align:right}.strata-sync tbody th{font-weight:500}.strata-sync tbody tr:last-child>*{border-bottom:0}.strata-sync .ss-before,.strata-sync .ss-after{text-align:right;font-variant-numeric:tabular-nums}.strata-sync .ss-before{color:inherit;opacity:.7}.strata-sync .ss-after{background:#21867b0b;font-weight:600}.strata-sync .ss-target{display:block;font-size:11px;font-weight:400;opacity:.65;margin-top:5px;overflow-wrap:anywhere}.strata-sync .ss-values{display:flex;gap:5px;flex-wrap:wrap;justify-content:flex-end}.strata-sync .ss-chip{display:inline-block;padding:2px 7px;border:1px solid #8883;border-radius:5px;font-size:12px;font-weight:400;max-width:100%;overflow-wrap:anywhere}.strata-sync .ss-details{text-align:left;font-weight:400;font-size:12px}.strata-sync .ss-details summary{cursor:pointer;overflow-wrap:anywhere}.strata-sync .ss-details pre{white-space:pre-wrap;overflow-wrap:anywhere;margin:8px 0 0;font-size:11px;line-height:1.5}.strata-sync .ss-empty{font-weight:400;font-size:12px;opacity:.65}@container(max-width:440px){.strata-sync tbody th:first-child{width:100%}.strata-sync table,.strata-sync tbody,.strata-sync tr,.strata-sync tbody th,.strata-sync td{display:block;width:100%}.strata-sync thead{display:none}.strata-sync tbody tr{padding:10px 12px;border-bottom:1px solid #8883}.strata-sync tbody th,.strata-sync td{border:0;padding:5px 0}.strata-sync td::before{content:attr(data-label);float:left;font-size:12px;font-weight:400;opacity:.7}.strata-sync td{display:flow-root;padding:7px}.strata-sync .ss-values{max-width:72%;margin-left:auto}.strata-sync .ss-details{margin-top:24px}}`;
402
+ const css = `.strata-sync{max-width:880px;color:inherit;font:inherit;line-height:1.6}.strata-sync *{box-sizing:border-box}.strata-sync header{border-bottom:1px solid #8884;padding-bottom:16px;margin-bottom:20px}.strata-sync h2{margin:0;font-size:22px}.strata-sync .ss-muted{opacity:.72;font-size:13px}.strata-sync .ss-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:14px}.strata-sync label{display:flex;flex-direction:column;gap:5px;font-size:14px}.strata-sync select,.strata-sync input[type=number],.strata-sync textarea{padding:9px;border:1px solid #8886;border-radius:7px;background:transparent;color:inherit;width:100%}.strata-sync textarea{resize:vertical;min-height:112px;font:inherit;line-height:1.5}.strata-sync .ss-prompt-label{margin:12px 0 8px}.strata-sync option{color:#17252b;background:#fff}.strata-sync button{padding:9px 15px;border:1px solid #8885;border-radius:7px;background:transparent;color:inherit;cursor:pointer}.strata-sync button.primary{background:#126c65;border-color:#126c65;color:#fff}.strata-sync button:disabled{opacity:.45;cursor:default}.strata-sync .ss-actions{display:flex;gap:9px;flex-wrap:wrap;margin:18px 0}.strata-sync .ss-check{display:flex;flex-direction:row;align-items:center;margin-top:13px}.strata-sync .ss-card{padding:16px;border:1px solid #8884;border-radius:9px;margin:16px 0}.strata-sync .ss-status{padding:10px 13px;border-left:3px solid #21867b;background:#21867b12;margin:13px 0;overflow-wrap:anywhere}.strata-sync .ss-error{border-color:#ce7140;background:#ce714012}.strata-sync table{width:100%;border-collapse:collapse;font-size:14px}.strata-sync td,.strata-sync th{padding:9px 8px;text-align:left;border-bottom:1px solid #8883;overflow-wrap:anywhere}.strata-sync code{font-size:12px;overflow-wrap:anywhere}.strata-sync .ss-scroll{overflow-x:auto;container-type:inline-size;border:1px solid #8883;border-radius:8px;margin-top:16px}.strata-sync table{table-layout:fixed}.strata-sync th,.strata-sync td{vertical-align:top;padding:12px;line-height:1.5}.strata-sync thead th{font-size:12px;font-weight:600;background:#8881}.strata-sync th:first-child{width:34%}.strata-sync thead th:not(:first-child){width:33%;text-align:right}.strata-sync tbody th{font-weight:500}.strata-sync tbody tr:last-child>*{border-bottom:0}.strata-sync .ss-before,.strata-sync .ss-after{text-align:right;font-variant-numeric:tabular-nums}.strata-sync .ss-before{color:inherit;opacity:.7}.strata-sync .ss-after{background:#21867b0b;font-weight:600}.strata-sync .ss-target{display:block;font-size:11px;font-weight:400;opacity:.65;margin-top:5px;overflow-wrap:anywhere}.strata-sync .ss-values{display:flex;gap:5px;flex-wrap:wrap;justify-content:flex-end}.strata-sync .ss-chip{display:inline-block;padding:2px 7px;border:1px solid #8883;border-radius:5px;font-size:12px;font-weight:400;max-width:100%;overflow-wrap:anywhere}.strata-sync .ss-details{text-align:left;font-weight:400;font-size:12px}.strata-sync .ss-details summary{cursor:pointer;overflow-wrap:anywhere}.strata-sync .ss-details pre{white-space:pre-wrap;overflow-wrap:anywhere;margin:8px 0 0;font-size:11px;line-height:1.5}.strata-sync .ss-empty{font-weight:400;font-size:12px;opacity:.65}@container(max-width:440px){.strata-sync tbody th:first-child{width:100%}.strata-sync table,.strata-sync tbody,.strata-sync tr,.strata-sync tbody th,.strata-sync td{display:block;width:100%}.strata-sync thead{display:none}.strata-sync tbody tr{padding:10px 12px;border-bottom:1px solid #8883}.strata-sync tbody th,.strata-sync td{border:0;padding:5px 0}.strata-sync td::before{content:attr(data-label);float:left;font-size:12px;font-weight:400;opacity:.7}.strata-sync td{display:flow-root;padding:7px}.strata-sync .ss-values{max-width:72%;margin-left:auto}.strata-sync .ss-details{margin-top:24px}}`;
343
403
  const fmt = (v) => v == null ? 'Not set' : typeof v === 'number' ? v.toLocaleString('en-US') : typeof v === 'object' ? JSON.stringify(v) : String(v);
344
404
  const warningText = {
345
405
  INPUT_UNCONFIRMED: 'Input types are missing or unsupported in server metadata. Existing input settings are preserved.',
@@ -367,19 +427,20 @@ REASONING_DEFAULT_CONFLICT: 'The provider default reasoning level is not support
367
427
  };
368
428
  const diagnostic = (message) => translations[message] ? t(message) : language === 'en' ? message : t('Server diagnostic: ') + message;
369
429
  const [state,setState] = React.useState(null), [preview,setPreview] = React.useState(null), [result,setResult] = React.useState(null);
370
- const [form,setForm] = React.useState({providerId:'',output:'',manual:false,auto:false,follow:true});
430
+ const [form,setForm] = React.useState({providerId:'',output:'',manual:false,auto:false,follow:true,summaryGuidance:true,summaryDivisor:'16',summaryOutputDivisor:'2',summarySourceDivisor:'2',summaryPrompt:''});
371
431
  const [pending,setPending] = React.useState(false), [error,setError] = React.useState(''), [loadError,setLoadError] = React.useState('');
372
432
  const [notice,setNotice] = React.useState(''), [conflict,setConflict] = React.useState(false);
373
433
  const formRef=React.useRef(form), baseline=React.useRef(null), requestSeq=React.useRef(0), alive=React.useRef(true);
374
- const values = (f) => ({providerId:f.providerId,outputTokens:f.manual ? (f.output.trim()==='' ? NaN : Number(f.output)) : null,autoSync:f.auto,followLoadedModel:f.follow});
375
- const preferences = (s) => ({providerId:s.providerId || '',outputTokens:s.outputTokens ?? null,autoSync:!!s.autoSync,followLoadedModel:s.followLoadedModel !== false});
434
+ const numberValue = value => value.trim()===''?NaN:Number(value);
435
+ const values = (f) => ({providerId:f.providerId,outputTokens:f.manual ? (f.output.trim()==='' ? NaN : Number(f.output)) : null,autoSync:f.auto,followLoadedModel:f.follow,summaryGuidance:f.summaryGuidance,summaryContextDivisor:numberValue(f.summaryDivisor),summaryOutputDivisor:numberValue(f.summaryOutputDivisor),summarySourceDivisor:numberValue(f.summarySourceDivisor),summaryPromptTemplate:f.summaryPrompt});
436
+ const preferences = (s) => ({providerId:s.providerId || '',outputTokens:s.outputTokens ?? null,autoSync:!!s.autoSync,followLoadedModel:s.followLoadedModel !== false,summaryGuidance:s.summaryGuidance!==false,summaryContextDivisor:s.summaryContextDivisor??16,summaryOutputDivisor:s.summaryOutputDivisor??2,summarySourceDivisor:s.summarySourceDivisor??2,summaryPromptTemplate:s.summaryPromptTemplate??s.defaultSummaryPromptTemplate??''});
376
437
  const equal = (a,b) => Object.keys(a).every(key=>Object.is(a[key],b[key]));
377
438
  const acceptStatus = React.useCallback((s,force=false) => {
378
439
  if (!alive.current) return;
379
440
  const next=preferences(s), old=baseline.current;
380
441
  const untouched=!old || equal(values(formRef.current),old.values);
381
442
  if (force || untouched) {
382
- const nextForm={providerId:next.providerId,output:next.outputTokens==null?'':String(next.outputTokens),manual:next.outputTokens!=null,auto:next.autoSync,follow:next.followLoadedModel};
443
+ const nextForm={providerId:next.providerId,output:next.outputTokens==null?'':String(next.outputTokens),manual:next.outputTokens!=null,auto:next.autoSync,follow:next.followLoadedModel,summaryGuidance:next.summaryGuidance,summaryDivisor:String(next.summaryContextDivisor),summaryOutputDivisor:String(next.summaryOutputDivisor),summarySourceDivisor:String(next.summarySourceDivisor),summaryPrompt:next.summaryPromptTemplate};
383
444
  formRef.current=nextForm; setForm(nextForm); setConflict(false);
384
445
  baseline.current={values:next,revision:s.settingsRevision};
385
446
  } else if (old.revision!==s.settingsRevision || !equal(old.values,next)) {
@@ -411,12 +472,15 @@ REASONING_DEFAULT_CONFLICT: 'The provider default reasoning level is not support
411
472
  else setError(e.code==='unsupported-route'?'This provider uses an unsupported model catalog configuration. Configure an explicit custom model list in DSH before syncing.':e.message||'The operation failed.');
412
473
  } finally { if(alive.current)setPending(false); }
413
474
  }
414
- const draft=values(form), valid=draft.outputTokens===null || Number.isSafeInteger(draft.outputTokens)&&draft.outputTokens>0;
475
+ const draft=values(form), isDivisor=value=>Number.isFinite(value)&&value>=1&&value<=Number.MAX_SAFE_INTEGER;
476
+ const validDivisor=[draft.summaryContextDivisor,draft.summaryOutputDivisor,draft.summarySourceDivisor].every(isDivisor);
477
+ const validPrompt=typeof draft.summaryPromptTemplate==='string'&&draft.summaryPromptTemplate.length>0&&draft.summaryPromptTemplate.length<=2048&&draft.summaryPromptTemplate.includes('{target}');
478
+ const valid=validDivisor&&validPrompt&&(draft.outputTokens===null || Number.isSafeInteger(draft.outputTokens)&&draft.outputTokens>0);
415
479
  const dirty=!!baseline.current&&!equal(draft,baseline.current.values);
416
480
  const displayedResult=!result || state?.lastApplied?.at>result.at ? state?.lastApplied : result;
417
481
  const activeBudget=preview?.budget;
418
- const summaryTargetCeiling=Number.isSafeInteger(activeBudget?.contextWindow)&&activeBudget.contextWindow>0&&Number.isSafeInteger(activeBudget?.summaryMaxTokens)&&activeBudget.summaryMaxTokens>0
419
- ? Math.floor(Math.min(activeBudget.contextWindow/16,activeBudget.summaryMaxTokens/2)) : null;
482
+ const summaryTargetCeiling=form.summaryGuidance&&validDivisor&&Number.isSafeInteger(activeBudget?.contextWindow)&&activeBudget.contextWindow>0&&Number.isSafeInteger(activeBudget?.summaryMaxTokens)&&activeBudget.summaryMaxTokens>0
483
+ ? Math.floor(Math.min(activeBudget.contextWindow/draft.summaryContextDivisor,activeBudget.summaryMaxTokens/draft.summaryOutputDivisor)) : null;
420
484
  const outputSource=(budget)=>t(({server:'Strata Server default',manual:'Manual override',calculated:'Automatic calculation'})[budget?.outputSource]||'Automatic calculation');
421
485
  const preserved=(view)=>{
422
486
  const status=view?.capabilityStatus;
@@ -458,6 +522,32 @@ REASONING_DEFAULT_CONFLICT: 'The provider default reasoning level is not support
458
522
  h('label',{className:'ss-check'},h('input',{type:'checkbox',checked:form.follow,onChange:e=>edit({follow:e.target.checked}),disabled:pending||!state}),t('Follow the loaded Strata model as the default')),
459
523
  h('label',{className:'ss-check'},h('input',{type:'checkbox',checked:form.auto,onChange:e=>edit({auto:e.target.checked}),disabled:pending||!state}),t('Automatically sync when DSH starts')),
460
524
  h('p',{className:'ss-muted'},t('Runs once on the next DSH start. Waits for running tasks to finish; no periodic server checks.')),
525
+ h('div',{className:'ss-card','data-testid':'summary-target'},
526
+ h('strong',null,t('Dynamic summary target')),
527
+ h('label',{className:'ss-check'},h('input',{type:'checkbox','data-testid':'summary-guidance-toggle','aria-label':t('Enable summary guidance'),checked:form.summaryGuidance,onChange:e=>edit({summaryGuidance:e.target.checked}),disabled:pending||!state}),t('Enable summary guidance')),
528
+ h('label',null,t('Context divisor (context ÷ N)'),h('input',{type:'number','data-testid':'summary-divisor','aria-label':t('Context divisor (context ÷ N)'),'aria-invalid':!isDivisor(draft.summaryContextDivisor),min:1,max:Number.MAX_SAFE_INTEGER,step:'any',value:form.summaryDivisor,onChange:e=>edit({summaryDivisor:e.target.value}),disabled:pending||!state})),
529
+ state&&!validDivisor&&h('p',{className:'ss-status ss-error',role:'alert'},t('Enter a finite divisor of at least 1; decimals are allowed.')),
530
+ h('label',{className:'ss-prompt-label'},t('Supplemental summary prompt'),h('textarea',{'data-testid':'summary-prompt','aria-label':t('Supplemental summary prompt'),'aria-invalid':!validPrompt,rows:5,value:form.summaryPrompt,onChange:e=>edit({summaryPrompt:e.target.value}),disabled:pending||!state,spellCheck:false})),
531
+ state&&!validPrompt&&h('p',{className:'ss-status ss-error',role:'alert'},t('Use 1–2,048 characters and include {target}.')),
532
+ h('button',{type:'button','data-testid':'summary-prompt-reset',disabled:pending||!state?.defaultSummaryPromptTemplate||form.summaryPrompt===state.defaultSummaryPromptTemplate,onClick:()=>edit({summaryPrompt:state.defaultSummaryPromptTemplate})},t('Restore default prompt')),
533
+ h('p',{className:'ss-muted'},t("Appended to DSH's native prompt; {target} becomes the calculated token target. Save preferences to apply.")),
534
+ !form.summaryGuidance&&h('p',{className:'ss-status','data-testid':'summary-guidance-off'},t('Summary guidance is off. No extra sentence is added to the native prompt.')),
535
+ summaryTargetCeiling!==null&&(summaryTargetCeiling>=256
536
+ ? h('p',{'data-testid':'summary-target-ceiling'},t('Maximum target for this preview: '),format(summaryTargetCeiling),' tokens')
537
+ : h('p',{className:'ss-status','data-testid':'summary-target-native'},t('This preview is below the 256-token minimum. The native summary prompt remains unchanged.'))),
538
+ h('details',{className:'ss-details','data-testid':'summary-details'},h('summary',null,t('Calculation and limits')),
539
+ h('div',{className:'ss-grid'},
540
+ h('label',null,t('Summary output divisor (output limit ÷ O)'),h('input',{type:'number','data-testid':'summary-output-divisor','aria-label':t('Summary output divisor (output limit ÷ O)'),'aria-invalid':!isDivisor(draft.summaryOutputDivisor),min:1,max:Number.MAX_SAFE_INTEGER,step:'any',value:form.summaryOutputDivisor,onChange:e=>edit({summaryOutputDivisor:e.target.value}),disabled:pending||!state})),
541
+ h('label',null,t('Source length divisor (source estimate ÷ S)'),h('input',{type:'number','data-testid':'summary-source-divisor','aria-label':t('Source length divisor (source estimate ÷ S)'),'aria-invalid':!isDivisor(draft.summarySourceDivisor),min:1,max:Number.MAX_SAFE_INTEGER,step:'any',value:form.summarySourceDivisor,onChange:e=>edit({summarySourceDivisor:e.target.value}),disabled:pending||!state}))),
542
+ h('p',{className:'ss-muted'},t("Enabled by default. Turning this off keeps DSH's native summary and automatic compaction behavior.")),
543
+ h('p',{className:'ss-muted'},t('Defaults: N = 16, O = 2, S = 2. Smaller divisors allow longer targets; the smallest of the three bounds wins. Decimals are supported.')),
544
+ h('p',{className:'ss-muted'},t('Target = floor(min(context / N, summary output limit / O, selected content estimate / S)).')),
545
+ h('p',{className:'ss-muted'},t('Context uses the smaller of the verified Strata window and the existing DSH model window. The preview ceiling assumes the proposed budgets; existing conversation settings can make the actual target lower.')),
546
+ h('p',{className:'ss-muted'},t('The actual target may be lower for shorter content. System prompts and tool schemas are excluded from the content estimate.')),
547
+ h('p',{className:'ss-muted'},t('The original prompt, output cap and other settings stay unchanged. This is a writing target, not a guaranteed length or an optimal value.')),
548
+ h('p',{className:'ss-muted'},t('Targets below 256 tokens leave the original prompt unchanged; the native summary still runs.')),
549
+ h('p',{className:'ss-muted'},t('If the route or native summary format cannot be verified, DSH uses its original prompt.')),
550
+ h('p',{className:'ss-muted'},t('Unpriced image/file attachments, cancellation or a disabled plugin preserve native request handling.')))),
461
551
  h('div',{className:'ss-actions'},
462
552
  h('button',{disabled:pending||!state||!form.providerId||!valid||!dirty||conflict,onClick:()=>action(async()=>{
463
553
  const changes=Object.fromEntries(Object.entries(values(formRef.current)).filter(([key,value])=>!Object.is(value,baseline.current.values[key])));
@@ -472,18 +562,6 @@ REASONING_DEFAULT_CONFLICT: 'The provider default reasoning level is not support
472
562
  setPreview(null);setNotice(r.applied?'Synced and backed up the original settings. Start a new conversation to use the updated compaction policy.':'Settings already match. No changes needed.');await refresh();
473
563
  })},t('Back up and sync'))),
474
564
  dirty&&!conflict&&h('p',{className:'ss-muted'},t('Save preferences before checking for differences.')),
475
- h('div',{className:'ss-card','data-testid':'summary-target'},
476
- h('strong',null,t('Dynamic summary target')),
477
- h('p',null,t("Before each supported Strata summary, calculates a length target and appends one sentence to DSH's native summary prompt.")),
478
- summaryTargetCeiling!==null&&h('p',{'data-testid':'summary-target-ceiling'},t('Maximum target for this preview: '),format(summaryTargetCeiling),' tokens'),
479
- h('details',{className:'ss-details'},h('summary',null,t('How the target is calculated')),
480
- h('p',{className:'ss-muted'},t('Target = floor(min(context / 16, summary output limit / 2, selected content estimate / 2)).')),
481
- h('p',{className:'ss-muted'},t('Context uses the smaller of the verified Strata window and the existing DSH model window. The preview ceiling assumes the proposed budgets; existing conversation settings can make the actual target lower.')),
482
- h('p',{className:'ss-muted'},t('The actual target may be lower for shorter content. System prompts and tool schemas are excluded from the content estimate.')),
483
- h('p',{className:'ss-muted'},t('The original prompt, output cap and other settings stay unchanged. This is a writing target, not a guaranteed length or an optimal value.')),
484
- h('p',{className:'ss-muted'},t('Targets below 256 tokens leave the original prompt unchanged; the native summary still runs.')),
485
- h('p',{className:'ss-muted'},t('If the route or native summary format cannot be verified, DSH uses its original prompt.')),
486
- h('p',{className:'ss-muted'},t('Unpriced image/file attachments, cancellation or a disabled plugin preserve native request handling.')))),
487
565
  preview&&h('div',{className:'ss-card'},
488
566
  h('strong',null,'Strata Server'),h('p',null,h('code',null,preview.server?.model?.id)),
489
567
  h('div',{className:'ss-grid'},
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import { mkdir, readFile, writeFile } from 'node:fs/promises';
3
3
  import { dirname, join } from 'node:path';
4
4
  import z from '@deepseek-ai/schemastery';
5
5
  import { discoverStrata, planBudget, StrataSyncError } from './core.mjs';
6
- import { installSummaryGuidance } from './summary.mjs';
6
+ import { DEFAULT_SUMMARY_DIVISOR, DEFAULT_SUMMARY_TEMPLATE, validSummaryDivisor, validSummaryTemplate, installSummaryGuidance } from './summary.mjs';
7
7
 
8
8
  export const inject = ['configEditor', 'connection', 'agents', 'credentials', 'agentPresets'];
9
9
  export const Config = z.object({
@@ -11,6 +11,11 @@ export const Config = z.object({
11
11
  autoSync: z.boolean().default(false),
12
12
  followLoadedModel: z.boolean().default(true),
13
13
  outputTokens: z.number().step(1).min(1),
14
+ summaryGuidance: z.boolean().default(true),
15
+ summaryContextDivisor: z.number().min(1).max(Number.MAX_SAFE_INTEGER).default(DEFAULT_SUMMARY_DIVISOR),
16
+ summaryOutputDivisor: z.number().min(1).max(Number.MAX_SAFE_INTEGER).default(2),
17
+ summarySourceDivisor: z.number().min(1).max(Number.MAX_SAFE_INTEGER).default(2),
18
+ summaryPromptTemplate: z.string().default(DEFAULT_SUMMARY_TEMPLATE),
14
19
  });
15
20
 
16
21
  const PACKAGE = 'dsh-strata-sync';
@@ -139,7 +144,10 @@ export function apply(ctx, initialConfig) {
139
144
  }
140
145
  function publicSettings(value = settings) {
141
146
  return { providerId: value.providerId ?? '', autoSync: value.autoSync ?? false,
142
- followLoadedModel: value.followLoadedModel ?? true, outputTokens: value.outputTokens ?? null };
147
+ followLoadedModel: value.followLoadedModel ?? true, outputTokens: value.outputTokens ?? null,
148
+ summaryGuidance: value.summaryGuidance ?? true, summaryContextDivisor: value.summaryContextDivisor ?? DEFAULT_SUMMARY_DIVISOR,
149
+ summaryOutputDivisor: value.summaryOutputDivisor ?? 2, summarySourceDivisor: value.summarySourceDivisor ?? 2,
150
+ summaryPromptTemplate: value.summaryPromptTemplate ?? DEFAULT_SUMMARY_TEMPLATE };
143
151
  }
144
152
  function status() {
145
153
  refreshSettings();
@@ -152,7 +160,7 @@ export function apply(ctx, initialConfig) {
152
160
  return { id, baseURL };
153
161
  });
154
162
  } catch { /* The page remains usable when the required provider is absent. */ }
155
- return { ...publicSettings(), settingsRevision: hash(publicSettings()),
163
+ return { ...publicSettings(), defaultSummaryPromptTemplate: DEFAULT_SUMMARY_TEMPLATE, settingsRevision: hash(publicSettings()),
156
164
  busy: busy() || !!writeBarrier, providers, lastPreview, lastApplied, lastError };
157
165
  }
158
166
  async function credential(providerId, route) {
@@ -193,7 +201,11 @@ export function apply(ctx, initialConfig) {
193
201
  // A server/client mismatch must not enlarge the target past either capacity.
194
202
  return Math.min(discovered.model.contextWindow, model.contextWindow);
195
203
  }
196
- installSummaryGuidance(ctx, summaryContext);
204
+ installSummaryGuidance(ctx, summaryContext, () => {
205
+ ensureOpen();
206
+ refreshSettings();
207
+ return settings;
208
+ });
197
209
  async function preview(request = {}) {
198
210
  ensureOpen();
199
211
  payload(request, ['providerId', 'outputTokens']);
@@ -377,7 +389,7 @@ export function apply(ctx, initialConfig) {
377
389
  }
378
390
  async function configure(request) {
379
391
  ensureOpen();
380
- payload(request, ['providerId', 'autoSync', 'followLoadedModel', 'outputTokens', 'expectedRevision']);
392
+ payload(request, ['providerId', 'autoSync', 'followLoadedModel', 'outputTokens', 'summaryGuidance', 'summaryContextDivisor', 'summaryOutputDivisor', 'summarySourceDivisor', 'summaryPromptTemplate', 'expectedRevision']);
381
393
  refreshSettings();
382
394
  if (typeof request.expectedRevision !== 'string' || request.expectedRevision !== hash(publicSettings())) {
383
395
  refuse('stale-settings', 'Sync preferences changed. Reload the latest preferences before saving.');
@@ -388,11 +400,20 @@ export function apply(ctx, initialConfig) {
388
400
  if (typeof request.providerId !== 'string') refuse('invalid-request', 'Provider must be a string.');
389
401
  next.providerId = request.providerId;
390
402
  }
391
- for (const key of ['autoSync', 'followLoadedModel']) {
403
+ for (const key of ['autoSync', 'followLoadedModel', 'summaryGuidance']) {
392
404
  if (!own(request, key)) continue;
393
405
  if (typeof request[key] !== 'boolean') refuse('invalid-request', `${key} must be a boolean.`);
394
406
  next[key] = request[key];
395
407
  }
408
+ for (const key of ['summaryContextDivisor', 'summaryOutputDivisor', 'summarySourceDivisor']) {
409
+ if (!own(request, key)) continue;
410
+ if (!validSummaryDivisor(request[key])) refuse('invalid-budget', 'Summary divisors must be finite numbers from 1 to Number.MAX_SAFE_INTEGER.');
411
+ next[key] = request[key];
412
+ }
413
+ if (own(request, 'summaryPromptTemplate')) {
414
+ if (!validSummaryTemplate(request.summaryPromptTemplate)) refuse('invalid-request', 'Summary prompt must include {target} and be at most 2048 characters.');
415
+ next.summaryPromptTemplate = request.summaryPromptTemplate;
416
+ }
396
417
  if (own(request, 'outputTokens')) {
397
418
  if (request.outputTokens === null) delete next.outputTokens;
398
419
  else if (Number.isSafeInteger(request.outputTokens) && request.outputTokens > 0) next.outputTokens = request.outputTokens;
@@ -403,8 +424,7 @@ export function apply(ctx, initialConfig) {
403
424
  // Editing our own entry can dispose this plugin before edit() resolves.
404
425
  // Build the response while this Cordis scope is still alive; never read its
405
426
  // injected services after Loader replaces it.
406
- const response = { ...status(), providerId: next.providerId, autoSync: next.autoSync,
407
- followLoadedModel: next.followLoadedModel, outputTokens: next.outputTokens ?? null,
427
+ const response = { ...status(), ...publicSettings(next),
408
428
  settingsRevision: hash(publicSettings(next)), lastPreview: null, lastError: null };
409
429
  await backup();
410
430
  ensureOpen();
package/dist/summary.mjs CHANGED
@@ -1,27 +1,34 @@
1
- /** A one-sentence, per-request supplement to DSH's native summary instruction. */
1
+ /** An editable, per-request supplement to DSH's native summary instruction. */
2
2
  const NATIVE_OPENING = 'You are now acting as a compaction engine for this AI coding assistant.';
3
3
  const NATIVE_HEADINGS = ['Primary Request and Intent', 'Key Technical Concepts', 'Files and Code',
4
4
  'Errors and Fixes', 'Pending Jobs', 'Current Work', 'Next Step', 'Critical Context'];
5
5
  const GUIDANCE_PREFIX = 'Keep the entire checkpoint, including all required headings, within a target of ';
6
6
  const positive = value => Number.isSafeInteger(value) && value > 0;
7
+ export const validSummaryDivisor = value => typeof value === 'number' && Number.isFinite(value) && value >= 1 && value <= Number.MAX_SAFE_INTEGER;
8
+ export const DEFAULT_SUMMARY_TEMPLATE = `${GUIDANCE_PREFIX}{target} tokens by retaining only information essential to continue the remaining work, consolidating prior summaries, and removing redundant or obsolete details.`;
9
+ export const validSummaryTemplate = value => typeof value === 'string' && value.length <= 2048 && value.includes('{target}');
10
+ // Share identity across hot reloads/copies without adding fields to native requests.
11
+ const augmentedMessages = globalThis[Symbol.for('dsh-strata-sync/summary-augmented')] ??= new WeakSet();
7
12
  // Eight mandatory headings need enough space for a meaningful checkpoint. Do
8
13
  // not round a tiny target UP; leave native behavior intact instead.
9
14
  export const MIN_SUMMARY_TARGET = 256;
15
+ export const DEFAULT_SUMMARY_DIVISOR = 16;
10
16
 
11
- export function planSummaryTarget(contextWindow, summaryMaxTokens, sourceTokens) {
12
- if (![contextWindow, summaryMaxTokens, sourceTokens].every(positive)) return null;
13
- const target = Math.floor(Math.min(contextWindow / 16, summaryMaxTokens / 2, sourceTokens / 2));
17
+ export function planSummaryTarget(contextWindow, summaryMaxTokens, sourceTokens, divisor = DEFAULT_SUMMARY_DIVISOR, outputDivisor = 2, sourceDivisor = 2) {
18
+ if (![contextWindow, summaryMaxTokens, sourceTokens].every(positive) || ![divisor, outputDivisor, sourceDivisor].every(validSummaryDivisor)) return null;
19
+ const target = Math.floor(Math.min(contextWindow / divisor, summaryMaxTokens / outputDivisor, sourceTokens / sourceDivisor));
14
20
  return target >= MIN_SUMMARY_TARGET ? target : null;
15
21
  }
16
22
 
17
- export function summaryInstruction(target) {
23
+ export function summaryInstruction(target, template = DEFAULT_SUMMARY_TEMPLATE) {
18
24
  if (!positive(target)) throw new TypeError('A positive integer summary target is required.');
19
- return `${GUIDANCE_PREFIX}${target} tokens by retaining only information essential to continue the remaining work, consolidating prior summaries, and removing redundant or obsolete details.`;
25
+ if (!validSummaryTemplate(template)) throw new TypeError('Summary prompt must include {target} and be at most 2048 characters.');
26
+ return template.replaceAll('{target}', String(target));
20
27
  }
21
28
 
22
29
  /** Recognize the shipped native prompt, without storing or replacing its text. */
23
30
  export function nativeSummaryMessage(options) {
24
- if (options?.purpose !== 'compaction' || !Array.isArray(options.messages)) return null;
31
+ if (options?.purpose !== 'compaction' || !Array.isArray(options.messages) || augmentedMessages.has(options.messages)) return null;
25
32
  const message = options.messages.at(-1);
26
33
  if (message?.role !== 'user' || message.content?.length !== 1 || message.content[0]?.type !== 'text') return null;
27
34
  const text = message.content[0].text;
@@ -65,12 +72,28 @@ export function estimateSummarySource(messages) {
65
72
  * messages property for the lifetime of this stream; never mutate frozen
66
73
  * messages or recursively dispatch a second LLM request.
67
74
  */
68
- export function installSummaryGuidance(ctx, resolveContext) {
75
+ export function installSummaryGuidance(ctx, resolveContext, getSettings = () => ({})) {
69
76
  let closed = false;
77
+ function preferences() {
78
+ try {
79
+ const value = getSettings();
80
+ const enabled = value.summaryGuidance === undefined ? true : value.summaryGuidance;
81
+ const divisor = value.summaryContextDivisor === undefined ? DEFAULT_SUMMARY_DIVISOR : value.summaryContextDivisor;
82
+ const outputDivisor = value.summaryOutputDivisor === undefined ? 2 : value.summaryOutputDivisor;
83
+ const sourceDivisor = value.summarySourceDivisor === undefined ? 2 : value.summarySourceDivisor;
84
+ const template = value.summaryPromptTemplate === undefined ? DEFAULT_SUMMARY_TEMPLATE : value.summaryPromptTemplate;
85
+ return enabled === true && [divisor, outputDivisor, sourceDivisor].every(validSummaryDivisor) && validSummaryTemplate(template)
86
+ ? { divisor, outputDivisor, sourceDivisor, template } : null;
87
+ } catch { return null; }
88
+ }
70
89
  const remove = ctx.on('llm/stream', (options, next) => {
71
90
  const native = nativeSummaryMessage(options);
72
91
  const descriptor = options && Object.getOwnPropertyDescriptor(options, 'messages');
73
92
  if (closed || !native || !descriptor?.writable || options.signal?.aborted) return next();
93
+ // Disabled guidance must not perform even metadata discovery. Native DSH
94
+ // compaction and its request options still pass through unchanged.
95
+ const selected = preferences();
96
+ if (!selected) return next();
74
97
  const sourceTokens = estimateSummarySource(options.messages);
75
98
  if (!positive(sourceTokens) || !positive(options.maxTokens)) return next();
76
99
  return (async function* () {
@@ -80,14 +103,17 @@ export function installSummaryGuidance(ctx, resolveContext) {
80
103
  let contextWindow;
81
104
  try { contextWindow = await resolveContext(options); }
82
105
  catch { /* Metadata/compatibility failure must not break native inference. */ }
83
- const target = planSummaryTarget(contextWindow, options.maxTokens, sourceTokens);
84
- if (!closed && !options.signal?.aborted && target !== null && options.messages === original) {
85
- const last = { ...native, content: [{ ...native.content[0], text: `${native.content[0].text}\n- ${summaryInstruction(target)}` }] };
106
+ const target = planSummaryTarget(contextWindow, options.maxTokens, sourceTokens, selected.divisor, selected.outputDivisor, selected.sourceDivisor);
107
+ const current = preferences();
108
+ if (!closed && !options.signal?.aborted && current && Object.keys(selected).every(key => selected[key] === current[key]) && target !== null && options.messages === original) {
109
+ const last = { ...native, content: [{ ...native.content[0], text: `${native.content[0].text}\n- ${summaryInstruction(target, selected.template)}` }] };
86
110
  augmented = [...original.slice(0, -1), last];
111
+ augmentedMessages.add(augmented);
87
112
  options.messages = augmented;
88
113
  }
89
114
  yield* next();
90
115
  } finally {
116
+ if (augmented) augmentedMessages.delete(augmented);
91
117
  if (augmented && options.messages === augmented) options.messages = original;
92
118
  }
93
119
  })();
@@ -6,28 +6,46 @@
6
6
 
7
7
  UI 支持 English、简体中文、繁體中文。自动跟随 DSH 界面语言并实时更新,不提供独立语言菜单。DSH 的 `zh` 对应简体中文,`zh-Hant`、`zh-TW`、`zh-HK`、`zh-MO` 对应繁体中文;其他语言使用英文。服务器返回的未知诊断消息保留原文,方便排查。
8
8
 
9
- 0.8.1 的动态摘要引导会在每次支持的 Strata 摘要请求前计算长度目标,并在 DSH 原生摘要提示词后**只补充一句**。保留原提示词、输出硬上限与其他设置;设置页解释公式,并区分写作目标与输出硬上限。已有同步保护、启动行为、能力验证及备份机制保持不变。
9
+ 0.10.0 精简摘要设置,并让补充提示词可直接编辑。可保留默认模板,或使用动态 `{target}` 自定义内容;DSH 原生提示词与输出硬上限保持不变。
10
10
 
11
11
  ## 功能
12
12
 
13
13
  ### 动态摘要引导
14
14
 
15
- 对于支持的 Strata 路由,插件在每次 DSH 原生摘要请求前重新计算写作目标:
15
+ 精简的“**动态摘要目标**”区域包含启用开关、上下文除数和可编辑的“**摘要补充提示词**”;展开“**计算方式与限制**”可调整摘要输出与原文长度的除数,并查看公式与限制。功能默认开启,关闭后仍保留 DSH 原生摘要与自动压缩。
16
+
17
+ 编辑框直接显示实际附加到原生提示词的模板,默认内容如下:
16
18
 
17
19
  ```text
18
- 上下文 = min(已确认的 Strata 上下文, DSH 已有模型上下文)
19
- 目标 = floor(min(上下文 / 16,
20
- 本次摘要实际 maxTokens / 2,
21
- 待替换内容的估算 tokens / 2))
20
+ Keep the entire checkpoint, including all required headings, within a target of {target} tokens by retaining only information essential to continue the remaining work, consolidating prior summaries, and removing redundant or obsolete details.
22
21
  ```
23
22
 
24
- 内容估算只包含这次要替换的对话片段,以及片段内已有的摘要;不包含系统提示词和工具定义。插件在每次摘要前通过只读查询核对 Strata 当前加载模型与上下文,并读取本次摘要请求的实际输出硬上限。上下文取服务器已确认值与 DSH 已有模型设置的较小值,不会因为服务器窗口较大而悄悄扩大较小的客户端预算。目标不是启动 DSH 或同步设置时固定下来的。
23
+ 模板须保留字面的 `{target}`,每次摘要时会将所有出现位置替换为本次计算的目标。长度最多 2,048 个字符;只做文本替换,不执行代码。“**恢复默认提示词**”会将编辑框恢复为原本的英文模板,修改或恢复后点击“**保存偏好**”才会应用。切换界面语言不会翻译或覆盖已保存的模板。这里只编辑补充内容,不修改 DSH 原生摘要指令。
24
+
25
+ | 插件偏好 | 默认值 | 含义 |
26
+ |---|---:|---|
27
+ | `summaryGuidance` | `true` | 对支持的摘要请求附加补充提示词。 |
28
+ | `summaryContextDivisor` | `16` | 以上下文除以此值。 |
29
+ | `summaryOutputDivisor` | `2` | 以本次摘要输出硬上限除以此值。 |
30
+ | `summarySourceDivisor` | `2` | 以原文估算长度除以此值。 |
31
+ | `summaryPromptTemplate` | 上方英文模板 | 替换 `{target}` 后附加的补充内容。 |
32
+
33
+ 三项除数都接受至少为 1 的有限数值,也支持 1.5、2.5 等小数。除数越大,对应限制越小;最终取三项限制的最小值并向下取整,不会改变实际输出硬上限。
34
+
35
+ 每次支持的摘要请求都会重新计算:
36
+
37
+ ```text
38
+ 上下文 = min(已确认的 Strata 上下文, DSH 已有模型上下文)
39
+ 目标 = floor(min(上下文 / summaryContextDivisor,
40
+ 本次摘要实际 maxTokens / summaryOutputDivisor,
41
+ 待替换内容的估算 tokens / summarySourceDivisor))
42
+ ```
25
43
 
26
- 摘要硬上限为 4,096、原文足够长时,32K 上下文对应 **2,048 tokens**,64K 对应 **2,048 tokens**;内容较短时目标会降低。**设置 → dsh-strata-sync** 显示公式与本次预览的目标上限,无法提前得知未来摘要的实际目标。
44
+ 原文估算涵盖这次要替换的对话片段及其中的旧摘要,不含系统提示词和工具定义。只读查询核对加载模型与上下文,实际输出硬上限来自当前请求。32K 上下文、4,096-token 摘要上限且原文足够长时,上下文除数 32、16、8 分别得到 1,024、2,048、2,048 tokens。UI 预览显示目标上限,不是未来请求的实际目标。
27
45
 
28
- 插件只补充一句英文,要求模型按目标长度完成摘要、保留接续未完成工作所需的信息、整合旧摘要,并删除重复或过时细节。原生提示词、八个章节和已有请求参数保持不变。这是启发式写作目标,**不是新的输出硬上限、最佳值,也不能保证不截断**;不增加自动重试流程或额外推理请求。
46
+ 保存这些控件只修改插件偏好,不会同步模型预算或修改 Strata Server 设置。DSH 原生提示词、八个章节和请求参数保持不变。默认补充内容只有一句,自定义模板则按输入内容附加。目标是写作指令,**不是新的输出硬上限,也不能保证不截断**;不增加重试流程或额外推理请求。
29
47
 
30
- 无法确认路由、必要信息或原生摘要格式,或无法安全估算图片/文件附件的 token 用量时,原请求不变并交给 DSH 处理。请求已取消或插件已禁用时也保留原生处理。计算目标小于 256 tokens 时不补充句子,避免给八个章节强加过小的目标;不取消原生摘要,也不以空摘要替换原文。禁用或移除插件后不再补充这句话,不会改写已保存的对话历史。
48
+ 无法确认路由或信息、原生提示词格式未知、附件用量无法估算、模板无效、请求取消、功能关闭或目标低于 256 tokens 时,保留原生请求。移除插件会停止附加内容,不改写已保存的对话历史。
31
49
 
32
50
  ### 模型能力自动同步(0.4.0)
33
51
 
@@ -66,7 +84,7 @@ dsh plugin --profile desktop add dsh-strata-sync --registry=https://registry.npm
66
84
 
67
85
  完成后重新打开 DSH。首次安装会自动选中 bundle;已安装但禁用的 bundle 需在插件管理器重新启用。
68
86
 
69
- 要安装**包含动态摘要引导的 0.8.1**,请使用 Node.js 22 或更新版本从此源码构建,并在最后的安装命令前完成任务、完全退出 DSH Desktop:
87
+ 要从源码安装**包含摘要提示词编辑器的 0.10.0**,请使用 Node.js 22 或更新版本构建,并在最后的安装命令前完成任务、完全退出 DSH Desktop:
70
88
 
71
89
  ```sh
72
90
  git clone https://github.com/ethan741205/dsh-strata-sync.git
@@ -74,7 +92,7 @@ cd dsh-strata-sync
74
92
  npm ci
75
93
  npm test
76
94
  npm pack
77
- dsh plugin --profile desktop add ./dsh-strata-sync-0.8.1.tgz
95
+ dsh plugin --profile desktop add ./dsh-strata-sync-0.10.0.tgz
78
96
  ```
79
97
 
80
98
  执行最后一行前同样需要完全退出 DSH Desktop,安装完成后重新打开。在其他目录执行时,请使用安装包的绝对路径。
@@ -87,6 +105,8 @@ dsh plugin --profile desktop add ./dsh-strata-sync-0.8.1.tgz
87
105
  4. 如需自动同步,勾选“启动 DSH 时自动同步”并保存,下次启动生效。原有 `autoSync: true` 会沿用为启动同步。
88
106
  5. 开启新对话,使用更新后的 Agent 预设。
89
107
 
108
+ “动态摘要目标”控件独立于模型同步。保持“启用摘要提示补充”开启、使用默认模板及 16/2/2 的除数,即沿用先前行为,也可修改后保存偏好。关闭此开关不会禁用 DSH 自身的压缩功能。
109
+
90
110
  无需把 API key 复制到此插件;认证问题请在 DSH 模型设置处理。
91
111
 
92
112
  ## 兼容性
@@ -135,6 +155,10 @@ ConfigEditor 负责单项原子写入与热加载;跨多项设置采用补偿
135
155
 
136
156
  ## 开发与版本
137
157
 
158
+ 0.10.0 加入补充提示词编辑器、动态 `{target}` 与恢复默认功能,精简三语摘要区域,新增高级输出/原文除数与三项除数的小数支持,并沿用备份与版本冲突保护。
159
+
160
+ 0.9.0 加入默认开启的摘要提示补充开关及可调整的上下文除数(默认 16),沿用偏好保存与版本冲突保护,并同步三语界面和文档。保存这两个控件不改变原生摘要指令、输出硬上限、其他预算或服务器设置。
161
+
138
162
  0.5.0 移除独立语言菜单与产品声明,改为跟随 DSH 语言及启动时单次同步。0.3.0 首次加入三语 UI,0.2.0 将 UI 移入原生设置。
139
163
 
140
164
  `src/plugin.mjs` 为宿主插件,`src/client.js` 为原生 ModuleLoader/React UI,`src/core.mjs` 负责信息读取与纯预算计算,`src/summary.mjs` 提供原生摘要请求中间件。执行 `npm test`、`npm run build`、`npm pack --dry-run` 验证。安装包只包含原创插件代码,不包含私人应用程序源码、认证或对话。
@@ -6,28 +6,46 @@
6
6
 
7
7
  UI 支援 English、简体中文、繁體中文。自動跟隨 DSH 介面語言並即時更新,不提供獨立語言選單。DSH 的 `zh` 對應簡體中文,`zh-Hant`、`zh-TW`、`zh-HK`、`zh-MO` 對應繁體中文;其他語言使用英文。伺服器傳回的未知診斷訊息保留原文,以利排查。
8
8
 
9
- 0.8.1 的動態摘要引導會在每次支援的 Strata 摘要請求前計算長度目標,並在 DSH 原生摘要提示詞後**只補充一句**。保留原提示詞、輸出硬上限與其他設定;設定頁解釋公式,並區分寫作目標與輸出硬上限。既有同步保護、啟動行為、能力驗證及備份機制保持不變。
9
+ 0.10.0 精簡摘要設定,並讓補充提示詞可直接編輯。可保留預設範本,或使用動態 `{target}` 自訂內容;DSH 原生提示詞與輸出硬上限保持不變。
10
10
 
11
11
  ## 功能
12
12
 
13
13
  ### 動態摘要引導
14
14
 
15
- 對於支援的 Strata 路由,外掛在每次 DSH 原生摘要請求前重新計算寫作目標:
15
+ 精簡的「**動態摘要目標**」區塊包含啟用開關、上下文除數和可編輯的「**摘要補充提示詞**」;展開「**計算方式與限制**」可調整摘要輸出與原文長度的除數,並查看公式與限制。功能預設開啟,關閉後仍保留 DSH 原生摘要與自動壓縮。
16
+
17
+ 編輯框直接顯示實際附加至原生提示詞的範本,預設內容如下:
16
18
 
17
19
  ```text
18
- 上下文 = min(已確認的 Strata 上下文, DSH 既有模型上下文)
19
- 目標 = floor(min(上下文 / 16,
20
- 本次摘要實際 maxTokens / 2,
21
- 待替換內容的估算 tokens / 2))
20
+ Keep the entire checkpoint, including all required headings, within a target of {target} tokens by retaining only information essential to continue the remaining work, consolidating prior summaries, and removing redundant or obsolete details.
22
21
  ```
23
22
 
24
- 內容估算只包含這次要替換的對話片段,以及片段內既有的摘要;不包含系統提示詞和工具定義。外掛在每次摘要前透過唯讀查詢核對 Strata 目前載入模型與上下文,並讀取本次摘要請求的實際輸出硬上限。上下文取伺服器已確認值與 DSH 既有模型設定的較小值,不會因為伺服器視窗較大而偷偷擴大較小的客戶端預算。目標不是啟動 DSH 或同步設定時固定下來的。
23
+ 範本須保留字面的 `{target}`,每次摘要時會將所有出現位置替換為當次計算的目標。長度最多 2,048 個字元;只做文字替換,不執行程式碼。「**還原預設提示詞**」會將編輯框恢復為原本的英文範本,修改或還原後按「**儲存偏好**」才會套用。切換介面語言不會翻譯或覆寫已儲存的範本。這裡只編輯補充內容,不修改 DSH 原生摘要指令。
24
+
25
+ | 外掛偏好 | 預設值 | 含義 |
26
+ |---|---:|---|
27
+ | `summaryGuidance` | `true` | 對支援的摘要請求附加補充提示詞。 |
28
+ | `summaryContextDivisor` | `16` | 以上下文除以此值。 |
29
+ | `summaryOutputDivisor` | `2` | 以當次摘要輸出硬上限除以此值。 |
30
+ | `summarySourceDivisor` | `2` | 以原文估算長度除以此值。 |
31
+ | `summaryPromptTemplate` | 上方英文範本 | 替換 `{target}` 後附加的補充內容。 |
32
+
33
+ 三項除數都接受至少為 1 的有限數值,也支援 1.5、2.5 等小數。除數越大,對應限制越小;最終取三項限制的最小值並向下取整,不會改變實際輸出硬上限。
34
+
35
+ 每次支援的摘要請求都會重新計算:
36
+
37
+ ```text
38
+ 上下文 = min(已確認的 Strata 上下文, DSH 既有模型上下文)
39
+ 目標 = floor(min(上下文 / summaryContextDivisor,
40
+ 本次摘要實際 maxTokens / summaryOutputDivisor,
41
+ 待替換內容的估算 tokens / summarySourceDivisor))
42
+ ```
25
43
 
26
- 摘要硬上限為 4,096、原文足夠長時,32K 上下文對應 **2,048 tokens**,64K 對應 **2,048 tokens**;內容較短時目標會降低。**設定 → dsh-strata-sync** 顯示公式與本次預覽的目標上限,無法提前得知未來摘要的實際目標。
44
+ 原文估算涵蓋這次要替換的對話片段與其中的舊摘要,不含系統提示詞和工具定義。唯讀查詢核對載入模型與上下文,實際輸出硬上限來自當次請求。32K 上下文、4,096-token 摘要上限且原文足夠長時,上下文除數 32、16、8 分別得到 1,024、2,048、2,048 tokens。UI 預覽顯示目標上限,不是未來請求的實際目標。
27
45
 
28
- 外掛只補充一句英文,要求模型依目標長度完成摘要、保留接續未完成工作所需的資訊、整合舊摘要,並刪除重複或過時細節。原生提示詞、八個章節和既有請求參數保持不變。這是啟發式寫作目標,**不是新的輸出硬上限、最佳值,也不能保證不截斷**;不增加自動重試流程或額外推論請求。
46
+ 儲存這些控制項只更改外掛偏好,不會同步模型預算或修改 Strata Server 設定。DSH 原生提示詞、八個章節和請求參數保持不變。預設補充內容只有一句,自訂範本則按輸入內容附加。目標是寫作指令,**不是新的輸出硬上限,也不能保證不截斷**;不增加重試流程或額外推論請求。
29
47
 
30
- 無法確認路由、必要資訊或原生摘要格式,或無法安全估算圖片/檔案附件的 token 用量時,原請求不變並交由 DSH 處理。請求已取消或外掛已停用時也保留原生處理。計算目標小於 256 tokens 時不補充句子,避免給八個章節強加過小的目標;不取消原生摘要,也不以空摘要替換原文。停用或移除外掛後不再補充這句話,不會改寫已儲存的對話歷史。
48
+ 無法確認路由或資訊、原生提示詞格式未知、附件用量無法估算、範本無效、請求取消、功能關閉或目標低於 256 tokens 時,保留原生請求。移除外掛會停止附加內容,不重寫已儲存的對話歷史。
31
49
 
32
50
  ### 模型能力自動同步(0.4.0)
33
51
 
@@ -66,7 +84,7 @@ dsh plugin --profile desktop add dsh-strata-sync --registry=https://registry.npm
66
84
 
67
85
  完成後重新開啟 DSH。首次安裝會自動選取 bundle;已安裝但停用的 bundle 需在外掛管理員重新啟用。
68
86
 
69
- 要安裝**包含動態摘要引導的 0.8.1**,請使用 Node.js 22 或更新版本從此原始碼建置,並在最後的安裝命令前完成工作、完全結束 DSH Desktop:
87
+ 要從原始碼安裝**包含摘要提示詞編輯器的 0.10.0**,請使用 Node.js 22 或更新版本建置,並在最後的安裝命令前完成工作、完全結束 DSH Desktop:
70
88
 
71
89
  ```sh
72
90
  git clone https://github.com/ethan741205/dsh-strata-sync.git
@@ -74,7 +92,7 @@ cd dsh-strata-sync
74
92
  npm ci
75
93
  npm test
76
94
  npm pack
77
- dsh plugin --profile desktop add ./dsh-strata-sync-0.8.1.tgz
95
+ dsh plugin --profile desktop add ./dsh-strata-sync-0.10.0.tgz
78
96
  ```
79
97
 
80
98
  執行最後一行前同樣需要完全結束 DSH Desktop,安裝完成後重新開啟。在其他目錄執行時,請使用套件的絕對路徑。
@@ -87,6 +105,8 @@ dsh plugin --profile desktop add ./dsh-strata-sync-0.8.1.tgz
87
105
  4. 如需自動同步,勾選「啟動 DSH 時自動同步」並儲存,下次啟動生效。原有 `autoSync: true` 會沿用為啟動同步。
88
106
  5. 開啟新對話,使用更新後的 Agent 範本。
89
107
 
108
+ 「動態摘要目標」控制項獨立於模型同步。保持「啟用摘要提示補充」開啟、使用預設範本及 16/2/2 的除數,即沿用先前行為,也可修改後儲存偏好。關閉此開關不會停用 DSH 自身的壓縮功能。
109
+
90
110
  不需要把 API key 複製到此外掛;認證問題請在 DSH 模型設定處理。
91
111
 
92
112
  ## 相容性
@@ -135,6 +155,10 @@ ConfigEditor 負責個別原子寫入與熱載入;跨多項設定採補償回
135
155
 
136
156
  ## 開發與版本
137
157
 
158
+ 0.10.0 加入補充提示詞編輯器、動態 `{target}` 與還原預設功能,精簡三語摘要區塊,新增進階輸出/原文除數與三項除數的小數支援,並沿用備份與版本衝突保護。
159
+
160
+ 0.9.0 加入預設開啟的摘要提示補充開關及可調整的上下文除數(預設 16),沿用偏好儲存與版本衝突保護,並同步三語介面和文件。儲存這兩個控制項不改變原生摘要指令、輸出硬上限、其他預算或伺服器設定。
161
+
138
162
  0.5.0 移除獨立語言選單與產品聲明,改為跟隨 DSH 語言及啟動時單次同步。0.3.0 首次加入三語 UI,0.2.0 將 UI 移入原生設定。
139
163
 
140
164
  `src/plugin.mjs` 為宿主外掛,`src/client.js` 為原生 ModuleLoader/React UI,`src/core.mjs` 負責資訊讀取與純預算計算,`src/summary.mjs` 提供原生摘要請求中介層。執行 `npm test`、`npm run build`、`npm pack --dry-run` 驗證。套件只包含原創外掛程式,不包含私人應用程式原始碼、認證或對話。
@@ -2,6 +2,20 @@
2
2
 
3
3
  Validation date: 2026-10-30 (Asia/Taipei).
4
4
 
5
+ ## Editable summary prompt and fractional divisors 0.10.0
6
+
7
+ - The compact settings card displays the exact editable supplement; detailed limits and the output/source divisors are inside a collapsed disclosure. All three locales preserve the saved prompt verbatim. Restore-default changes the draft until preferences are saved.
8
+ - `summaryPromptTemplate` requires literal `{target}` and at most 2,048 characters. Substitution is plain text, never code evaluation; the native instruction remains intact. All three divisors accept finite numbers from 1 to `Number.MAX_SAFE_INTEGER`, including fractions. Defaults remain 16/2/2; the final target is floored.
9
+ - All **151 automated tests pass**, including 38 UI tests and the package verifier. Checks cover saved custom prompts, restore-default, locale stability, fractional bounds, invalid input, stale preferences during discovery and saving, and custom-text deduplication through the native waterfall. Saving only these preferences leaves model and compaction settings unchanged.
10
+ - A local probe exercised the installed DSH 0.2.1-rc.3 native summary engine with a custom supplement and divisors 8.5/1.5/2.5. At 32K with a 4,096 output cap and sufficient source, the appended target was 2,730 tokens. Defaults, off state, native prompt preservation and unload also passed. Synthetic adapter output was used; no inference requests or real-model truncation claims are involved.
11
+
12
+ ## Summary guidance controls 0.9.0
13
+
14
+ - Adds persisted `summaryGuidance` (default `true`) and `summaryContextDivisor` (default `16`). The divisor must be a positive safe integer. Saving these controls uses the existing backup, idle and revision checks and leaves model settings and compaction budgets unchanged.
15
+ - Backend tests cover native schema defaults and validation, persisted controls across reloads, disabling without metadata requests, re-enabling with a custom divisor, unchanged native compaction, source/output bounds, and changes during discovery. The three-language UI tests cover default-on controls, saving, invalid input, external changes/conflicts, custom previews, disabled state and below-256 fallback messages.
16
+ - A local probe exercised the installed DSH 0.2.1-rc.3 `BasicCompactionEngine.summarize()` through its actual Cordis waterfall. At 32K with a 4,096 summary cap, the default divisor produced 2,048, divisor 32 produced 1,024, disabled guidance preserved the native prompt without discovery, and divisor 8 remained capped at 2,048. Original prompt content, input, output cap, tools and configuration were preserved; unloading restored native behavior. Responses were synthetic: no inference requests were sent.
17
+ - All **142 automated tests pass**, including the package verifier and 33 settings UI tests. These checks validate configuration and integration, not a measured reduction in real-model truncation or compaction frequency.
18
+
5
19
  ## Package rename and target ratio 0.8.1
6
20
 
7
21
  - Package identity, native bundle and client module now use `dsh-strata-sync`; the GitHub repository is unchanged. Existing scoped installations must be migrated, not loaded alongside the renamed plugin.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-strata-sync",
3
- "version": "0.8.1",
3
+ "version": "0.10.0",
4
4
  "description": "Native DSH settings UI for synchronizing Strata Server models, context and compaction budgets",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",