dsh-manage-session 0.3.3 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.ja.md +242 -188
- package/CHANGELOG.ko.md +242 -189
- package/CHANGELOG.md +341 -261
- package/INSTALL.ja.md +179 -164
- package/INSTALL.ko.md +179 -164
- package/INSTALL.md +179 -164
- package/INSTALL.zh.md +179 -164
- package/LICENSE +21 -21
- package/README.de.md +120 -0
- package/README.en.md +12 -0
- package/README.es.md +120 -0
- package/README.fr.md +120 -0
- package/README.it.md +120 -0
- package/README.ja.md +12 -0
- package/README.ko.md +12 -0
- package/README.md +14 -0
- package/README.ru.md +120 -0
- package/cordis.patch.yml +11 -11
- package/dsh.plugin.json +22 -22
- package/lib/client.js +623 -35
- package/lib/index.d.ts +123 -8
- package/lib/index.js +1147 -815
- package/package.json +125 -122
- package/src/client/card.tsx +9 -12
- package/src/client/health-panel.tsx +51 -0
- package/src/client/history-panel.tsx +319 -319
- package/src/client/host-api.ts +14 -1
- package/src/client/index.ts +150 -30
- package/src/client/locales.ts +491 -1
- package/src/config.ts +33 -33
- package/src/host/health/attribution.ts +118 -118
- package/src/host/health/gates.ts +4 -1
- package/src/host/health/lossless.ts +128 -128
- package/src/host/health/source-kind.ts +350 -0
- package/src/host/history/archive-source.ts +202 -202
- package/src/host/history/archive.ts +194 -191
- package/src/host/history/purge.ts +370 -323
- package/src/index.ts +56 -48
package/src/client/host-api.ts
CHANGED
|
@@ -73,6 +73,8 @@ export interface HistoryRow {
|
|
|
73
73
|
bytes: number
|
|
74
74
|
/** 投影缓存占用字节数。 */
|
|
75
75
|
cacheBytes: number
|
|
76
|
+
/** 署名转换备份占用字节数(`bytes` 的子集,单列防误读)。 */
|
|
77
|
+
backupBytes: number
|
|
76
78
|
}
|
|
77
79
|
|
|
78
80
|
/** 历史文件列表响应。 */
|
|
@@ -91,6 +93,8 @@ export interface HistoryPurgeResponse {
|
|
|
91
93
|
ok: boolean
|
|
92
94
|
purged?: number
|
|
93
95
|
freedBytes?: number
|
|
96
|
+
/** 一并删除的署名转换备份个数。 */
|
|
97
|
+
backupsRemoved?: number
|
|
94
98
|
failures?: { sessionId: string; reason: string }[]
|
|
95
99
|
requiresRestart?: boolean
|
|
96
100
|
error?: string
|
|
@@ -98,7 +102,7 @@ export interface HistoryPurgeResponse {
|
|
|
98
102
|
|
|
99
103
|
/** 一个 gate 的结果(与 host 侧 GateResult 对齐)。 */
|
|
100
104
|
export interface HealthGate {
|
|
101
|
-
id: 'generation' | 'log-integrity' | 'projection-cache' | 'lossless-json' | 'cold-read'
|
|
105
|
+
id: 'generation' | 'log-integrity' | 'projection-cache' | 'lossless-json' | 'cold-read' | 'source-kind'
|
|
102
106
|
level: 'ok' | 'warn' | 'fail' | 'skipped'
|
|
103
107
|
evidence: string
|
|
104
108
|
attribution?: { projection?: string; package?: string; field?: string }
|
|
@@ -171,6 +175,15 @@ export interface HealthRepairResponse {
|
|
|
171
175
|
error?: string
|
|
172
176
|
}
|
|
173
177
|
|
|
178
|
+
/** 署名转换(session-health-source-migrate)响应。 */
|
|
179
|
+
export interface SourceMigrateResponse {
|
|
180
|
+
ok: boolean
|
|
181
|
+
outcome?: { ok: boolean; path: string; backup?: string; changedRows?: number; byPlugin?: Record<string, number>; error?: string }
|
|
182
|
+
/** 转换后的最新体检报告(无论成败都给出,便于面板对照)。 */
|
|
183
|
+
after?: HealthReport
|
|
184
|
+
error?: string
|
|
185
|
+
}
|
|
186
|
+
|
|
174
187
|
/** 状态响应。 */
|
|
175
188
|
export interface HealthStatusResponse {
|
|
176
189
|
ok: boolean
|
package/src/client/index.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* 会话管家客户端半身:样式、字典、侧边栏入口(dsh-manage-session)与设置卡。
|
|
3
3
|
*
|
|
4
4
|
* 侧边栏入口点击后打开「养老院 / 体检」双页签对话框;两个页签的可见性由配置开关决定,
|
|
5
|
-
* 关掉的子域不渲染页签,也不留空壳。开关读取走
|
|
6
|
-
*
|
|
5
|
+
* 关掉的子域不渲染页签,也不留空壳。开关读取走 configForms(0.1.7,entry id 键)/
|
|
6
|
+
* 旧宿主设置服务的订阅快照;配置写入由 volatile 字段自动生成的设置表单承担。
|
|
7
7
|
*/
|
|
8
8
|
import type { Context } from 'cordis'
|
|
9
9
|
import { createElement, useState, useSyncExternalStore, type ReactElement } from 'react'
|
|
@@ -13,8 +13,8 @@ import {
|
|
|
13
13
|
STEWARD_SETTINGS_NAMESPACE,
|
|
14
14
|
type StewardConfig,
|
|
15
15
|
} from '../config.ts'
|
|
16
|
-
import { StewardSettingsCard, type
|
|
17
|
-
import { en, translate, zh, type LocaleKey } from './locales.ts'
|
|
16
|
+
import { StewardCardScope, StewardSettingsCard, type CardTranslate } from './card.tsx'
|
|
17
|
+
import { de, en, es, fr, it, ru, translate, zh, type LocaleKey } from './locales.ts'
|
|
18
18
|
import { StewardFooter, StewardPanel, TAB_HEALTH, TAB_HISTORY } from './panel.tsx'
|
|
19
19
|
|
|
20
20
|
export { STEWARD_ENTRY_ID, STEWARD_SETTINGS_NAMESPACE, TAB_HEALTH, TAB_HISTORY }
|
|
@@ -27,18 +27,29 @@ export const NS = 'dsh-manage-session'
|
|
|
27
27
|
interface StewardSlotsService {
|
|
28
28
|
inject(name: string, callback: () => unknown, label?: string): void
|
|
29
29
|
register(config: Record<string, unknown>, component: unknown): unknown
|
|
30
|
+
entries(name: string): Array<{ options: { id?: string; order?: number; label?: unknown } }>
|
|
31
|
+
getVersion(name: string): number
|
|
32
|
+
subscribe(name: string, listener: () => void): () => void
|
|
30
33
|
}
|
|
31
34
|
|
|
32
35
|
/** locale 服务面。 */
|
|
33
36
|
interface StewardLocaleService {
|
|
34
|
-
register(
|
|
37
|
+
register(
|
|
38
|
+
ns: string,
|
|
39
|
+
dictionaries: { zh: unknown; en: unknown; fr?: unknown; de?: unknown; it?: unknown; ru?: unknown; es?: unknown },
|
|
40
|
+
): () => void
|
|
35
41
|
}
|
|
36
42
|
|
|
37
|
-
/**
|
|
43
|
+
/** 旧宿主设置服务面(0.1.7 前的回退路径,结构化镜像)。 */
|
|
38
44
|
interface StewardSettingsScope {
|
|
39
45
|
bind<U>(input: { namespace: string }): (StewardCardScope & { getSnapshot(): { value: U | undefined } }) | undefined
|
|
40
46
|
}
|
|
41
47
|
|
|
48
|
+
/** configForms 服务面(0.1.7:以 profile entry id 取句柄)。 */
|
|
49
|
+
interface StewardConfigForms {
|
|
50
|
+
get<U>(entryId: string): StewardCardScope & { getSnapshot(): { value: U | undefined } }
|
|
51
|
+
}
|
|
52
|
+
|
|
42
53
|
/** 客户端插件声明的注入面。 */
|
|
43
54
|
export const inject = ['slots']
|
|
44
55
|
|
|
@@ -58,9 +69,31 @@ function injectStyles(): () => void {
|
|
|
58
69
|
const style = document.createElement('style')
|
|
59
70
|
style.id = id
|
|
60
71
|
style.textContent = `
|
|
72
|
+
/* 侧栏 footer 槽位公约(2026-09-26;2026-10-01 收紧行距并强制居中):一行多
|
|
73
|
+
入口(第三方 dsh-context 等)会互相挤占 —— 宿主 .footerActions 是 nowrap
|
|
74
|
+
flex 行。这里允许容器换行,并把本插件入口钉成独占一整行(flex-basis:100%);
|
|
75
|
+
其余入口(含第三方)自然落到后续行,各行内部自行布局,谁也不挤谁。
|
|
76
|
+
justify-content:center 让行内所有入口(含不占满行的)统一居中;row-gap:0
|
|
77
|
+
配合入口自身 32px 高度压缩纵向占位。类名用 [class*=] 中段匹配:宿主是 CSS
|
|
78
|
+
Module 哈希类名(实测形如 hHd-Xa_footerActions —— <hash>_<name>,哈希在前),
|
|
79
|
+
中段跨版本稳定。 */
|
|
80
|
+
[class*="footerActions"]{flex-wrap:wrap;justify-content:center;row-gap:0;height:auto;min-height:0}
|
|
81
|
+
/* —— 第三方矫正:dsh-context「上下文洞察」入口(2026-10-01)——
|
|
82
|
+
.lc-ov-entry 按"独占整行"设计(width:calc(100% + 4px)、无 justify-content、
|
|
83
|
+
42px 高、不对称 padding),与本槽位公约(每个入口独占一行、行内居中、32px)
|
|
84
|
+
冲突,表现为文字靠左、纵向松散。这里按公约强制矫正;:not() 排除收起轨道的
|
|
85
|
+
36px 圆钮形态。lc-ov-* 是 dsh-context 源码硬编码类名(非构建哈希),跨版本
|
|
86
|
+
稳定(实测 0.56.1 / 0.60.0 规则一致)。两个自有插件各自携带同一矫正块:
|
|
87
|
+
任一被禁用时矫正仍生效。 */
|
|
88
|
+
.lc-ov-entry:not(.lc-ov-entry-rail){width:auto!important;flex:0 0 100%!important;min-width:0!important;justify-content:center!important;height:32px!important;margin:0!important;padding:0 10px!important}
|
|
89
|
+
.lc-ov-entry-label{flex:0 1 auto!important}
|
|
90
|
+
/* 宽栏:独占一整行、放弃主动收缩(flex-shrink:0),行内居中;收起轨道回落自然宽度。 */
|
|
61
91
|
.dss_entryWrap{flex:0 1 auto;display:inline-flex;align-items:center;min-width:0}
|
|
62
|
-
.dss_entryWrapWide{margin-left:
|
|
63
|
-
|
|
92
|
+
.dss_entryWrapWide{flex:0 0 100%;width:100%;justify-content:center;margin-left:0}
|
|
93
|
+
/* 高度 32px(内容 22px 行高 + 上下各 5px):宿主默认 42px 的上下裕度在多行
|
|
94
|
+
堆叠后过于松散;左右内边距对称(10px/10px),否则整行居中时按钮内容会因
|
|
95
|
+
不对称 padding 向左偏 1px。 */
|
|
96
|
+
.dss_footerEntry{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:4px;height:32px;padding:0 10px;border:none;border-radius:12px;background:transparent;cursor:pointer;color:var(--dsw-alias-label-primary);font-family:inherit;font-size:14px;line-height:22px;white-space:nowrap;overflow:hidden;transition:background-color 160ms ease-out,color 160ms ease-out}
|
|
64
97
|
.dss_footerEntry:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
|
65
98
|
.dss_footerEntryRail{width:28px;height:28px;padding:0;gap:0;border-radius:50%}
|
|
66
99
|
.dss_footerIcon{flex:none;font-size:15px;line-height:1}
|
|
@@ -149,46 +182,133 @@ function injectStyles(): () => void {
|
|
|
149
182
|
|
|
150
183
|
/**
|
|
151
184
|
* 客户端插件主体。
|
|
152
|
-
* @param ctx - 客户端上下文(slots、可选 locale /
|
|
185
|
+
* @param ctx - 客户端上下文(slots、可选 locale / configForms)。
|
|
153
186
|
*/
|
|
154
187
|
export function apply(ctx: Context): void {
|
|
155
188
|
ctx.effect(() => injectStyles(), 'dsh-manage-session: stylesheet')
|
|
156
189
|
|
|
157
190
|
const locale = ctx.get('locale') as StewardLocaleService | undefined
|
|
158
191
|
if (locale !== undefined && typeof locale.register === 'function') {
|
|
159
|
-
ctx.effect(() => locale.register(NS, { zh, en }), 'dsh-manage-session: dictionaries')
|
|
192
|
+
ctx.effect(() => locale.register(NS, { zh, en, fr, de, it, ru, es }), 'dsh-manage-session: dictionaries')
|
|
160
193
|
}
|
|
161
194
|
|
|
162
|
-
|
|
163
|
-
const
|
|
195
|
+
// 0.1.7:configForms 以 entry id 取句柄;旧宿主回退到按命名空间绑定。
|
|
196
|
+
const configForms = ctx.get('configForms') as StewardConfigForms | undefined
|
|
197
|
+
const legacySettings = ctx.get('settingsScope') as StewardSettingsScope | undefined
|
|
198
|
+
const bound = configForms?.get<StewardConfig>(STEWARD_ENTRY_ID)
|
|
199
|
+
?? legacySettings?.bind<StewardConfig>({ namespace: STEWARD_SETTINGS_NAMESPACE })
|
|
164
200
|
|
|
165
201
|
const slots = ctx.get('slots') as StewardSlotsService | undefined
|
|
166
202
|
if (slots === undefined) return
|
|
167
203
|
|
|
168
204
|
// 侧边栏入口:一个按钮,打开「养老院 / 体检」双页签面板。
|
|
169
205
|
slots.inject('sidebar.footer.action', () => slots.register(
|
|
170
|
-
{ name: 'sidebar.footer.action', id: STEWARD_ENTRY_ID, order:
|
|
206
|
+
{ name: 'sidebar.footer.action', id: STEWARD_ENTRY_ID, order: 6 },
|
|
171
207
|
(props: StewardFooterProps) => createElement(StewardEntry, { ...props, scope: bound }),
|
|
172
208
|
), 'dsh-manage-session: sidebar footer entry')
|
|
173
209
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
set: async () => {},
|
|
189
|
-
},
|
|
210
|
+
// 插件族共用设置 tab(dsh-thinking-levels 的顶级「起子插件设置」节声明该子
|
|
211
|
+
// 席位)。thinking-levels 缺席时本 inject 静默等待,不阻塞客户端半。
|
|
212
|
+
slots.inject('dsh-family.tab', () => slots.register(
|
|
213
|
+
{
|
|
214
|
+
name: 'dsh-family.tab',
|
|
215
|
+
id: STEWARD_ENTRY_ID,
|
|
216
|
+
order: 40,
|
|
217
|
+
// 账本 label:读期求值,跟随当前 locale(translate 内建 zh/en 兜底)。
|
|
218
|
+
label: () => translate((locale as { bind?: (n: string) => CardTranslate } | undefined)?.bind?.(NS), 'card.title'),
|
|
219
|
+
locale: NS,
|
|
220
|
+
},
|
|
221
|
+
(props: { t?: CardTranslate }) => createElement(StewardSettingsCard, {
|
|
222
|
+
scope: bound as StewardCardScope,
|
|
223
|
+
t: props.t,
|
|
190
224
|
}),
|
|
191
|
-
|
|
225
|
+
), 'dsh-manage-session: family settings tab')
|
|
226
|
+
|
|
227
|
+
// 家族节宿主选举(次优先级):宿主 dsh-thinking-levels 缺席时顶上「起子插件
|
|
228
|
+
// 设置」节。下载量排名 session-steward(1090) 低于 session-guard(1522) → 宽限期
|
|
229
|
+
// 更长,guard 先尝试接管;guard 也缺席时本插件按同一 id `dsh-family` 接管,
|
|
230
|
+
// 声明同一个 dsh-family.tab 子席位并通用渲染全部贡献卡。与 guard 的并发竞争
|
|
231
|
+
// 由 ui-slots 同 id 重复注册抛错仲裁,先到者胜、后到者放弃。
|
|
232
|
+
const familyTabsHooks = makeFamilyTabsHooks(slots)
|
|
233
|
+
ctx.effect(() => {
|
|
234
|
+
let claimed: unknown
|
|
235
|
+
const timer = setTimeout(() => {
|
|
236
|
+
if (slots.entries('settings.section').some(e => e.options.id === FAMILY_SECTION_ID)) return
|
|
237
|
+
try {
|
|
238
|
+
claimed = slots.register({
|
|
239
|
+
name: 'settings.section',
|
|
240
|
+
id: FAMILY_SECTION_ID,
|
|
241
|
+
order: 40,
|
|
242
|
+
label: () => translate((locale as { bind?: (n: string) => CardTranslate } | undefined)?.bind?.(NS), 'family.title'),
|
|
243
|
+
locale: NS,
|
|
244
|
+
inject: () => ({ hooks: { tabs: familyTabsHooks } }),
|
|
245
|
+
children: { [FAMILY_CHILD_KEY]: { kind: 'list', scope: 'root' } },
|
|
246
|
+
}, StewardFamilySection)
|
|
247
|
+
} catch {
|
|
248
|
+
// 并发接管竞争落败(或设置壳未声明席位):胜出方的节服务整个家族。
|
|
249
|
+
}
|
|
250
|
+
}, FAMILY_HOST_GRACE_MS)
|
|
251
|
+
return () => { clearTimeout(timer); if (typeof claimed === 'function') (claimed as () => void)() }
|
|
252
|
+
}, 'dsh-manage-session: family fallback host')
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** 家族节固定 id(与 thinking-levels / guard 的注册严格一致)。 */
|
|
256
|
+
const FAMILY_SECTION_ID = 'dsh-family'
|
|
257
|
+
/** 家族子席位 key(与 thinking-levels 的声明严格一致)。 */
|
|
258
|
+
const FAMILY_CHILD_KEY = 'dsh-family.tab'
|
|
259
|
+
/** 接管宽限期:guard 2000ms 先试,本插件 2600ms 兜底。 */
|
|
260
|
+
const FAMILY_HOST_GRACE_MS = 2600
|
|
261
|
+
|
|
262
|
+
/** 接管节的账本投影 hooks(与 thinking-levels 的 FamilySectionInjected 同形;locale 变更也触发重渲染)。 */
|
|
263
|
+
function makeFamilyTabsHooks(slots: StewardSlotsService): {
|
|
264
|
+
getSnapshot: () => readonly FamilyTabEntry[]
|
|
265
|
+
subscribe: (listener: () => void) => () => void
|
|
266
|
+
} {
|
|
267
|
+
let version = -1
|
|
268
|
+
let tabs: readonly FamilyTabEntry[] = []
|
|
269
|
+
return {
|
|
270
|
+
getSnapshot: () => {
|
|
271
|
+
const next = slots.getVersion(FAMILY_CHILD_KEY)
|
|
272
|
+
if (next !== version) {
|
|
273
|
+
version = next
|
|
274
|
+
tabs = slots.entries(FAMILY_CHILD_KEY)
|
|
275
|
+
.map(entry => ({
|
|
276
|
+
id: entry.options.id ?? '',
|
|
277
|
+
order: entry.options.order ?? 0,
|
|
278
|
+
label: typeof entry.options.label === 'function'
|
|
279
|
+
? (() => { try { return String((entry.options.label as () => unknown)() ?? entry.options.id ?? '') } catch { return String(entry.options.id ?? '') } })()
|
|
280
|
+
: String(entry.options.label ?? entry.options.id ?? ''),
|
|
281
|
+
}))
|
|
282
|
+
.sort((a, b) => a.order - b.order)
|
|
283
|
+
}
|
|
284
|
+
return tabs
|
|
285
|
+
},
|
|
286
|
+
subscribe: (listener: () => void) => slots.subscribe(FAMILY_CHILD_KEY, listener),
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** 账本条目(id/order/label,label 已解析为字符串)。 */
|
|
291
|
+
interface FamilyTabEntry {
|
|
292
|
+
id: string
|
|
293
|
+
order: number
|
|
294
|
+
label: string
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* 家族节接管组件(fallback host section):纯通用渲染 —— 把 `dsh-family.tab`
|
|
299
|
+
* 账本里的每张贡献卡(含本插件自己的,接管时本插件也声明了该子席位)按
|
|
300
|
+
* order 依次 renderSlot;各卡本身是默认展开的抽屉,整节即一页抽屉。
|
|
301
|
+
*/
|
|
302
|
+
function StewardFamilySection(props: {
|
|
303
|
+
renderSlot: (key: string, owner?: object, opts?: { only?: string; fallback?: unknown }) => unknown
|
|
304
|
+
hooks: { tabs: { getSnapshot: () => readonly FamilyTabEntry[]; subscribe: (listener: () => void) => () => void } }
|
|
305
|
+
}): ReactElement {
|
|
306
|
+
const tabs = useSyncExternalStore(props.hooks.tabs.subscribe, props.hooks.tabs.getSnapshot)
|
|
307
|
+
return createElement('div', { style: { display: 'grid', gap: '12px' } },
|
|
308
|
+
...tabs.map(row => createElement('div', { key: row.id },
|
|
309
|
+
props.renderSlot(FAMILY_CHILD_KEY, {}, { only: row.id, fallback: null }) as ReactElement,
|
|
310
|
+
)),
|
|
311
|
+
)
|
|
192
312
|
}
|
|
193
313
|
|
|
194
314
|
/** 入口按钮:持有面板开关;两个页签的可见性来自配置快照。 */
|