@starwish-ai/xingyuan-dsh 0.6.1 → 0.6.2

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/CHANGELOG.md CHANGED
@@ -5,6 +5,35 @@ All notable changes to this project are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.6.2] - 2026-09-06
9
+
10
+ ### Added
11
+ - **Fine-grained write-confirmation settings**: the master switch ("写操作二次确认")
12
+ now expands into six per-category detail toggles — create (incl. micro-action
13
+ breakdown), check-in, cancel check-in, claim, edit, memory save. Detail rows stay
14
+ visible under the master switch and grey out (explicit disabled styling) when it is
15
+ off; the delete row remains locked (always confirmed, no off switch). Defaults
16
+ reproduce the previous matrix: create / check-in / cancel check-in ask; claim /
17
+ edit / memory save run on your instruction. Claim, edit and memory-save tools
18
+ gained real confirmation hooks (600 s HITL timeout, validation before asking,
19
+ cancel exits without writing). Chat-side HITL only — page dialogs are unchanged.
20
+ Zero migration: absent settings resolve to the defaults.
21
+
22
+ ### Changed
23
+ - `rename_wish_category` validates before asking: renaming a non-existent category
24
+ errors up front instead of after a confirmation; renaming a color-override-only
25
+ category (zero wishes) now succeeds and reports "0 wishes" instead of falsely
26
+ erroring "category does not exist" after the override had already been migrated.
27
+
28
+ ### Fixed
29
+ - sqlite backend `close()` now actually releases the database handle (was an empty
30
+ implementation), so hot-reload / unload no longer keeps the file locked.
31
+ - Bundle main row declares the previously missing
32
+ `storageBackendServiceKey('sqlite')` injection key.
33
+ - StaleBanner text contrast: new `--xyd-on-warn` token pair reaches WCAG 4.5:1 on the
34
+ warn background in both light and dark themes (locked by a WCAG computation test).
35
+ - README confirmation wording no longer contradicts AGENTS.md §5.5.
36
+
8
37
  ## [0.6.1] - 2026-09-05
9
38
 
10
39
  ### Added
package/README.en-US.md CHANGED
@@ -18,7 +18,7 @@ XingYuan is a wish & habit-building companion, shipped as a DeepSeek Harness plu
18
18
  - **Session view tabs**: Today / Wishes / Tasks / Calendar / Growth / Memory — buttons call action endpoints directly; shown only in XingYuan-preset sessions by default, switchable to always show/hide and per-tab in Settings
19
19
  - **Memory**: important memories injected into context (limit configurable), full CRUD
20
20
  - **Charts**: 15 chart types rendered as cards
21
- - **Safe writes**: write operations confirmed by default (configurable); deletion always confirmed
21
+ - **Safe writes**: write confirmation is per-category (create / check-in / undo / claim / edit / save memory); deletion always confirms and cannot be turned off; defaults match previous versions
22
22
  - **Theming**: light/dark follows the app theme
23
23
 
24
24
  Tools are only mounted on sessions using the XingYuan preset; other sessions are unaffected.
@@ -41,7 +41,7 @@ Business data lives at `~/.dsh/xingyuan/xingyuan.sqlite` and survives uninstall
41
41
  | --- | --- |
42
42
  | Coach style | Gentle / humorous / strict |
43
43
  | User profile | Nickname, occupation, interests |
44
- | Write confirmation | Whether create / check-in writes need confirmation (deletion always does) |
44
+ | Write confirmation | Master switch + per-category toggles (create / check-in / undo / claim / edit / save memory); deletion always confirms and cannot be turned off |
45
45
  | Memory injection limit | Max memories injected per turn |
46
46
  | Tab visibility | Three modes (follow session / always show / always hide) plus per-tab toggles; defaults to follow session |
47
47
  | Confirm card language | Language of the in-chat confirmation card (Chinese / English; the platform does not expose UI language to plugins, defaults to Chinese) |
package/README.md CHANGED
@@ -20,7 +20,7 @@
20
20
  默认仅星愿预设的会话显示,可在设置里切换「始终显示 / 始终隐藏」并按标签勾选
21
21
  - **记忆**:重要记忆自动注入上下文(上限可配),支持增删改查
22
22
  - **图表**:15 种统计图表自动渲染为卡片
23
- - **安全确认**:写操作默认二次确认(可关闭),删除始终确认
23
+ - **安全确认**:写操作二次确认按类目细化(创建 / 打卡 / 取消打卡 / 领取 / 修改 / 保存记忆),删除始终确认不可关闭;默认与此前版本一致
24
24
  - **主题适配**:深浅色跟随应用主题
25
25
 
26
26
  工具只挂载在选择星愿 preset 的会话上,不影响其他会话。
@@ -43,7 +43,7 @@ dsh plugin --profile web add @starwish-ai/xingyuan-dsh
43
43
  | --- | --- |
44
44
  | 教练风格 | 温柔 / 幽默 / 严格 |
45
45
  | 用户画像 | 昵称、职业、兴趣 |
46
- | 写操作二次确认 | 创建 / 打卡类写操作是否确认(删除始终确认) |
46
+ | 写操作二次确认 | 总开关 + 六个类目明细(创建 / 打卡 / 取消打卡 / 领取 / 修改 / 保存记忆)是否确认;删除始终确认、不可关闭 |
47
47
  | 记忆注入上限 | 每次注入上下文的记忆条数上限 |
48
48
  | 标签页显示 | 三态模式(跟随会话 / 始终显示 / 始终隐藏)+ 六个标签逐个勾选;默认跟随会话 |
49
49
  | 确认卡语言 | 对话中确认卡的显示语言(中文 / English;平台不向插件暴露界面语言,默认中文) |
@@ -275,7 +275,17 @@ export const ZH_DICT = {
275
275
  'settings.pref.notRegistered': '偏好暂不可用(组件未就绪),请稍后重试。',
276
276
  'settings.pref.readOnly': '当前设置为只读,无法保存修改。',
277
277
  'settings.pref.confirmWrites': '写操作二次确认',
278
- 'settings.pref.confirmWritesHint': '创建愿望/任务、打卡、取消打卡时先弹应用内确认卡(删除始终确认);关闭后对话中的这类操作将直接执行。',
278
+ 'settings.pref.confirmWritesHint': '总开关:开启后按下方类目决定是否弹确认卡,关闭后除删除(始终确认)外一律直接执行。',
279
+ 'settings.pref.confirmOps.group': '确认类目',
280
+ 'settings.pref.confirmOps.create': '创建愿望/任务(含微行动拆解)',
281
+ 'settings.pref.confirmOps.checkin': '打卡',
282
+ 'settings.pref.confirmOps.cancelCheckin': '取消打卡',
283
+ 'settings.pref.confirmOps.claim': '领取任务',
284
+ 'settings.pref.confirmOps.update': '修改愿望/任务/分类',
285
+ 'settings.pref.confirmOps.memorySave': '保存记忆',
286
+ 'settings.pref.confirmOps.delete': '删除(含批量、微行动重开)',
287
+ 'settings.pref.confirmOps.deleteHint': '始终确认,不可关闭:删除不可恢复。',
288
+ 'settings.pref.confirmOps.hint': '类目明细在总开关开启时生效;默认与之前版本一致。',
279
289
  'settings.pref.memoryLimit': '记忆注入上限',
280
290
  'settings.pref.memoryLimitHint': '每次对话自动注入上下文的记忆条数上限(5-200,默认 40);失焦后保存。',
281
291
  'settings.pref.limitInvalid': '请输入 5-200 的整数',
@@ -694,7 +704,17 @@ const EN_DICT = {
694
704
  'settings.pref.notRegistered': 'Preferences are unavailable right now (component not ready); please retry later.',
695
705
  'settings.pref.readOnly': 'Settings are read-only; changes cannot be saved.',
696
706
  'settings.pref.confirmWrites': 'Confirm write actions',
697
- 'settings.pref.confirmWritesHint': 'Show an in-app confirmation before creating wishes/tasks, checking in or canceling (deletes always confirm). Turn off to execute directly.',
707
+ 'settings.pref.confirmWritesHint': 'Master switch: when on, confirmations follow the categories below; when off, everything except deletes (always confirmed) runs directly.',
708
+ 'settings.pref.confirmOps.group': 'Confirmation categories',
709
+ 'settings.pref.confirmOps.create': 'Create wishes/tasks (incl. micro-action plans)',
710
+ 'settings.pref.confirmOps.checkin': 'Check-ins',
711
+ 'settings.pref.confirmOps.cancelCheckin': 'Cancel check-ins',
712
+ 'settings.pref.confirmOps.claim': 'Claiming tasks',
713
+ 'settings.pref.confirmOps.update': 'Editing wishes/tasks/categories',
714
+ 'settings.pref.confirmOps.memorySave': 'Saving memories',
715
+ 'settings.pref.confirmOps.delete': 'Deletes (incl. batch & restarts)',
716
+ 'settings.pref.confirmOps.deleteHint': 'Always confirms and cannot be turned off; deletes are irreversible.',
717
+ 'settings.pref.confirmOps.hint': 'Category switches apply while the master switch is on; defaults match previous versions.',
698
718
  'settings.pref.memoryLimit': 'Memory injection limit',
699
719
  'settings.pref.memoryLimitHint': 'Max memories auto-injected per chat (5-200, default 40); saved when you leave the field.',
700
720
  'settings.pref.limitInvalid': 'Enter an integer between 5 and 200',
@@ -6,7 +6,7 @@ import { getJson, postJson } from '../api.js';
6
6
  import { toastError } from '../ui.js';
7
7
  import { useXyT, activeLocale } from '../i18n.js';
8
8
  import { TAB_IDS } from '../../tab-policy.js';
9
- import { MEMORY_LIMIT_MAX, MEMORY_LIMIT_MIN, PREF_DEFAULTS, parseMemoryLimit, } from '../../pref-policy.js';
9
+ import { CONFIRM_OPS, MEMORY_LIMIT_MAX, MEMORY_LIMIT_MIN, PREF_DEFAULTS, normalizeConfirmOps, parseMemoryLimit, } from '../../pref-policy.js';
10
10
  const COACH_IDS = ['gentle', 'strict', 'humorous'];
11
11
  const COACH_KEYS = ['settings.coach.gentle', 'settings.coach.strict', 'settings.coach.humorous'];
12
12
  /** 显隐三态(顺序即分段按钮顺序;与 tab-policy 字面量同源)。 */
@@ -24,6 +24,15 @@ const TAB_LABEL_KEYS = {
24
24
  growth: 'tab.growth',
25
25
  memory: 'tab.memory',
26
26
  };
27
+ /** 确认类目的显示名键(与 pref-policy CONFIRM_OPS 一一对应)。 */
28
+ const CONFIRM_OP_LABEL_KEYS = {
29
+ create: 'settings.pref.confirmOps.create',
30
+ checkin: 'settings.pref.confirmOps.checkin',
31
+ cancelCheckin: 'settings.pref.confirmOps.cancelCheckin',
32
+ claim: 'settings.pref.confirmOps.claim',
33
+ update: 'settings.pref.confirmOps.update',
34
+ memorySave: 'settings.pref.confirmOps.memorySave',
35
+ };
27
36
  export function SettingsSection(props) {
28
37
  const t = useXyT();
29
38
  const scope = props.scope;
@@ -52,6 +61,8 @@ export function SettingsSection(props) {
52
61
  const [savedMsg, setSavedMsg] = useState('');
53
62
  // 二次确认开关:本地乐观值(undefined=跟随远端快照)——写入在途时 UI 立即响应,失败回滚
54
63
  const [pendingToggle, setPendingToggle] = useState(undefined);
64
+ // 确认类目明细:乐观值为「合并后的完整对象」(每次写整体下发),失败回滚
65
+ const [pendingOps, setPendingOps] = useState(undefined);
55
66
  // 标签页显隐:模式与勾选各自乐观(写入在途时禁用对应控件,失败回滚 + toast)
56
67
  const [pendingMode, setPendingMode] = useState(undefined);
57
68
  const [pendingHidden, setPendingHidden] = useState(undefined);
@@ -67,6 +78,8 @@ export function SettingsSection(props) {
67
78
  // 开关显示值:乐观本地值优先(写入在途),否则远端快照,值缺席回落 schema 默认。
68
79
  // 不可写成 `!== false`——那样「值未知」会被渲染成「已开启」,安全策略类开关尤其不能撒谎。
69
80
  const confirmWrites = pendingToggle ?? snap.value?.confirmWrites ?? PREF_DEFAULTS.confirmWrites;
81
+ // 确认类目显示值:乐观优先 → 快照(脏键容错回落默认)→ 全默认
82
+ const confirmOps = pendingOps ?? normalizeConfirmOps(snap.value?.confirmOps);
70
83
  // 偏好区提示:判定顺序不可换——memory 模式下 status 同样是 unavailable,
71
84
  // 先判 mode 才不会把「远程/临时模式」误报成「命名空间未就绪」
72
85
  const prefNoticeKey = snap.status === 'loading' ? 'settings.pref.loading'
@@ -165,7 +178,13 @@ export function SettingsSection(props) {
165
178
  const verifyWritten = (seq, field, value) => {
166
179
  if (seq !== writeSeqRef.current)
167
180
  return;
168
- if (scope.getSnapshot().value?.[field] !== value)
181
+ const current = scope.getSnapshot().value?.[field];
182
+ // 对象字段(confirmOps)按 CONFIRM_OPS 逐键比对(对键序不敏感;JSON.stringify
183
+ // 比对会依赖两侧对象键序巧合一致);标量直接全等
184
+ const same = typeof value === 'object'
185
+ ? CONFIRM_OPS.every((key) => current?.[key] === value[key])
186
+ : current === value;
187
+ if (!same)
169
188
  toastError(new Error(t('settings.pref.writeFailed')));
170
189
  };
171
190
  /** 标签页显隐命名空间的写后校验(与 verifyWritten 同款,命名空间独立故写队列与序号独立)。 */
@@ -195,6 +214,26 @@ export function SettingsSection(props) {
195
214
  toastError(err);
196
215
  });
197
216
  };
217
+ /**
218
+ * 切换单个确认类目:整体写合并后的 confirmOps 对象(与 hiddenTabs 整组写入同款),
219
+ * 乐观值顶住回显、失败回滚 + toast。总开关关闭时明细行禁用(不弹卡由总开关决定)。
220
+ */
221
+ const toggleConfirmOp = (op, next) => {
222
+ if (!writable || pendingOps !== undefined || !confirmWrites || confirmOps[op] === next)
223
+ return;
224
+ const merged = { ...confirmOps, [op]: next };
225
+ setPendingOps(merged);
226
+ const seq = ++writeSeqRef.current;
227
+ void scope.set('confirmOps', merged)
228
+ .then(() => {
229
+ setPendingOps(undefined);
230
+ verifyWritten(seq, 'confirmOps', merged);
231
+ })
232
+ .catch((err) => {
233
+ setPendingOps(undefined);
234
+ toastError(err);
235
+ });
236
+ };
198
237
  const commitLimit = () => {
199
238
  const raw = limitDraft;
200
239
  setLimitDraft(null);
@@ -319,7 +358,32 @@ export function SettingsSection(props) {
319
358
  toastError(err);
320
359
  });
321
360
  },
322
- }), t('settings.pref.confirmWrites')), createElement('p', { className: 'xy-hint' }, t('settings.pref.confirmWritesHint'))), createElement('label', { className: 'xy-field' }, createElement('span', { className: 'xy-field-head' }, t('settings.pref.memoryLimit')), createElement('input', {
361
+ }), t('settings.pref.confirmWrites')), createElement('p', { className: 'xy-hint' }, t('settings.pref.confirmWritesHint'))),
362
+ // 确认类目明细(锁定删除行恒展示):总开关关闭时整组禁用置灰——不弹卡由总开关
363
+ // 决定;禁用原因由组尾 hint 承担(aria-describedby 关联),不在每行重复
364
+ createElement('div', {
365
+ className: 'xy-confirm-ops',
366
+ role: 'group',
367
+ 'aria-labelledby': 'xy-confirm-ops-head',
368
+ 'aria-describedby': 'xy-confirm-ops-hint',
369
+ }, createElement('span', { className: 'xy-op-group-head', id: 'xy-confirm-ops-head' }, t('settings.pref.confirmOps.group')), ...CONFIRM_OPS.map((op) => createElement('label', { className: 'xy-op-row', key: op }, createElement('input', {
370
+ type: 'checkbox',
371
+ className: 'xy-toggle',
372
+ name: `confirmOps.${op}`,
373
+ checked: confirmOps[op],
374
+ disabled: !writable || pendingOps !== undefined || !confirmWrites,
375
+ onChange: (e) => toggleConfirmOp(op, e.target.checked),
376
+ }), t(CONFIRM_OP_LABEL_KEYS[op]))),
377
+ // 锁定类目:div + 只读开关(不可交互故不用 label),说明单独一行与文字对齐
378
+ createElement('div', { className: 'xy-op-row xy-op-row-locked' }, createElement('input', {
379
+ type: 'checkbox',
380
+ className: 'xy-toggle',
381
+ name: 'confirmOps.deleteLocked',
382
+ checked: true,
383
+ readOnly: true,
384
+ disabled: true,
385
+ 'aria-label': t('settings.pref.confirmOps.delete'),
386
+ }), createElement('span', null, t('settings.pref.confirmOps.delete'))), createElement('span', { className: 'xy-hint xy-op-locked-hint' }, t('settings.pref.confirmOps.deleteHint')), createElement('span', { className: 'xy-hint', id: 'xy-confirm-ops-hint' }, t('settings.pref.confirmOps.hint'))), createElement('label', { className: 'xy-field' }, createElement('span', { className: 'xy-field-head' }, t('settings.pref.memoryLimit')), createElement('input', {
323
387
  type: 'number', min: MEMORY_LIMIT_MIN, max: MEMORY_LIMIT_MAX,
324
388
  className: 'xy-input xy-input-num', name: 'memoryInjectLimit', inputMode: 'numeric',
325
389
  'aria-label': t('settings.pref.memoryLimit'),
@@ -60,6 +60,9 @@ export const STYLE_TEXT = `
60
60
  --xyd-on-c2:#7c4a03;
61
61
  --xyd-on-c3:#065f46;
62
62
  --xyd-on-dcell:#0b3d26;
63
+ /* warn-soft 底的前景伙伴(StaleBanner 文字):壳通用次要色对琥珀柔底浅 4.19 /
64
+ 深 2.95,跌破 WCAG 1.4.3 AA——按 ≥4.5:1 校准的专用对(on-c2 同族暖琥珀前景) */
65
+ --xyd-on-warn:#7c4a03;
63
66
  /* 阴影对(卡片悬停/toast/弹窗/英雄卡):深浅各自配档,禁止正文区裸写 rgba */
64
67
  --xyd-shadow-card:0 2px 10px rgba(15,23,42,.07);
65
68
  --xyd-shadow-toast:0 8px 24px -8px rgba(15,23,42,.2),0 2px 8px -2px rgba(15,23,42,.1);
@@ -109,6 +112,7 @@ body[data-ds-dark-theme]{
109
112
  --xyd-on-c2:#ffd9a8;
110
113
  --xyd-on-c3:#a7f3c9;
111
114
  --xyd-on-dcell:#a7f3c9;
115
+ --xyd-on-warn:#ffd9a8;
112
116
  --xyd-shadow-card:0 2px 10px rgba(0,0,0,.32);
113
117
  --xyd-shadow-toast:0 8px 24px -8px rgba(0,0,0,.5),0 2px 8px -2px rgba(0,0,0,.35);
114
118
  --xyd-shadow-modal:0 24px 48px -16px rgba(0,0,0,.55),0 4px 12px -4px rgba(0,0,0,.4);
@@ -276,6 +280,9 @@ body[data-ds-dark-theme] .xy-badge-cat{background:hsl(var(--cat-h,275) calc(var(
276
280
  .xy-banner-ok{margin-top:10px;padding:10px 12px;border-radius:var(--xyd-r-inner);background:var(--xyd-ok-soft);border:1px solid var(--xyd-ok-border);font-size:13px}
277
281
  /* 刷新失败但旧数据仍在的诚实降级横幅:琥珀虚线面板(警示但不恐吓——动作已成功) */
278
282
  .xy-stalerow{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:10px;padding:8px 12px;border:1px dashed var(--xyd-warn-border);border-radius:var(--xyd-r-inner);background:var(--xyd-warn-soft)}
283
+ /* 提示文字用琥珀柔底的专用前景对(--xyd-on-warn):次要通用色在此底上浅 4.19 / 深
284
+ * 2.95,深色远低于 AA;重试键保持 label-primary(对琥珀柔底 >10:1),无需处理 */
285
+ .xy-stalerow .xy-meta{color:var(--xyd-on-warn)}
279
286
  .xy-stalerow .xy-btn-inline{margin-left:0}
280
287
 
281
288
  /* ===== 分组卡(任务页状态分桶 / 今日页待打卡·已完成共用)=====
@@ -518,6 +525,16 @@ button.xy-growth-col.xy-hover{box-shadow:inset 0 0 0 2px var(--xyd-accent-ring)}
518
525
  .xy-toggle::after{content:'';position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:var(--dsw-alias-label-primary);transition:transform .15s}
519
526
  .xy-toggle:checked::after{transform:translateX(16px);background:var(--xyd-on-accent)}
520
527
  .xy-hint{color:var(--dsw-alias-label-secondary);font-size:12px;margin:0}
528
+ /* 确认类目明细:缩进嵌在总开关下(与「总开关 + 明细」分层同构),行 = 开关 + 名称;
529
+ * 锁定行(删除)为只读展示,其说明行缩进与文字对齐(开关 38px + 间距 8px) */
530
+ .xy-confirm-ops{display:flex;flex-direction:column;gap:8px;margin-left:22px}
531
+ /* 禁用弱化必须显式写:自定义外观的 toggle 不吃原生禁用变灰,总开关关闭时明细行
532
+ * 否则与可用态无法区分;锁定的删除行恒禁用,常态弱化即「冻结在开启态」的语义 */
533
+ .xy-confirm-ops .xy-toggle:disabled{opacity:.4;cursor:default}
534
+ .xy-op-group-head{font-weight:600;font-size:13px}
535
+ .xy-op-row{display:flex;align-items:center;gap:8px;cursor:pointer;font-size:13px}
536
+ .xy-op-row-locked{color:var(--dsw-alias-label-secondary);cursor:default}
537
+ .xy-op-locked-hint{padding-left:46px}
521
538
 
522
539
  /* ===== 详情聚合视图 ===== */
523
540
  /* 段间 10px 呼吸;每段内部「标签 → 内容」固定 5px 纵向节奏(label 不再与内容文字挤行)。
package/lib/client.js CHANGED
@@ -268,7 +268,17 @@ const ZH_DICT = {
268
268
  "settings.pref.notRegistered": "偏好暂不可用(组件未就绪),请稍后重试。",
269
269
  "settings.pref.readOnly": "当前设置为只读,无法保存修改。",
270
270
  "settings.pref.confirmWrites": "写操作二次确认",
271
- "settings.pref.confirmWritesHint": "创建愿望/任务、打卡、取消打卡时先弹应用内确认卡(删除始终确认);关闭后对话中的这类操作将直接执行。",
271
+ "settings.pref.confirmWritesHint": "总开关:开启后按下方类目决定是否弹确认卡,关闭后除删除(始终确认)外一律直接执行。",
272
+ "settings.pref.confirmOps.group": "确认类目",
273
+ "settings.pref.confirmOps.create": "创建愿望/任务(含微行动拆解)",
274
+ "settings.pref.confirmOps.checkin": "打卡",
275
+ "settings.pref.confirmOps.cancelCheckin": "取消打卡",
276
+ "settings.pref.confirmOps.claim": "领取任务",
277
+ "settings.pref.confirmOps.update": "修改愿望/任务/分类",
278
+ "settings.pref.confirmOps.memorySave": "保存记忆",
279
+ "settings.pref.confirmOps.delete": "删除(含批量、微行动重开)",
280
+ "settings.pref.confirmOps.deleteHint": "始终确认,不可关闭:删除不可恢复。",
281
+ "settings.pref.confirmOps.hint": "类目明细在总开关开启时生效;默认与之前版本一致。",
272
282
  "settings.pref.memoryLimit": "记忆注入上限",
273
283
  "settings.pref.memoryLimitHint": "每次对话自动注入上下文的记忆条数上限(5-200,默认 40);失焦后保存。",
274
284
  "settings.pref.limitInvalid": "请输入 5-200 的整数",
@@ -675,7 +685,17 @@ const EN_DICT = {
675
685
  "settings.pref.notRegistered": "Preferences are unavailable right now (component not ready); please retry later.",
676
686
  "settings.pref.readOnly": "Settings are read-only; changes cannot be saved.",
677
687
  "settings.pref.confirmWrites": "Confirm write actions",
678
- "settings.pref.confirmWritesHint": "Show an in-app confirmation before creating wishes/tasks, checking in or canceling (deletes always confirm). Turn off to execute directly.",
688
+ "settings.pref.confirmWritesHint": "Master switch: when on, confirmations follow the categories below; when off, everything except deletes (always confirmed) runs directly.",
689
+ "settings.pref.confirmOps.group": "Confirmation categories",
690
+ "settings.pref.confirmOps.create": "Create wishes/tasks (incl. micro-action plans)",
691
+ "settings.pref.confirmOps.checkin": "Check-ins",
692
+ "settings.pref.confirmOps.cancelCheckin": "Cancel check-ins",
693
+ "settings.pref.confirmOps.claim": "Claiming tasks",
694
+ "settings.pref.confirmOps.update": "Editing wishes/tasks/categories",
695
+ "settings.pref.confirmOps.memorySave": "Saving memories",
696
+ "settings.pref.confirmOps.delete": "Deletes (incl. batch & restarts)",
697
+ "settings.pref.confirmOps.deleteHint": "Always confirms and cannot be turned off; deletes are irreversible.",
698
+ "settings.pref.confirmOps.hint": "Category switches apply while the master switch is on; defaults match previous versions.",
679
699
  "settings.pref.memoryLimit": "Memory injection limit",
680
700
  "settings.pref.memoryLimitHint": "Max memories auto-injected per chat (5-200, default 40); saved when you leave the field.",
681
701
  "settings.pref.limitInvalid": "Enter an integer between 5 and 200",
@@ -1368,6 +1388,9 @@ const STYLE_TEXT = `
1368
1388
  --xyd-on-c2:#7c4a03;
1369
1389
  --xyd-on-c3:#065f46;
1370
1390
  --xyd-on-dcell:#0b3d26;
1391
+ /* warn-soft 底的前景伙伴(StaleBanner 文字):壳通用次要色对琥珀柔底浅 4.19 /
1392
+ 深 2.95,跌破 WCAG 1.4.3 AA——按 ≥4.5:1 校准的专用对(on-c2 同族暖琥珀前景) */
1393
+ --xyd-on-warn:#7c4a03;
1371
1394
  /* 阴影对(卡片悬停/toast/弹窗/英雄卡):深浅各自配档,禁止正文区裸写 rgba */
1372
1395
  --xyd-shadow-card:0 2px 10px rgba(15,23,42,.07);
1373
1396
  --xyd-shadow-toast:0 8px 24px -8px rgba(15,23,42,.2),0 2px 8px -2px rgba(15,23,42,.1);
@@ -1417,6 +1440,7 @@ body[data-ds-dark-theme]{
1417
1440
  --xyd-on-c2:#ffd9a8;
1418
1441
  --xyd-on-c3:#a7f3c9;
1419
1442
  --xyd-on-dcell:#a7f3c9;
1443
+ --xyd-on-warn:#ffd9a8;
1420
1444
  --xyd-shadow-card:0 2px 10px rgba(0,0,0,.32);
1421
1445
  --xyd-shadow-toast:0 8px 24px -8px rgba(0,0,0,.5),0 2px 8px -2px rgba(0,0,0,.35);
1422
1446
  --xyd-shadow-modal:0 24px 48px -16px rgba(0,0,0,.55),0 4px 12px -4px rgba(0,0,0,.4);
@@ -1584,6 +1608,9 @@ body[data-ds-dark-theme] .xy-badge-cat{background:hsl(var(--cat-h,275) calc(var(
1584
1608
  .xy-banner-ok{margin-top:10px;padding:10px 12px;border-radius:var(--xyd-r-inner);background:var(--xyd-ok-soft);border:1px solid var(--xyd-ok-border);font-size:13px}
1585
1609
  /* 刷新失败但旧数据仍在的诚实降级横幅:琥珀虚线面板(警示但不恐吓——动作已成功) */
1586
1610
  .xy-stalerow{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:10px;padding:8px 12px;border:1px dashed var(--xyd-warn-border);border-radius:var(--xyd-r-inner);background:var(--xyd-warn-soft)}
1611
+ /* 提示文字用琥珀柔底的专用前景对(--xyd-on-warn):次要通用色在此底上浅 4.19 / 深
1612
+ * 2.95,深色远低于 AA;重试键保持 label-primary(对琥珀柔底 >10:1),无需处理 */
1613
+ .xy-stalerow .xy-meta{color:var(--xyd-on-warn)}
1587
1614
  .xy-stalerow .xy-btn-inline{margin-left:0}
1588
1615
 
1589
1616
  /* ===== 分组卡(任务页状态分桶 / 今日页待打卡·已完成共用)=====
@@ -1826,6 +1853,16 @@ button.xy-growth-col.xy-hover{box-shadow:inset 0 0 0 2px var(--xyd-accent-ring)}
1826
1853
  .xy-toggle::after{content:'';position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;background:var(--dsw-alias-label-primary);transition:transform .15s}
1827
1854
  .xy-toggle:checked::after{transform:translateX(16px);background:var(--xyd-on-accent)}
1828
1855
  .xy-hint{color:var(--dsw-alias-label-secondary);font-size:12px;margin:0}
1856
+ /* 确认类目明细:缩进嵌在总开关下(与「总开关 + 明细」分层同构),行 = 开关 + 名称;
1857
+ * 锁定行(删除)为只读展示,其说明行缩进与文字对齐(开关 38px + 间距 8px) */
1858
+ .xy-confirm-ops{display:flex;flex-direction:column;gap:8px;margin-left:22px}
1859
+ /* 禁用弱化必须显式写:自定义外观的 toggle 不吃原生禁用变灰,总开关关闭时明细行
1860
+ * 否则与可用态无法区分;锁定的删除行恒禁用,常态弱化即「冻结在开启态」的语义 */
1861
+ .xy-confirm-ops .xy-toggle:disabled{opacity:.4;cursor:default}
1862
+ .xy-op-group-head{font-weight:600;font-size:13px}
1863
+ .xy-op-row{display:flex;align-items:center;gap:8px;cursor:pointer;font-size:13px}
1864
+ .xy-op-row-locked{color:var(--dsw-alias-label-secondary);cursor:default}
1865
+ .xy-op-locked-hint{padding-left:46px}
1829
1866
 
1830
1867
  /* ===== 详情聚合视图 ===== */
1831
1868
  /* 段间 10px 呼吸;每段内部「标签 → 内容」固定 5px 纵向节奏(label 不再与内容文字挤行)。
@@ -4838,9 +4875,12 @@ function installTabVisibility(ctx) {
4838
4875
  * `@deepseek-ai/dsh-settings`,绝不可进浏览器包;而设置页需要上下界与默认值
4839
4876
  * 做输入夹取、回显兜底与不可用态兜底。形态对标 tab-policy.ts。
4840
4877
  *
4841
- * 决策口径:
4842
- * - confirmWrites:创建/打卡/取消打卡类写操作是否二次确认,默认开;
4843
- * 删除类始终确认,不受此开关控制(破坏性操作不设开关)。
4878
+ * 决策口径(确认分层模型见 AGENTS.md §10 决策 8):
4879
+ * - confirmWrites:写操作总开关。关闭时除锁定类目(删除)外所有类目一律不弹确认卡;
4880
+ * 开启时按 confirmOps 各类目明细决定。默认开。
4881
+ * - confirmOps:六个可配确认类目的明细开关(创建/打卡/取消打卡/领取/修改/记忆保存),
4882
+ * 默认值 = 总开关粒度时代的现行矩阵(创建/打卡/取消默认确认,其余默认不确认);
4883
+ * 删除为锁定类目,不设开关、始终确认(破坏性操作不设开关)。
4844
4884
  * - memoryInjectLimit:记忆注入条数上限,默认 40,合法区间 [5,200] 的整数。
4845
4885
  * 越界输入夹取到区间内并回显;非整数/非数字/空一律拒绝,不静默改值。
4846
4886
  * - confirmLang:对话内确认卡(HITL 卡头/按钮/问题文案)的显示语言,默认 zh。
@@ -4852,9 +4892,43 @@ function installTabVisibility(ctx) {
4852
4892
  const MEMORY_LIMIT_MIN = 5;
4853
4893
  /** 记忆注入上限的上界(含)。 */
4854
4894
  const MEMORY_LIMIT_MAX = 200;
4895
+ /**
4896
+ * 可配确认类目(与工具面确认挂点、设置页明细行、i18n 键三方同源,勿改语义):
4897
+ * - create:创建愿望/任务(含批量)+ 微行动拆解;
4898
+ * - checkin / cancelCheckin:打卡 / 取消打卡;
4899
+ * - claim:领取任务(误领取不可逆,恢复 = 删除重建,故提供可开确认的档位);
4900
+ * - update:修改愿望/任务/分类改名/记忆更新;
4901
+ * - memorySave:记忆保存。
4902
+ * 删除(含批量、微行动重开)为锁定类目,不在其中——始终确认,见 ADR-0001。
4903
+ */
4904
+ const CONFIRM_OPS = [
4905
+ "create",
4906
+ "checkin",
4907
+ "cancelCheckin",
4908
+ "claim",
4909
+ "update",
4910
+ "memorySave"
4911
+ ];
4912
+ /** 各类目默认值(= 2026-09 分层化之前的现行矩阵)。 */
4913
+ const CONFIRM_OP_DEFAULTS = {
4914
+ create: true,
4915
+ checkin: true,
4916
+ cancelCheckin: true,
4917
+ claim: false,
4918
+ update: false,
4919
+ memorySave: false
4920
+ };
4921
+ /** 脏值容错:confirmOps 快照缺键/非布尔键一律回落类目默认(与 confirmLang 同口径)。 */
4922
+ function normalizeConfirmOps(value) {
4923
+ const source = typeof value === "object" && value !== null ? value : {};
4924
+ const out = {};
4925
+ for (const op of CONFIRM_OPS) out[op] = typeof source[op] === "boolean" ? source[op] : CONFIRM_OP_DEFAULTS[op];
4926
+ return out;
4927
+ }
4855
4928
  /** 命名空间缺省(与 xingyuan-pref schema 默认值同源,见 src/pref-settings.ts)。 */
4856
4929
  const PREF_DEFAULTS = {
4857
4930
  confirmWrites: true,
4931
+ confirmOps: { ...CONFIRM_OP_DEFAULTS },
4858
4932
  memoryInjectLimit: 40,
4859
4933
  confirmLang: "zh"
4860
4934
  };
@@ -4914,6 +4988,15 @@ const TAB_LABEL_KEYS = {
4914
4988
  growth: "tab.growth",
4915
4989
  memory: "tab.memory"
4916
4990
  };
4991
+ /** 确认类目的显示名键(与 pref-policy CONFIRM_OPS 一一对应)。 */
4992
+ const CONFIRM_OP_LABEL_KEYS = {
4993
+ create: "settings.pref.confirmOps.create",
4994
+ checkin: "settings.pref.confirmOps.checkin",
4995
+ cancelCheckin: "settings.pref.confirmOps.cancelCheckin",
4996
+ claim: "settings.pref.confirmOps.claim",
4997
+ update: "settings.pref.confirmOps.update",
4998
+ memorySave: "settings.pref.confirmOps.memorySave"
4999
+ };
4917
5000
  function SettingsSection(props) {
4918
5001
  const t = useXyT();
4919
5002
  const scope = props.scope;
@@ -4934,6 +5017,7 @@ function SettingsSection(props) {
4934
5017
  const [saving, setSaving] = (0, react.useState)(false);
4935
5018
  const [savedMsg, setSavedMsg] = (0, react.useState)("");
4936
5019
  const [pendingToggle, setPendingToggle] = (0, react.useState)(void 0);
5020
+ const [pendingOps, setPendingOps] = (0, react.useState)(void 0);
4937
5021
  const [pendingMode, setPendingMode] = (0, react.useState)(void 0);
4938
5022
  const [pendingHidden, setPendingHidden] = (0, react.useState)(void 0);
4939
5023
  const savedTimer = (0, react.useRef)(void 0);
@@ -4941,6 +5025,7 @@ function SettingsSection(props) {
4941
5025
  const writeSeqRef = (0, react.useRef)(0);
4942
5026
  const uiWriteSeqRef = (0, react.useRef)(0);
4943
5027
  const confirmWrites = pendingToggle ?? snap.value?.confirmWrites ?? PREF_DEFAULTS.confirmWrites;
5028
+ const confirmOps = pendingOps ?? normalizeConfirmOps(snap.value?.confirmOps);
4944
5029
  const prefNoticeKey = snap.status === "loading" ? "settings.pref.loading" : snap.mode === "memory" ? "settings.pref.unavailable" : snap.status === "unavailable" ? "settings.pref.notRegistered" : !snap.writable ? "settings.pref.readOnly" : void 0;
4945
5030
  const tabMode = pendingMode ?? uisnap.value?.tabVisibilityMode ?? "follow";
4946
5031
  const hiddenTabs = pendingHidden ?? uisnap.value?.hiddenTabs ?? [];
@@ -5010,7 +5095,8 @@ function SettingsSection(props) {
5010
5095
  */
5011
5096
  const verifyWritten = (seq, field, value) => {
5012
5097
  if (seq !== writeSeqRef.current) return;
5013
- if (scope.getSnapshot().value?.[field] !== value) toastError(new Error(t("settings.pref.writeFailed")));
5098
+ const current = scope.getSnapshot().value?.[field];
5099
+ if (!(typeof value === "object" ? CONFIRM_OPS.every((key) => current?.[key] === value[key]) : current === value)) toastError(new Error(t("settings.pref.writeFailed")));
5014
5100
  };
5015
5101
  /** 标签页显隐命名空间的写后校验(与 verifyWritten 同款,命名空间独立故写队列与序号独立)。 */
5016
5102
  const verifyUiWritten = (seq, field, value) => {
@@ -5031,6 +5117,26 @@ function SettingsSection(props) {
5031
5117
  toastError(err);
5032
5118
  });
5033
5119
  };
5120
+ /**
5121
+ * 切换单个确认类目:整体写合并后的 confirmOps 对象(与 hiddenTabs 整组写入同款),
5122
+ * 乐观值顶住回显、失败回滚 + toast。总开关关闭时明细行禁用(不弹卡由总开关决定)。
5123
+ */
5124
+ const toggleConfirmOp = (op, next) => {
5125
+ if (!writable || pendingOps !== void 0 || !confirmWrites || confirmOps[op] === next) return;
5126
+ const merged = {
5127
+ ...confirmOps,
5128
+ [op]: next
5129
+ };
5130
+ setPendingOps(merged);
5131
+ const seq = ++writeSeqRef.current;
5132
+ scope.set("confirmOps", merged).then(() => {
5133
+ setPendingOps(void 0);
5134
+ verifyWritten(seq, "confirmOps", merged);
5135
+ }).catch((err) => {
5136
+ setPendingOps(void 0);
5137
+ toastError(err);
5138
+ });
5139
+ };
5034
5140
  const commitLimit = () => {
5035
5141
  const raw = limitDraft;
5036
5142
  setLimitDraft(null);
@@ -5157,7 +5263,36 @@ function SettingsSection(props) {
5157
5263
  toastError(err);
5158
5264
  });
5159
5265
  }
5160
- }), t("settings.pref.confirmWrites")), (0, react.createElement)("p", { className: "xy-hint" }, t("settings.pref.confirmWritesHint"))), (0, react.createElement)("label", { className: "xy-field" }, (0, react.createElement)("span", { className: "xy-field-head" }, t("settings.pref.memoryLimit")), (0, react.createElement)("input", {
5266
+ }), t("settings.pref.confirmWrites")), (0, react.createElement)("p", { className: "xy-hint" }, t("settings.pref.confirmWritesHint"))), (0, react.createElement)("div", {
5267
+ className: "xy-confirm-ops",
5268
+ role: "group",
5269
+ "aria-labelledby": "xy-confirm-ops-head",
5270
+ "aria-describedby": "xy-confirm-ops-hint"
5271
+ }, (0, react.createElement)("span", {
5272
+ className: "xy-op-group-head",
5273
+ id: "xy-confirm-ops-head"
5274
+ }, t("settings.pref.confirmOps.group")), ...CONFIRM_OPS.map((op) => (0, react.createElement)("label", {
5275
+ className: "xy-op-row",
5276
+ key: op
5277
+ }, (0, react.createElement)("input", {
5278
+ type: "checkbox",
5279
+ className: "xy-toggle",
5280
+ name: `confirmOps.${op}`,
5281
+ checked: confirmOps[op],
5282
+ disabled: !writable || pendingOps !== void 0 || !confirmWrites,
5283
+ onChange: (e) => toggleConfirmOp(op, e.target.checked)
5284
+ }), t(CONFIRM_OP_LABEL_KEYS[op]))), (0, react.createElement)("div", { className: "xy-op-row xy-op-row-locked" }, (0, react.createElement)("input", {
5285
+ type: "checkbox",
5286
+ className: "xy-toggle",
5287
+ name: "confirmOps.deleteLocked",
5288
+ checked: true,
5289
+ readOnly: true,
5290
+ disabled: true,
5291
+ "aria-label": t("settings.pref.confirmOps.delete")
5292
+ }), (0, react.createElement)("span", null, t("settings.pref.confirmOps.delete"))), (0, react.createElement)("span", { className: "xy-hint xy-op-locked-hint" }, t("settings.pref.confirmOps.deleteHint")), (0, react.createElement)("span", {
5293
+ className: "xy-hint",
5294
+ id: "xy-confirm-ops-hint"
5295
+ }, t("settings.pref.confirmOps.hint"))), (0, react.createElement)("label", { className: "xy-field" }, (0, react.createElement)("span", { className: "xy-field-head" }, t("settings.pref.memoryLimit")), (0, react.createElement)("input", {
5161
5296
  type: "number",
5162
5297
  min: 5,
5163
5298
  max: 200,
package/lib/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import z from '@deepseek-ai/schemastery';
2
+ import { storageBackendServiceKey } from '@deepseek-ai/dsh-storage';
2
3
  import { makeXingyuanStore, xingyuanDomainSpec } from './domain.js';
3
4
  import { installPrefSettings } from './pref-settings.js';
4
5
  import { registerXingyuanRoutes } from './routes/index.js';
@@ -16,8 +17,10 @@ export const Config = z.object({
16
17
  memoryListLimit: z.number().default(500),
17
18
  repairSessionLogs: z.boolean().default(true),
18
19
  });
19
- /** 依赖:storageDomain(领域设施)、webServer(页面路由)、sessions(活会话枚举)。 */
20
- export const inject = ['webServer', 'storageDomain', 'sessions'];
20
+ /** 依赖:storageDomain(领域设施)、webServer(页面路由)、sessions(活会话枚举)、
21
+ * sqlite 后端生命周期键(官方 storage 契约:数据形式提供方注入它,使激活不与
22
+ * 后端注册发生竞态——不依赖 patch 的行顺序)。 */
23
+ export const inject = ['webServer', 'storageDomain', 'sessions', storageBackendServiceKey('sqlite')];
21
24
  export async function apply(ctx, config) {
22
25
  let disposed = false;
23
26
  let domain;
@@ -5,9 +5,12 @@
5
5
  * `@deepseek-ai/dsh-settings`,绝不可进浏览器包;而设置页需要上下界与默认值
6
6
  * 做输入夹取、回显兜底与不可用态兜底。形态对标 tab-policy.ts。
7
7
  *
8
- * 决策口径:
9
- * - confirmWrites:创建/打卡/取消打卡类写操作是否二次确认,默认开;
10
- * 删除类始终确认,不受此开关控制(破坏性操作不设开关)。
8
+ * 决策口径(确认分层模型见 AGENTS.md §10 决策 8):
9
+ * - confirmWrites:写操作总开关。关闭时除锁定类目(删除)外所有类目一律不弹确认卡;
10
+ * 开启时按 confirmOps 各类目明细决定。默认开。
11
+ * - confirmOps:六个可配确认类目的明细开关(创建/打卡/取消打卡/领取/修改/记忆保存),
12
+ * 默认值 = 总开关粒度时代的现行矩阵(创建/打卡/取消默认确认,其余默认不确认);
13
+ * 删除为锁定类目,不设开关、始终确认(破坏性操作不设开关)。
11
14
  * - memoryInjectLimit:记忆注入条数上限,默认 40,合法区间 [5,200] 的整数。
12
15
  * 越界输入夹取到区间内并回显;非整数/非数字/空一律拒绝,不静默改值。
13
16
  * - confirmLang:对话内确认卡(HITL 卡头/按钮/问题文案)的显示语言,默认 zh。
@@ -21,9 +24,40 @@ export const MEMORY_LIMIT_MIN = 5;
21
24
  export const MEMORY_LIMIT_MAX = 200;
22
25
  /** 确认卡语言的可选值(schema 枚举与设置页选项同源)。 */
23
26
  export const CONFIRM_LANGS = ['zh', 'en'];
27
+ /**
28
+ * 可配确认类目(与工具面确认挂点、设置页明细行、i18n 键三方同源,勿改语义):
29
+ * - create:创建愿望/任务(含批量)+ 微行动拆解;
30
+ * - checkin / cancelCheckin:打卡 / 取消打卡;
31
+ * - claim:领取任务(误领取不可逆,恢复 = 删除重建,故提供可开确认的档位);
32
+ * - update:修改愿望/任务/分类改名/记忆更新;
33
+ * - memorySave:记忆保存。
34
+ * 删除(含批量、微行动重开)为锁定类目,不在其中——始终确认,见 ADR-0001。
35
+ */
36
+ export const CONFIRM_OPS = ['create', 'checkin', 'cancelCheckin', 'claim', 'update', 'memorySave'];
37
+ /** 各类目默认值(= 2026-09 分层化之前的现行矩阵)。 */
38
+ export const CONFIRM_OP_DEFAULTS = {
39
+ create: true,
40
+ checkin: true,
41
+ cancelCheckin: true,
42
+ claim: false,
43
+ update: false,
44
+ memorySave: false,
45
+ };
46
+ /** 脏值容错:confirmOps 快照缺键/非布尔键一律回落类目默认(与 confirmLang 同口径)。 */
47
+ export function normalizeConfirmOps(value) {
48
+ const source = typeof value === 'object' && value !== null
49
+ ? value
50
+ : {};
51
+ const out = {};
52
+ for (const op of CONFIRM_OPS) {
53
+ out[op] = typeof source[op] === 'boolean' ? source[op] : CONFIRM_OP_DEFAULTS[op];
54
+ }
55
+ return out;
56
+ }
24
57
  /** 命名空间缺省(与 xingyuan-pref schema 默认值同源,见 src/pref-settings.ts)。 */
25
58
  export const PREF_DEFAULTS = {
26
59
  confirmWrites: true,
60
+ confirmOps: { ...CONFIRM_OP_DEFAULTS },
27
61
  memoryInjectLimit: 40,
28
62
  confirmLang: 'zh',
29
63
  };
@@ -1,10 +1,20 @@
1
1
  import z from '@deepseek-ai/schemastery';
2
- import { CONFIRM_LANGS, MEMORY_LIMIT_MAX, MEMORY_LIMIT_MIN, PREF_DEFAULTS, } from './pref-policy.js';
2
+ import { CONFIRM_LANGS, CONFIRM_OP_DEFAULTS, MEMORY_LIMIT_MAX, MEMORY_LIMIT_MIN, PREF_DEFAULTS, } from './pref-policy.js';
3
3
  /** 对话偏好命名空间(client 半侧 settingsScope 同名配对,勿改;小写连字符文法由新 API 类型校验)。 */
4
4
  export const PREF_NS = 'xingyuan-pref';
5
5
  /** 配置 schema(默认值写进 schema;step(1) 让服务端也拒绝小数,防手改文档/RPC 直写)。 */
6
6
  export const PrefSettingsSchema = z.object({
7
7
  confirmWrites: z.boolean().default(PREF_DEFAULTS.confirmWrites),
8
+ // 确认类目明细:对象整体作为一个命名空间字段下发(客户端每次写合并后的完整对象);
9
+ // 键与 pref-policy 的 CONFIRM_OPS 一一对应(测试对拍),旧存量值缺该键由 default 兜底
10
+ confirmOps: z.object({
11
+ create: z.boolean().default(CONFIRM_OP_DEFAULTS.create),
12
+ checkin: z.boolean().default(CONFIRM_OP_DEFAULTS.checkin),
13
+ cancelCheckin: z.boolean().default(CONFIRM_OP_DEFAULTS.cancelCheckin),
14
+ claim: z.boolean().default(CONFIRM_OP_DEFAULTS.claim),
15
+ update: z.boolean().default(CONFIRM_OP_DEFAULTS.update),
16
+ memorySave: z.boolean().default(CONFIRM_OP_DEFAULTS.memorySave),
17
+ }).default({ ...CONFIRM_OP_DEFAULTS }),
8
18
  memoryInjectLimit: z.number().step(1).min(MEMORY_LIMIT_MIN).max(MEMORY_LIMIT_MAX)
9
19
  .default(PREF_DEFAULTS.memoryInjectLimit),
10
20
  // schemastery 无 z.enum,两值枚举用 const+union 表达(与 ui-settings 的显隐三态同款)