@lmzhen/dsh-evolution-settings-ui 0.7.0 → 0.9.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 +11 -2
- package/lib/client.js +218 -147
- package/lib/types/client/ParamCard.d.ts +14 -3
- package/lib/types/client/generated-params.d.ts +2 -0
- package/lib/types/client/messages.d.ts +14 -14
- package/lib/types/client/settle.d.ts +25 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,11 +16,19 @@ The cards claim the keyed child slot per namespace, so a namespace the Host does
|
|
|
16
16
|
|
|
17
17
|
## What a card does
|
|
18
18
|
|
|
19
|
-
A card renders the namespace's user-writable (E3) fields with the registry's Chinese label, its unit, a
|
|
19
|
+
A card renders the namespace's user-writable (E3) fields with the registry's Chinese label, its unit, a 我改过/默认 source chip, a control **typed from the registry** (switch / select / number / text) holding the current value, the registry's hint, and a per-field 恢复默认值 action when a user override exists. A select's option TEXT comes from the registry's optional `valueLabels`; the option VALUE stays the raw spelling the settings document stores. The card's foot carries one 放弃修改 / 保存 pair: edits live in a draft, 保存 writes only the dirty fields in order, and a settling effect clears the draft once the Host kept every written key.
|
|
20
|
+
|
|
21
|
+
Success is never assumed from the write call: the client settings scope RESOLVES a refused write (it recovers the snapshot and returns; the remote call never rejects). Both scopes this bundle runs on — the platform's own controller and the bridge variant — finish that recovery read BEFORE the write promise resolves, so the user section read in the settling effect already reflects the outcome. Presence cannot BE the verdict: on a field the operator had already overridden, a refused value's key is present too, so the card records what it asked for (`{ id, want }` per staged write) and settles on whether the section now holds that value (`src/client/settle.ts`). A refusal then renders a line at the card's foot, below the field list, and the draft stays — nothing the operator typed is lost and a refusal never looks like a silent revert. The verdict cannot be read from the save callback itself: the raw snapshot is reachable only through the render-time seat (see the next section).
|
|
20
22
|
|
|
21
23
|
Writes go through the client settings scope (`set`/`unset`), which carries the revision it read as the write's fence; a field whose key is present in the raw user section reads as a user override even when its value equals the deployment value.
|
|
22
24
|
|
|
23
|
-
The field list AND the per-field UI metadata are GENERATED from the parameter registry (`packages/scripts/gen-param-client-view.mjs` writes `src/client/generated-params.ts`): id, group, the English summary, and the E3 rows' label / hint / control / unit / values. The browser half therefore cannot name a parameter the Host does not register, and cannot invent a control the registry does not declare. Regenerate after every registry edit; the family gate runs the generator with `--check`.
|
|
25
|
+
The field list AND the per-field UI metadata are GENERATED from the parameter registry (`packages/scripts/gen-param-client-view.mjs` writes `src/client/generated-params.ts`): id, group, the English summary, and the E3 rows' label / hint / control / unit / values / valueLabels. The browser half therefore cannot name a parameter the Host does not register, and cannot invent a control the registry does not declare. Regenerate after every registry edit; the family gate runs the generator with `--check`.
|
|
26
|
+
|
|
27
|
+
## The `hooks` compartment never reaches the component
|
|
28
|
+
|
|
29
|
+
A card's inject face carries `hooks: { paramSection: source }` — that is the SHELL's seat, not a prop. The renderer binds each entry to a `use<Name>` seat and hands the component the face with `hooks` REMOVED (the slot contract is `PropsHooks<face['hooks']>` plus `Omit<face, 'hooks'>`). Reading `props.hooks` therefore reads a prop that never exists, and the failure only shows up at runtime, inside the save path.
|
|
30
|
+
|
|
31
|
+
The props type is spelled the way the renderer derives it — `Omit<ParamCardFace, 'hooks'> & { useParamSection }` — so re-introducing that mistake fails `tsc` instead of failing the operator's save. 0.7.0 shipped the mistake; 0.7.1 fixed it and derived the type this way.
|
|
24
32
|
|
|
25
33
|
## Styling
|
|
26
34
|
|
|
@@ -40,3 +48,4 @@ The browser half ships as the client module system's lazy CJS factory artifact (
|
|
|
40
48
|
- The section renders plain controls styled with the design tokens rather than the platform's `@deepseek-ai/dsh-client-ui-primitives` kit: that module IS available to out-of-repo bundles (the market plugin requires it), but its prop shapes are not published, and guessing them would break the live GUI.
|
|
41
49
|
- An unsaved draft lives in the card's own component state and the settings shell renders only the active section, so switching to another section drops a draft that was not saved yet. Values already written are unaffected; moving the draft into an apply-time store is the 0.7.x follow-up.
|
|
42
50
|
- A deployment that overrides a field through the `evolution-policy` row does not show on the card: the card reports the deployment value the settings scope serves, while the policy snapshot can differ. That divergence stays a doctor / `/evolution params` matter.
|
|
51
|
+
- The browser half has no RENDERED spec: the family specs are Node-level and the family carries no React runtime, so the card itself is covered by `tsc`, the bundle build, the gate steps and a live pass on the installed artifact (0.7.0's save defect was found exactly there). The verdict is pinned instead by `tests/settle.spec.ts` (the pure `landedWrites` decision, including the already-overridden refusal case); rendering the card with the props the renderer actually builds — no `hooks`, the bound seat stubbed — remains the follow-up that would catch the wiring in CI.
|
package/lib/client.js
CHANGED
|
@@ -14,121 +14,133 @@ window.__ModuleLoader__.load({
|
|
|
14
14
|
id: "curatorIntervalHours",
|
|
15
15
|
group: "curator",
|
|
16
16
|
doc: "Minimum hours between deterministic curation passes.",
|
|
17
|
-
label: "
|
|
18
|
-
hint: "
|
|
17
|
+
label: "自动整理间隔",
|
|
18
|
+
hint: "两次自动整理之间至少间隔多少小时。",
|
|
19
19
|
control: "number",
|
|
20
20
|
unit: "小时",
|
|
21
|
-
values: []
|
|
21
|
+
values: [],
|
|
22
|
+
valueLabels: []
|
|
22
23
|
},
|
|
23
24
|
{
|
|
24
25
|
id: "staleAfterDays",
|
|
25
26
|
group: "curator",
|
|
26
27
|
doc: "Inactive days before a skill counts as stale.",
|
|
27
|
-
label: "
|
|
28
|
-
hint: "
|
|
28
|
+
label: "多久没用算过时",
|
|
29
|
+
hint: "技能连续多少天没被用到就算过时。",
|
|
29
30
|
control: "number",
|
|
30
31
|
unit: "天",
|
|
31
|
-
values: []
|
|
32
|
+
values: [],
|
|
33
|
+
valueLabels: []
|
|
32
34
|
},
|
|
33
35
|
{
|
|
34
36
|
id: "archiveAfterDays",
|
|
35
37
|
group: "curator",
|
|
36
38
|
doc: "Inactive days before a stale skill is archived (must be >= staleAfterDays).",
|
|
37
|
-
label: "
|
|
38
|
-
hint: "
|
|
39
|
+
label: "过时后归档",
|
|
40
|
+
hint: "过时技能再过多少天移入归档(不能小于上一项)。",
|
|
39
41
|
control: "number",
|
|
40
42
|
unit: "天",
|
|
41
|
-
values: []
|
|
43
|
+
values: [],
|
|
44
|
+
valueLabels: []
|
|
42
45
|
},
|
|
43
46
|
{
|
|
44
47
|
id: "qualityWarnStaleAfterDays",
|
|
45
48
|
group: "curator",
|
|
46
49
|
doc: "Age at which a low quality score starts warning.",
|
|
47
|
-
label: "
|
|
48
|
-
hint: "
|
|
50
|
+
label: "质量偏低提醒",
|
|
51
|
+
hint: "质量评分偏低时,从多少天开始提醒。",
|
|
49
52
|
control: "number",
|
|
50
53
|
unit: "天",
|
|
51
|
-
values: []
|
|
54
|
+
values: [],
|
|
55
|
+
valueLabels: []
|
|
52
56
|
},
|
|
53
57
|
{
|
|
54
58
|
id: "minIdleHours",
|
|
55
59
|
group: "curator",
|
|
56
60
|
doc: "Idle hours required before an automatic curation pass runs.",
|
|
57
|
-
label: "
|
|
58
|
-
hint: "
|
|
61
|
+
label: "最少空闲时长",
|
|
62
|
+
hint: "你至少多少小时没用,才运行自动整理。",
|
|
59
63
|
control: "number",
|
|
60
64
|
unit: "小时",
|
|
61
|
-
values: []
|
|
65
|
+
values: [],
|
|
66
|
+
valueLabels: []
|
|
62
67
|
},
|
|
63
68
|
{
|
|
64
69
|
id: "minIdleFailOpen",
|
|
65
70
|
group: "curator",
|
|
66
71
|
doc: "Let the idle gate open when the activity probe is unavailable.",
|
|
67
|
-
label: "
|
|
68
|
-
hint: "
|
|
72
|
+
label: "检测不到活动也运行",
|
|
73
|
+
hint: "无法判断你是否空闲时,仍然运行自动整理。",
|
|
69
74
|
control: "switch",
|
|
70
75
|
unit: "",
|
|
71
|
-
values: []
|
|
76
|
+
values: [],
|
|
77
|
+
valueLabels: []
|
|
72
78
|
},
|
|
73
79
|
{
|
|
74
80
|
id: "llmReview",
|
|
75
81
|
group: "curator",
|
|
76
82
|
doc: "Enable the LLM nomination pass on top of the deterministic lifecycle.",
|
|
77
|
-
label: "
|
|
78
|
-
hint: "
|
|
83
|
+
label: "用模型挑选",
|
|
84
|
+
hint: "在固定规则之外,再用模型挑一遍候选项。",
|
|
79
85
|
control: "switch",
|
|
80
86
|
unit: "",
|
|
81
|
-
values: []
|
|
87
|
+
values: [],
|
|
88
|
+
valueLabels: []
|
|
82
89
|
},
|
|
83
90
|
{
|
|
84
91
|
id: "curatorReviewMaxTokens",
|
|
85
92
|
group: "curator",
|
|
86
93
|
doc: "Token budget of the curator LLM review.",
|
|
87
|
-
label: "
|
|
88
|
-
hint: "
|
|
94
|
+
label: "模型处理上限",
|
|
95
|
+
hint: "每次用模型挑选时最多消耗多少 token。",
|
|
89
96
|
control: "number",
|
|
90
97
|
unit: "token",
|
|
91
|
-
values: []
|
|
98
|
+
values: [],
|
|
99
|
+
valueLabels: []
|
|
92
100
|
},
|
|
93
101
|
{
|
|
94
102
|
id: "curatorReviewTimeoutMs",
|
|
95
103
|
group: "curator",
|
|
96
104
|
doc: "Wall-clock bound of the curator LLM review.",
|
|
97
|
-
label: "
|
|
98
|
-
hint: "
|
|
105
|
+
label: "模型处理超时",
|
|
106
|
+
hint: "一次模型挑选最长允许运行多久。",
|
|
99
107
|
control: "number",
|
|
100
108
|
unit: "毫秒",
|
|
101
|
-
values: []
|
|
109
|
+
values: [],
|
|
110
|
+
valueLabels: []
|
|
102
111
|
},
|
|
103
112
|
{
|
|
104
113
|
id: "healthSoftBodyChars",
|
|
105
114
|
group: "curator",
|
|
106
115
|
doc: "Body character line the health view judges against.",
|
|
107
|
-
label: "
|
|
108
|
-
hint: "
|
|
116
|
+
label: "正文长度参考值",
|
|
117
|
+
hint: "技能正文超过多少字符算偏长(只用于健康提示)。",
|
|
109
118
|
control: "number",
|
|
110
119
|
unit: "字符",
|
|
111
|
-
values: []
|
|
120
|
+
values: [],
|
|
121
|
+
valueLabels: []
|
|
112
122
|
},
|
|
113
123
|
{
|
|
114
124
|
id: "healthStampDensityPerKb",
|
|
115
125
|
group: "curator",
|
|
116
126
|
doc: "Stamp density per KB that flags log-like content in a body.",
|
|
117
|
-
label: "
|
|
118
|
-
hint: "每 KB
|
|
127
|
+
label: "日志化判定阈值",
|
|
128
|
+
hint: "每 KB 出现多少个时间标记,就判为「像日志」。",
|
|
119
129
|
control: "number",
|
|
120
130
|
unit: "处/KB",
|
|
121
|
-
values: []
|
|
131
|
+
values: [],
|
|
132
|
+
valueLabels: []
|
|
122
133
|
},
|
|
123
134
|
{
|
|
124
135
|
id: "healthChurnMinPatches",
|
|
125
136
|
group: "curator",
|
|
126
137
|
doc: "Patches without a read that flag a write-ghost skill.",
|
|
127
|
-
label: "
|
|
128
|
-
hint: "
|
|
138
|
+
label: "只写不读判定次数",
|
|
139
|
+
hint: "连续多少次改动都没有被读取,就判为「只写不读」。",
|
|
129
140
|
control: "number",
|
|
130
141
|
unit: "处",
|
|
131
|
-
values: []
|
|
142
|
+
values: [],
|
|
143
|
+
valueLabels: []
|
|
132
144
|
}
|
|
133
145
|
]
|
|
134
146
|
},
|
|
@@ -139,41 +151,45 @@ window.__ModuleLoader__.load({
|
|
|
139
151
|
id: "memoryChars",
|
|
140
152
|
group: "memory",
|
|
141
153
|
doc: "Character budget the memory store enforces for MEMORY.md.",
|
|
142
|
-
label: "
|
|
143
|
-
hint: "
|
|
154
|
+
label: "记忆库上限",
|
|
155
|
+
hint: "长期记忆文件(MEMORY.md)最多多少字符。",
|
|
144
156
|
control: "number",
|
|
145
157
|
unit: "字符",
|
|
146
|
-
values: []
|
|
158
|
+
values: [],
|
|
159
|
+
valueLabels: []
|
|
147
160
|
},
|
|
148
161
|
{
|
|
149
162
|
id: "userChars",
|
|
150
163
|
group: "memory",
|
|
151
164
|
doc: "Character budget the memory store enforces for USER.md.",
|
|
152
|
-
label: "
|
|
153
|
-
hint: "
|
|
165
|
+
label: "用户画像上限",
|
|
166
|
+
hint: "用户画像文件(USER.md)最多多少字符。",
|
|
154
167
|
control: "number",
|
|
155
168
|
unit: "字符",
|
|
156
|
-
values: []
|
|
169
|
+
values: [],
|
|
170
|
+
valueLabels: []
|
|
157
171
|
},
|
|
158
172
|
{
|
|
159
173
|
id: "addDatePrefix",
|
|
160
174
|
group: "memory",
|
|
161
175
|
doc: "Prefix stored memory entries with their date heading.",
|
|
162
|
-
label: "
|
|
163
|
-
hint: "
|
|
176
|
+
label: "记忆条目加日期",
|
|
177
|
+
hint: "写入时在条目开头加上日期。",
|
|
164
178
|
control: "switch",
|
|
165
179
|
unit: "",
|
|
166
|
-
values: []
|
|
180
|
+
values: [],
|
|
181
|
+
valueLabels: []
|
|
167
182
|
},
|
|
168
183
|
{
|
|
169
184
|
id: "maxConsolidationFailures",
|
|
170
185
|
group: "memory",
|
|
171
186
|
doc: "Consolidation failures one turn tolerates before the tool gives up.",
|
|
172
|
-
label: "
|
|
173
|
-
hint: "
|
|
187
|
+
label: "合并失败重试",
|
|
188
|
+
hint: "合并记忆连续失败多少次后停止。",
|
|
174
189
|
control: "number",
|
|
175
190
|
unit: "次",
|
|
176
|
-
values: []
|
|
191
|
+
values: [],
|
|
192
|
+
valueLabels: []
|
|
177
193
|
}
|
|
178
194
|
]
|
|
179
195
|
},
|
|
@@ -183,76 +199,87 @@ window.__ModuleLoader__.load({
|
|
|
183
199
|
{
|
|
184
200
|
id: "reviewSkillInterval",
|
|
185
201
|
group: "review",
|
|
186
|
-
doc: "
|
|
187
|
-
label: "
|
|
188
|
-
hint: "
|
|
202
|
+
doc: "Tool calls between skill-review injections: a turn advances by its tool-call count (a turn with none counts as one), or by 1 when the turn itself used a skill.",
|
|
203
|
+
label: "技能检查间隔",
|
|
204
|
+
hint: "每累计多少次工具调用检查一次技能:一轮按该轮工具调用数累加(一轮没有工具调用也算 1 次),本轮本身用到技能则只算 1 次。",
|
|
189
205
|
control: "number",
|
|
190
206
|
unit: "次",
|
|
191
|
-
values: []
|
|
207
|
+
values: [],
|
|
208
|
+
valueLabels: []
|
|
192
209
|
},
|
|
193
210
|
{
|
|
194
211
|
id: "reviewMemoryInterval",
|
|
195
212
|
group: "review",
|
|
196
|
-
doc: "
|
|
197
|
-
label: "
|
|
198
|
-
hint: "
|
|
213
|
+
doc: "Tool calls between memory-review injections: a turn advances by its tool-call count (a turn with none counts as one), or by 1 when the turn itself touched memory.",
|
|
214
|
+
label: "记忆检查间隔",
|
|
215
|
+
hint: "每累计多少次工具调用检查一次记忆:一轮按该轮工具调用数累加(一轮没有工具调用也算 1 次),本轮本身用到记忆则只算 1 次。",
|
|
199
216
|
control: "number",
|
|
200
217
|
unit: "次",
|
|
201
|
-
values: []
|
|
218
|
+
values: [],
|
|
219
|
+
valueLabels: []
|
|
202
220
|
},
|
|
203
221
|
{
|
|
204
222
|
id: "skillReviewTrigger",
|
|
205
223
|
group: "review",
|
|
206
224
|
doc: "Which channel may inject a skill review (cadence, completion, both).",
|
|
207
|
-
label: "
|
|
208
|
-
hint: "
|
|
225
|
+
label: "技能检查时机",
|
|
226
|
+
hint: "什么时候检查技能:按间隔/按任务完成/两者。",
|
|
209
227
|
control: "select",
|
|
210
228
|
unit: "",
|
|
211
229
|
values: [
|
|
212
230
|
"cadence",
|
|
213
231
|
"completion",
|
|
214
232
|
"both"
|
|
233
|
+
],
|
|
234
|
+
valueLabels: [
|
|
235
|
+
"按间隔",
|
|
236
|
+
"按任务完成",
|
|
237
|
+
"两者"
|
|
215
238
|
]
|
|
216
239
|
},
|
|
217
240
|
{
|
|
218
241
|
id: "skillReviewCompletionMinToolCalls",
|
|
219
242
|
group: "review",
|
|
220
243
|
doc: "Tool calls a task needs before the completion channel injects.",
|
|
221
|
-
label: "
|
|
222
|
-
hint: "
|
|
244
|
+
label: "完成时的最少工具调用",
|
|
245
|
+
hint: "选「按任务完成」时,任务至少用了几次工具才检查。",
|
|
223
246
|
control: "number",
|
|
224
247
|
unit: "次",
|
|
225
|
-
values: []
|
|
248
|
+
values: [],
|
|
249
|
+
valueLabels: []
|
|
226
250
|
},
|
|
227
251
|
{
|
|
228
252
|
id: "reviewEnabled",
|
|
229
253
|
group: "review",
|
|
230
254
|
doc: "Master switch for the review plugin.",
|
|
231
|
-
label: "
|
|
232
|
-
hint: "
|
|
255
|
+
label: "启用自动检查",
|
|
256
|
+
hint: "关闭后不再自动回顾会话。",
|
|
233
257
|
control: "switch",
|
|
234
258
|
unit: "",
|
|
235
|
-
values: []
|
|
259
|
+
values: [],
|
|
260
|
+
valueLabels: []
|
|
236
261
|
},
|
|
237
262
|
{
|
|
238
263
|
id: "reviewMode",
|
|
239
264
|
group: "review",
|
|
240
265
|
doc: "Run the review in the parent session (inject) or on a subagent.",
|
|
241
|
-
label: "
|
|
242
|
-
hint: "
|
|
266
|
+
label: "运行位置",
|
|
267
|
+
hint: "在哪里运行检查:在当前会话里/交给子代理。",
|
|
243
268
|
control: "select",
|
|
244
269
|
unit: "",
|
|
245
|
-
values: ["subagent", "inject"]
|
|
270
|
+
values: ["subagent", "inject"],
|
|
271
|
+
valueLabels: ["交给子代理", "在当前会话里"]
|
|
246
272
|
},
|
|
247
273
|
{
|
|
248
274
|
id: "reviewWakeInject",
|
|
249
275
|
group: "review",
|
|
250
276
|
doc: "Deliver the deferred review as a waking follow-up message.",
|
|
251
|
-
label: "
|
|
252
|
-
hint: "
|
|
277
|
+
label: "延后结果发提醒",
|
|
278
|
+
hint: "会话空闲时,把延后的检查结果作为一条提醒发给你。",
|
|
253
279
|
control: "switch",
|
|
254
280
|
unit: "",
|
|
255
|
-
values: []
|
|
281
|
+
values: [],
|
|
282
|
+
valueLabels: []
|
|
256
283
|
}
|
|
257
284
|
]
|
|
258
285
|
},
|
|
@@ -263,81 +290,89 @@ window.__ModuleLoader__.load({
|
|
|
263
290
|
id: "skillContentChars",
|
|
264
291
|
group: "write-caps",
|
|
265
292
|
doc: "Character cap on a SKILL.md body (tighten-only).",
|
|
266
|
-
label: "
|
|
267
|
-
hint: "
|
|
293
|
+
label: "技能文件正文上限",
|
|
294
|
+
hint: "技能 Markdown 正文最多多少字符(只能调小)。",
|
|
268
295
|
control: "number",
|
|
269
296
|
unit: "字符",
|
|
270
|
-
values: []
|
|
297
|
+
values: [],
|
|
298
|
+
valueLabels: []
|
|
271
299
|
},
|
|
272
300
|
{
|
|
273
301
|
id: "maxSkillFileBytes",
|
|
274
302
|
group: "write-caps",
|
|
275
303
|
doc: "Byte cap on one support file (tighten-only).",
|
|
276
|
-
label: "
|
|
277
|
-
hint: "
|
|
304
|
+
label: "附带文件大小上限",
|
|
305
|
+
hint: "技能附带文件单个最大多少字节(只能调小)。",
|
|
278
306
|
control: "number",
|
|
279
307
|
unit: "字节",
|
|
280
|
-
values: []
|
|
308
|
+
values: [],
|
|
309
|
+
valueLabels: []
|
|
281
310
|
},
|
|
282
311
|
{
|
|
283
312
|
id: "maxSkillNameLength",
|
|
284
313
|
group: "write-caps",
|
|
285
314
|
doc: "Character cap on a skill name (tighten-only).",
|
|
286
|
-
label: "
|
|
287
|
-
hint: "
|
|
315
|
+
label: "技能名长度上限",
|
|
316
|
+
hint: "技能名最多多少字符(只能调小)。",
|
|
288
317
|
control: "number",
|
|
289
318
|
unit: "字符",
|
|
290
|
-
values: []
|
|
319
|
+
values: [],
|
|
320
|
+
valueLabels: []
|
|
291
321
|
},
|
|
292
322
|
{
|
|
293
323
|
id: "maxDescriptionLength",
|
|
294
324
|
group: "write-caps",
|
|
295
325
|
doc: "Character cap on a skill description (tighten-only).",
|
|
296
|
-
label: "
|
|
297
|
-
hint: "
|
|
326
|
+
label: "技能描述长度上限",
|
|
327
|
+
hint: "技能描述最多多少字符(只能调小)。",
|
|
298
328
|
control: "number",
|
|
299
329
|
unit: "字符",
|
|
300
|
-
values: []
|
|
330
|
+
values: [],
|
|
331
|
+
valueLabels: []
|
|
301
332
|
},
|
|
302
333
|
{
|
|
303
334
|
id: "descriptionStrict",
|
|
304
335
|
group: "write-caps",
|
|
305
336
|
doc: "Refuse a description over the authoring bar instead of advising.",
|
|
306
|
-
label: "
|
|
307
|
-
hint: "
|
|
337
|
+
label: "描述不合规即拒绝",
|
|
338
|
+
hint: "描述不符合要求时直接拒绝写入(默认只提醒)。",
|
|
308
339
|
control: "switch",
|
|
309
340
|
unit: "",
|
|
310
|
-
values: []
|
|
341
|
+
values: [],
|
|
342
|
+
valueLabels: []
|
|
311
343
|
},
|
|
312
344
|
{
|
|
313
345
|
id: "strictCrossSource",
|
|
314
346
|
group: "write-caps",
|
|
315
347
|
doc: "Refuse writes whose catalog entry resolves outside the family.",
|
|
316
|
-
label: "
|
|
317
|
-
hint: "
|
|
348
|
+
label: "引用外部内容即拒绝",
|
|
349
|
+
hint: "技能引用了本插件之外的文件时拒绝写入。",
|
|
318
350
|
control: "switch",
|
|
319
351
|
unit: "",
|
|
320
|
-
values: []
|
|
352
|
+
values: [],
|
|
353
|
+
valueLabels: []
|
|
321
354
|
},
|
|
322
355
|
{
|
|
323
356
|
id: "citationPolicy",
|
|
324
357
|
group: "write-caps",
|
|
325
358
|
doc: "Refuse a move that would leave a dangling reference, or verify it.",
|
|
326
|
-
label: "
|
|
327
|
-
hint: "
|
|
359
|
+
label: "引用检查方式",
|
|
360
|
+
hint: "移动或合并技能时怎么处理引用:先检查/直接拒绝。",
|
|
328
361
|
control: "select",
|
|
329
362
|
unit: "",
|
|
330
|
-
values: ["verify", "refuse"]
|
|
363
|
+
values: ["verify", "refuse"],
|
|
364
|
+
valueLabels: ["先检查", "直接拒绝"]
|
|
331
365
|
},
|
|
332
366
|
{
|
|
333
367
|
id: "supportFileCharPolicy",
|
|
334
368
|
group: "write-caps",
|
|
335
369
|
doc: "Warn about an oversize support file, or refuse the write.",
|
|
336
|
-
label: "
|
|
337
|
-
hint: "
|
|
370
|
+
label: "附带文件超限时",
|
|
371
|
+
hint: "附带文件超出上限时:只提醒/拒绝写入。",
|
|
338
372
|
control: "select",
|
|
339
373
|
unit: "",
|
|
340
|
-
values: ["report", "enforce"]
|
|
374
|
+
values: ["report", "enforce"],
|
|
375
|
+
valueLabels: ["只提醒", "拒绝写入"]
|
|
341
376
|
}
|
|
342
377
|
]
|
|
343
378
|
},
|
|
@@ -347,11 +382,12 @@ window.__ModuleLoader__.load({
|
|
|
347
382
|
id: "entryPreviewChars",
|
|
348
383
|
group: "memory",
|
|
349
384
|
doc: "Characters of one memory entry shown in a tool result preview.",
|
|
350
|
-
label: "
|
|
351
|
-
hint: "
|
|
385
|
+
label: "记忆预览长度",
|
|
386
|
+
hint: "列表里每条记忆最多显示多少字符。",
|
|
352
387
|
control: "number",
|
|
353
388
|
unit: "字符",
|
|
354
|
-
values: []
|
|
389
|
+
values: [],
|
|
390
|
+
valueLabels: []
|
|
355
391
|
}]
|
|
356
392
|
}
|
|
357
393
|
];
|
|
@@ -372,46 +408,46 @@ window.__ModuleLoader__.load({
|
|
|
372
408
|
/** Chinese copy (the family's primary language). */
|
|
373
409
|
const zh = {
|
|
374
410
|
title: "自进化",
|
|
375
|
-
subtitle: "
|
|
376
|
-
overridden: "
|
|
377
|
-
deployment: "
|
|
378
|
-
loading: "
|
|
379
|
-
unavailable: "
|
|
380
|
-
readonly: "
|
|
411
|
+
subtitle: "下面按功能列出你能改的参数;没有列出的由安装时的配置决定。",
|
|
412
|
+
overridden: "我改过",
|
|
413
|
+
deployment: "默认",
|
|
414
|
+
loading: "正在读取设置…",
|
|
415
|
+
unavailable: "这个功能没有启动(对应的插件没有安装)",
|
|
416
|
+
readonly: "本次会话不允许写入设置(临时运行模式)",
|
|
381
417
|
apply: "保存",
|
|
382
|
-
reset: "
|
|
383
|
-
empty: "
|
|
418
|
+
reset: "恢复默认值",
|
|
419
|
+
empty: "这里没有可改项",
|
|
384
420
|
changed: "{n} 项已改",
|
|
385
421
|
discard: "放弃修改",
|
|
386
422
|
saving: "保存中…",
|
|
387
|
-
refused: "
|
|
388
|
-
cardReview: "
|
|
389
|
-
cardMemory: "
|
|
390
|
-
cardToolMemory: "
|
|
391
|
-
cardCurator: "
|
|
392
|
-
cardSkills: "
|
|
423
|
+
refused: "这个值没有被接受(界面上保持原样)。想看原因:运行 /evolution params 或 /evolution doctor。",
|
|
424
|
+
cardReview: "会话回顾",
|
|
425
|
+
cardMemory: "长期记忆",
|
|
426
|
+
cardToolMemory: "记忆写入",
|
|
427
|
+
cardCurator: "技能整理",
|
|
428
|
+
cardSkills: "技能写入规则"
|
|
393
429
|
};
|
|
394
430
|
/** English copy, key for key with {@link zh}. */
|
|
395
431
|
const en = {
|
|
396
432
|
title: "Self-evolution",
|
|
397
|
-
subtitle: "
|
|
398
|
-
overridden: "
|
|
399
|
-
deployment: "
|
|
400
|
-
loading: "reading
|
|
401
|
-
unavailable: "this
|
|
402
|
-
readonly: "
|
|
433
|
+
subtitle: "Changeable parameters are listed by feature below; anything not listed comes from the deployment configuration.",
|
|
434
|
+
overridden: "edited by you",
|
|
435
|
+
deployment: "default",
|
|
436
|
+
loading: "reading settings…",
|
|
437
|
+
unavailable: "this feature is not running (its plugin is not installed)",
|
|
438
|
+
readonly: "settings cannot be written in this session (temporary mode)",
|
|
403
439
|
apply: "Save",
|
|
404
|
-
reset: "Reset to
|
|
405
|
-
empty: "
|
|
440
|
+
reset: "Reset to default",
|
|
441
|
+
empty: "nothing to change here",
|
|
406
442
|
changed: "{n} changed",
|
|
407
443
|
discard: "Discard",
|
|
408
444
|
saving: "Saving…",
|
|
409
|
-
refused: "The
|
|
410
|
-
cardReview: "
|
|
411
|
-
cardMemory: "
|
|
412
|
-
cardToolMemory: "
|
|
413
|
-
cardCurator: "
|
|
414
|
-
cardSkills: "
|
|
445
|
+
refused: "The value was not accepted (the page keeps what you see). For the reason run /evolution params or /evolution doctor.",
|
|
446
|
+
cardReview: "Session review",
|
|
447
|
+
cardMemory: "Long-term memory",
|
|
448
|
+
cardToolMemory: "Writing memory",
|
|
449
|
+
cardCurator: "Skill tidy-up",
|
|
450
|
+
cardSkills: "Skill write rules"
|
|
415
451
|
};
|
|
416
452
|
/**
|
|
417
453
|
* Card titles per settings namespace. A namespace without an entry falls back to
|
|
@@ -435,6 +471,29 @@ window.__ModuleLoader__.load({
|
|
|
435
471
|
return language === "zh" ? zh[key] : en[key];
|
|
436
472
|
}
|
|
437
473
|
//#endregion
|
|
474
|
+
//#region src/client/settle.ts
|
|
475
|
+
/**
|
|
476
|
+
* Structural equality for the values a parameter can hold.
|
|
477
|
+
* @param a - one side of the comparison.
|
|
478
|
+
* @param b - the other side.
|
|
479
|
+
* @returns true when the two are the same value.
|
|
480
|
+
*/
|
|
481
|
+
function sameValue(a, b) {
|
|
482
|
+
if (Object.is(a, b)) return true;
|
|
483
|
+
if (typeof a !== typeof b || a === null || b === null) return false;
|
|
484
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Whether every staged write is visible in the user layer.
|
|
488
|
+
* @param pending - the writes awaiting a verdict.
|
|
489
|
+
* @param user - the user layer of the post-write snapshot; a non-section reads as empty.
|
|
490
|
+
* @returns true when each staged id now holds the value that was requested.
|
|
491
|
+
*/
|
|
492
|
+
function landedWrites(pending, user) {
|
|
493
|
+
const section = typeof user === "object" && user !== null && !Array.isArray(user) ? user : {};
|
|
494
|
+
return pending.every((entry) => sameValue(section[entry.id], entry.want));
|
|
495
|
+
}
|
|
496
|
+
//#endregion
|
|
438
497
|
//#region src/client/ParamCard.ts
|
|
439
498
|
/**
|
|
440
499
|
* One namespace's parameter card.
|
|
@@ -481,12 +540,6 @@ window.__ModuleLoader__.load({
|
|
|
481
540
|
function isSection(value) {
|
|
482
541
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
483
542
|
}
|
|
484
|
-
/** Let the settings store settle one write before the read-back (it batches updates). */
|
|
485
|
-
async function settle() {
|
|
486
|
-
await new Promise((done) => {
|
|
487
|
-
setTimeout(done, 60);
|
|
488
|
-
});
|
|
489
|
-
}
|
|
490
543
|
/** One field's control, typed from the registry. */
|
|
491
544
|
function FieldControl(props) {
|
|
492
545
|
const { field, text } = props;
|
|
@@ -514,10 +567,10 @@ window.__ModuleLoader__.load({
|
|
|
514
567
|
}, [field.values.includes(text) || text === "" ? null : (0, react.createElement)("option", {
|
|
515
568
|
key: text,
|
|
516
569
|
value: text
|
|
517
|
-
}, text), ...field.values.map((value) => (0, react.createElement)("option", {
|
|
570
|
+
}, text), ...field.values.map((value, index) => (0, react.createElement)("option", {
|
|
518
571
|
key: value,
|
|
519
572
|
value
|
|
520
|
-
}, value))]);
|
|
573
|
+
}, field.valueLabels[index] ?? value))]);
|
|
521
574
|
return (0, react.createElement)("input", {
|
|
522
575
|
...common,
|
|
523
576
|
type: field.control === "number" ? "number" : "text",
|
|
@@ -554,12 +607,14 @@ window.__ModuleLoader__.load({
|
|
|
554
607
|
const [draft, setDraft] = (0, react.useState)({});
|
|
555
608
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
556
609
|
const [error, setError] = (0, react.useState)("");
|
|
610
|
+
const [pending, setPending] = (0, react.useState)(null);
|
|
557
611
|
const snapshot = props.useParamSection((state) => state);
|
|
558
612
|
if (snapshot.status === "loading") return (0, react.createElement)("p", { className: "evolution-param-note" }, t("loading"));
|
|
559
613
|
if (snapshot.status === "unavailable") return (0, react.createElement)("p", { className: "evolution-param-note" }, t("unavailable"));
|
|
560
614
|
const user = isSection(snapshot.user) ? snapshot.user : {};
|
|
561
615
|
const value = isSection(snapshot.value) ? snapshot.value : {};
|
|
562
616
|
const disabled = !snapshot.writable;
|
|
617
|
+
const locked = disabled || busy;
|
|
563
618
|
const titleKey = NAMESPACE_TITLES[namespace];
|
|
564
619
|
const title = titleKey === void 0 ? namespace : t(titleKey);
|
|
565
620
|
const changed = fields.filter((field) => Object.hasOwn(user, field.id)).length;
|
|
@@ -571,27 +626,43 @@ window.__ModuleLoader__.load({
|
|
|
571
626
|
[id]: next
|
|
572
627
|
});
|
|
573
628
|
};
|
|
629
|
+
/**
|
|
630
|
+
* Settle a staged write against the Host's answer.
|
|
631
|
+
*
|
|
632
|
+
* A resolved write promise is not the verdict: both settings scopes this bundle runs
|
|
633
|
+
* on (the shell's own controller and the bridge variant) finish their recovery read
|
|
634
|
+
* BEFORE it resolves, so a value the Host refused is simply still the previous one.
|
|
635
|
+
* Presence in the user layer therefore cannot answer the question — on a field the
|
|
636
|
+
* operator had already overridden, a refused value's key is present as well. The
|
|
637
|
+
* verdict asks whether each staged id now holds the value this card requested.
|
|
638
|
+
* The check lives in an effect, not in the save callback, because the raw snapshot is
|
|
639
|
+
* reachable only through the render-time seat — the face's `hooks` compartment never
|
|
640
|
+
* reaches the component.
|
|
641
|
+
*/
|
|
642
|
+
(0, react.useEffect)(() => {
|
|
643
|
+
if (pending === null) return;
|
|
644
|
+
setPending(null);
|
|
645
|
+
setBusy(false);
|
|
646
|
+
if (landedWrites(pending, snapshot.user)) setDraft({});
|
|
647
|
+
else setError(t("refused"));
|
|
648
|
+
}, [
|
|
649
|
+
pending,
|
|
650
|
+
snapshot.user,
|
|
651
|
+
t
|
|
652
|
+
]);
|
|
574
653
|
const save = () => {
|
|
575
654
|
setBusy(true);
|
|
576
655
|
setError("");
|
|
577
656
|
(async () => {
|
|
578
657
|
try {
|
|
579
658
|
for (const field of dirty) await write(field.id, parseFor(field.control, textOf(field)));
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
landed = isSection(fresh.user) ? fresh.user : {};
|
|
585
|
-
}
|
|
586
|
-
if (dirty.some((field) => !Object.hasOwn(landed, field.id))) {
|
|
587
|
-
setError(t("refused"));
|
|
588
|
-
return;
|
|
589
|
-
}
|
|
590
|
-
setDraft({});
|
|
659
|
+
setPending(dirty.map((field) => ({
|
|
660
|
+
id: field.id,
|
|
661
|
+
want: parseFor(field.control, textOf(field))
|
|
662
|
+
})));
|
|
591
663
|
} catch (caught) {
|
|
592
|
-
setError(caught instanceof Error && caught.message !== "" ? caught.message : t("refused"));
|
|
593
|
-
} finally {
|
|
594
664
|
setBusy(false);
|
|
665
|
+
setError(caught instanceof Error && caught.message !== "" ? caught.message : t("refused"));
|
|
595
666
|
}
|
|
596
667
|
})();
|
|
597
668
|
};
|
|
@@ -607,7 +678,7 @@ window.__ModuleLoader__.load({
|
|
|
607
678
|
field,
|
|
608
679
|
text: textOf(field),
|
|
609
680
|
overridden: Object.hasOwn(user, field.id),
|
|
610
|
-
disabled,
|
|
681
|
+
disabled: locked,
|
|
611
682
|
t,
|
|
612
683
|
onChange: change,
|
|
613
684
|
clear
|
|
@@ -17,7 +17,14 @@ import { type ReactNode } from 'react';
|
|
|
17
17
|
import type { ClientParamField } from './generated-params.ts';
|
|
18
18
|
import { type MessageKey } from './messages.ts';
|
|
19
19
|
import type { ParamSectionSnapshot, ParamSectionSource } from './seam.ts';
|
|
20
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* Injected face: plain data and callbacks, plus the hook seat.
|
|
22
|
+
*
|
|
23
|
+
* The `hooks` compartment belongs to the shell, not to the component: the
|
|
24
|
+
* renderer binds its entries to `use<Name>` props and omits `hooks` from what
|
|
25
|
+
* the component receives. A card that reads `props.hooks` therefore reads a prop
|
|
26
|
+
* that never exists.
|
|
27
|
+
*/
|
|
21
28
|
export interface ParamCardFace {
|
|
22
29
|
namespace: string;
|
|
23
30
|
fields: readonly ClientParamField[];
|
|
@@ -28,8 +35,12 @@ export interface ParamCardFace {
|
|
|
28
35
|
paramSection: ParamSectionSource;
|
|
29
36
|
};
|
|
30
37
|
}
|
|
31
|
-
/**
|
|
32
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Props the renderer binds for one card: the inject face minus its hook compartment,
|
|
40
|
+
* plus the seats bound from it. Spelled the way the shell derives it, so reaching for
|
|
41
|
+
* `props.hooks` fails the type check instead of failing at runtime.
|
|
42
|
+
*/
|
|
43
|
+
export type ParamCardProps = Omit<ParamCardFace, 'hooks'> & {
|
|
33
44
|
/** Bound from \`hooks.paramSection\` by the renderer. */
|
|
34
45
|
readonly useParamSection: <T>(selector: (state: ParamSectionSnapshot) => T) => T;
|
|
35
46
|
};
|
|
@@ -20,6 +20,8 @@ export interface ClientParamField {
|
|
|
20
20
|
unit: string;
|
|
21
21
|
/** Allowed values for a select field ([] otherwise). */
|
|
22
22
|
values: readonly string[];
|
|
23
|
+
/** Display names for `values`, same order ([] shows the raw value). */
|
|
24
|
+
valueLabels: readonly string[];
|
|
23
25
|
}
|
|
24
26
|
/** One card: the settings namespace plus the fields it exposes. */
|
|
25
27
|
export interface ClientParamSection {
|
|
@@ -13,24 +13,24 @@ export declare const NS = "evolution-settings";
|
|
|
13
13
|
/** Chinese copy (the family's primary language). */
|
|
14
14
|
export declare const zh: {
|
|
15
15
|
readonly title: "自进化";
|
|
16
|
-
readonly subtitle: "
|
|
17
|
-
readonly overridden: "
|
|
18
|
-
readonly deployment: "
|
|
19
|
-
readonly loading: "
|
|
20
|
-
readonly unavailable: "
|
|
21
|
-
readonly readonly: "
|
|
16
|
+
readonly subtitle: "下面按功能列出你能改的参数;没有列出的由安装时的配置决定。";
|
|
17
|
+
readonly overridden: "我改过";
|
|
18
|
+
readonly deployment: "默认";
|
|
19
|
+
readonly loading: "正在读取设置…";
|
|
20
|
+
readonly unavailable: "这个功能没有启动(对应的插件没有安装)";
|
|
21
|
+
readonly readonly: "本次会话不允许写入设置(临时运行模式)";
|
|
22
22
|
readonly apply: "保存";
|
|
23
|
-
readonly reset: "
|
|
24
|
-
readonly empty: "
|
|
23
|
+
readonly reset: "恢复默认值";
|
|
24
|
+
readonly empty: "这里没有可改项";
|
|
25
25
|
readonly changed: "{n} 项已改";
|
|
26
26
|
readonly discard: "放弃修改";
|
|
27
27
|
readonly saving: "保存中…";
|
|
28
|
-
readonly refused: "
|
|
29
|
-
readonly cardReview: "
|
|
30
|
-
readonly cardMemory: "
|
|
31
|
-
readonly cardToolMemory: "
|
|
32
|
-
readonly cardCurator: "
|
|
33
|
-
readonly cardSkills: "
|
|
28
|
+
readonly refused: "这个值没有被接受(界面上保持原样)。想看原因:运行 /evolution params 或 /evolution doctor。";
|
|
29
|
+
readonly cardReview: "会话回顾";
|
|
30
|
+
readonly cardMemory: "长期记忆";
|
|
31
|
+
readonly cardToolMemory: "记忆写入";
|
|
32
|
+
readonly cardCurator: "技能整理";
|
|
33
|
+
readonly cardSkills: "技能写入规则";
|
|
34
34
|
};
|
|
35
35
|
/** English copy, key for key with {@link zh}. */
|
|
36
36
|
export declare const en: Record<keyof typeof zh, string>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The save verdict: did the Host take the values this card asked for?
|
|
3
|
+
*
|
|
4
|
+
* A write promise that resolves is not a verdict. Both settings scopes this bundle runs
|
|
5
|
+
* on finish a recovery read before it resolves, so a value the Host refused is simply
|
|
6
|
+
* still the previous one. Presence in the user layer cannot tell refusal from acceptance
|
|
7
|
+
* on a field the operator had already overridden — the refused value's key is present
|
|
8
|
+
* too — so the verdict compares what each staged id holds now against what was requested.
|
|
9
|
+
* @module @lmzhen/dsh-evolution-settings-ui
|
|
10
|
+
*/
|
|
11
|
+
/** One staged write awaiting its verdict. */
|
|
12
|
+
export interface PendingWrite {
|
|
13
|
+
/** Parameter id the write named. */
|
|
14
|
+
readonly id: string;
|
|
15
|
+
/** The value handed to the scope's `set`. */
|
|
16
|
+
readonly want: unknown;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Whether every staged write is visible in the user layer.
|
|
20
|
+
* @param pending - the writes awaiting a verdict.
|
|
21
|
+
* @param user - the user layer of the post-write snapshot; a non-section reads as empty.
|
|
22
|
+
* @returns true when each staged id now holds the value that was requested.
|
|
23
|
+
*/
|
|
24
|
+
export declare function landedWrites(pending: readonly PendingWrite[], user: unknown): boolean;
|
|
25
|
+
//# sourceMappingURL=settle.d.ts.map
|
package/package.json
CHANGED