dsh-manage-session 0.5.1 → 0.5.4

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 (63) hide show
  1. package/CHANGELOG.ja.md +207 -242
  2. package/CHANGELOG.ko.md +207 -242
  3. package/CHANGELOG.md +341 -341
  4. package/INSTALL.ja.md +164 -179
  5. package/INSTALL.ko.md +164 -179
  6. package/INSTALL.md +164 -179
  7. package/INSTALL.zh.md +164 -179
  8. package/LICENSE +21 -21
  9. package/README.en.md +4 -12
  10. package/README.ja.md +0 -12
  11. package/README.ko.md +0 -12
  12. package/README.md +4 -14
  13. package/cordis.patch.yml +16 -11
  14. package/dsh.plugin.json +4 -4
  15. package/lib/client.js +3882 -690
  16. package/lib/index.d.ts +759 -29
  17. package/lib/index.js +2604 -93
  18. package/locale/de.json +6 -0
  19. package/locale/en.json +6 -0
  20. package/locale/es.json +6 -0
  21. package/locale/fr.json +6 -0
  22. package/locale/it.json +6 -0
  23. package/locale/ja.json +6 -0
  24. package/locale/ko.json +6 -0
  25. package/locale/ru.json +6 -0
  26. package/locale/zh.json +6 -0
  27. package/package.json +43 -32
  28. package/src/client/bridge-scope.ts +213 -0
  29. package/src/client/card.tsx +150 -150
  30. package/src/client/history-panel.tsx +319 -319
  31. package/src/client/index.ts +465 -354
  32. package/src/client/locales.ts +857 -698
  33. package/src/client/panel.tsx +139 -118
  34. package/src/client/search/card.tsx +563 -0
  35. package/src/client/search/host-api.ts +208 -0
  36. package/src/client/search/locales.ts +780 -0
  37. package/src/client/search/manage.tsx +400 -0
  38. package/src/client/search/panel.tsx +815 -0
  39. package/src/client/search/platform.ts +136 -0
  40. package/src/config.ts +82 -33
  41. package/src/host/health/attribution.ts +118 -118
  42. package/src/host/health/lossless.ts +128 -128
  43. package/src/host/health/source-kind.ts +350 -350
  44. package/src/host/history/archive-source.ts +250 -202
  45. package/src/host/history/archive.ts +249 -194
  46. package/src/host/history/favorites.ts +71 -0
  47. package/src/host/history/purge.ts +370 -370
  48. package/src/host/index/archive-source.ts +90 -0
  49. package/src/host/index/engine.ts +571 -0
  50. package/src/host/index/extract.ts +272 -0
  51. package/src/host/index/peers.ts +62 -0
  52. package/src/host/index/rebuild.ts +368 -0
  53. package/src/host/index/schema.ts +136 -0
  54. package/src/host/index/snapshot.ts +135 -0
  55. package/src/host/index/sync.ts +286 -0
  56. package/src/host/registry/archive-registry.ts +317 -0
  57. package/src/host/settings-bridge.ts +237 -0
  58. package/src/index.ts +1421 -532
  59. package/README.de.md +0 -120
  60. package/README.es.md +0 -120
  61. package/README.fr.md +0 -120
  62. package/README.it.md +0 -120
  63. package/README.ru.md +0 -120
@@ -0,0 +1,237 @@
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
+ * 两个命名空间**并存且互不迁移**(config.ts 的存储键稳定承诺):
10
+ * `session-steward`(管家字段)与 `switch-search`(搜索字段)。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
+ SWITCH_DEFAULT_CONFIG,
23
+ SWITCH_SEARCH_SETTINGS_NAMESPACE,
24
+ } from '../config.ts'
25
+
26
+ /** 老宿主 settings 服务面(结构化镜像,绝不 value-import 官方包)。 */
27
+ export interface LegacySettingsFace {
28
+ register?: (ns: string, schema: unknown) => unknown
29
+ get?: (ns: string) => unknown
30
+ update?: (ns: string, patch: Record<string, unknown>) => Promise<unknown>
31
+ }
32
+
33
+ /** 桥对 handleMethod 暴露的面。 */
34
+ export interface LegacySettingsBridge {
35
+ /** 读一个命名空间文档(无文档/非对象 → undefined)。 */
36
+ get(ns: string): Record<string, unknown> | undefined
37
+ /** 合并写一个命名空间文档;宿主拒绝时抛错(调用方转显式失败)。 */
38
+ update(ns: string, patch: Record<string, unknown>): Promise<void>
39
+ }
40
+
41
+ const SHARED_LEASE = Symbol.for('dsh-manage-session/legacy-settings-bridge')
42
+
43
+ /** 管家命名空间 schema(纯 plain,无 volatile —— 老线 register 拒收 volatile 节点)。 */
44
+ const StewardNsSchema = z.object({
45
+ enabled: z.boolean().default(true),
46
+ historyFiles: z.boolean().default(true),
47
+ healthCheck: z.boolean().default(true),
48
+ search: z.boolean().default(true),
49
+ })
50
+
51
+ /** 搜索命名空间 schema(同上;存储键与 config.ts 承诺一致)。 */
52
+ const SwitchNsSchema = z.object({
53
+ enabled: z.boolean().default(true),
54
+ defaultMode: z.union(['title', 'content']).default('title'),
55
+ autoSync: z.boolean().default(true),
56
+ syncIntervalMs: z.number().default(30_000),
57
+ archiveKeep: z.number().default(2),
58
+ indexDir: z.string().default(''),
59
+ })
60
+
61
+ /** settings.get 返回值是否像一份文档(老宿主对未写过的 ns 可能回 undefined)。 */
62
+ function asDoc(value: unknown): Record<string, unknown> | undefined {
63
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) return undefined
64
+ return value as Record<string, unknown>
65
+ }
66
+
67
+ /**
68
+ * 在老宿主上租约注册两个命名空间;返回**同步创建的可变 holder**,桥面在
69
+ * `ctx.inject(['settings'], …)` 回调里填入(cordis 作用域注入——settings 服务
70
+ * 只在回调的 sctx 上可见,`ctx.get('settings')` 在 0.1.0 拿不到;0.1.7+ 无
71
+ * register,回调即 return,holder 永远空桥 = describe 只读、mutate 显式拒绝)。
72
+ * 重复 apply 经 Symbol 租约只注册一次;回调体内全部 try/catch——桥的任何
73
+ * 失败都不允许拖死插件树(0.1.0 实机教训:严格 ctx 上裸属性读即 fatal)。
74
+ */
75
+ export function installLegacySettingsBridge(ctx: unknown): BridgeState {
76
+ const state: BridgeState = { bridge: undefined, revision: 0 }
77
+ const injectFn = (ctx as {
78
+ inject?: (deps: string[], fn: (sctx: { settings?: LegacySettingsFace }) => void) => void
79
+ }).inject
80
+ if (typeof injectFn !== 'function') return state
81
+ try {
82
+ injectFn.call(ctx, ['settings'], (sctx) => {
83
+ try {
84
+ const settings = sctx?.settings
85
+ if (settings === undefined || typeof settings.register !== 'function') return
86
+ const carrier = settings as LegacySettingsFace & { [SHARED_LEASE]?: true }
87
+ if (carrier[SHARED_LEASE] !== true) {
88
+ settings.register(STEWARD_SETTINGS_NAMESPACE, StewardNsSchema)
89
+ settings.register(SWITCH_SEARCH_SETTINGS_NAMESPACE, SwitchNsSchema)
90
+ try {
91
+ Object.defineProperty(carrier, SHARED_LEASE, {
92
+ configurable: false,
93
+ enumerable: false,
94
+ writable: false,
95
+ value: true,
96
+ })
97
+ } catch {
98
+ /* 服务对象冻结时无法钉标记:退化为可能重复注册(幂等宿主无碍) */
99
+ }
100
+ }
101
+ state.bridge = {
102
+ get: (ns) => {
103
+ try {
104
+ return asDoc(settings.get?.(ns))
105
+ } catch {
106
+ return undefined
107
+ }
108
+ },
109
+ update: async (ns, patch) => {
110
+ await settings.update?.(ns, patch)
111
+ },
112
+ }
113
+ console.log(`[dsh-manage-session] legacy settings bridge engaged (${STEWARD_SETTINGS_NAMESPACE} + ${SWITCH_SEARCH_SETTINGS_NAMESPACE})`)
114
+ } catch (err) {
115
+ console.log(`[dsh-manage-session] legacy settings bridge FAILED (cards degrade to read-only): ${String(err instanceof Error ? err.message : err)}`)
116
+ }
117
+ })
118
+ } catch {
119
+ /* ctx 无 inject(异常宿主):保持空桥 */
120
+ }
121
+ return state
122
+ }
123
+
124
+ /** 单字段校验器:通过返回清洗后的值,否则返回错误文案。 */
125
+ type FieldVerdict = { ok: true; value: unknown } | { ok: false; error: string }
126
+
127
+ function asBoolean(v: unknown): FieldVerdict {
128
+ return typeof v === 'boolean' ? { ok: true, value: v } : { ok: false, error: '期望布尔值' }
129
+ }
130
+
131
+ /** 各命名空间允许写入的字段与校验(未列出的字段一律拒绝——桥不是任意写缝)。 */
132
+ const FIELD_VALIDATORS: Record<string, Record<string, (v: unknown) => FieldVerdict>> = {
133
+ [STEWARD_SETTINGS_NAMESPACE]: {
134
+ enabled: asBoolean,
135
+ historyFiles: asBoolean,
136
+ healthCheck: asBoolean,
137
+ search: asBoolean,
138
+ },
139
+ [SWITCH_SEARCH_SETTINGS_NAMESPACE]: {
140
+ enabled: asBoolean,
141
+ defaultMode: (v) => (v === 'title' || v === 'content' ? { ok: true, value: v } : { ok: false, error: 'defaultMode 只接受 title|content' }),
142
+ autoSync: asBoolean,
143
+ syncIntervalMs: (v) => {
144
+ if (typeof v !== 'number' || !Number.isFinite(v)) return { ok: false, error: 'syncIntervalMs 必须是有限数字' }
145
+ return { ok: true, value: Math.min(3_600_000, Math.max(5_000, Math.round(v))) }
146
+ },
147
+ archiveKeep: (v) => {
148
+ if (typeof v !== 'number' || !Number.isInteger(v)) return { ok: false, error: 'archiveKeep 必须是整数' }
149
+ return { ok: true, value: Math.min(20, Math.max(0, v)) }
150
+ },
151
+ indexDir: (v) => {
152
+ if (typeof v !== 'string') return { ok: false, error: 'indexDir 必须是字符串' }
153
+ const trimmed = v.trim()
154
+ if (trimmed.length > 500) return { ok: false, error: 'indexDir 过长(>500 字符)' }
155
+ return { ok: true, value: trimmed }
156
+ },
157
+ },
158
+ }
159
+
160
+ /** 两份命名空间文档(describe/mutate 响应的统一形状)。 */
161
+ export interface BridgeNamespaces {
162
+ [STEWARD_SETTINGS_NAMESPACE]: Record<string, unknown>
163
+ [SWITCH_SEARCH_SETTINGS_NAMESPACE]: Record<string, unknown>
164
+ }
165
+
166
+ /** 桥运行时状态(revision 随每次成功写自增,客户端据此识别权威新值)。 */
167
+ export interface BridgeState {
168
+ bridge: LegacySettingsBridge | undefined
169
+ revision: number
170
+ }
171
+
172
+ /** 汇总两份命名空间文档:legacy 读盘上文档合并缺省;modern 返回空投影(writable=false)。 */
173
+ export function describeNamespaces(state: BridgeState): BridgeNamespaces {
174
+ const { bridge } = state
175
+ if (bridge !== undefined) {
176
+ return {
177
+ [STEWARD_SETTINGS_NAMESPACE]: { ...DEFAULT_CONFIG, ...(bridge.get(STEWARD_SETTINGS_NAMESPACE) ?? {}) },
178
+ [SWITCH_SEARCH_SETTINGS_NAMESPACE]: { ...SWITCH_DEFAULT_CONFIG, ...(bridge.get(SWITCH_SEARCH_SETTINGS_NAMESPACE) ?? {}) },
179
+ }
180
+ }
181
+ return { [STEWARD_SETTINGS_NAMESPACE]: {}, [SWITCH_SEARCH_SETTINGS_NAMESPACE]: {} }
182
+ }
183
+
184
+ /** describe 响应。 */
185
+ export function settingsDescribe(state: BridgeState): {
186
+ ok: boolean
187
+ namespaces: BridgeNamespaces
188
+ writable: boolean
189
+ revision: number
190
+ } {
191
+ return {
192
+ ok: true,
193
+ namespaces: describeNamespaces(state),
194
+ writable: state.bridge !== undefined,
195
+ revision: state.revision,
196
+ }
197
+ }
198
+
199
+ /** mutate 请求载荷形状。 */
200
+ export interface MutatePayload {
201
+ ns?: unknown
202
+ fields?: unknown
203
+ }
204
+
205
+ /** 合并写一个命名空间;任何校验/宿主拒绝都显式失败,成功回权威文档。 */
206
+ export async function settingsMutate(
207
+ state: BridgeState,
208
+ payload: unknown,
209
+ ): Promise<{ ok: boolean; error?: string; namespaces?: BridgeNamespaces; revision?: number }> {
210
+ if (state.bridge === undefined) {
211
+ return { ok: false, error: '本宿主无 legacy settings 服务(0.1.7+ 走 configForms 原生句柄),桥不可写' }
212
+ }
213
+ const request = (payload ?? {}) as MutatePayload
214
+ if (typeof request.ns !== 'string' || !(request.ns in FIELD_VALIDATORS)) {
215
+ return { ok: false, error: `未知设置命名空间:${String(request.ns)}` }
216
+ }
217
+ if (request.fields === null || typeof request.fields !== 'object' || Array.isArray(request.fields)) {
218
+ return { ok: false, error: 'fields 必须是对象' }
219
+ }
220
+ const validators = FIELD_VALIDATORS[request.ns]
221
+ const clean: Record<string, unknown> = {}
222
+ for (const [field, value] of Object.entries(request.fields as Record<string, unknown>)) {
223
+ const validate = validators[field]
224
+ if (validate === undefined) return { ok: false, error: `命名空间 ${request.ns} 不接受字段 "${field}"` }
225
+ const verdict = validate(value)
226
+ if (!verdict.ok) return { ok: false, error: `${field}: ${verdict.error}` }
227
+ clean[field] = verdict.value
228
+ }
229
+ if (Object.keys(clean).length === 0) return { ok: false, error: 'fields 为空' }
230
+ try {
231
+ await state.bridge.update(request.ns, clean)
232
+ } catch (err) {
233
+ return { ok: false, error: `宿主拒绝写入:${String(err instanceof Error ? err.message : err)}` }
234
+ }
235
+ state.revision += 1
236
+ return { ok: true, namespaces: describeNamespaces(state), revision: state.revision }
237
+ }