dsh-manage-session 2.0.3 → 2.0.5

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.
Files changed (43) hide show
  1. package/CHANGELOG.ja.md +33 -33
  2. package/CHANGELOG.ko.md +33 -33
  3. package/CHANGELOG.md +33 -33
  4. package/INSTALL.ja.md +164 -164
  5. package/INSTALL.ko.md +164 -164
  6. package/INSTALL.md +164 -164
  7. package/INSTALL.zh.md +164 -164
  8. package/LICENSE +21 -21
  9. package/README.en.md +76 -62
  10. package/README.ja.md +53 -39
  11. package/README.ko.md +52 -38
  12. package/README.md +18 -4
  13. package/cordis.patch.yml +19 -19
  14. package/dsh.plugin.json +2 -2
  15. package/locale/de.json +6 -6
  16. package/locale/en.json +6 -6
  17. package/locale/es.json +6 -6
  18. package/locale/fr.json +6 -6
  19. package/locale/it.json +6 -6
  20. package/locale/ja.json +6 -6
  21. package/locale/ko.json +6 -6
  22. package/locale/ru.json +6 -6
  23. package/locale/zh.json +6 -6
  24. package/package.json +9 -9
  25. package/screenshots.json +3 -3
  26. package/src/client/bridge-scope.ts +202 -202
  27. package/src/client/card.tsx +154 -154
  28. package/src/client/host-api.ts +61 -61
  29. package/src/client/index.ts +594 -594
  30. package/src/client/locales.ts +893 -893
  31. package/src/client/manage-locales.ts +178 -178
  32. package/src/client/manage.tsx +430 -430
  33. package/src/client/panel.tsx +131 -131
  34. package/src/config.ts +43 -43
  35. package/src/host/health/attribution.ts +118 -118
  36. package/src/host/health/lossless.ts +128 -128
  37. package/src/host/health/source-kind.ts +350 -350
  38. package/src/host/history/archive-source.ts +250 -250
  39. package/src/host/history/archive.ts +249 -249
  40. package/src/host/history/favorites.ts +71 -71
  41. package/src/host/history/purge.ts +370 -370
  42. package/src/host/registry/archive-registry.ts +317 -317
  43. package/src/host/settings-bridge.ts +222 -222
@@ -1,222 +1,222 @@
1
- /**
2
- * Legacy 设置桥(宿主半身,≤0.1.5 专用):老宿主的服务端 settings 服务保留
3
- * register/get/update 三件套 —— 这是 context-compression legacy 租约(六格
4
- * 实证 POST 200 + 持久化)验证过的持久化写路径。0.1.7+ 宿主删掉了 register
5
- * (审计 P1-13:API 不存在,不是探测跳过),配置活在 entry-config volatile
6
- * 字段里、client configForms 原生可用 —— 本桥在那些线上解析为 undefined,
7
- * describe/mutate 方法降级为「显式拒绝写」。
8
- *
9
- * 设置命名空间只有一个:`manage-session`(搜索子域已拆回 dsh-search-index,
10
- * 搜索字段与命名空间不在本包)。schema 必须
11
- * **纯 plain**(不 withVolatile)—— volatile 节点会被 legacy register 拒收
12
- * (`$.enabled expected boolean`,TL beta.18 教训)。
13
- *
14
- * 同一 settings 服务上重复 apply(HMR/双 loader 行)的防重:Symbol.for 键挂
15
- * 在服务对象上(cordis 同服务的所有代理共享该属性,cci 租约同款),首见者
16
- * 注册,后来者直接共享。
17
- */
18
- import z from '@deepseek-ai/schemastery'
19
- import {
20
- DEFAULT_CONFIG,
21
- STEWARD_SETTINGS_NAMESPACE,
22
- } from '../config.ts'
23
-
24
- /** 老宿主 settings 服务面(结构化镜像,绝不 value-import 官方包)。 */
25
- export interface LegacySettingsFace {
26
- register?: (ns: string, schema: unknown) => unknown
27
- get?: (ns: string) => unknown
28
- update?: (ns: string, patch: Record<string, unknown>) => Promise<unknown>
29
- }
30
-
31
- /** 桥对 handleMethod 暴露的面。 */
32
- export interface LegacySettingsBridge {
33
- /** 读一个命名空间文档(无文档/非对象 → undefined)。 */
34
- get(ns: string): Record<string, unknown> | undefined
35
- /** 合并写一个命名空间文档;宿主拒绝时抛错(调用方转显式失败)。 */
36
- update(ns: string, patch: Record<string, unknown>): Promise<void>
37
- }
38
-
39
- const SHARED_LEASE = Symbol.for('dsh-manage-session/legacy-settings-bridge')
40
-
41
- /** 管理命名空间 schema(纯 plain,无 volatile —— 老线 register 拒收 volatile 节点)。 */
42
- const StewardNsSchema = z.object({
43
- enabled: z.boolean().default(true),
44
- historyFiles: z.boolean().default(true),
45
- healthCheck: z.boolean().default(true),
46
- directDelete: z.boolean().default(false),
47
- })
48
-
49
- /** settings.get 返回值是否像一份文档(老宿主对未写过的 ns 可能回 undefined)。 */
50
- function asDoc(value: unknown): Record<string, unknown> | undefined {
51
- if (value === null || typeof value !== 'object' || Array.isArray(value)) return undefined
52
- return value as Record<string, unknown>
53
- }
54
-
55
- /**
56
- * 在老宿主上租约注册两个命名空间;返回**同步创建的可变 holder**,桥面在
57
- * `ctx.inject(['settings'], …)` 回调里填入(cordis 作用域注入——settings 服务
58
- * 只在回调的 sctx 上可见,`ctx.get('settings')` 在 0.1.0 拿不到;0.1.7+ 无
59
- * register,回调即 return,holder 永远空桥 = describe 只读、mutate 显式拒绝)。
60
- * 重复 apply 经 Symbol 租约只注册一次;回调体内全部 try/catch——桥的任何
61
- * 失败都不允许拖死插件树(0.1.0 实机教训:严格 ctx 上裸属性读即 fatal)。
62
- */
63
- export function installLegacySettingsBridge(ctx: unknown): BridgeState {
64
- const state: BridgeState = { bridge: undefined, revision: 0 }
65
- const injectFn = (ctx as {
66
- inject?: (deps: string[], fn: (sctx: { settings?: LegacySettingsFace }) => void) => void
67
- }).inject
68
- if (typeof injectFn !== 'function') return state
69
- try {
70
- injectFn.call(ctx, ['settings'], (sctx) => {
71
- try {
72
- const settings = sctx?.settings
73
- if (settings === undefined || typeof settings.register !== 'function') return
74
- const carrier = settings as LegacySettingsFace & { [SHARED_LEASE]?: true }
75
- if (carrier[SHARED_LEASE] !== true) {
76
- settings.register(STEWARD_SETTINGS_NAMESPACE, StewardNsSchema)
77
- try {
78
- Object.defineProperty(carrier, SHARED_LEASE, {
79
- configurable: false,
80
- enumerable: false,
81
- writable: false,
82
- value: true,
83
- })
84
- } catch {
85
- /* 服务对象冻结时无法钉标记:退化为可能重复注册(幂等宿主无碍) */
86
- }
87
- }
88
- state.bridge = {
89
- get: (ns) => {
90
- try {
91
- return asDoc(settings.get?.(ns))
92
- } catch {
93
- return undefined
94
- }
95
- },
96
- update: async (ns, patch) => {
97
- await settings.update?.(ns, patch)
98
- },
99
- }
100
-
101
- } catch (err) {
102
- console.log(`[dsh-manage-session] legacy settings bridge FAILED (cards degrade to read-only): ${String(err instanceof Error ? err.message : err)}`)
103
- }
104
- })
105
- } catch {
106
- /* ctx 无 inject(异常宿主):保持空桥 */
107
- }
108
- return state
109
- }
110
-
111
- /** 单字段校验器:通过返回清洗后的值,否则返回错误文案。 */
112
- type FieldVerdict = { ok: true; value: unknown } | { ok: false; error: string }
113
-
114
- function asBoolean(v: unknown): FieldVerdict {
115
- return typeof v === 'boolean' ? { ok: true, value: v } : { ok: false, error: '期望布尔值' }
116
- }
117
-
118
- /** 各命名空间允许写入的字段与校验(未列出的字段一律拒绝——桥不是任意写缝)。 */
119
- const FIELD_VALIDATORS: Record<string, Record<string, (v: unknown) => FieldVerdict>> = {
120
- [STEWARD_SETTINGS_NAMESPACE]: {
121
- enabled: asBoolean,
122
- historyFiles: asBoolean,
123
- healthCheck: asBoolean,
124
- directDelete: asBoolean,
125
- },
126
- }
127
-
128
- /** 两份命名空间文档(describe/mutate 响应的统一形状)。 */
129
- export interface BridgeNamespaces {
130
- [STEWARD_SETTINGS_NAMESPACE]: Record<string, unknown>
131
- }
132
-
133
- /** 桥运行时状态(revision 随每次成功写自增,客户端据此识别权威新值)。 */
134
- export interface BridgeState {
135
- bridge: LegacySettingsBridge | undefined
136
- revision: number
137
- }
138
-
139
- /** 汇总两份命名空间文档:legacy 读盘上文档合并缺省;modern 返回空投影(writable=false)。 */
140
- export function describeNamespaces(state: BridgeState): BridgeNamespaces {
141
- const { bridge } = state
142
- if (bridge !== undefined) {
143
- return {
144
- [STEWARD_SETTINGS_NAMESPACE]: { ...DEFAULT_CONFIG, ...(bridge.get(STEWARD_SETTINGS_NAMESPACE) ?? {}) },
145
- }
146
- }
147
- return { [STEWARD_SETTINGS_NAMESPACE]: {} }
148
- }
149
-
150
- /**
151
- * 运行时叠加文档(≤0.1.5 专用,3085 实锤补口):卡写路径落宿主 settings
152
- * 服务,而组合入口 config 是静态缺省——不叠加则开关「拨得动、不生效」。
153
- * 与 describe 不同:**不铺缺省**,只回两 ns 的原始已写键(盘上 settings.yaml
154
- * 实测只含被拨过的键,未写键缺席→自然回退组合 config)。
155
- */
156
- export interface BridgeOverlayDocs {
157
- /** manage-session ns 原始已写文档(桥未咬合时 = {})。 */
158
- steward: Record<string, unknown>
159
- }
160
-
161
- export function bridgeOverlay(state: BridgeState): BridgeOverlayDocs {
162
- const { bridge } = state
163
- if (bridge === undefined) return { steward: {} }
164
- return {
165
- steward: bridge.get(STEWARD_SETTINGS_NAMESPACE) ?? {},
166
- }
167
- }
168
-
169
- /** describe 响应。 */
170
- export function settingsDescribe(state: BridgeState): {
171
- ok: boolean
172
- namespaces: BridgeNamespaces
173
- writable: boolean
174
- revision: number
175
- } {
176
- return {
177
- ok: true,
178
- namespaces: describeNamespaces(state),
179
- writable: state.bridge !== undefined,
180
- revision: state.revision,
181
- }
182
- }
183
-
184
- /** mutate 请求载荷形状。 */
185
- export interface MutatePayload {
186
- ns?: unknown
187
- fields?: unknown
188
- }
189
-
190
- /** 合并写一个命名空间;任何校验/宿主拒绝都显式失败,成功回权威文档。 */
191
- export async function settingsMutate(
192
- state: BridgeState,
193
- payload: unknown,
194
- ): Promise<{ ok: boolean; error?: string; namespaces?: BridgeNamespaces; revision?: number }> {
195
- if (state.bridge === undefined) {
196
- return { ok: false, error: '本宿主无 legacy settings 服务(0.1.7+ 走 configForms 原生句柄),桥不可写' }
197
- }
198
- const request = (payload ?? {}) as MutatePayload
199
- if (typeof request.ns !== 'string' || !(request.ns in FIELD_VALIDATORS)) {
200
- return { ok: false, error: `未知设置命名空间:${String(request.ns)}` }
201
- }
202
- if (request.fields === null || typeof request.fields !== 'object' || Array.isArray(request.fields)) {
203
- return { ok: false, error: 'fields 必须是对象' }
204
- }
205
- const validators = FIELD_VALIDATORS[request.ns]
206
- const clean: Record<string, unknown> = {}
207
- for (const [field, value] of Object.entries(request.fields as Record<string, unknown>)) {
208
- const validate = validators[field]
209
- if (validate === undefined) return { ok: false, error: `命名空间 ${request.ns} 不接受字段 "${field}"` }
210
- const verdict = validate(value)
211
- if (!verdict.ok) return { ok: false, error: `${field}: ${verdict.error}` }
212
- clean[field] = verdict.value
213
- }
214
- if (Object.keys(clean).length === 0) return { ok: false, error: 'fields 为空' }
215
- try {
216
- await state.bridge.update(request.ns, clean)
217
- } catch (err) {
218
- return { ok: false, error: `宿主拒绝写入:${String(err instanceof Error ? err.message : err)}` }
219
- }
220
- state.revision += 1
221
- return { ok: true, namespaces: describeNamespaces(state), revision: state.revision }
222
- }
1
+ /**
2
+ * Legacy 设置桥(宿主半身,≤0.1.5 专用):老宿主的服务端 settings 服务保留
3
+ * register/get/update 三件套 —— 这是 context-compression legacy 租约(六格
4
+ * 实证 POST 200 + 持久化)验证过的持久化写路径。0.1.7+ 宿主删掉了 register
5
+ * (审计 P1-13:API 不存在,不是探测跳过),配置活在 entry-config volatile
6
+ * 字段里、client configForms 原生可用 —— 本桥在那些线上解析为 undefined,
7
+ * describe/mutate 方法降级为「显式拒绝写」。
8
+ *
9
+ * 设置命名空间只有一个:`manage-session`(搜索子域已拆回 dsh-search-index,
10
+ * 搜索字段与命名空间不在本包)。schema 必须
11
+ * **纯 plain**(不 withVolatile)—— volatile 节点会被 legacy register 拒收
12
+ * (`$.enabled expected boolean`,TL beta.18 教训)。
13
+ *
14
+ * 同一 settings 服务上重复 apply(HMR/双 loader 行)的防重:Symbol.for 键挂
15
+ * 在服务对象上(cordis 同服务的所有代理共享该属性,cci 租约同款),首见者
16
+ * 注册,后来者直接共享。
17
+ */
18
+ import z from '@deepseek-ai/schemastery'
19
+ import {
20
+ DEFAULT_CONFIG,
21
+ STEWARD_SETTINGS_NAMESPACE,
22
+ } from '../config.ts'
23
+
24
+ /** 老宿主 settings 服务面(结构化镜像,绝不 value-import 官方包)。 */
25
+ export interface LegacySettingsFace {
26
+ register?: (ns: string, schema: unknown) => unknown
27
+ get?: (ns: string) => unknown
28
+ update?: (ns: string, patch: Record<string, unknown>) => Promise<unknown>
29
+ }
30
+
31
+ /** 桥对 handleMethod 暴露的面。 */
32
+ export interface LegacySettingsBridge {
33
+ /** 读一个命名空间文档(无文档/非对象 → undefined)。 */
34
+ get(ns: string): Record<string, unknown> | undefined
35
+ /** 合并写一个命名空间文档;宿主拒绝时抛错(调用方转显式失败)。 */
36
+ update(ns: string, patch: Record<string, unknown>): Promise<void>
37
+ }
38
+
39
+ const SHARED_LEASE = Symbol.for('dsh-manage-session/legacy-settings-bridge')
40
+
41
+ /** 管理命名空间 schema(纯 plain,无 volatile —— 老线 register 拒收 volatile 节点)。 */
42
+ const StewardNsSchema = z.object({
43
+ enabled: z.boolean().default(true),
44
+ historyFiles: z.boolean().default(true),
45
+ healthCheck: z.boolean().default(true),
46
+ directDelete: z.boolean().default(false),
47
+ })
48
+
49
+ /** settings.get 返回值是否像一份文档(老宿主对未写过的 ns 可能回 undefined)。 */
50
+ function asDoc(value: unknown): Record<string, unknown> | undefined {
51
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) return undefined
52
+ return value as Record<string, unknown>
53
+ }
54
+
55
+ /**
56
+ * 在老宿主上租约注册两个命名空间;返回**同步创建的可变 holder**,桥面在
57
+ * `ctx.inject(['settings'], …)` 回调里填入(cordis 作用域注入——settings 服务
58
+ * 只在回调的 sctx 上可见,`ctx.get('settings')` 在 0.1.0 拿不到;0.1.7+ 无
59
+ * register,回调即 return,holder 永远空桥 = describe 只读、mutate 显式拒绝)。
60
+ * 重复 apply 经 Symbol 租约只注册一次;回调体内全部 try/catch——桥的任何
61
+ * 失败都不允许拖死插件树(0.1.0 实机教训:严格 ctx 上裸属性读即 fatal)。
62
+ */
63
+ export function installLegacySettingsBridge(ctx: unknown): BridgeState {
64
+ const state: BridgeState = { bridge: undefined, revision: 0 }
65
+ const injectFn = (ctx as {
66
+ inject?: (deps: string[], fn: (sctx: { settings?: LegacySettingsFace }) => void) => void
67
+ }).inject
68
+ if (typeof injectFn !== 'function') return state
69
+ try {
70
+ injectFn.call(ctx, ['settings'], (sctx) => {
71
+ try {
72
+ const settings = sctx?.settings
73
+ if (settings === undefined || typeof settings.register !== 'function') return
74
+ const carrier = settings as LegacySettingsFace & { [SHARED_LEASE]?: true }
75
+ if (carrier[SHARED_LEASE] !== true) {
76
+ settings.register(STEWARD_SETTINGS_NAMESPACE, StewardNsSchema)
77
+ try {
78
+ Object.defineProperty(carrier, SHARED_LEASE, {
79
+ configurable: false,
80
+ enumerable: false,
81
+ writable: false,
82
+ value: true,
83
+ })
84
+ } catch {
85
+ /* 服务对象冻结时无法钉标记:退化为可能重复注册(幂等宿主无碍) */
86
+ }
87
+ }
88
+ state.bridge = {
89
+ get: (ns) => {
90
+ try {
91
+ return asDoc(settings.get?.(ns))
92
+ } catch {
93
+ return undefined
94
+ }
95
+ },
96
+ update: async (ns, patch) => {
97
+ await settings.update?.(ns, patch)
98
+ },
99
+ }
100
+
101
+ } catch (err) {
102
+ console.log(`[dsh-manage-session] legacy settings bridge FAILED (cards degrade to read-only): ${String(err instanceof Error ? err.message : err)}`)
103
+ }
104
+ })
105
+ } catch {
106
+ /* ctx 无 inject(异常宿主):保持空桥 */
107
+ }
108
+ return state
109
+ }
110
+
111
+ /** 单字段校验器:通过返回清洗后的值,否则返回错误文案。 */
112
+ type FieldVerdict = { ok: true; value: unknown } | { ok: false; error: string }
113
+
114
+ function asBoolean(v: unknown): FieldVerdict {
115
+ return typeof v === 'boolean' ? { ok: true, value: v } : { ok: false, error: '期望布尔值' }
116
+ }
117
+
118
+ /** 各命名空间允许写入的字段与校验(未列出的字段一律拒绝——桥不是任意写缝)。 */
119
+ const FIELD_VALIDATORS: Record<string, Record<string, (v: unknown) => FieldVerdict>> = {
120
+ [STEWARD_SETTINGS_NAMESPACE]: {
121
+ enabled: asBoolean,
122
+ historyFiles: asBoolean,
123
+ healthCheck: asBoolean,
124
+ directDelete: asBoolean,
125
+ },
126
+ }
127
+
128
+ /** 两份命名空间文档(describe/mutate 响应的统一形状)。 */
129
+ export interface BridgeNamespaces {
130
+ [STEWARD_SETTINGS_NAMESPACE]: Record<string, unknown>
131
+ }
132
+
133
+ /** 桥运行时状态(revision 随每次成功写自增,客户端据此识别权威新值)。 */
134
+ export interface BridgeState {
135
+ bridge: LegacySettingsBridge | undefined
136
+ revision: number
137
+ }
138
+
139
+ /** 汇总两份命名空间文档:legacy 读盘上文档合并缺省;modern 返回空投影(writable=false)。 */
140
+ export function describeNamespaces(state: BridgeState): BridgeNamespaces {
141
+ const { bridge } = state
142
+ if (bridge !== undefined) {
143
+ return {
144
+ [STEWARD_SETTINGS_NAMESPACE]: { ...DEFAULT_CONFIG, ...(bridge.get(STEWARD_SETTINGS_NAMESPACE) ?? {}) },
145
+ }
146
+ }
147
+ return { [STEWARD_SETTINGS_NAMESPACE]: {} }
148
+ }
149
+
150
+ /**
151
+ * 运行时叠加文档(≤0.1.5 专用,3085 实锤补口):卡写路径落宿主 settings
152
+ * 服务,而组合入口 config 是静态缺省——不叠加则开关「拨得动、不生效」。
153
+ * 与 describe 不同:**不铺缺省**,只回两 ns 的原始已写键(盘上 settings.yaml
154
+ * 实测只含被拨过的键,未写键缺席→自然回退组合 config)。
155
+ */
156
+ export interface BridgeOverlayDocs {
157
+ /** manage-session ns 原始已写文档(桥未咬合时 = {})。 */
158
+ steward: Record<string, unknown>
159
+ }
160
+
161
+ export function bridgeOverlay(state: BridgeState): BridgeOverlayDocs {
162
+ const { bridge } = state
163
+ if (bridge === undefined) return { steward: {} }
164
+ return {
165
+ steward: bridge.get(STEWARD_SETTINGS_NAMESPACE) ?? {},
166
+ }
167
+ }
168
+
169
+ /** describe 响应。 */
170
+ export function settingsDescribe(state: BridgeState): {
171
+ ok: boolean
172
+ namespaces: BridgeNamespaces
173
+ writable: boolean
174
+ revision: number
175
+ } {
176
+ return {
177
+ ok: true,
178
+ namespaces: describeNamespaces(state),
179
+ writable: state.bridge !== undefined,
180
+ revision: state.revision,
181
+ }
182
+ }
183
+
184
+ /** mutate 请求载荷形状。 */
185
+ export interface MutatePayload {
186
+ ns?: unknown
187
+ fields?: unknown
188
+ }
189
+
190
+ /** 合并写一个命名空间;任何校验/宿主拒绝都显式失败,成功回权威文档。 */
191
+ export async function settingsMutate(
192
+ state: BridgeState,
193
+ payload: unknown,
194
+ ): Promise<{ ok: boolean; error?: string; namespaces?: BridgeNamespaces; revision?: number }> {
195
+ if (state.bridge === undefined) {
196
+ return { ok: false, error: '本宿主无 legacy settings 服务(0.1.7+ 走 configForms 原生句柄),桥不可写' }
197
+ }
198
+ const request = (payload ?? {}) as MutatePayload
199
+ if (typeof request.ns !== 'string' || !(request.ns in FIELD_VALIDATORS)) {
200
+ return { ok: false, error: `未知设置命名空间:${String(request.ns)}` }
201
+ }
202
+ if (request.fields === null || typeof request.fields !== 'object' || Array.isArray(request.fields)) {
203
+ return { ok: false, error: 'fields 必须是对象' }
204
+ }
205
+ const validators = FIELD_VALIDATORS[request.ns]
206
+ const clean: Record<string, unknown> = {}
207
+ for (const [field, value] of Object.entries(request.fields as Record<string, unknown>)) {
208
+ const validate = validators[field]
209
+ if (validate === undefined) return { ok: false, error: `命名空间 ${request.ns} 不接受字段 "${field}"` }
210
+ const verdict = validate(value)
211
+ if (!verdict.ok) return { ok: false, error: `${field}: ${verdict.error}` }
212
+ clean[field] = verdict.value
213
+ }
214
+ if (Object.keys(clean).length === 0) return { ok: false, error: 'fields 为空' }
215
+ try {
216
+ await state.bridge.update(request.ns, clean)
217
+ } catch (err) {
218
+ return { ok: false, error: `宿主拒绝写入:${String(err instanceof Error ? err.message : err)}` }
219
+ }
220
+ state.revision += 1
221
+ return { ok: true, namespaces: describeNamespaces(state), revision: state.revision }
222
+ }