@starwish-ai/xingyuan-dsh 0.5.8 → 0.6.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/CHANGELOG.md +78 -0
- package/README.en-US.md +1 -0
- package/README.md +2 -0
- package/lib/client/cards.js +3 -1
- package/lib/client/chart-labels.js +4 -5
- package/lib/client/i18n.js +21 -10
- package/lib/client/index.js +7 -6
- package/lib/client/pages/wishes.js +43 -31
- package/lib/client/tab-visibility.js +1 -1
- package/lib/client.js +66 -48
- package/lib/growth.js +3 -1
- package/lib/pref-settings.js +11 -7
- package/lib/preset/charts.js +11 -19
- package/lib/preset/prompts.js +23 -6
- package/lib/preset/tools.js +198 -33
- package/lib/routes/api.js +4 -0
- package/lib/routes/pages-html.js +2 -2
- package/lib/store.js +82 -22
- package/lib/types/client/cards.d.ts +1 -1
- package/lib/types/client/chart-labels.d.ts +1 -1
- package/lib/types/client/i18n.d.ts +10 -5
- package/lib/types/client/index.d.ts +3 -3
- package/lib/types/client/pages/types.d.ts +4 -0
- package/lib/types/client/tab-visibility.d.ts +1 -1
- package/lib/types/events.d.ts +10 -0
- package/lib/types/pref-settings.d.ts +8 -4
- package/lib/types/store.d.ts +36 -7
- package/lib/types/ui-settings.d.ts +4 -3
- package/lib/ui-settings.js +8 -7
- package/package.json +36 -30
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,84 @@ 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.0] - 2026-09-05
|
|
9
|
+
|
|
10
|
+
> **Note:** behavioral revision — long-term ratios (wish progress, progress charts) join
|
|
11
|
+
> the commitment semantics: unclaimed tasks no longer count in the denominator, and
|
|
12
|
+
> achievement additionally requires zero unclaimed tasks. No data format change; upgrades
|
|
13
|
+
> from 0.5.x are seamless — the switch can only raise progress, and every newly-qualifying
|
|
14
|
+
> wish carries unclaimed candidates that the achievement gate holds back (nothing archives
|
|
15
|
+
> by accident).
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- **Achievement gate and the "wrapping up" state**: a wish achieves only at 100% progress
|
|
19
|
+
with no unclaimed tasks (single formula `wishProgressFromAgg` shared by the write path and
|
|
20
|
+
the read side). Full progress with unclaimed tasks parks the wish at "wrapping up" — the
|
|
21
|
+
bar is full, the wish is not archived — and the card shows the pending count with the
|
|
22
|
+
guidance line "claim to continue, or delete to achieve". The call is always the user's:
|
|
23
|
+
the assistant never claims or deletes on its own to force achievement.
|
|
24
|
+
- **Gate flips always reach the model**: check-in / claim / update / cancel / delete / create
|
|
25
|
+
(including batches) compare before/after wish views and, on achieve / wrap-up / rollback,
|
|
26
|
+
say so in the tool reply and re-emit the wish event, so chat card, reply and wishes page
|
|
27
|
+
stay in sync. Count drift without a flip also re-emits (the "N to claim" badge never
|
|
28
|
+
freezes).
|
|
29
|
+
- **Single-source display bits**: `pendingCount` / `settled` / `planning` / `achieved` are
|
|
30
|
+
produced by `freshWishes` and delivered via `/api/wishes` and `xingyuan/wish` events;
|
|
31
|
+
replays of older events degrade honestly to a plain progress display.
|
|
32
|
+
- **Wording standard**: all user-facing and verbatim-relay surfaces (i18n, tool replies,
|
|
33
|
+
prompt guides, standalone pages) say "待领取的任务" / "待收尾" — no internal jargon.
|
|
34
|
+
Locked by a banned-word test (`test/prompts.test.ts`) and a shared guidance-line constant.
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
- `wishProgress` and `taskCompletionRate` now divide by claimed-task days only; the
|
|
38
|
+
subtitle is fixed to "已领取任务" (the conditional "含未领取任务" wording is gone —
|
|
39
|
+
one metric, one meaning).
|
|
40
|
+
- Growth-page achievement counts and the `wishAchievement` chart consume the derived
|
|
41
|
+
achieved bit instead of `progress >= 100`, matching the wish card exactly.
|
|
42
|
+
- The session-opening overview reads fresh derived views (not the stored `archived` bit)
|
|
43
|
+
and distinguishes "all wishes achieved" from "no wishes yet".
|
|
44
|
+
|
|
45
|
+
### Fixed
|
|
46
|
+
- Creating a bare wish showed "进度 0%" on the chat card while the wishes page said
|
|
47
|
+
"计划中" (the create event dropped the derived bits).
|
|
48
|
+
- Claiming a standalone task (no parent wish) claimed its days were folded into "wish
|
|
49
|
+
progress".
|
|
50
|
+
- Batch-delete replies glued the achievement guidance onto the summary line; batch paths
|
|
51
|
+
no longer re-scan the task table per wish on the after side.
|
|
52
|
+
|
|
53
|
+
## [0.5.9] - 2026-09-04
|
|
54
|
+
|
|
55
|
+
> **Note:** this release migrates the plugin to DeepSeek Harness `0.1.2-rc.1`
|
|
56
|
+
> (the host is still a technical preview and broke APIs on this bump). No data
|
|
57
|
+
> format changes: existing databases, settings and recorded sessions replay as
|
|
58
|
+
> before. On older hosts (`0.1.1-rc.2`) this version no longer loads — pin
|
|
59
|
+
> `0.5.8` if you must stay on the previous harness.
|
|
60
|
+
|
|
61
|
+
### Changed
|
|
62
|
+
- **Settings namespaces now install through the service face**
|
|
63
|
+
(`ctx.inject(['settings'], (inv) => inv.settings.installSection(...))`):
|
|
64
|
+
`dsh-settings` removed the standalone `installSettingsSection` /
|
|
65
|
+
`settingsNamespace` helpers, so `xingyuan-pref` and `xingyuan-ui` register
|
|
66
|
+
via the new API and are no longer imported at runtime. The persistence
|
|
67
|
+
guarantee is unchanged — namespaces stay on the bundle-resident layer.
|
|
68
|
+
- **Client half re-wired to the reshaped browser packages**:
|
|
69
|
+
`@deepseek-ai/dsh-client-runtime` is gone; `ClientContext` is now plain
|
|
70
|
+
Cordis `Context`. The `ChatNodeDataMap` declaration merge moved to
|
|
71
|
+
`@deepseek-ai/dsh-client-ui-chat/client`, `ConversationStepDataMap` stays in
|
|
72
|
+
`dsh-client-ui-conversation/client`, event-card definitions register through
|
|
73
|
+
`ctx.uiConversation.events`, and the slot service is provided by
|
|
74
|
+
`dsh-client-ui-renderer`. `dsh.client.inject` was rewritten to the new
|
|
75
|
+
package-row semantics (baseline modules are seeded by the shell and need no
|
|
76
|
+
declaration).
|
|
77
|
+
- **Session-preset detection for tab visibility** now reads
|
|
78
|
+
`projectionValues.agentPreset` from the sessions list row — the
|
|
79
|
+
`SessionSummary.agentPreset` field was removed upstream in
|
|
80
|
+
`0.1.2-rc.1` (this was silently hiding all Xingyuan view tabs after the
|
|
81
|
+
host bump; same data source the official preset label uses).
|
|
82
|
+
- **Peer dependency ranges** bumped to `^0.1.2-rc.1` (Cordis `^4.0.2`);
|
|
83
|
+
AGENTS.md records the migration facts and the new troubleshooting rows
|
|
84
|
+
(`cannot get property without inject`, missing view tabs).
|
|
85
|
+
|
|
8
86
|
## [0.5.8] - 2026-09-04
|
|
9
87
|
|
|
10
88
|
> **Note:** this release is the fourth six-perspective quality pass. It finishes
|
package/README.en-US.md
CHANGED
|
@@ -12,6 +12,7 @@ XingYuan is a wish & habit-building companion, shipped as a DeepSeek Harness plu
|
|
|
12
12
|
|
|
13
13
|
- **Conversation-driven**: create wishes / tasks in natural language with duplicate detection
|
|
14
14
|
- **Opportunity-day check-ins**: check-in, make-up, cancel, and future pre-checks; progress always recomputed against today
|
|
15
|
+
- **Commitment-based progress**: only claimed tasks count; a wish achieves when everything committed is done and nothing is left to claim — full progress with unclaimed tasks leaves it "wrapping up" for your call (claim to continue, or delete to achieve), never nagging, never deciding for you
|
|
15
16
|
- **Micro-actions**: break an overwhelming goal into 3–7 tiny steps and walk through them guided
|
|
16
17
|
- **Growth system**: levels Lv.1–Lv.10, streak bonuses, stat cards, 30-day bar chart
|
|
17
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
|
package/README.md
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
|
|
13
13
|
- **对话驱动**:自然语言创建愿望 / 任务,自动查重相似项
|
|
14
14
|
- **机会日打卡**:周期任务打卡、补卡、取消、未来预勾,进度跨天恒为最新
|
|
15
|
+
- **承诺口径进度**:只有领取的任务算进度;达成 = 承诺全部完成且没有待领取的任务——
|
|
16
|
+
做完但有没领的,愿望停在「待收尾」等你决定(领了继续,或删掉就达成),不催也不代劳
|
|
15
17
|
- **微行动拆解**:把无从下手的目标拆成 3–7 个小步,引导式逐个完成或跳过
|
|
16
18
|
- **成长体系**:等级 Lv.1–Lv.10、连续加成、统计卡与近 30 天柱状图
|
|
17
19
|
- **会话视图页**:今日 / 愿望 / 任务 / 日历 / 成长 / 记忆 六个标签页,页面按钮直连动作接口;
|
package/lib/client/cards.js
CHANGED
|
@@ -22,9 +22,11 @@ function WishView(props) {
|
|
|
22
22
|
style: categoryVars(wish.wish.colorKey, wish.wish.categoryName),
|
|
23
23
|
}, t('badge.wish')), createElement('span', { className: 'xy-title' }, deleted ? t('state.deleted', { title: wish.wish.title }) : wish.wish.title)),
|
|
24
24
|
// 已删除卡不再渲染活体进度(删除态 × 进度百分比自相矛盾)
|
|
25
|
+
// 三态位(achieved/planning/settled/pendingCount)来自事件载荷(wishProgressFromAgg 派生),
|
|
26
|
+
// 与愿望页/工具回包同一称呼;旧事件无这些位则如实降级为纯「进度 X%」显示(不重建谓词,§5.2 规则 7)
|
|
25
27
|
createElement('div', { className: 'xy-meta' }, deleted
|
|
26
28
|
? wish.wish.categoryName
|
|
27
|
-
: `${wish.wish.categoryName} · ${t('wish.progress', { percent: wish.wish.progress })}`));
|
|
29
|
+
: `${wish.wish.categoryName} · ${wish.wish.achieved ? t('wish.achieved') : wish.wish.planning ? t('wish.planning') : t('wish.progress', { percent: wish.wish.progress })}${wish.wish.pendingCount ? ` · ${t('wish.pendingCount', { n: wish.wish.pendingCount })}` : ''}${wish.wish.settled ? ` · ${t('wish.settled')}` : ''}`));
|
|
28
30
|
}
|
|
29
31
|
function TaskView(props) {
|
|
30
32
|
const t = useXyT();
|
|
@@ -81,12 +81,11 @@ export const CHART_SUBTITLE_KEYS = {
|
|
|
81
81
|
'按任务 TopN': 'chart.subtitle.byTaskTopN',
|
|
82
82
|
'按愿望 TopN': 'chart.subtitle.byWishTopN',
|
|
83
83
|
'按实际打卡时刻分桶;过滤范围为打卡日区间': 'chart.subtitle.byHourBucket',
|
|
84
|
-
'
|
|
85
|
-
'%(应打天数完成率,含未领取任务)': 'chart.subtitle.progressPercentPending',
|
|
84
|
+
'%(已领取任务完成率)': 'chart.subtitle.claimedPercent',
|
|
86
85
|
};
|
|
87
86
|
/**
|
|
88
87
|
* 副标题本地化:固定短语查表;含数字的模板(近 N 天 / A/B 天 / 当前 A 天 / 最长 B 天 /
|
|
89
|
-
*
|
|
88
|
+
* 已领取任务口径)按正则重排——服务端模板变化时未知副标题原样回显(诚实降级)。
|
|
90
89
|
*/
|
|
91
90
|
export function localizeChartSubtitle(subtitle) {
|
|
92
91
|
const fixed = CHART_SUBTITLE_KEYS[subtitle];
|
|
@@ -95,9 +94,9 @@ export function localizeChartSubtitle(subtitle) {
|
|
|
95
94
|
let m = /^近 (\d+) 天$/.exec(subtitle);
|
|
96
95
|
if (m !== null)
|
|
97
96
|
return t('chart.subtitle.lastDays', { n: m[1] });
|
|
98
|
-
m = /^(\d+)\/(\d+)
|
|
97
|
+
m = /^(\d+)\/(\d+) 天(已领取任务)$/.exec(subtitle);
|
|
99
98
|
if (m !== null)
|
|
100
|
-
return t('chart.subtitle.
|
|
99
|
+
return t('chart.subtitle.daysClaimed', { done: m[1], due: m[2] });
|
|
101
100
|
m = /^(\d+)\/(\d+) 天$/.exec(subtitle);
|
|
102
101
|
if (m !== null)
|
|
103
102
|
return t('chart.subtitle.daysRatio', { done: m[1], due: m[2] });
|
package/lib/client/i18n.js
CHANGED
|
@@ -58,11 +58,18 @@ export const ZH_DICT = {
|
|
|
58
58
|
'state.done': '已完成',
|
|
59
59
|
// --- 愿望卡 / 愿望页 ---
|
|
60
60
|
'wish.progress': '进度 {percent}%',
|
|
61
|
-
'wish.
|
|
61
|
+
'wish.planning': '计划中',
|
|
62
|
+
'wish.achieved': '已达成 🎉',
|
|
63
|
+
'wish.pendingCount': '{n} 个任务待领取',
|
|
64
|
+
'wish.settled': '待收尾',
|
|
65
|
+
'wish.settleHint': '答应自己的都做完了——待领取的领了继续,或删掉就达成',
|
|
62
66
|
'wish.eta': '预计完成 {date}',
|
|
63
67
|
'wish.noTasks': '暂无下属任务——在对话里告诉我,让我为它推荐打卡任务',
|
|
64
68
|
'wish.pageTitle': '我的愿望',
|
|
65
|
-
'wish.summary': '{active} 个进行中{
|
|
69
|
+
'wish.summary': '{active} 个进行中{planning}{settled}{achieved}',
|
|
70
|
+
'wish.planningSuffix': ' · {n} 个计划中',
|
|
71
|
+
'wish.settledSuffix': ' · {n} 个待收尾',
|
|
72
|
+
'wish.achievedSuffix': ' · {n} 个已达成',
|
|
66
73
|
'wish.sectionAchieved': '已达成',
|
|
67
74
|
'wish.empty.title': '还没有愿望',
|
|
68
75
|
'wish.empty.hint': '告诉我想实现什么,我来帮你拆解成可坚持的计划。',
|
|
@@ -343,10 +350,9 @@ export const ZH_DICT = {
|
|
|
343
350
|
'chart.subtitle.byTaskTopN': '按任务 TopN',
|
|
344
351
|
'chart.subtitle.byWishTopN': '按愿望 TopN',
|
|
345
352
|
'chart.subtitle.byHourBucket': '按实际打卡时刻分桶;过滤范围为打卡日区间',
|
|
346
|
-
'chart.subtitle.
|
|
347
|
-
'chart.subtitle.progressPercentPending': '%(应打天数完成率,含未领取任务)',
|
|
353
|
+
'chart.subtitle.claimedPercent': '%(已领取任务完成率)',
|
|
348
354
|
'chart.subtitle.lastDays': '近 {n} 天',
|
|
349
|
-
'chart.subtitle.
|
|
355
|
+
'chart.subtitle.daysClaimed': '{done}/{due} 天(已领取任务)',
|
|
350
356
|
'chart.subtitle.daysRatio': '{done}/{due} 天',
|
|
351
357
|
'chart.subtitle.streak': '当前 {current} 天 / 最长 {best} 天',
|
|
352
358
|
// --- 详情聚合视图 ---
|
|
@@ -480,11 +486,17 @@ const EN_DICT = {
|
|
|
480
486
|
'state.deleted': 'Deleted “{title}”',
|
|
481
487
|
'state.done': 'Done',
|
|
482
488
|
'wish.progress': 'Progress {percent}%',
|
|
483
|
-
'wish.
|
|
489
|
+
'wish.planning': 'Planning',
|
|
490
|
+
'wish.achieved': 'Achieved 🎉',
|
|
491
|
+
'wish.pendingCount': '{n} to claim',
|
|
492
|
+
'wish.settled': 'Wrapping up',
|
|
493
|
+
'wish.settleHint': 'Everything you committed to is done — claim the rest to keep going, or delete them to achieve the wish',
|
|
484
494
|
'wish.eta': 'Target date {date}',
|
|
485
495
|
'wish.noTasks': 'No tasks yet — tell me in chat and I will suggest check-in tasks for it',
|
|
486
496
|
'wish.pageTitle': 'My Wishes',
|
|
487
|
-
'wish.summary': '{active} in progress{achieved}',
|
|
497
|
+
'wish.summary': '{active} in progress{planning}{settled}{achieved}',
|
|
498
|
+
'wish.planningSuffix': ' · {n} planning',
|
|
499
|
+
'wish.settledSuffix': ' · {n} wrapping up',
|
|
488
500
|
'wish.achievedSuffix': ' · {n} achieved',
|
|
489
501
|
'wish.sectionAchieved': 'Achieved',
|
|
490
502
|
'wish.empty.title': 'No wishes yet',
|
|
@@ -751,10 +763,9 @@ const EN_DICT = {
|
|
|
751
763
|
'chart.subtitle.byTaskTopN': 'By task (top N)',
|
|
752
764
|
'chart.subtitle.byWishTopN': 'By wish (top N)',
|
|
753
765
|
'chart.subtitle.byHourBucket': 'Bucketed by actual check-in time; filtered to the check-in date range',
|
|
754
|
-
'chart.subtitle.
|
|
755
|
-
'chart.subtitle.progressPercentPending': '% of scheduled-day completion (incl. unclaimed)',
|
|
766
|
+
'chart.subtitle.claimedPercent': '% of committed-task completion',
|
|
756
767
|
'chart.subtitle.lastDays': 'Last {n} days',
|
|
757
|
-
'chart.subtitle.
|
|
768
|
+
'chart.subtitle.daysClaimed': '{done}/{due} days (claimed tasks)',
|
|
758
769
|
'chart.subtitle.daysRatio': '{done}/{due} days',
|
|
759
770
|
'chart.subtitle.streak': 'Current {current}d / best {best}d',
|
|
760
771
|
'detail.grid.title': 'Check-in record',
|
package/lib/client/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { STYLE_TEXT } from './styles.js';
|
|
|
4
4
|
import { CARD_VIEWS } from './cards.js';
|
|
5
5
|
import { installTabVisibility } from './tab-visibility.js';
|
|
6
6
|
import { SettingsSection } from './pages/settings.js';
|
|
7
|
-
export const inject = ['slots', 'settingsScope', '
|
|
7
|
+
export const inject = ['slots', 'settingsScope', 'uiConversation'];
|
|
8
8
|
/** 事件类型 → 渲染 kind。 */
|
|
9
9
|
const KIND_BY_EVENT = {
|
|
10
10
|
'xingyuan/wish': 'xy-wish',
|
|
@@ -59,11 +59,12 @@ export function apply(ctx) {
|
|
|
59
59
|
disposeToasts();
|
|
60
60
|
disposeConfirms();
|
|
61
61
|
});
|
|
62
|
-
|
|
63
|
-
ctx.
|
|
64
|
-
ctx.
|
|
65
|
-
ctx.
|
|
66
|
-
ctx.
|
|
62
|
+
// dsh 0.1.2:事件 Definition 注册收进 uiConversation 服务的 events 子注册表。
|
|
63
|
+
ctx.uiConversation.events.register(definition('xy-wish'));
|
|
64
|
+
ctx.uiConversation.events.register(definition('xy-task'));
|
|
65
|
+
ctx.uiConversation.events.register(definition('xy-checkin'));
|
|
66
|
+
ctx.uiConversation.events.register(definition('xy-chart'));
|
|
67
|
+
ctx.uiConversation.events.register(definition('xy-micro'));
|
|
67
68
|
// 卡片注册不声明 locale:t 席位由组件内 useXyT() 取代(键受查 + 订阅刷新),
|
|
68
69
|
// 避免 'conversation' 窄域 t 与本插件命名空间的类型冲突。
|
|
69
70
|
ctx.slots.inject('conversation.chat.node', () => [
|
|
@@ -39,8 +39,12 @@ export function WishesPage() {
|
|
|
39
39
|
const data = page.data;
|
|
40
40
|
if (data === undefined)
|
|
41
41
|
return createElement(PageSkeleton);
|
|
42
|
-
|
|
42
|
+
// 页头四态计数:进行中/计划中/待收尾/已达成各计各的,与卡头徽章同一称呼
|
|
43
|
+
// (消灭「摘要算进行中、卡上写计划中」的并见歧义);全部取服务端派生位,展示层不重建谓词
|
|
43
44
|
const achieved = data.wishes.filter((w) => w.archived);
|
|
45
|
+
const settledCount = data.wishes.filter((w) => w.settled).length;
|
|
46
|
+
const planningCount = data.wishes.filter((w) => !w.archived && w.planning).length;
|
|
47
|
+
const active = data.wishes.filter((w) => !w.archived);
|
|
44
48
|
/** 删除愿望(级联下属任务与打卡记录,服务端同一写路径):确认后直连动作并整页刷新。
|
|
45
49
|
* 行移除后触发按钮销毁、焦点落空——刷新完成后把焦点交给页面标题兜底。 */
|
|
46
50
|
const removeWish = (wish) => {
|
|
@@ -70,37 +74,45 @@ export function WishesPage() {
|
|
|
70
74
|
? [line, createElement(TaskDetailPanel, { key: `d-${task.taskId}`, id: `xy-task-detail-${task.taskId}`, taskId: task.taskId, today: data.today, onChanged: page.reload })]
|
|
71
75
|
: [line];
|
|
72
76
|
});
|
|
73
|
-
const card = (wish) =>
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
:
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
'
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
className: 'xy-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
'
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
77
|
+
const card = (wish) => {
|
|
78
|
+
// 承诺口径(§5.2 规则 7):进度分母只计已领取任务。展示三态:
|
|
79
|
+
// 计划中(无已领取任务)/ 进行中(正常百分比)/ 待结算(满进度而有候选——不归档,附收尾指引)。
|
|
80
|
+
// settled/pendingCount/planning 直取服务端 wishProgressFromAgg 派生位(随 /api/wishes 下发),
|
|
81
|
+
// 展示层禁止重建 `progress>=100 && 有候选` 闸门或 planning 谓词(单一判定源,防与工具回包分叉)
|
|
82
|
+
const { pendingCount, settled, planning } = wish;
|
|
83
|
+
const progressLabel = planning ? t('wish.planning') : t('wish.progress', { percent: wish.progress });
|
|
84
|
+
const pendingMeta = pendingCount > 0 ? ` · ${t('wish.pendingCount', { n: pendingCount })}` : '';
|
|
85
|
+
return createElement('div', { key: wish.wishId, className: 'xy-wishcard' }, createElement('div', { className: 'xy-card-head' }, createElement('span', {
|
|
86
|
+
className: 'xy-badge xy-badge-cat',
|
|
87
|
+
style: categoryVars(wish.colorKey, wish.categoryName),
|
|
88
|
+
}, wish.categoryName), createElement('span', { className: 'xy-title' }, wish.title), createElement('span', { className: 'xy-progress-num' }, progressLabel, pendingCount > 0
|
|
89
|
+
? createElement('span', { className: 'xy-meta' }, pendingMeta)
|
|
90
|
+
: null), createElement('button', {
|
|
91
|
+
className: 'xy-btn xy-btn-danger xy-btn-icon',
|
|
92
|
+
disabled: deleting,
|
|
93
|
+
'aria-label': t('common.delete') + ' · ' + wish.title,
|
|
94
|
+
title: t('common.delete'),
|
|
95
|
+
onClick: () => removeWish(wish),
|
|
96
|
+
}, createElement(IconTrash))), wish.description !== undefined ? createElement('div', { className: 'xy-meta' }, wish.description) : null, wish.estimatedCompletionDate !== undefined
|
|
97
|
+
? createElement('div', { className: 'xy-meta' }, t('wish.eta', { date: formatMediumDate(wish.estimatedCompletionDate) }))
|
|
98
|
+
: null,
|
|
99
|
+
// 与今日页 hero 进度条同一语义语法:读屏可感知进度(role+valuenow),不只是一根哑条
|
|
100
|
+
createElement('div', {
|
|
101
|
+
className: 'xy-bar',
|
|
102
|
+
role: 'progressbar',
|
|
103
|
+
'aria-valuemin': 0,
|
|
104
|
+
'aria-valuemax': 100,
|
|
105
|
+
'aria-valuenow': wish.progress,
|
|
106
|
+
'aria-label': progressLabel + pendingMeta,
|
|
107
|
+
}, createElement('div', { className: 'xy-bar-fill', style: { transform: `scaleX(${Math.min(wish.progress, 100) / 100})` } })), settled ? createElement('div', { className: 'xy-meta' }, t('wish.settleHint')) : null, wish.tasks.length > 0
|
|
108
|
+
? createElement('div', { className: 'xy-wishtasks' }, ...wishTasks(wish))
|
|
109
|
+
: createElement('div', { className: 'xy-meta' }, t('wish.noTasks')));
|
|
110
|
+
};
|
|
102
111
|
return createElement('div', { className: 'xy-page', ref: stabilize }, page.error !== undefined ? createElement(StaleBanner, { onRetry: () => void page.reload() }) : null, showNoPresetHint ? createElement('p', { className: 'xy-hint' }, t('today.noPresetHint')) : null, createElement('div', { className: 'xy-page-head' }, createElement('h2', { className: 'xy-page-title' }, t('wish.pageTitle')), createElement('span', { className: 'xy-meta' }, t('wish.summary', {
|
|
103
|
-
|
|
112
|
+
// 四态计数各计各的(进行中扣除计划中与待收尾,不相套);列表渲染仍归未完成组(未完成≠进行中计数)
|
|
113
|
+
active: active.length - settledCount - planningCount,
|
|
114
|
+
planning: planningCount > 0 ? t('wish.planningSuffix', { n: planningCount }) : '',
|
|
115
|
+
settled: settledCount > 0 ? t('wish.settledSuffix', { n: settledCount }) : '',
|
|
104
116
|
achieved: achieved.length > 0 ? t('wish.achievedSuffix', { n: achieved.length }) : '',
|
|
105
117
|
})),
|
|
106
118
|
// 动作组整体右置(单一 margin-left:auto),避免多按钮各自 auto margin 平分剩余空间
|
|
@@ -33,7 +33,7 @@ export function installTabVisibility(ctx) {
|
|
|
33
33
|
const hidden = value?.hiddenTabs ?? TAB_VISIBILITY_DEFAULTS.hiddenTabs;
|
|
34
34
|
const list = sessions?.list?.getSnapshot();
|
|
35
35
|
const row = list?.current !== undefined ? list.byId[list.current] : undefined;
|
|
36
|
-
const isXingyuan = row?.agentPreset === 'xingyuan';
|
|
36
|
+
const isXingyuan = row?.projectionValues?.agentPreset === 'xingyuan';
|
|
37
37
|
// 「始终显示 × 非星愿会话」的今日页轻提示(Q4=b 结论),其余模式不提示
|
|
38
38
|
setTodayHint(mode === 'show' && !isXingyuan);
|
|
39
39
|
const visible = new Set(visibleTabIds(mode, hidden, isXingyuan));
|
package/lib/client.js
CHANGED
|
@@ -61,11 +61,17 @@ const ZH_DICT = {
|
|
|
61
61
|
"state.deleted": "已删除「{title}」",
|
|
62
62
|
"state.done": "已完成",
|
|
63
63
|
"wish.progress": "进度 {percent}%",
|
|
64
|
-
"wish.
|
|
64
|
+
"wish.planning": "计划中",
|
|
65
|
+
"wish.achieved": "已达成 🎉",
|
|
66
|
+
"wish.pendingCount": "{n} 个任务待领取",
|
|
67
|
+
"wish.settled": "待收尾",
|
|
68
|
+
"wish.settleHint": "答应自己的都做完了——待领取的领了继续,或删掉就达成",
|
|
65
69
|
"wish.eta": "预计完成 {date}",
|
|
66
70
|
"wish.noTasks": "暂无下属任务——在对话里告诉我,让我为它推荐打卡任务",
|
|
67
71
|
"wish.pageTitle": "我的愿望",
|
|
68
|
-
"wish.summary": "{active} 个进行中{achieved}",
|
|
72
|
+
"wish.summary": "{active} 个进行中{planning}{settled}{achieved}",
|
|
73
|
+
"wish.planningSuffix": " · {n} 个计划中",
|
|
74
|
+
"wish.settledSuffix": " · {n} 个待收尾",
|
|
69
75
|
"wish.achievedSuffix": " · {n} 个已达成",
|
|
70
76
|
"wish.sectionAchieved": "已达成",
|
|
71
77
|
"wish.empty.title": "还没有愿望",
|
|
@@ -332,10 +338,9 @@ const ZH_DICT = {
|
|
|
332
338
|
"chart.subtitle.byTaskTopN": "按任务 TopN",
|
|
333
339
|
"chart.subtitle.byWishTopN": "按愿望 TopN",
|
|
334
340
|
"chart.subtitle.byHourBucket": "按实际打卡时刻分桶;过滤范围为打卡日区间",
|
|
335
|
-
"chart.subtitle.
|
|
336
|
-
"chart.subtitle.progressPercentPending": "%(应打天数完成率,含未领取任务)",
|
|
341
|
+
"chart.subtitle.claimedPercent": "%(已领取任务完成率)",
|
|
337
342
|
"chart.subtitle.lastDays": "近 {n} 天",
|
|
338
|
-
"chart.subtitle.
|
|
343
|
+
"chart.subtitle.daysClaimed": "{done}/{due} 天(已领取任务)",
|
|
339
344
|
"chart.subtitle.daysRatio": "{done}/{due} 天",
|
|
340
345
|
"chart.subtitle.streak": "当前 {current} 天 / 最长 {best} 天",
|
|
341
346
|
"detail.grid.title": "打卡记录",
|
|
@@ -462,11 +467,17 @@ const EN_DICT = {
|
|
|
462
467
|
"state.deleted": "Deleted “{title}”",
|
|
463
468
|
"state.done": "Done",
|
|
464
469
|
"wish.progress": "Progress {percent}%",
|
|
465
|
-
"wish.
|
|
470
|
+
"wish.planning": "Planning",
|
|
471
|
+
"wish.achieved": "Achieved 🎉",
|
|
472
|
+
"wish.pendingCount": "{n} to claim",
|
|
473
|
+
"wish.settled": "Wrapping up",
|
|
474
|
+
"wish.settleHint": "Everything you committed to is done — claim the rest to keep going, or delete them to achieve the wish",
|
|
466
475
|
"wish.eta": "Target date {date}",
|
|
467
476
|
"wish.noTasks": "No tasks yet — tell me in chat and I will suggest check-in tasks for it",
|
|
468
477
|
"wish.pageTitle": "My Wishes",
|
|
469
|
-
"wish.summary": "{active} in progress{achieved}",
|
|
478
|
+
"wish.summary": "{active} in progress{planning}{settled}{achieved}",
|
|
479
|
+
"wish.planningSuffix": " · {n} planning",
|
|
480
|
+
"wish.settledSuffix": " · {n} wrapping up",
|
|
470
481
|
"wish.achievedSuffix": " · {n} achieved",
|
|
471
482
|
"wish.sectionAchieved": "Achieved",
|
|
472
483
|
"wish.empty.title": "No wishes yet",
|
|
@@ -733,10 +744,9 @@ const EN_DICT = {
|
|
|
733
744
|
"chart.subtitle.byTaskTopN": "By task (top N)",
|
|
734
745
|
"chart.subtitle.byWishTopN": "By wish (top N)",
|
|
735
746
|
"chart.subtitle.byHourBucket": "Bucketed by actual check-in time; filtered to the check-in date range",
|
|
736
|
-
"chart.subtitle.
|
|
737
|
-
"chart.subtitle.progressPercentPending": "% of scheduled-day completion (incl. unclaimed)",
|
|
747
|
+
"chart.subtitle.claimedPercent": "% of committed-task completion",
|
|
738
748
|
"chart.subtitle.lastDays": "Last {n} days",
|
|
739
|
-
"chart.subtitle.
|
|
749
|
+
"chart.subtitle.daysClaimed": "{done}/{due} days (claimed tasks)",
|
|
740
750
|
"chart.subtitle.daysRatio": "{done}/{due} days",
|
|
741
751
|
"chart.subtitle.streak": "Current {current}d / best {best}d",
|
|
742
752
|
"detail.grid.title": "Check-in record",
|
|
@@ -2107,20 +2117,19 @@ const CHART_SUBTITLE_KEYS = {
|
|
|
2107
2117
|
"按任务 TopN": "chart.subtitle.byTaskTopN",
|
|
2108
2118
|
"按愿望 TopN": "chart.subtitle.byWishTopN",
|
|
2109
2119
|
"按实际打卡时刻分桶;过滤范围为打卡日区间": "chart.subtitle.byHourBucket",
|
|
2110
|
-
"
|
|
2111
|
-
"%(应打天数完成率,含未领取任务)": "chart.subtitle.progressPercentPending"
|
|
2120
|
+
"%(已领取任务完成率)": "chart.subtitle.claimedPercent"
|
|
2112
2121
|
};
|
|
2113
2122
|
/**
|
|
2114
2123
|
* 副标题本地化:固定短语查表;含数字的模板(近 N 天 / A/B 天 / 当前 A 天 / 最长 B 天 /
|
|
2115
|
-
*
|
|
2124
|
+
* 已领取任务口径)按正则重排——服务端模板变化时未知副标题原样回显(诚实降级)。
|
|
2116
2125
|
*/
|
|
2117
2126
|
function localizeChartSubtitle(subtitle) {
|
|
2118
2127
|
const fixed = CHART_SUBTITLE_KEYS[subtitle];
|
|
2119
2128
|
if (fixed !== void 0) return t(fixed);
|
|
2120
2129
|
let m = /^近 (\d+) 天$/.exec(subtitle);
|
|
2121
2130
|
if (m !== null) return t("chart.subtitle.lastDays", { n: m[1] });
|
|
2122
|
-
m = /^(\d+)\/(\d+)
|
|
2123
|
-
if (m !== null) return t("chart.subtitle.
|
|
2131
|
+
m = /^(\d+)\/(\d+) 天(已领取任务)$/.exec(subtitle);
|
|
2132
|
+
if (m !== null) return t("chart.subtitle.daysClaimed", {
|
|
2124
2133
|
done: m[1],
|
|
2125
2134
|
due: m[2]
|
|
2126
2135
|
});
|
|
@@ -2392,7 +2401,7 @@ function WishView(props) {
|
|
|
2392
2401
|
return (0, react.createElement)("div", { className: `xy-card ${deleted ? "xy-deleted" : ""}` }, (0, react.createElement)("div", { className: "xy-card-head" }, (0, react.createElement)("span", {
|
|
2393
2402
|
className: "xy-badge xy-badge-cat",
|
|
2394
2403
|
style: categoryVars(wish.wish.colorKey, wish.wish.categoryName)
|
|
2395
|
-
}, t("badge.wish")), (0, react.createElement)("span", { className: "xy-title" }, deleted ? t("state.deleted", { title: wish.wish.title }) : wish.wish.title)), (0, react.createElement)("div", { className: "xy-meta" }, deleted ? wish.wish.categoryName : `${wish.wish.categoryName} · ${t("wish.progress", { percent: wish.wish.progress })}`));
|
|
2404
|
+
}, t("badge.wish")), (0, react.createElement)("span", { className: "xy-title" }, deleted ? t("state.deleted", { title: wish.wish.title }) : wish.wish.title)), (0, react.createElement)("div", { className: "xy-meta" }, deleted ? wish.wish.categoryName : `${wish.wish.categoryName} · ${wish.wish.achieved ? t("wish.achieved") : wish.wish.planning ? t("wish.planning") : t("wish.progress", { percent: wish.wish.progress })}${wish.wish.pendingCount ? ` · ${t("wish.pendingCount", { n: wish.wish.pendingCount })}` : ""}${wish.wish.settled ? ` · ${t("wish.settled")}` : ""}`));
|
|
2396
2405
|
}
|
|
2397
2406
|
function TaskView(props) {
|
|
2398
2407
|
const t = useXyT();
|
|
@@ -3660,8 +3669,10 @@ function WishesPage() {
|
|
|
3660
3669
|
});
|
|
3661
3670
|
const data = page.data;
|
|
3662
3671
|
if (data === void 0) return (0, react.createElement)(PageSkeleton);
|
|
3663
|
-
const active = data.wishes.filter((w) => !w.archived);
|
|
3664
3672
|
const achieved = data.wishes.filter((w) => w.archived);
|
|
3673
|
+
const settledCount = data.wishes.filter((w) => w.settled).length;
|
|
3674
|
+
const planningCount = data.wishes.filter((w) => !w.archived && w.planning).length;
|
|
3675
|
+
const active = data.wishes.filter((w) => !w.archived);
|
|
3665
3676
|
/** 删除愿望(级联下属任务与打卡记录,服务端同一写路径):确认后直连动作并整页刷新。
|
|
3666
3677
|
* 行移除后触发按钮销毁、焦点落空——刷新完成后把焦点交给页面标题兜底。 */
|
|
3667
3678
|
const removeWish = (wish) => {
|
|
@@ -3696,34 +3707,41 @@ function WishesPage() {
|
|
|
3696
3707
|
onChanged: page.reload
|
|
3697
3708
|
})] : [line];
|
|
3698
3709
|
});
|
|
3699
|
-
const card = (wish) =>
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
className: "xy-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
title:
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
"
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3710
|
+
const card = (wish) => {
|
|
3711
|
+
const { pendingCount, settled, planning } = wish;
|
|
3712
|
+
const progressLabel = planning ? t("wish.planning") : t("wish.progress", { percent: wish.progress });
|
|
3713
|
+
const pendingMeta = pendingCount > 0 ? ` · ${t("wish.pendingCount", { n: pendingCount })}` : "";
|
|
3714
|
+
return (0, react.createElement)("div", {
|
|
3715
|
+
key: wish.wishId,
|
|
3716
|
+
className: "xy-wishcard"
|
|
3717
|
+
}, (0, react.createElement)("div", { className: "xy-card-head" }, (0, react.createElement)("span", {
|
|
3718
|
+
className: "xy-badge xy-badge-cat",
|
|
3719
|
+
style: categoryVars(wish.colorKey, wish.categoryName)
|
|
3720
|
+
}, wish.categoryName), (0, react.createElement)("span", { className: "xy-title" }, wish.title), (0, react.createElement)("span", { className: "xy-progress-num" }, progressLabel, pendingCount > 0 ? (0, react.createElement)("span", { className: "xy-meta" }, pendingMeta) : null), (0, react.createElement)("button", {
|
|
3721
|
+
className: "xy-btn xy-btn-danger xy-btn-icon",
|
|
3722
|
+
disabled: deleting,
|
|
3723
|
+
"aria-label": t("common.delete") + " · " + wish.title,
|
|
3724
|
+
title: t("common.delete"),
|
|
3725
|
+
onClick: () => removeWish(wish)
|
|
3726
|
+
}, (0, react.createElement)(IconTrash))), wish.description !== void 0 ? (0, react.createElement)("div", { className: "xy-meta" }, wish.description) : null, wish.estimatedCompletionDate !== void 0 ? (0, react.createElement)("div", { className: "xy-meta" }, t("wish.eta", { date: formatMediumDate(wish.estimatedCompletionDate) })) : null, (0, react.createElement)("div", {
|
|
3727
|
+
className: "xy-bar",
|
|
3728
|
+
role: "progressbar",
|
|
3729
|
+
"aria-valuemin": 0,
|
|
3730
|
+
"aria-valuemax": 100,
|
|
3731
|
+
"aria-valuenow": wish.progress,
|
|
3732
|
+
"aria-label": progressLabel + pendingMeta
|
|
3733
|
+
}, (0, react.createElement)("div", {
|
|
3734
|
+
className: "xy-bar-fill",
|
|
3735
|
+
style: { transform: `scaleX(${Math.min(wish.progress, 100) / 100})` }
|
|
3736
|
+
})), settled ? (0, react.createElement)("div", { className: "xy-meta" }, t("wish.settleHint")) : null, wish.tasks.length > 0 ? (0, react.createElement)("div", { className: "xy-wishtasks" }, ...wishTasks(wish)) : (0, react.createElement)("div", { className: "xy-meta" }, t("wish.noTasks")));
|
|
3737
|
+
};
|
|
3722
3738
|
return (0, react.createElement)("div", {
|
|
3723
3739
|
className: "xy-page",
|
|
3724
3740
|
ref: stabilize
|
|
3725
3741
|
}, page.error !== void 0 ? (0, react.createElement)(StaleBanner, { onRetry: () => void page.reload() }) : null, showNoPresetHint ? (0, react.createElement)("p", { className: "xy-hint" }, t("today.noPresetHint")) : null, (0, react.createElement)("div", { className: "xy-page-head" }, (0, react.createElement)("h2", { className: "xy-page-title" }, t("wish.pageTitle")), (0, react.createElement)("span", { className: "xy-meta" }, t("wish.summary", {
|
|
3726
|
-
active: active.length,
|
|
3742
|
+
active: active.length - settledCount - planningCount,
|
|
3743
|
+
planning: planningCount > 0 ? t("wish.planningSuffix", { n: planningCount }) : "",
|
|
3744
|
+
settled: settledCount > 0 ? t("wish.settledSuffix", { n: settledCount }) : "",
|
|
3727
3745
|
achieved: achieved.length > 0 ? t("wish.achievedSuffix", { n: achieved.length }) : ""
|
|
3728
3746
|
})), (0, react.createElement)("div", { className: "xy-page-actions" }, (0, react.createElement)("button", {
|
|
3729
3747
|
className: "xy-btn",
|
|
@@ -4732,7 +4750,7 @@ function installTabVisibility(ctx) {
|
|
|
4732
4750
|
const mode = value?.tabVisibilityMode ?? TAB_VISIBILITY_DEFAULTS.tabVisibilityMode;
|
|
4733
4751
|
const hidden = value?.hiddenTabs ?? TAB_VISIBILITY_DEFAULTS.hiddenTabs;
|
|
4734
4752
|
const list = sessions?.list?.getSnapshot();
|
|
4735
|
-
const isXingyuan = (list?.current !== void 0 ? list.byId[list.current] : void 0)?.agentPreset === "xingyuan";
|
|
4753
|
+
const isXingyuan = (list?.current !== void 0 ? list.byId[list.current] : void 0)?.projectionValues?.agentPreset === "xingyuan";
|
|
4736
4754
|
setTodayHint(mode === "show" && !isXingyuan);
|
|
4737
4755
|
const visible = new Set(visibleTabIds(mode, hidden, isXingyuan));
|
|
4738
4756
|
for (const entry of TAB_ENTRIES) {
|
|
@@ -5151,7 +5169,7 @@ function SettingsSection(props) {
|
|
|
5151
5169
|
const inject = [
|
|
5152
5170
|
"slots",
|
|
5153
5171
|
"settingsScope",
|
|
5154
|
-
"
|
|
5172
|
+
"uiConversation"
|
|
5155
5173
|
];
|
|
5156
5174
|
/** 事件类型 → 渲染 kind。 */
|
|
5157
5175
|
const KIND_BY_EVENT = {
|
|
@@ -5205,11 +5223,11 @@ function apply(ctx) {
|
|
|
5205
5223
|
disposeToasts();
|
|
5206
5224
|
disposeConfirms();
|
|
5207
5225
|
});
|
|
5208
|
-
ctx.
|
|
5209
|
-
ctx.
|
|
5210
|
-
ctx.
|
|
5211
|
-
ctx.
|
|
5212
|
-
ctx.
|
|
5226
|
+
ctx.uiConversation.events.register(definition("xy-wish"));
|
|
5227
|
+
ctx.uiConversation.events.register(definition("xy-task"));
|
|
5228
|
+
ctx.uiConversation.events.register(definition("xy-checkin"));
|
|
5229
|
+
ctx.uiConversation.events.register(definition("xy-chart"));
|
|
5230
|
+
ctx.uiConversation.events.register(definition("xy-micro"));
|
|
5213
5231
|
ctx.slots.inject("conversation.chat.node", () => [
|
|
5214
5232
|
ctx.slots.register({
|
|
5215
5233
|
name: "conversation.chat.node",
|
package/lib/growth.js
CHANGED
|
@@ -117,7 +117,9 @@ export function resolveLevel(totalExperience) {
|
|
|
117
117
|
export function growthSummary(store, today = todayIso()) {
|
|
118
118
|
// 单遍任务索引批量新鲜化(freshWishes),替代逐愿望嵌套全表扫描 O(W×T)
|
|
119
119
|
const wishes = freshWishes(store);
|
|
120
|
-
|
|
120
|
+
// 达成口径与愿望卡一致(freshWishes 派生的 archived 谓词):承诺全部完成且无未处理候选——
|
|
121
|
+
// 禁止在此重复写 progress >= 100 谓词(候选拦达成会漏掉,见 §5.2 规则 7)
|
|
122
|
+
const completedWishes = wishes.filter((wish) => wish.archived).length;
|
|
121
123
|
let totalTasks = 0;
|
|
122
124
|
let completedTasks = 0;
|
|
123
125
|
for (const [, task] of store.domain.table('tasks').entries()) {
|
package/lib/pref-settings.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import z from '@deepseek-ai/schemastery';
|
|
2
|
-
import { installSettingsSection, settingsNamespace } from '@deepseek-ai/dsh-settings';
|
|
3
2
|
import { CONFIRM_LANGS, MEMORY_LIMIT_MAX, MEMORY_LIMIT_MIN, PREF_DEFAULTS, } from './pref-policy.js';
|
|
4
|
-
/** 对话偏好命名空间(client 半侧 settingsScope
|
|
5
|
-
export const PREF_NS =
|
|
3
|
+
/** 对话偏好命名空间(client 半侧 settingsScope 同名配对,勿改;小写连字符文法由新 API 类型校验)。 */
|
|
4
|
+
export const PREF_NS = 'xingyuan-pref';
|
|
6
5
|
/** 配置 schema(默认值写进 schema;step(1) 让服务端也拒绝小数,防手改文档/RPC 直写)。 */
|
|
7
6
|
export const PrefSettingsSchema = z.object({
|
|
8
7
|
confirmWrites: z.boolean().default(PREF_DEFAULTS.confirmWrites),
|
|
@@ -20,10 +19,15 @@ export const PrefSettingsSchema = z.object({
|
|
|
20
19
|
export function installPrefSettings(ctx) {
|
|
21
20
|
// 闭包局部,非模块级单例(AGENTS.md §8:不做任何跨重载的模块级单例状态)
|
|
22
21
|
let read = () => PREF_DEFAULTS;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
// 回调形参 inv 才是声明了 settings 依赖的上下文:cordis 4.0.2 起,
|
|
23
|
+
// 未在当前 fiber 声明的服务属性不得访问(外层 ctx 直接读 ctx.settings 会
|
|
24
|
+
// 抛 "cannot get property without inject"),注册归属(owner)仍传插件 fiber。
|
|
25
|
+
ctx.inject(['settings'], (inv) => {
|
|
26
|
+
inv.settings.installSection(ctx, PREF_NS, PrefSettingsSchema, PREF_DEFAULTS, {
|
|
27
|
+
setSource: (current) => { read = current; },
|
|
28
|
+
// 消费方(工具/提示词)每次执行即时读 thunk,无需在此重建任何东西
|
|
29
|
+
onChange: () => { },
|
|
30
|
+
});
|
|
27
31
|
});
|
|
28
32
|
return () => read();
|
|
29
33
|
}
|