@anionex/dsh-turn-rewind 0.3.8 → 0.3.10

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
@@ -6,7 +6,7 @@ import { readFileSync } from 'node:fs'
6
6
  import type { Context } from '@deepseek-ai/cordis'
7
7
  import { ChangeLedgerEngine } from './engine.js'
8
8
  import { installManageHttp, installRewindHttp, TurnCheckpointCoordinator } from './rewind-host.js'
9
- import { installTurnRewindSettings } from './settings.js'
9
+ import { installTurnRewindSettings, TurnRewindSettingsSchema } from './settings.js'
10
10
  import type { ChangeLedgerConfig } from './types.js'
11
11
 
12
12
  export * from './engine.js'
@@ -32,6 +32,17 @@ function pluginVersion(): string {
32
32
  }
33
33
  }
34
34
 
35
+ /** Volatile config values are wrappers in DSH 0.1.7 and plain values on older hosts. */
36
+ function unwrapVolatileConfig(config: ChangeLedgerConfig): ChangeLedgerConfig {
37
+ const result: Record<string, unknown> = {}
38
+ for (const [key, value] of Object.entries(config)) {
39
+ result[key] = value !== null && typeof value === 'object' && typeof (value as { get?: unknown }).get === 'function'
40
+ ? (value as { get(): unknown }).get()
41
+ : value
42
+ }
43
+ return result as ChangeLedgerConfig
44
+ }
45
+
35
46
  declare module '@deepseek-ai/cordis' {
36
47
  interface Context {
37
48
  changeLedger: ChangeLedgerService
@@ -40,12 +51,15 @@ declare module '@deepseek-ai/cordis' {
40
51
 
41
52
  /** Cordis service exposed as `ctx.changeLedger` for other DSH plugins. */
42
53
  export class ChangeLedgerService {
54
+ /** DSH 0.1.7 projects volatile Config fields into its settings form. */
55
+ static Config = TurnRewindSettingsSchema
43
56
  readonly engine: ChangeLedgerEngine
44
57
 
45
58
  /** Register the service and startup reconciliation. */
46
59
  constructor(ctx: Context, config: ChangeLedgerConfig = {}) {
60
+ const normalized = unwrapVolatileConfig(config)
47
61
  ctx.provide('changeLedger', this)
48
- this.engine = new ChangeLedgerEngine(config)
62
+ this.engine = new ChangeLedgerEngine(normalized)
49
63
  const checkpoints = new TurnCheckpointCoordinator(this.engine)
50
64
  ctx.logger.info(`[turn-rewind] v${pluginVersion()} active; workspace modes: git worktree, ordinary directory`)
51
65
  ctx.inject(['agents'], (scope: Context) => { checkpoints.install(scope) })
@@ -55,7 +69,7 @@ export class ChangeLedgerService {
55
69
  installRewindHttp(scope, this.engine, checkpoints)
56
70
  installManageHttp(scope, this.engine)
57
71
  })
58
- installTurnRewindSettings(ctx, config, this.engine)
72
+ installTurnRewindSettings(ctx, normalized, this.engine)
59
73
  void this.engine.initialize().then((reconciled) => {
60
74
  if (reconciled > 0) {
61
75
  ctx.logger.warn(`[change-ledger] reconciled ${reconciled} interrupted durable operation(s)`)
@@ -678,8 +678,14 @@ async function createConversationRestart(
678
678
  // that returns undefined instead of failing the request.
679
679
  const controller = ctx.get('sessionController') as SessionControllerLike | undefined
680
680
  if (controller !== undefined) {
681
+ // Modern hosts require workspace membership independently of Session cwd.
682
+ // Preserve the source association; never guess a workspace from its path.
683
+ const registry = ctx.get('workspaceRegistry') as {
684
+ list(): readonly { readonly id: string; readonly sessionIds: readonly string[] }[]
685
+ } | undefined
686
+ const workspace = registry?.list().find(candidate => candidate.sessionIds.includes(sourceId))
681
687
  const created = checkpoint.previousTurnEndSeq === undefined
682
- ? await controller.create({ cwd: checkpoint.cwd })
688
+ ? await controller.create(workspace === undefined ? { cwd: checkpoint.cwd } : { workspaceId: workspace.id })
683
689
  : await controller.fork({ sessionId: sourceId, atSeq: checkpoint.previousTurnEndSeq })
684
690
  return { sessionId: requiredText(created.sessionId, 'sessionController sessionId') }
685
691
  }
package/src/settings.ts CHANGED
@@ -19,7 +19,7 @@ import type { ChangeLedgerConfig, ResolvedChangeLedgerConfig } from './types.js'
19
19
  * a compile-time-only brand, so a plain lowercase literal is the entire runtime
20
20
  * value — the upstream convention (`const CHAT_SETTINGS_NAMESPACE = 'ui-chat'`).
21
21
  * The literal satisfies 0.1.5's registration grammar `/^[a-z][a-z0-9-]*$/` and is
22
- * the exact key the browser card binds through `ctx.settingsScope.bind`.
22
+ * the exact key both browser settings transports use.
23
23
  */
24
24
  export const TURN_REWIND_SETTINGS_NAMESPACE = 'turn-rewind'
25
25
 
@@ -49,21 +49,27 @@ export interface TurnRewindSettings {
49
49
  turnCheckpointTrust: 'fast' | 'strict'
50
50
  }
51
51
 
52
- /** Schemastery schema for the `turn-rewind` settings namespace. */
52
+ /** Older Schemastery releases have no volatile marker; their settings provider owns updates. */
53
+ function volatileWhenSupported<T>(schema: z<T>): z<T> {
54
+ const compatible = schema as z<T> & { volatile?: () => z<T> }
55
+ return typeof compatible.volatile === 'function' ? compatible.volatile() : schema
56
+ }
57
+
58
+ /** Shared schema for the legacy settings namespace and DSH 0.1.7 Config form. */
53
59
  export const TurnRewindSettingsSchema: z<TurnRewindSettings> = (() => {
54
60
  const defaults = tunableSettings(resolveConfig({}))
55
61
  return z.object({
56
- maxRestorePoints: z.number().step(1).min(1).default(defaults.maxRestorePoints),
57
- maxTurnCheckpointsPerSession: z.number().step(1).min(1).default(defaults.maxTurnCheckpointsPerSession),
58
- maxFiles: z.number().step(1).min(1).default(defaults.maxFiles),
59
- maxFileBytes: z.number().step(1).min(1).default(defaults.maxFileBytes),
60
- maxSnapshotBytes: z.number().step(1).min(1).default(defaults.maxSnapshotBytes),
61
- planTtlMs: z.number().step(1).min(1).default(defaults.planTtlMs),
62
- staleLockMs: z.number().step(1).min(1).default(defaults.staleLockMs),
63
- turnCheckpointMode: z.union(['off', 'auto', 'git-native', 'legacy']).default(defaults.turnCheckpointMode),
64
- turnCheckpointTimeoutMs: z.number().step(1).min(1).default(defaults.turnCheckpointTimeoutMs),
65
- turnCheckpointMaxNewBytes: z.number().step(1).min(1).default(defaults.turnCheckpointMaxNewBytes),
66
- turnCheckpointTrust: z.union(['fast', 'strict']).default(defaults.turnCheckpointTrust),
62
+ maxRestorePoints: volatileWhenSupported(z.number().step(1).min(1).default(defaults.maxRestorePoints)),
63
+ maxTurnCheckpointsPerSession: volatileWhenSupported(z.number().step(1).min(1).default(defaults.maxTurnCheckpointsPerSession)),
64
+ maxFiles: volatileWhenSupported(z.number().step(1).min(1).default(defaults.maxFiles)),
65
+ maxFileBytes: volatileWhenSupported(z.number().step(1).min(1).default(defaults.maxFileBytes)),
66
+ maxSnapshotBytes: volatileWhenSupported(z.number().step(1).min(1).default(defaults.maxSnapshotBytes)),
67
+ planTtlMs: volatileWhenSupported(z.number().step(1).min(1).default(defaults.planTtlMs)),
68
+ staleLockMs: volatileWhenSupported(z.number().step(1).min(1).default(defaults.staleLockMs)),
69
+ turnCheckpointMode: volatileWhenSupported(z.union(['off', 'auto', 'git-native', 'legacy']).default(defaults.turnCheckpointMode)),
70
+ turnCheckpointTimeoutMs: volatileWhenSupported(z.number().step(1).min(1).default(defaults.turnCheckpointTimeoutMs)),
71
+ turnCheckpointMaxNewBytes: volatileWhenSupported(z.number().step(1).min(1).default(defaults.turnCheckpointMaxNewBytes)),
72
+ turnCheckpointTrust: volatileWhenSupported(z.union(['fast', 'strict']).default(defaults.turnCheckpointTrust)),
67
73
  })
68
74
  })()
69
75
 
@@ -106,7 +112,7 @@ interface SettingsSectionHooksLike<T> {
106
112
  * {@link SettingsProvider} type at the single boundary where it is used.
107
113
  */
108
114
  interface SettingsProviderLike {
109
- register<T>(ns: string, schema: z<T>, options?: { readonly base?: Partial<T> }): SettingsScopeLike<T>
115
+ register?<T>(ns: string, schema: z<T>, options?: { readonly base?: Partial<T> }): SettingsScopeLike<T>
110
116
  installSection?<T>(owner: Context, ns: string, schema: z<T>, entry: T, hooks: SettingsSectionHooksLike<T>): void
111
117
  }
112
118
 
@@ -142,6 +148,9 @@ export function installTurnRewindSettings(ctx: Context, config: ChangeLedgerConf
142
148
  provider.installSection(ctx, TURN_REWIND_SETTINGS_NAMESPACE, TurnRewindSettingsSchema, entry, hooks)
143
149
  return
144
150
  }
151
+ // DSH 0.1.7 projects the plugin's Config schema instead of registering a
152
+ // separate namespace. Its settings service exposes neither old method.
153
+ if (typeof provider.register !== 'function') return
145
154
  // Older hosts shipped that wiring only as the free `installSettingsSection`,
146
155
  // which 0.1.5 removed, so reproduce its body against `register` + `scope.get()`
147
156
  // + `scope.watch()` — present in every supported version.