@anionex/dsh-turn-rewind 0.3.7 → 0.3.9

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/engine.ts CHANGED
@@ -58,7 +58,7 @@ const DEFAULTS = {
58
58
  maxSnapshotBytes: 512 * 1024 * 1024,
59
59
  planTtlMs: 15 * 60 * 1_000,
60
60
  staleLockMs: 30_000,
61
- turnCheckpointMode: 'legacy',
61
+ turnCheckpointMode: 'auto',
62
62
  turnCheckpointTimeoutMs: 60_000,
63
63
  turnCheckpointMaxNewBytes: 32 * 1024 * 1024,
64
64
  turnCheckpointTrust: 'fast',
@@ -316,7 +316,12 @@ export class ChangeLedgerEngine {
316
316
  let manifest: RestorePointManifest
317
317
  // Git-native checkpoints live in the repository object database, so an
318
318
  // ordinary directory always uses the ledger's own content-addressed store.
319
- if (this.config.turnCheckpointMode === 'legacy' || source.state.type === 'directory') {
319
+ // `auto` picks the fastest path each workspace can support: a Git
320
+ // worktree reuses the repository's own object database, while an
321
+ // ordinary directory can only use the ledger's store.
322
+ const gitNative = this.config.turnCheckpointMode === 'git-native'
323
+ || (this.config.turnCheckpointMode === 'auto' && source.state.type === 'git')
324
+ if (!gitNative) {
320
325
  manifest = await this.createLocked({
321
326
  cwd: source.state.root,
322
327
  kind: 'turn',
@@ -1396,8 +1401,8 @@ function requireNonEmptyString(value: string, name: string): string {
1396
1401
  }
1397
1402
 
1398
1403
  function checkpointMode(value: ChangeLedgerConfig['turnCheckpointMode']): ResolvedChangeLedgerConfig['turnCheckpointMode'] {
1399
- if (value !== 'off' && value !== 'git-native' && value !== 'legacy') {
1400
- throw new ChangeLedgerError('INVALID_CONFIG', 'turnCheckpointMode must be off, git-native, or legacy')
1404
+ if (value !== 'off' && value !== 'auto' && value !== 'git-native' && value !== 'legacy') {
1405
+ throw new ChangeLedgerError('INVALID_CONFIG', 'turnCheckpointMode must be off, auto, git-native, or legacy')
1401
1406
  }
1402
1407
  return value
1403
1408
  }
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
 
@@ -40,7 +40,7 @@ export interface TurnRewindSettings {
40
40
  /** Age after which a lock whose owner is gone may be reclaimed. */
41
41
  staleLockMs: number
42
42
  /** Automatic turn-checkpoint implementation; `off` records durable skips instead. */
43
- turnCheckpointMode: 'off' | 'git-native' | 'legacy'
43
+ turnCheckpointMode: 'off' | 'auto' | 'git-native' | 'legacy'
44
44
  /** Maximum time one automatic checkpoint may block the first Agent step. */
45
45
  turnCheckpointTimeoutMs: number
46
46
  /** Maximum uncached worktree bytes read by one automatic Git-native checkpoint. */
@@ -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', '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.
package/src/types.ts CHANGED
@@ -244,7 +244,7 @@ export interface ChangeLedgerConfig {
244
244
  /** Age after which a lock whose owner is gone may be reclaimed. */
245
245
  readonly staleLockMs?: number
246
246
  /** Automatic turn-checkpoint implementation. Git-native is bounded and avoids rereading clean tracked content. */
247
- readonly turnCheckpointMode?: 'off' | 'git-native' | 'legacy'
247
+ readonly turnCheckpointMode?: 'off' | 'auto' | 'git-native' | 'legacy'
248
248
  /** Maximum time one automatic checkpoint may block the first Agent step. */
249
249
  readonly turnCheckpointTimeoutMs?: number
250
250
  /** Maximum uncached worktree bytes read by one automatic Git-native checkpoint. */
@@ -263,7 +263,7 @@ export interface ResolvedChangeLedgerConfig {
263
263
  readonly maxSnapshotBytes: number
264
264
  readonly planTtlMs: number
265
265
  readonly staleLockMs: number
266
- readonly turnCheckpointMode: 'off' | 'git-native' | 'legacy'
266
+ readonly turnCheckpointMode: 'off' | 'auto' | 'git-native' | 'legacy'
267
267
  readonly turnCheckpointTimeoutMs: number
268
268
  readonly turnCheckpointMaxNewBytes: number
269
269
  readonly turnCheckpointTrust: 'fast' | 'strict'