dsh-strata-sync 0.9.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,36 +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.9.0 adds a **default-on summary guidance switch** and an adjustable **context divisor** to the settings page. When enabled, the plugin 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. Existing synchronization safeguards, startup behavior, capability verification and backups are retained.
9
+ Version 0.10.1 shortens the summary settings explanation. Keep the default prompt 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
- Summary guidance is enabled by default. 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
- divisor = summaryContextDivisor (default: 16)
20
- target = floor(min(context / divisor,
21
- actual summary maxTokens / 2,
22
- 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.
23
21
  ```
24
22
 
25
- 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.
26
-
27
- With the default divisor of 16, 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.
28
-
29
- In the **Dynamic summary target** card, toggle **Enable summary guidance** and set **Context divisor (context ÷ N)** to a positive safe integer. A smaller divisor allows a larger target; the summary-output and source-content bounds still apply. For example, with 32K context, a 4,096-token summary cap and enough content, divisors 32, 16 and 8 produce targets of 1,024, 2,048 and 2,048 tokens respectively. Click **Save preferences** to apply your choice to subsequent supported summary requests. Saving these controls does not synchronize model budgets or change Strata Server settings.
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.
30
24
 
31
25
  | Plugin preference | Default | Meaning |
32
26
  |---|---:|---|
33
- | `summaryGuidance` | `true` | Append the length-guidance sentence when the request is supported. Turning it off preserves DSH's native summary and automatic compaction. |
34
- | `summaryContextDivisor` | `16` | Divide the verified context by this positive safe integer when calculating the target. |
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}`. |
35
32
 
36
- 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.
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.
37
34
 
38
- 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, disabled guidance 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 the guidance or removing this plugin removes the supplemental sentence; it does not rewrite saved conversation history.
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
+ ```
43
+
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.
45
+
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.
47
+
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.
39
49
 
40
50
  ### Automatic model capabilities (0.4.0)
41
51
 
@@ -85,7 +95,7 @@ dsh plugin --profile desktop add dsh-strata-sync --registry=https://registry.npm
85
95
 
86
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.
87
97
 
88
- To install **0.9.0 with summary guidance controls** from source, use Node.js 22 or newer, finish active tasks and fully quit DSH Desktop before the final installation command:
98
+ To install **0.10.1 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:
89
99
 
90
100
  ```sh
91
101
  git clone https://github.com/ethan741205/dsh-strata-sync.git
@@ -93,7 +103,7 @@ cd dsh-strata-sync
93
103
  npm ci
94
104
  npm test
95
105
  npm pack
96
- dsh plugin --profile desktop add ./dsh-strata-sync-0.9.0.tgz
106
+ dsh plugin --profile desktop add ./dsh-strata-sync-0.10.1.tgz
97
107
  ```
98
108
 
99
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.
@@ -106,10 +116,20 @@ Then open **Settings → dsh-strata-sync**:
106
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.
107
117
  5. Start a new conversation to use the new Agent preset revision.
108
118
 
109
- The **Dynamic summary target** card is independent of model synchronization. Leave its guidance enabled and divisor at 16 to retain the previous behavior, or change these preferences and save. Turning this switch off does not disable DSH's own compaction.
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.
110
120
 
111
121
  No API key needs to be copied into this plugin. Set up or repair credentials in DSH Models settings.
112
122
 
123
+ ## Changes in 0.10.1
124
+
125
+ - Shortens the summary UI explanation to the formula and native-summary fallback. Calculation and runtime behavior are unchanged; full details remain in this README.
126
+
127
+ ## Changes in 0.10.0
128
+
129
+ - Makes the actual supplemental prompt editable, with a dynamic `{target}` placeholder and Restore default prompt action.
130
+ - 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.
131
+ - Validates and saves prompt edits through existing backup and revision safeguards without changing native instructions or other budgets.
132
+
113
133
  ## Changes in 0.9.0
114
134
 
115
135
  - Adds a default-on switch for the supplemental summary guidance and an adjustable context divisor, defaulting to 16.
package/dist/client.js CHANGED
@@ -87,6 +87,26 @@ 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": "目标如何计算"
@@ -95,9 +115,13 @@ window.__ModuleLoader__.load({
95
115
  "zh-Hant": "啟用後,在每次支援的 Strata 摘要請求前,計算長度目標,並在 DSH 原生摘要提示詞後補充一句。",
96
116
  "zh-Hans": "启用后,在每次支持的 Strata 摘要请求前,计算长度目标,并在 DSH 原生摘要提示词后补充一句。"
97
117
  },
98
- "Target = floor(min(context / N, summary output limit / 2, selected content estimate / 2)).": {
99
- "zh-Hant": "目標 = floor(min(上下文 / N, 摘要輸出上限 / 2, 待壓縮內容估算 / 2))。",
100
- "zh-Hans": "目标 = floor(min(上下文 / N, 摘要输出上限 / 2, 待压缩内容估算 / 2))。"
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
+ "When guidance cannot be applied, DSH keeps its native summary behavior.": {
123
+ "zh-Hant": "無法套用時,沿用 DSH 原生摘要。",
124
+ "zh-Hans": "无法应用时,沿用 DSH 原生摘要。"
101
125
  },
102
126
  "Enable summary guidance": {
103
127
  "zh-Hant": "啟用摘要提示補充",
@@ -111,13 +135,21 @@ window.__ModuleLoader__.load({
111
135
  "zh-Hant": "上下文除數(上下文 ÷ N)",
112
136
  "zh-Hans": "上下文除数(上下文 ÷ N)"
113
137
  },
114
- "Default: 16. A smaller divisor allows a larger target, still bounded by half the summary output limit and half the selected content estimate.": {
115
- "zh-Hant": "預設為 16。除數越小,允許的目標越大,但仍受摘要輸出上限的一半與待壓縮內容估算的一半限制。",
116
- "zh-Hans": "默认为 16。除数越小,允许的目标越大,但仍受摘要输出上限的一半与待压缩内容估算的一半限制。"
138
+ "Summary output divisor (output limit ÷ O)": {
139
+ "zh-Hant": "摘要輸出除數(輸出上限 ÷ O)",
140
+ "zh-Hans": "摘要输出除数(输出上限 ÷ O)"
141
+ },
142
+ "Source length divisor (source estimate ÷ S)": {
143
+ "zh-Hant": "原文長度除數(原文估算 ÷ S)",
144
+ "zh-Hans": "原文长度除数(原文估算 ÷ S)"
145
+ },
146
+ "Defaults: N = 16, O = 2, S = 2. Smaller divisors allow longer targets; the smallest of the three bounds wins. Decimals are supported.": {
147
+ "zh-Hant": "預設 N = 16、O = 2、S = 2。除數越小,目標可越長;最後取三項限制的最小值。支援小數。",
148
+ "zh-Hans": "默认 N = 16、O = 2、S = 2。除数越小,目标可越长;最后取三项限制的最小值。支持小数。"
117
149
  },
118
- "Enter a positive whole-number divisor.": {
119
- "zh-Hant": "請輸入正整數除數。",
120
- "zh-Hans": "请输入正整数除数。"
150
+ "Enter a finite divisor of at least 1; decimals are allowed.": {
151
+ "zh-Hant": "請輸入大於或等於 1 的有限除數,可使用小數。",
152
+ "zh-Hans": "请输入大于或等于 1 的有限除数,可使用小数。"
121
153
  },
122
154
  "This preview is below the 256-token minimum. The native summary prompt remains unchanged.": {
123
155
  "zh-Hant": "本次預覽低於 256 tokens 的最低目標,將保留原生摘要提示詞。",
@@ -371,7 +403,7 @@ window.__ModuleLoader__.load({
371
403
  model: 'Model', modelId: 'Model', defaultModel: 'Default model',
372
404
  thresholdRatio: 'Compaction ratio ceiling', retainRatio: 'Recent conversation retention ratio',
373
405
  };
374
- 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}}`;
406
+ 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}}`;
375
407
  const fmt = (v) => v == null ? 'Not set' : typeof v === 'number' ? v.toLocaleString('en-US') : typeof v === 'object' ? JSON.stringify(v) : String(v);
376
408
  const warningText = {
377
409
  INPUT_UNCONFIRMED: 'Input types are missing or unsupported in server metadata. Existing input settings are preserved.',
@@ -399,19 +431,20 @@ REASONING_DEFAULT_CONFLICT: 'The provider default reasoning level is not support
399
431
  };
400
432
  const diagnostic = (message) => translations[message] ? t(message) : language === 'en' ? message : t('Server diagnostic: ') + message;
401
433
  const [state,setState] = React.useState(null), [preview,setPreview] = React.useState(null), [result,setResult] = React.useState(null);
402
- const [form,setForm] = React.useState({providerId:'',output:'',manual:false,auto:false,follow:true,summaryGuidance:true,summaryDivisor:'16'});
434
+ const [form,setForm] = React.useState({providerId:'',output:'',manual:false,auto:false,follow:true,summaryGuidance:true,summaryDivisor:'16',summaryOutputDivisor:'2',summarySourceDivisor:'2',summaryPrompt:''});
403
435
  const [pending,setPending] = React.useState(false), [error,setError] = React.useState(''), [loadError,setLoadError] = React.useState('');
404
436
  const [notice,setNotice] = React.useState(''), [conflict,setConflict] = React.useState(false);
405
437
  const formRef=React.useRef(form), baseline=React.useRef(null), requestSeq=React.useRef(0), alive=React.useRef(true);
406
- 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:f.summaryDivisor.trim()===''?NaN:Number(f.summaryDivisor)});
407
- 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});
438
+ const numberValue = value => value.trim()===''?NaN:Number(value);
439
+ 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});
440
+ 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??''});
408
441
  const equal = (a,b) => Object.keys(a).every(key=>Object.is(a[key],b[key]));
409
442
  const acceptStatus = React.useCallback((s,force=false) => {
410
443
  if (!alive.current) return;
411
444
  const next=preferences(s), old=baseline.current;
412
445
  const untouched=!old || equal(values(formRef.current),old.values);
413
446
  if (force || untouched) {
414
- 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)};
447
+ 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};
415
448
  formRef.current=nextForm; setForm(nextForm); setConflict(false);
416
449
  baseline.current={values:next,revision:s.settingsRevision};
417
450
  } else if (old.revision!==s.settingsRevision || !equal(old.values,next)) {
@@ -443,13 +476,15 @@ REASONING_DEFAULT_CONFLICT: 'The provider default reasoning level is not support
443
476
  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.');
444
477
  } finally { if(alive.current)setPending(false); }
445
478
  }
446
- const draft=values(form), validDivisor=Number.isSafeInteger(draft.summaryContextDivisor)&&draft.summaryContextDivisor>0;
447
- const valid=validDivisor&&(draft.outputTokens===null || Number.isSafeInteger(draft.outputTokens)&&draft.outputTokens>0);
479
+ const draft=values(form), isDivisor=value=>Number.isFinite(value)&&value>=1&&value<=Number.MAX_SAFE_INTEGER;
480
+ const validDivisor=[draft.summaryContextDivisor,draft.summaryOutputDivisor,draft.summarySourceDivisor].every(isDivisor);
481
+ const validPrompt=typeof draft.summaryPromptTemplate==='string'&&draft.summaryPromptTemplate.length>0&&draft.summaryPromptTemplate.length<=2048&&draft.summaryPromptTemplate.includes('{target}');
482
+ const valid=validDivisor&&validPrompt&&(draft.outputTokens===null || Number.isSafeInteger(draft.outputTokens)&&draft.outputTokens>0);
448
483
  const dirty=!!baseline.current&&!equal(draft,baseline.current.values);
449
484
  const displayedResult=!result || state?.lastApplied?.at>result.at ? state?.lastApplied : result;
450
485
  const activeBudget=preview?.budget;
451
486
  const summaryTargetCeiling=form.summaryGuidance&&validDivisor&&Number.isSafeInteger(activeBudget?.contextWindow)&&activeBudget.contextWindow>0&&Number.isSafeInteger(activeBudget?.summaryMaxTokens)&&activeBudget.summaryMaxTokens>0
452
- ? Math.floor(Math.min(activeBudget.contextWindow/draft.summaryContextDivisor,activeBudget.summaryMaxTokens/2)) : null;
487
+ ? Math.floor(Math.min(activeBudget.contextWindow/draft.summaryContextDivisor,activeBudget.summaryMaxTokens/draft.summaryOutputDivisor)) : null;
453
488
  const outputSource=(budget)=>t(({server:'Strata Server default',manual:'Manual override',calculated:'Automatic calculation'})[budget?.outputSource]||'Automatic calculation');
454
489
  const preserved=(view)=>{
455
490
  const status=view?.capabilityStatus;
@@ -494,24 +529,22 @@ REASONING_DEFAULT_CONFLICT: 'The provider default reasoning level is not support
494
529
  h('div',{className:'ss-card','data-testid':'summary-target'},
495
530
  h('strong',null,t('Dynamic summary target')),
496
531
  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')),
497
- h('p',{className:'ss-muted'},t("Enabled by default. Turning this off keeps DSH's native summary and automatic compaction behavior.")),
498
- 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':!validDivisor,min:1,max:Number.MAX_SAFE_INTEGER,step:1,value:form.summaryDivisor,onChange:e=>edit({summaryDivisor:e.target.value}),disabled:pending||!state})),
499
- state&&!validDivisor&&h('p',{className:'ss-status ss-error',role:'alert'},t('Enter a positive whole-number divisor.')),
500
- h('p',{className:'ss-muted'},t('Default: 16. A smaller divisor allows a larger target, still bounded by half the summary output limit and half the selected content estimate.')),
501
- h('p',{className:'ss-muted'},t('Save preferences to apply these controls to subsequent summary requests; other model and compaction settings stay unchanged.')),
532
+ 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})),
533
+ state&&!validDivisor&&h('p',{className:'ss-status ss-error',role:'alert'},t('Enter a finite divisor of at least 1; decimals are allowed.')),
534
+ 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})),
535
+ state&&!validPrompt&&h('p',{className:'ss-status ss-error',role:'alert'},t('Use 1–2,048 characters and include {target}.')),
536
+ 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')),
537
+ h('p',{className:'ss-muted'},t("Appended to DSH's native prompt; {target} becomes the calculated token target. Save preferences to apply.")),
502
538
  !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.')),
503
- h('p',null,t("When enabled, before each supported Strata summary, calculates a length target and appends one sentence to DSH's native summary prompt.")),
504
539
  summaryTargetCeiling!==null&&(summaryTargetCeiling>=256
505
540
  ? h('p',{'data-testid':'summary-target-ceiling'},t('Maximum target for this preview: '),format(summaryTargetCeiling),' tokens')
506
541
  : 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.'))),
507
- h('details',{className:'ss-details'},h('summary',null,t('How the target is calculated')),
508
- h('p',{className:'ss-muted'},t('Target = floor(min(context / N, summary output limit / 2, selected content estimate / 2)).')),
509
- 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.')),
510
- 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.')),
511
- 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.')),
512
- h('p',{className:'ss-muted'},t('Targets below 256 tokens leave the original prompt unchanged; the native summary still runs.')),
513
- h('p',{className:'ss-muted'},t('If the route or native summary format cannot be verified, DSH uses its original prompt.')),
514
- h('p',{className:'ss-muted'},t('Unpriced image/file attachments, cancellation or a disabled plugin preserve native request handling.')))),
542
+ h('details',{className:'ss-details','data-testid':'summary-details'},h('summary',null,t('Calculation and limits')),
543
+ h('div',{className:'ss-grid'},
544
+ 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})),
545
+ 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}))),
546
+ h('p',{className:'ss-muted'},t('Target = floor(min(context / N, summary output limit / O, selected content estimate / S)).')),
547
+ h('p',{className:'ss-muted'},t('When guidance cannot be applied, DSH keeps its native summary behavior.')))),
515
548
  h('div',{className:'ss-actions'},
516
549
  h('button',{disabled:pending||!state||!form.providerId||!valid||!dirty||conflict,onClick:()=>action(async()=>{
517
550
  const changes=Object.fromEntries(Object.entries(values(formRef.current)).filter(([key,value])=>!Object.is(value,baseline.current.values[key])));
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 { DEFAULT_SUMMARY_DIVISOR, 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({
@@ -12,7 +12,10 @@ export const Config = z.object({
12
12
  followLoadedModel: z.boolean().default(true),
13
13
  outputTokens: z.number().step(1).min(1),
14
14
  summaryGuidance: z.boolean().default(true),
15
- summaryContextDivisor: z.number().step(1).min(1).max(Number.MAX_SAFE_INTEGER).default(DEFAULT_SUMMARY_DIVISOR),
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),
16
19
  });
17
20
 
18
21
  const PACKAGE = 'dsh-strata-sync';
@@ -142,7 +145,9 @@ export function apply(ctx, initialConfig) {
142
145
  function publicSettings(value = settings) {
143
146
  return { providerId: value.providerId ?? '', autoSync: value.autoSync ?? false,
144
147
  followLoadedModel: value.followLoadedModel ?? true, outputTokens: value.outputTokens ?? null,
145
- summaryGuidance: value.summaryGuidance ?? true, summaryContextDivisor: value.summaryContextDivisor ?? DEFAULT_SUMMARY_DIVISOR };
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 };
146
151
  }
147
152
  function status() {
148
153
  refreshSettings();
@@ -155,7 +160,7 @@ export function apply(ctx, initialConfig) {
155
160
  return { id, baseURL };
156
161
  });
157
162
  } catch { /* The page remains usable when the required provider is absent. */ }
158
- return { ...publicSettings(), settingsRevision: hash(publicSettings()),
163
+ return { ...publicSettings(), defaultSummaryPromptTemplate: DEFAULT_SUMMARY_TEMPLATE, settingsRevision: hash(publicSettings()),
159
164
  busy: busy() || !!writeBarrier, providers, lastPreview, lastApplied, lastError };
160
165
  }
161
166
  async function credential(providerId, route) {
@@ -384,7 +389,7 @@ export function apply(ctx, initialConfig) {
384
389
  }
385
390
  async function configure(request) {
386
391
  ensureOpen();
387
- payload(request, ['providerId', 'autoSync', 'followLoadedModel', 'outputTokens', 'summaryGuidance', 'summaryContextDivisor', 'expectedRevision']);
392
+ payload(request, ['providerId', 'autoSync', 'followLoadedModel', 'outputTokens', 'summaryGuidance', 'summaryContextDivisor', 'summaryOutputDivisor', 'summarySourceDivisor', 'summaryPromptTemplate', 'expectedRevision']);
388
393
  refreshSettings();
389
394
  if (typeof request.expectedRevision !== 'string' || request.expectedRevision !== hash(publicSettings())) {
390
395
  refuse('stale-settings', 'Sync preferences changed. Reload the latest preferences before saving.');
@@ -400,11 +405,14 @@ export function apply(ctx, initialConfig) {
400
405
  if (typeof request[key] !== 'boolean') refuse('invalid-request', `${key} must be a boolean.`);
401
406
  next[key] = request[key];
402
407
  }
403
- if (own(request, 'summaryContextDivisor')) {
404
- if (!Number.isSafeInteger(request.summaryContextDivisor) || request.summaryContextDivisor < 1) {
405
- refuse('invalid-budget', 'Summary context divisor must be a positive safe integer.');
406
- }
407
- next.summaryContextDivisor = request.summaryContextDivisor;
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;
408
416
  }
409
417
  if (own(request, 'outputTokens')) {
410
418
  if (request.outputTokens === null) delete next.outputTokens;
package/dist/summary.mjs CHANGED
@@ -1,28 +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;
10
15
  export const DEFAULT_SUMMARY_DIVISOR = 16;
11
16
 
12
- export function planSummaryTarget(contextWindow, summaryMaxTokens, sourceTokens, divisor = DEFAULT_SUMMARY_DIVISOR) {
13
- if (![contextWindow, summaryMaxTokens, sourceTokens, divisor].every(positive)) return null;
14
- const target = Math.floor(Math.min(contextWindow / divisor, 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));
15
20
  return target >= MIN_SUMMARY_TARGET ? target : null;
16
21
  }
17
22
 
18
- export function summaryInstruction(target) {
23
+ export function summaryInstruction(target, template = DEFAULT_SUMMARY_TEMPLATE) {
19
24
  if (!positive(target)) throw new TypeError('A positive integer summary target is required.');
20
- 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));
21
27
  }
22
28
 
23
29
  /** Recognize the shipped native prompt, without storing or replacing its text. */
24
30
  export function nativeSummaryMessage(options) {
25
- 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;
26
32
  const message = options.messages.at(-1);
27
33
  if (message?.role !== 'user' || message.content?.length !== 1 || message.content[0]?.type !== 'text') return null;
28
34
  const text = message.content[0].text;
@@ -73,7 +79,11 @@ export function installSummaryGuidance(ctx, resolveContext, getSettings = () =>
73
79
  const value = getSettings();
74
80
  const enabled = value.summaryGuidance === undefined ? true : value.summaryGuidance;
75
81
  const divisor = value.summaryContextDivisor === undefined ? DEFAULT_SUMMARY_DIVISOR : value.summaryContextDivisor;
76
- return enabled === true && positive(divisor) ? { divisor } : null;
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;
77
87
  } catch { return null; }
78
88
  }
79
89
  const remove = ctx.on('llm/stream', (options, next) => {
@@ -93,15 +103,17 @@ export function installSummaryGuidance(ctx, resolveContext, getSettings = () =>
93
103
  let contextWindow;
94
104
  try { contextWindow = await resolveContext(options); }
95
105
  catch { /* Metadata/compatibility failure must not break native inference. */ }
96
- const target = planSummaryTarget(contextWindow, options.maxTokens, sourceTokens, selected.divisor);
106
+ const target = planSummaryTarget(contextWindow, options.maxTokens, sourceTokens, selected.divisor, selected.outputDivisor, selected.sourceDivisor);
97
107
  const current = preferences();
98
- if (!closed && !options.signal?.aborted && current?.divisor === selected.divisor && target !== null && options.messages === original) {
99
- const last = { ...native, content: [{ ...native.content[0], text: `${native.content[0].text}\n- ${summaryInstruction(target)}` }] };
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)}` }] };
100
110
  augmented = [...original.slice(0, -1), last];
111
+ augmentedMessages.add(augmented);
101
112
  options.messages = augmented;
102
113
  }
103
114
  yield* next();
104
115
  } finally {
116
+ if (augmented) augmentedMessages.delete(augmented);
105
117
  if (augmented && options.messages === augmented) options.messages = original;
106
118
  }
107
119
  })();
@@ -6,36 +6,46 @@
6
6
 
7
7
  UI 支持 English、简体中文、繁體中文。自动跟随 DSH 界面语言并实时更新,不提供独立语言菜单。DSH 的 `zh` 对应简体中文,`zh-Hant`、`zh-TW`、`zh-HK`、`zh-MO` 对应繁体中文;其他语言使用英文。服务器返回的未知诊断消息保留原文,方便排查。
8
8
 
9
- 0.9.0 在设置页加入**默认开启的摘要提示补充开关**与可调整的**上下文除数**。启用时,在每次支持的 Strata 摘要请求前计算长度目标,并在 DSH 原生摘要提示词后**只补充一句**。保留原提示词、输出硬上限与其他设置;已有同步保护、启动行为、能力验证及备份机制保持不变。
9
+ 0.10.1 进一步精简摘要设置说明。可保留默认提示词模板,或使用动态 `{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
- 除数 = summaryContextDivisor(默认 16)
20
- 目标 = floor(min(上下文 / 除数,
21
- 本次摘要实际 maxTokens / 2,
22
- 待替换内容的估算 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.
23
21
  ```
24
22
 
25
- 内容估算只包含这次要替换的对话片段,以及片段内已有的摘要;不包含系统提示词和工具定义。插件在每次摘要前通过只读查询核对 Strata 当前加载模型与上下文,并读取本次摘要请求的实际输出硬上限。上下文取服务器已确认值与 DSH 已有模型设置的较小值,不会因为服务器窗口较大而悄悄扩大较小的客户端预算。目标不是启动 DSH 或同步设置时固定下来的。
26
-
27
- 采用默认除数 16、摘要硬上限为 4,096、原文足够长时,32K 上下文对应 **2,048 tokens**,64K 对应 **2,048 tokens**;内容较短时目标会降低。**设置 → dsh-strata-sync** 显示公式与本次预览的目标上限,无法提前得知未来摘要的实际目标。
28
-
29
- 在“动态摘要目标”区域,可切换“**启用摘要提示补充**”,并将“**上下文除数(上下文 ÷ N)**”设为正的安全整数。除数越小,目标可能越大,但仍受摘要输出上限和原文长度约束。例如,32K 上下文、4,096-token 摘要上限且原文足够长时,除数 32、16、8 的目标分别为 1,024、2,048、2,048 tokens。点击“**保存偏好**”后,后续支持的摘要请求会采用新选择。仅保存这两个控件不会同步模型预算或修改 Strata Server 设置。
23
+ 模板须保留字面的 `{target}`,每次摘要时会将所有出现位置替换为本次计算的目标。长度最多 2,048 个字符;只做文本替换,不执行代码。“**恢复默认提示词**”会将编辑框恢复为原本的英文模板,修改或恢复后点击“**保存偏好**”才会应用。切换界面语言不会翻译或覆盖已保存的模板。这里只编辑补充内容,不修改 DSH 原生摘要指令。
30
24
 
31
25
  | 插件偏好 | 默认值 | 含义 |
32
26
  |---|---:|---|
33
- | `summaryGuidance` | `true` | 对支持的摘要请求补充长度指令;关闭后仍保留 DSH 原生摘要和自动压缩。 |
34
- | `summaryContextDivisor` | `16` | 以上下文除以此正的安全整数,计算摘要目标的一个约束。 |
27
+ | `summaryGuidance` | `true` | 对支持的摘要请求附加补充提示词。 |
28
+ | `summaryContextDivisor` | `16` | 以上下文除以此值。 |
29
+ | `summaryOutputDivisor` | `2` | 以本次摘要输出硬上限除以此值。 |
30
+ | `summarySourceDivisor` | `2` | 以原文估算长度除以此值。 |
31
+ | `summaryPromptTemplate` | 上方英文模板 | 替换 `{target}` 后附加的补充内容。 |
35
32
 
36
- 插件只补充一句英文,要求模型按目标长度完成摘要、保留接续未完成工作所需的信息、整合旧摘要,并删除重复或过时细节。原生提示词、八个章节和已有请求参数保持不变。这是启发式写作目标,**不是新的输出硬上限、最佳值,也不能保证不截断**;不增加自动重试流程或额外推理请求。
33
+ 三项除数都接受至少为 1 的有限数值,也支持 1.5、2.5 等小数。除数越大,对应限制越小;最终取三项限制的最小值并向下取整,不会改变实际输出硬上限。
37
34
 
38
- 无法确认路由、必要信息或原生摘要格式,或无法安全估算图片/文件附件的 token 用量时,原请求不变并交给 DSH 处理。请求已取消、提示补充已关闭或插件已禁用时也保留原生处理。计算目标小于 256 tokens 时不补充句子,避免给八个章节强加过小的目标;不取消原生摘要,也不以空摘要替换原文。关闭提示补充或移除插件后不再补充这句话,不会改写已保存的对话历史。
35
+ 每次支持的摘要请求都会重新计算:
36
+
37
+ ```text
38
+ 上下文 = min(已确认的 Strata 上下文, DSH 已有模型上下文)
39
+ 目标 = floor(min(上下文 / summaryContextDivisor,
40
+ 本次摘要实际 maxTokens / summaryOutputDivisor,
41
+ 待替换内容的估算 tokens / summarySourceDivisor))
42
+ ```
43
+
44
+ 原文估算涵盖这次要替换的对话片段及其中的旧摘要,不含系统提示词和工具定义。只读查询核对加载模型与上下文,实际输出硬上限来自当前请求。32K 上下文、4,096-token 摘要上限且原文足够长时,上下文除数 32、16、8 分别得到 1,024、2,048、2,048 tokens。UI 预览显示目标上限,不是未来请求的实际目标。
45
+
46
+ 保存这些控件只修改插件偏好,不会同步模型预算或修改 Strata Server 设置。DSH 原生提示词、八个章节和请求参数保持不变。默认补充内容只有一句,自定义模板则按输入内容附加。目标是写作指令,**不是新的输出硬上限,也不能保证不截断**;不增加重试流程或额外推理请求。
47
+
48
+ 无法确认路由或信息、原生提示词格式未知、附件用量无法估算、模板无效、请求取消、功能关闭或目标低于 256 tokens 时,保留原生请求。移除插件会停止附加内容,不改写已保存的对话历史。
39
49
 
40
50
  ### 模型能力自动同步(0.4.0)
41
51
 
@@ -74,7 +84,7 @@ dsh plugin --profile desktop add dsh-strata-sync --registry=https://registry.npm
74
84
 
75
85
  完成后重新打开 DSH。首次安装会自动选中 bundle;已安装但禁用的 bundle 需在插件管理器重新启用。
76
86
 
77
- 要从源码安装**包含摘要提示补充控件的 0.9.0**,请使用 Node.js 22 或更新版本构建,并在最后的安装命令前完成任务、完全退出 DSH Desktop:
87
+ 要从源码安装**包含摘要提示词编辑器的 0.10.1**,请使用 Node.js 22 或更新版本构建,并在最后的安装命令前完成任务、完全退出 DSH Desktop:
78
88
 
79
89
  ```sh
80
90
  git clone https://github.com/ethan741205/dsh-strata-sync.git
@@ -82,7 +92,7 @@ cd dsh-strata-sync
82
92
  npm ci
83
93
  npm test
84
94
  npm pack
85
- dsh plugin --profile desktop add ./dsh-strata-sync-0.9.0.tgz
95
+ dsh plugin --profile desktop add ./dsh-strata-sync-0.10.1.tgz
86
96
  ```
87
97
 
88
98
  执行最后一行前同样需要完全退出 DSH Desktop,安装完成后重新打开。在其他目录执行时,请使用安装包的绝对路径。
@@ -95,7 +105,7 @@ dsh plugin --profile desktop add ./dsh-strata-sync-0.9.0.tgz
95
105
  4. 如需自动同步,勾选“启动 DSH 时自动同步”并保存,下次启动生效。原有 `autoSync: true` 会沿用为启动同步。
96
106
  5. 开启新对话,使用更新后的 Agent 预设。
97
107
 
98
- “动态摘要目标”控件独立于模型同步。保持“启用摘要提示补充”开启、除数为 16 即沿用先前行为,也可修改后保存偏好。关闭此开关不会禁用 DSH 自身的压缩功能。
108
+ “动态摘要目标”控件独立于模型同步。保持“启用摘要提示补充”开启、使用默认模板及 16/2/2 的除数,即沿用先前行为,也可修改后保存偏好。关闭此开关不会禁用 DSH 自身的压缩功能。
99
109
 
100
110
  无需把 API key 复制到此插件;认证问题请在 DSH 模型设置处理。
101
111
 
@@ -145,6 +155,10 @@ ConfigEditor 负责单项原子写入与热加载;跨多项设置采用补偿
145
155
 
146
156
  ## 开发与版本
147
157
 
158
+ 0.10.1 将摘要界面说明缩短为公式与沿用原生摘要的提示,计算及运行机制不变,完整说明保留在 README。
159
+
160
+ 0.10.0 加入补充提示词编辑器、动态 `{target}` 与恢复默认功能,精简三语摘要区域,新增高级输出/原文除数与三项除数的小数支持,并沿用备份与版本冲突保护。
161
+
148
162
  0.9.0 加入默认开启的摘要提示补充开关及可调整的上下文除数(默认 16),沿用偏好保存与版本冲突保护,并同步三语界面和文档。保存这两个控件不改变原生摘要指令、输出硬上限、其他预算或服务器设置。
149
163
 
150
164
  0.5.0 移除独立语言菜单与产品声明,改为跟随 DSH 语言及启动时单次同步。0.3.0 首次加入三语 UI,0.2.0 将 UI 移入原生设置。
@@ -6,36 +6,46 @@
6
6
 
7
7
  UI 支援 English、简体中文、繁體中文。自動跟隨 DSH 介面語言並即時更新,不提供獨立語言選單。DSH 的 `zh` 對應簡體中文,`zh-Hant`、`zh-TW`、`zh-HK`、`zh-MO` 對應繁體中文;其他語言使用英文。伺服器傳回的未知診斷訊息保留原文,以利排查。
8
8
 
9
- 0.9.0 在設定頁加入**預設開啟的摘要提示補充開關**與可調整的**上下文除數**。啟用時,在每次支援的 Strata 摘要請求前計算長度目標,並在 DSH 原生摘要提示詞後**只補充一句**。保留原提示詞、輸出硬上限與其他設定;既有同步保護、啟動行為、能力驗證及備份機制保持不變。
9
+ 0.10.1 進一步精簡摘要設定說明。可保留預設提示詞範本,或使用動態 `{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
- 除數 = summaryContextDivisor(預設 16)
20
- 目標 = floor(min(上下文 / 除數,
21
- 本次摘要實際 maxTokens / 2,
22
- 待替換內容的估算 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.
23
21
  ```
24
22
 
25
- 內容估算只包含這次要替換的對話片段,以及片段內既有的摘要;不包含系統提示詞和工具定義。外掛在每次摘要前透過唯讀查詢核對 Strata 目前載入模型與上下文,並讀取本次摘要請求的實際輸出硬上限。上下文取伺服器已確認值與 DSH 既有模型設定的較小值,不會因為伺服器視窗較大而偷偷擴大較小的客戶端預算。目標不是啟動 DSH 或同步設定時固定下來的。
26
-
27
- 採用預設除數 16、摘要硬上限為 4,096、原文足夠長時,32K 上下文對應 **2,048 tokens**,64K 對應 **2,048 tokens**;內容較短時目標會降低。**設定 → dsh-strata-sync** 顯示公式與本次預覽的目標上限,無法提前得知未來摘要的實際目標。
28
-
29
- 在「動態摘要目標」區域,可切換「**啟用摘要提示補充**」,並將「**上下文除數(上下文 ÷ N)**」設為正的安全整數。除數越小,目標可能越大,但仍受摘要輸出上限和原文長度約束。例如,32K 上下文、4,096-token 摘要上限且原文足夠長時,除數 32、16、8 的目標分別為 1,024、2,048、2,048 tokens。按「**儲存偏好**」後,後續支援的摘要請求會採用新選擇。僅儲存這兩個控制項不會同步模型預算或修改 Strata Server 設定。
23
+ 範本須保留字面的 `{target}`,每次摘要時會將所有出現位置替換為當次計算的目標。長度最多 2,048 個字元;只做文字替換,不執行程式碼。「**還原預設提示詞**」會將編輯框恢復為原本的英文範本,修改或還原後按「**儲存偏好**」才會套用。切換介面語言不會翻譯或覆寫已儲存的範本。這裡只編輯補充內容,不修改 DSH 原生摘要指令。
30
24
 
31
25
  | 外掛偏好 | 預設值 | 含義 |
32
26
  |---|---:|---|
33
- | `summaryGuidance` | `true` | 對支援的摘要請求補充長度指令;關閉後仍保留 DSH 原生摘要和自動壓縮。 |
34
- | `summaryContextDivisor` | `16` | 以上下文除以此正的安全整數,計算摘要目標的一項限制。 |
27
+ | `summaryGuidance` | `true` | 對支援的摘要請求附加補充提示詞。 |
28
+ | `summaryContextDivisor` | `16` | 以上下文除以此值。 |
29
+ | `summaryOutputDivisor` | `2` | 以當次摘要輸出硬上限除以此值。 |
30
+ | `summarySourceDivisor` | `2` | 以原文估算長度除以此值。 |
31
+ | `summaryPromptTemplate` | 上方英文範本 | 替換 `{target}` 後附加的補充內容。 |
35
32
 
36
- 外掛只補充一句英文,要求模型依目標長度完成摘要、保留接續未完成工作所需的資訊、整合舊摘要,並刪除重複或過時細節。原生提示詞、八個章節和既有請求參數保持不變。這是啟發式寫作目標,**不是新的輸出硬上限、最佳值,也不能保證不截斷**;不增加自動重試流程或額外推論請求。
33
+ 三項除數都接受至少為 1 的有限數值,也支援 1.5、2.5 等小數。除數越大,對應限制越小;最終取三項限制的最小值並向下取整,不會改變實際輸出硬上限。
37
34
 
38
- 無法確認路由、必要資訊或原生摘要格式,或無法安全估算圖片/檔案附件的 token 用量時,原請求不變並交由 DSH 處理。請求已取消、提示補充已關閉或外掛已停用時也保留原生處理。計算目標小於 256 tokens 時不補充句子,避免給八個章節強加過小的目標;不取消原生摘要,也不以空摘要替換原文。關閉提示補充或移除外掛後不再補充這句話,不會改寫已儲存的對話歷史。
35
+ 每次支援的摘要請求都會重新計算:
36
+
37
+ ```text
38
+ 上下文 = min(已確認的 Strata 上下文, DSH 既有模型上下文)
39
+ 目標 = floor(min(上下文 / summaryContextDivisor,
40
+ 本次摘要實際 maxTokens / summaryOutputDivisor,
41
+ 待替換內容的估算 tokens / summarySourceDivisor))
42
+ ```
43
+
44
+ 原文估算涵蓋這次要替換的對話片段與其中的舊摘要,不含系統提示詞和工具定義。唯讀查詢核對載入模型與上下文,實際輸出硬上限來自當次請求。32K 上下文、4,096-token 摘要上限且原文足夠長時,上下文除數 32、16、8 分別得到 1,024、2,048、2,048 tokens。UI 預覽顯示目標上限,不是未來請求的實際目標。
45
+
46
+ 儲存這些控制項只更改外掛偏好,不會同步模型預算或修改 Strata Server 設定。DSH 原生提示詞、八個章節和請求參數保持不變。預設補充內容只有一句,自訂範本則按輸入內容附加。目標是寫作指令,**不是新的輸出硬上限,也不能保證不截斷**;不增加重試流程或額外推論請求。
47
+
48
+ 無法確認路由或資訊、原生提示詞格式未知、附件用量無法估算、範本無效、請求取消、功能關閉或目標低於 256 tokens 時,保留原生請求。移除外掛會停止附加內容,不重寫已儲存的對話歷史。
39
49
 
40
50
  ### 模型能力自動同步(0.4.0)
41
51
 
@@ -74,7 +84,7 @@ dsh plugin --profile desktop add dsh-strata-sync --registry=https://registry.npm
74
84
 
75
85
  完成後重新開啟 DSH。首次安裝會自動選取 bundle;已安裝但停用的 bundle 需在外掛管理員重新啟用。
76
86
 
77
- 要從原始碼安裝**包含摘要提示補充控制項的 0.9.0**,請使用 Node.js 22 或更新版本建置,並在最後的安裝命令前完成工作、完全結束 DSH Desktop:
87
+ 要從原始碼安裝**包含摘要提示詞編輯器的 0.10.1**,請使用 Node.js 22 或更新版本建置,並在最後的安裝命令前完成工作、完全結束 DSH Desktop:
78
88
 
79
89
  ```sh
80
90
  git clone https://github.com/ethan741205/dsh-strata-sync.git
@@ -82,7 +92,7 @@ cd dsh-strata-sync
82
92
  npm ci
83
93
  npm test
84
94
  npm pack
85
- dsh plugin --profile desktop add ./dsh-strata-sync-0.9.0.tgz
95
+ dsh plugin --profile desktop add ./dsh-strata-sync-0.10.1.tgz
86
96
  ```
87
97
 
88
98
  執行最後一行前同樣需要完全結束 DSH Desktop,安裝完成後重新開啟。在其他目錄執行時,請使用套件的絕對路徑。
@@ -95,7 +105,7 @@ dsh plugin --profile desktop add ./dsh-strata-sync-0.9.0.tgz
95
105
  4. 如需自動同步,勾選「啟動 DSH 時自動同步」並儲存,下次啟動生效。原有 `autoSync: true` 會沿用為啟動同步。
96
106
  5. 開啟新對話,使用更新後的 Agent 範本。
97
107
 
98
- 「動態摘要目標」控制項獨立於模型同步。保持「啟用摘要提示補充」開啟、除數為 16 即沿用先前行為,也可修改後儲存偏好。關閉此開關不會停用 DSH 自身的壓縮功能。
108
+ 「動態摘要目標」控制項獨立於模型同步。保持「啟用摘要提示補充」開啟、使用預設範本及 16/2/2 的除數,即沿用先前行為,也可修改後儲存偏好。關閉此開關不會停用 DSH 自身的壓縮功能。
99
109
 
100
110
  不需要把 API key 複製到此外掛;認證問題請在 DSH 模型設定處理。
101
111
 
@@ -145,6 +155,10 @@ ConfigEditor 負責個別原子寫入與熱載入;跨多項設定採補償回
145
155
 
146
156
  ## 開發與版本
147
157
 
158
+ 0.10.1 將摘要介面說明縮短為公式與沿用原生摘要的提示,計算及執行機制不變,完整說明保留在 README。
159
+
160
+ 0.10.0 加入補充提示詞編輯器、動態 `{target}` 與還原預設功能,精簡三語摘要區塊,新增進階輸出/原文除數與三項除數的小數支援,並沿用備份與版本衝突保護。
161
+
148
162
  0.9.0 加入預設開啟的摘要提示補充開關及可調整的上下文除數(預設 16),沿用偏好儲存與版本衝突保護,並同步三語介面和文件。儲存這兩個控制項不改變原生摘要指令、輸出硬上限、其他預算或伺服器設定。
149
163
 
150
164
  0.5.0 移除獨立語言選單與產品聲明,改為跟隨 DSH 語言及啟動時單次同步。0.3.0 首次加入三語 UI,0.2.0 將 UI 移入原生設定。
@@ -2,6 +2,18 @@
2
2
 
3
3
  Validation date: 2026-10-30 (Asia/Taipei).
4
4
 
5
+ ## Concise summary help 0.10.1
6
+
7
+ - Reduces the expanded summary help to the calculation formula and one native-fallback sentence in all three locales. Full explanations remain in the READMEs. No runtime behavior, settings, defaults or prompt text changes.
8
+ - Verification is limited to the 38 affected UI tests and package checks; inference and native integration tests are not repeated for this wording-only change.
9
+
10
+ ## Editable summary prompt and fractional divisors 0.10.0
11
+
12
+ - 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.
13
+ - `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.
14
+ - 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.
15
+ - 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.
16
+
5
17
  ## Summary guidance controls 0.9.0
6
18
 
7
19
  - 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-strata-sync",
3
- "version": "0.9.0",
3
+ "version": "0.10.1",
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",