dsh-manage-session 0.2.3 → 0.4.8

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/src/index.ts CHANGED
@@ -26,20 +26,40 @@ import { purgeHistory } from './host/history/purge.ts'
26
26
  import { buildProjectionOwnerIndex, createAttributor, defaultProfileNodeModules } from './host/health/attribution.ts'
27
27
  import { HealthCache } from './host/health/cache.ts'
28
28
  import { buildSessionReport, type SessionHealthReport } from './host/health/gates.ts'
29
+ import { decodeSessionLogFile } from './host/health/decode.ts'
29
30
  import { assessRepair, prescribe, quarantineProjectionCache } from './host/health/repair.ts'
30
- import { countCorpus, findSessionLog, scanSessions } from './host/health/scan.ts'
31
+ import { countCorpus, findSession, scanSessions } from './host/health/scan.ts'
32
+ import { gateSourceKind, migrateSessionSourceKind, readSourceKindFacts } from './host/health/source-kind.ts'
31
33
 
32
34
  export { DEFAULT_CONFIG, STEWARD_API_PREFIX, STEWARD_SETTINGS_NAMESPACE } from './config.ts'
33
35
  export type { StewardConfig } from './config.ts'
34
36
  export { readArchiveSet, pruneArchiveFile, editWorkspaceDocument } from './host/history/archive-source.ts'
35
37
  export { listHistory, pruneHistory } from './host/history/archive.ts'
36
- export { purgeHistory, locateSessionUsage, indexSessionDirs, isSafeChild, dirSize, sessionsRootFor, projCacheRootFor } from './host/history/purge.ts'
37
- export { buildSessionReport, gateColdRead, gateLogIntegrity, gateLosslessJson, gateProjectionCache, readProjectionCache, readTailFacts } from './host/health/gates.ts'
38
+ export { purgeHistory, locateSessionUsage, indexSessionDirs, isSafeChild, dirSize, sessionsRootFor, projCacheRootFor, isSourceMigrateBackupName, backupFilesIn } from './host/history/purge.ts'
39
+ export { buildSessionReport, gateColdRead, gateGeneration, gateLogIntegrity, gateLosslessJson, gateProjectionCache, readProjectionCache, readTailFacts } from './host/health/gates.ts'
40
+ export { gateSourceKind, migrateSessionSourceKind, migrateLegacySource, readSourceKindFacts, SOURCE_MIGRATE_BACKUP_SUFFIX, V4_HOST_MIN } from './host/health/source-kind.ts'
38
41
  export { firstLosslessViolation, isLossless } from './host/health/lossless.ts'
39
42
  export { decodeSessionLogBytes, decodeSessionLogFile, scanZstdFrames } from './host/health/decode.ts'
43
+ export {
44
+ classifyGenerationFilename,
45
+ generationLogFilename,
46
+ isMigrationStagingFilename,
47
+ latestArtifactMtime,
48
+ parseGenerationLogFilename,
49
+ readSessionGenerations,
50
+ sessionPriority,
51
+ } from './host/health/generation.ts'
52
+ export type {
53
+ GenerationArtifact,
54
+ LogArtifact,
55
+ LogCompression,
56
+ SessionGenerations,
57
+ SessionPriority,
58
+ } from './host/health/generation.ts'
40
59
  export { buildProjectionOwnerIndex, createAttributor } from './host/health/attribution.ts'
41
60
  export { prescribe, quarantineProjectionCache } from './host/health/repair.ts'
42
- export { countCorpus, discoverSessions, findSessionLog, scanSessions } from './host/health/scan.ts'
61
+ export { countCorpus, discoverSessions, findSession, findSessionLog, scanSessions } from './host/health/scan.ts'
62
+ export type { DiscoveredSession } from './host/health/scan.ts'
43
63
  export { HealthCache } from './host/health/cache.ts'
44
64
  export type { HealthCacheEntry } from './host/health/cache.ts'
45
65
  export { assessRepair } from './host/health/repair.ts'
@@ -62,51 +82,26 @@ interface StewardWebRuntime {
62
82
  trustedHosts: readonly string[]
63
83
  }
64
84
 
65
- /** settings 服务面(结构化镜像)。 */
66
- interface SettingsScopeLike {
67
- get(): unknown
68
- watch(callback: () => void): () => void
85
+ /** Live reference the 0.1.7 loader hands `apply` for `.volatile()` config fields. */
86
+ interface VolatileRef<T> {
87
+ get(): T
69
88
  }
70
- interface SettingsServiceLike {
71
- register(ns: string, schema: unknown, options?: { base?: unknown }): SettingsScopeLike
72
- }
73
- interface SettingsAwareCtx {
74
- inject(deps: readonly string[], fn: (sctx: {
75
- settings: SettingsServiceLike
76
- effect(cleanup: () => (() => void) | void, label?: string): void
77
- }) => void): void
89
+
90
+ /** Resolve one possibly-volatile field: a live ref on 0.1.7+, a plain value otherwise. */
91
+ function readVolatileValue<T>(value: T | VolatileRef<T> | undefined): T | undefined {
92
+ if (value !== null && typeof value === 'object' && typeof (value as VolatileRef<T>).get === 'function') {
93
+ return (value as VolatileRef<T>).get()
94
+ }
95
+ return value as T | undefined
78
96
  }
79
97
 
80
- /** 运行时配置 schema(与 src/config.ts 的形状保持一致)。 */
81
- const Config = z.object({
82
- enabled: z.boolean().default(true),
83
- historyFiles: z.boolean().default(true),
84
- healthCheck: z.boolean().default(true),
98
+ /** 运行时配置 schema(与 src/config.ts 的形状保持一致)。0.1.7:volatile 字段即设置表单。 */
99
+ export const Config = z.object({
100
+ enabled: z.boolean().default(true).volatile(),
101
+ historyFiles: z.boolean().default(true).volatile(),
102
+ healthCheck: z.boolean().default(true).volatile(),
85
103
  })
86
104
 
87
- /**
88
- * 官方 `installSettingsSection` 的内联等价:通过 settings 服务注册命名空间、
89
- * 以组合入口作为 `base` 层、并保持运行时来源实时(与 toggle / thinking-levels 同范式)。
90
- */
91
- function installSettingsSection<T>(
92
- ctx: Context,
93
- ns: string,
94
- schema: unknown,
95
- entry: T,
96
- hooks: { setSource: (source: () => T) => void; onChange: () => void },
97
- ): void {
98
- ;(ctx as unknown as SettingsAwareCtx).inject(['settings'], (sctx) => {
99
- const scope = sctx.settings.register(ns, schema, { base: entry })
100
- hooks.setSource(() => scope.get() as T)
101
- hooks.onChange()
102
- sctx.effect(() => () => {
103
- hooks.setSource(() => entry)
104
- hooks.onChange()
105
- })
106
- scope.watch(() => hooks.onChange())
107
- })
108
- }
109
-
110
105
  /** 单次请求体的上限(防御无界读取)。 */
111
106
  const MAX_BODY_BYTES = 16 << 20
112
107
 
@@ -228,6 +223,7 @@ export const HEALTH_METHODS = [
228
223
  'session-health-scan',
229
224
  'session-health-session',
230
225
  'session-health-repair',
226
+ 'session-health-source-migrate',
231
227
  ] as const
232
228
 
233
229
  /** 依据开关判定某方法是否启用。 */
@@ -335,14 +331,17 @@ export async function handleMethod(
335
331
  const request = (payload ?? {}) as { sessionId?: unknown }
336
332
  const sessionId = asSessionId(request.sessionId)
337
333
  if (sessionId === undefined) return { ok: false, error: '缺少合法的 sessionId' }
338
- const logPath = findSessionLog(sessionId, runtime.dshHome)
339
- if (logPath === undefined) return { ok: false, error: `未找到会话日志:${sessionId}` }
334
+ // 按代次定位:会话存在但尚未发布当前代时 logPath 为 undefined,此时仍要出报告
335
+ // (generation / log-integrity 两门如实报出),不能笼统回「未找到会话日志」。
336
+ const session = findSession(sessionId, runtime.dshHome)
337
+ if (session === undefined) return { ok: false, error: `未找到会话日志:${sessionId}` }
340
338
 
341
339
  const projectionState = runtime.projectionStateFor(sessionId)
342
340
  const reportFor = (): SessionHealthReport => buildSessionReport({
343
341
  sessionId,
344
342
  dshHome: runtime.dshHome,
345
- logPath,
343
+ ...(session.logPath === undefined ? {} : { logPath: session.logPath }),
344
+ generations: session.generations,
346
345
  ...(projectionState === undefined ? {} : { projectionState }),
347
346
  attribute: runtime.attribute,
348
347
  })
@@ -355,6 +354,32 @@ export async function handleMethod(
355
354
  return { ok: true, report, prescriptions: prescribe(report, runtime.dshHome) }
356
355
  }
357
356
 
357
+ // session-health-source-migrate:把 v4 日志里的旧署名行(kind:'plugin' + plugin 字段)
358
+ // 转换为 producer-owned kind。这是「禁止改写会话日志」红线的唯一显式例外:
359
+ // 只动 source 署名字段、先整文件备份、v3 及更早版本线拒绝执行(见 source-kind.ts 的版本依赖表)。
360
+ if (method === 'session-health-source-migrate') {
361
+ if (session.logPath === undefined) {
362
+ return { ok: false, error: '会话尚未发布当前代日志,无从转换(先用会话代次工具发布)' }
363
+ }
364
+ const log = decodeSessionLogFile(session.logPath)
365
+ const outcome = migrateSessionSourceKind(sessionId, session.logPath, log, runtime.dshHome)
366
+ // 转换后重出报告:source-kind 门应转 ok;失败时报告原样(便于面板对照)。
367
+ const after = buildSessionReport({
368
+ sessionId,
369
+ dshHome: runtime.dshHome,
370
+ logPath: session.logPath,
371
+ generations: session.generations,
372
+ ...(projectionState === undefined ? {} : { projectionState }),
373
+ attribute: runtime.attribute,
374
+ })
375
+ runtime.cache?.patch(after)
376
+ runtime.log(
377
+ `source-kind migrate ${sessionId}: ok=${outcome.ok} changed=${outcome.changedRows ?? 0}` +
378
+ (outcome.error === undefined ? '' : ` error=${outcome.error}`),
379
+ )
380
+ return { ok: outcome.ok, outcome, after, report: after, error: outcome.error }
381
+ }
382
+
358
383
  // session-health-repair:出院前的可逆处置 + before/after 对照 + 结果定性
359
384
  const before = reportFor()
360
385
  if (before.level === 'ok') {
@@ -371,7 +396,7 @@ export async function handleMethod(
371
396
  verdict: clean.verdict,
372
397
  explanation: clean.explanation,
373
398
  residual: clean.residual,
374
- prescriptions: ['# 四门全绿:无需处置'],
399
+ prescriptions: ['# 全部检查通过:无需处置'],
375
400
  }
376
401
  }
377
402
  const repair = quarantineProjectionCache(sessionId, runtime.dshHome)
@@ -398,14 +423,17 @@ export async function handleMethod(
398
423
  }
399
424
 
400
425
  /**
401
- * 插件主体:注册设置命名空间、装配运行时、挂载 fenced 路由。
402
- * @param ctx - host 插件上下文(webServer / webRuntime / 可选 settings、sessionQuery、sessions、sessionProjections)。
426
+ * 插件主体:装配运行时、挂载 fenced 路由。
427
+ * @param ctx - host 插件上下文(webServer / webRuntime / 可选 sessionQuery、sessions、sessionProjections)。
428
+ * @param config - 组合条目(0.1.7:`.volatile()` 字段为 live ref)。
403
429
  */
404
- export function apply(ctx: Context): void {
405
- let current: () => Required<StewardConfig> = () => DEFAULT_CONFIG
406
- installSettingsSection(ctx, STEWARD_SETTINGS_NAMESPACE, Config, DEFAULT_CONFIG, {
407
- setSource: (source) => { current = () => ({ ...DEFAULT_CONFIG, ...(source() as Partial<StewardConfig>) }) },
408
- onChange: () => {},
430
+ export function apply(ctx: Context, config: Partial<StewardConfig> = {}): void {
431
+ // 0.1.7:volatile 字段每次读取解引出最新快照(开关即时生效),不再有任何
432
+ // settings 注册调用。
433
+ const current = (): Required<StewardConfig> => ({
434
+ enabled: readVolatileValue(config.enabled) ?? DEFAULT_CONFIG.enabled,
435
+ historyFiles: readVolatileValue(config.historyFiles) ?? DEFAULT_CONFIG.historyFiles,
436
+ healthCheck: readVolatileValue(config.healthCheck) ?? DEFAULT_CONFIG.healthCheck,
409
437
  })
410
438
 
411
439
  const log = (message: string): void => {