fix(cli): default session root in shipped config
This commit is contained in:
@@ -67,14 +67,10 @@
|
|||||||
apiKey: !!js process.env.ANTHROPIC_API_KEY
|
apiKey: !!js process.env.ANTHROPIC_API_KEY
|
||||||
baseURL: !!js process.env.ANTHROPIC_BASE_URL
|
baseURL: !!js process.env.ANTHROPIC_BASE_URL
|
||||||
|
|
||||||
# The session store root is the launcher's policy, not a plugin's: `dsh` shares
|
|
||||||
# one store under the Harness home across every cwd, so `/resume` spans
|
|
||||||
# workspaces. Without a launcher the project-local fallback keeps
|
|
||||||
# an embedder's sessions beside its project.
|
|
||||||
- id: session-persistence-jsonl
|
- id: session-persistence-jsonl
|
||||||
name: '@deepseek-ai/dsh-session-persistence-jsonl'
|
name: '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||||
config:
|
config:
|
||||||
root: !!js launcherSessionsRoot ?? './.sessions'
|
root: !!js path.join(process.env.DSH_HOME || path.join(os.homedir(), '.dsh'), 'sessions')
|
||||||
|
|
||||||
- id: subprocess
|
- id: subprocess
|
||||||
name: '@deepseek-ai/dsh-subprocess-local'
|
name: '@deepseek-ai/dsh-subprocess-local'
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import type { FiberState } from 'cordis'
|
|||||||
import type { PatchOptions } from '@cordisjs/plugin-include'
|
import type { PatchOptions } from '@cordisjs/plugin-include'
|
||||||
import yaml from 'js-yaml'
|
import yaml from 'js-yaml'
|
||||||
import { boot, installFailLoud, loadEnv, loadOverlayPatches, loadPersonalPatches } from '@deepseek-ai/dsh-app-boot'
|
import { boot, installFailLoud, loadEnv, loadOverlayPatches, loadPersonalPatches } from '@deepseek-ai/dsh-app-boot'
|
||||||
import { resolveDshHome, resolveSessionsRoot } from '@deepseek-ai/dsh-paths'
|
import { resolveDshHome } from '@deepseek-ai/dsh-paths'
|
||||||
// Empty type import carries the httpServer Context merge for the port read below.
|
// Empty type import carries the httpServer Context merge for the port read below.
|
||||||
import type {} from '@deepseek-ai/dsh-host-webserver'
|
import type {} from '@deepseek-ai/dsh-host-webserver'
|
||||||
|
|
||||||
@@ -189,11 +189,6 @@ export class AppCLIEntry {
|
|||||||
overrides.set(entryId, bag)
|
overrides.set(entryId, bag)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Source 0: computed engineering defaults. The session store is the one
|
|
||||||
// shared root every dsh surface resolves, so history follows the user across
|
|
||||||
// working directories instead of splitting per project. The profile
|
|
||||||
// (Source 1) overwrites this same field via last-write-wins in put().
|
|
||||||
put('session-persistence-jsonl', 'root', resolveSessionsRoot())
|
|
||||||
|
|
||||||
// Source 1: profile json (missing file = empty; unmapped key = loud).
|
// Source 1: profile json (missing file = empty; unmapped key = loud).
|
||||||
for (const [key, value] of Object.entries(this.readProfile())) {
|
for (const [key, value] of Object.entries(this.readProfile())) {
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import {
|
|||||||
loadPersonalPatches,
|
loadPersonalPatches,
|
||||||
resolveConfigPath,
|
resolveConfigPath,
|
||||||
} from '@deepseek-ai/dsh-app-boot'
|
} from '@deepseek-ai/dsh-app-boot'
|
||||||
import { resolveDshHome, resolveSessionsRoot } from '@deepseek-ai/dsh-paths'
|
import { resolveDshHome } from '@deepseek-ai/dsh-paths'
|
||||||
import { SessionId } from '@deepseek-ai/dsh-session'
|
import { SessionId } from '@deepseek-ai/dsh-session'
|
||||||
import { SESSION_QUERY_SQLITE_PATH_KEY } from '@deepseek-ai/dsh-session-query-sqlite'
|
import { SESSION_QUERY_SQLITE_PATH_KEY } from '@deepseek-ai/dsh-session-query-sqlite'
|
||||||
import { CONFIGURED_AGENT_IDENTITIES_KEY } from '@deepseek-ai/dsh-agent-loop'
|
import { CONFIGURED_AGENT_IDENTITIES_KEY } from '@deepseek-ai/dsh-agent-loop'
|
||||||
@@ -39,7 +39,6 @@ import type { Context } from 'cordis'
|
|||||||
import {
|
import {
|
||||||
INITIAL_SKILL_KEY,
|
INITIAL_SKILL_KEY,
|
||||||
MAIN_SESSION_ID_KEY,
|
MAIN_SESSION_ID_KEY,
|
||||||
SESSIONS_ROOT_KEY,
|
|
||||||
TUI_GOODBYE_MESSAGE_KEY,
|
TUI_GOODBYE_MESSAGE_KEY,
|
||||||
type MainSessionIdentity,
|
type MainSessionIdentity,
|
||||||
type TuiResumeHost,
|
type TuiResumeHost,
|
||||||
@@ -65,18 +64,6 @@ const SESSION_QUERY_DB = `session-query-${String(process.pid)}-${randomUUID()}.d
|
|||||||
// symlink, an arbitrary cwd). The agent is told where its own source lives.
|
// symlink, an arbitrary cwd). The agent is told where its own source lives.
|
||||||
const SOURCE_ROOT = fileURLToPath(new URL('../../..', import.meta.url))
|
const SOURCE_ROOT = fileURLToPath(new URL('../../..', import.meta.url))
|
||||||
|
|
||||||
/**
|
|
||||||
* The value `dsh` provides on the {@link SESSIONS_ROOT_KEY} boot slot: its
|
|
||||||
* shared session-store root, `sessions` under the Harness home. Shared-store
|
|
||||||
* policy is the launcher's alone — the app bundle treats the slot as opaque and
|
|
||||||
* keeps a project-local fallback, so only `dsh` decides that sessions are
|
|
||||||
* shared across working directories (making `/resume` span every workspace).
|
|
||||||
* @returns the absolute session-store root this launcher shares.
|
|
||||||
*/
|
|
||||||
export function launcherSessionsRoot(): string {
|
|
||||||
return resolveSessionsRoot()
|
|
||||||
}
|
|
||||||
|
|
||||||
/* v8 ignore start -- composition over the unit-tested dsh-app-boot helpers;
|
/* v8 ignore start -- composition over the unit-tested dsh-app-boot helpers;
|
||||||
the CLI PTY smoke drives this path end to end, personal overlay included */
|
the CLI PTY smoke drives this path end to end, personal overlay included */
|
||||||
/**
|
/**
|
||||||
@@ -238,7 +225,6 @@ export async function runTui(
|
|||||||
// Shared-store policy is the launcher's: sessions live in one root under
|
// Shared-store policy is the launcher's: sessions live in one root under
|
||||||
// the Harness home across every cwd, so /resume sees every workspace.
|
// the Harness home across every cwd, so /resume sees every workspace.
|
||||||
// The bundle treats the slot as opaque.
|
// The bundle treats the slot as opaque.
|
||||||
hostCtx.provide(SESSIONS_ROOT_KEY, launcherSessionsRoot())
|
|
||||||
// The agent-loop row reads this to bind `main`, and the tui row reads the
|
// The agent-loop row reads this to bind `main`, and the tui row reads the
|
||||||
// same id, so a personal overlay repointing the model route cannot drop
|
// same id, so a personal overlay repointing the model route cannot drop
|
||||||
// the session identity or desynchronise the two.
|
// the session identity or desynchronise the two.
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
/**
|
|
||||||
* Pins the launcher side of the shared-session-store contract: `dsh` defaults
|
|
||||||
* its opaque `SESSIONS_ROOT_KEY` boot-slot value to `DSH_HOME/sessions`. The
|
|
||||||
* plugin side — the slot treated as opaque, explicit config winning, and a
|
|
||||||
* project-local fallback with no globality assumption — is pinned by
|
|
||||||
* the former bundled TUI tests.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { join, resolve } from 'node:path'
|
|
||||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
||||||
import { launcherSessionsRoot } from '../src/tui.ts'
|
|
||||||
|
|
||||||
afterEach(() => vi.unstubAllEnvs())
|
|
||||||
|
|
||||||
describe('launcherSessionsRoot', () => {
|
|
||||||
it('defaults the boot slot to sessions under DSH_HOME', () => {
|
|
||||||
vi.stubEnv('DSH_HOME', '/tmp/dsh-slot-home')
|
|
||||||
expect(launcherSessionsRoot()).toBe(resolve(join('/tmp/dsh-slot-home', 'sessions')))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -182,8 +182,6 @@ declare module 'cordis' {
|
|||||||
tuiGoodbyeMessage: string | undefined
|
tuiGoodbyeMessage: string | undefined
|
||||||
/** Skill the launcher wants auto-invoked as the fresh session's first turn; absent leaves it to the user. */
|
/** Skill the launcher wants auto-invoked as the fresh session's first turn; absent leaves it to the user. */
|
||||||
tuiInitialSkill: string | undefined
|
tuiInitialSkill: string | undefined
|
||||||
/** Launcher-owned session-store root the app bundle defaults to; absent keeps the bundle's project-local default. */
|
|
||||||
launcherSessionsRoot: string | undefined
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,16 +226,6 @@ export const TUI_GOODBYE_MESSAGE_KEY = 'tuiGoodbyeMessage'
|
|||||||
*/
|
*/
|
||||||
export const INITIAL_SKILL_KEY = 'tuiInitialSkill'
|
export const INITIAL_SKILL_KEY = 'tuiInitialSkill'
|
||||||
|
|
||||||
/**
|
|
||||||
* Context key a launcher sets before any Loader entry mounts
|
|
||||||
* (`ctx.provide(SESSIONS_ROOT_KEY, root)`) to supply its session-store root as
|
|
||||||
* the app bundle's default persistence root. Shared-store policy (one store
|
|
||||||
* across every cwd) belongs to the launcher — the dsh CLI resolves it under the
|
|
||||||
* Harness home — never to a plugin; a bundle without this slot keeps its own
|
|
||||||
* project-local default, and an explicit `persistenceRoot` config still wins.
|
|
||||||
*/
|
|
||||||
export const SESSIONS_ROOT_KEY = 'launcherSessionsRoot'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optional terminal-local interaction service provided by one mounted TUI.
|
* Optional terminal-local interaction service provided by one mounted TUI.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -52,27 +52,6 @@ export function resolveDshHome(configured?: string, env: Record<string, string |
|
|||||||
return resolve(expandHomePath(selected))
|
return resolve(expandHomePath(selected))
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Directory name for persisted session logs under the Harness home. */
|
|
||||||
export const SESSIONS_DIR_NAME = 'sessions'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve the shared session-store root under the Harness home.
|
|
||||||
*
|
|
||||||
* Every surface that persists sessions resolves this one directory, so history
|
|
||||||
* is shared across working directories instead of scattered per project. A
|
|
||||||
* persistence backend may still partition inside it. Two surfaces resolving
|
|
||||||
* different roots would silently split one user's history into disjoint stores,
|
|
||||||
* so this is a single owned fact rather than a per-caller `join`.
|
|
||||||
* @param configuredHome - explicit harness-home override, which has highest precedence.
|
|
||||||
* @param env - environment mapping used to read `DSH_HOME`.
|
|
||||||
* @returns the normalized absolute session-store root.
|
|
||||||
*/
|
|
||||||
export function resolveSessionsRoot(
|
|
||||||
configuredHome?: string, env: Record<string, string | undefined> = process.env,
|
|
||||||
): string {
|
|
||||||
return join(resolveDshHome(configuredHome, env), SESSIONS_DIR_NAME)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describe a resolved harness home symbolically for user-facing display.
|
* Describe a resolved harness home symbolically for user-facing display.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -4,12 +4,10 @@ import { describe, expect, it } from 'vitest'
|
|||||||
import {
|
import {
|
||||||
DEFAULT_DSH_HOME_DISPLAY,
|
DEFAULT_DSH_HOME_DISPLAY,
|
||||||
DSH_HOME_DIR_NAME,
|
DSH_HOME_DIR_NAME,
|
||||||
SESSIONS_DIR_NAME,
|
|
||||||
defaultDshHome,
|
defaultDshHome,
|
||||||
dshHomeDisplay,
|
dshHomeDisplay,
|
||||||
expandHomePath,
|
expandHomePath,
|
||||||
resolveDshHome,
|
resolveDshHome,
|
||||||
resolveSessionsRoot,
|
|
||||||
} from '@deepseek-ai/dsh-paths'
|
} from '@deepseek-ai/dsh-paths'
|
||||||
|
|
||||||
describe('dsh path helpers', () => {
|
describe('dsh path helpers', () => {
|
||||||
@@ -40,15 +38,6 @@ describe('dsh path helpers', () => {
|
|||||||
expect(resolveDshHome(undefined, { DSH_HOME: ' ' })).toBe(defaultDshHome())
|
expect(resolveDshHome(undefined, { DSH_HOME: ' ' })).toBe(defaultDshHome())
|
||||||
})
|
})
|
||||||
|
|
||||||
it('resolves the session store under the home it was given, by the same precedence', () => {
|
|
||||||
expect(SESSIONS_DIR_NAME).toBe('sessions')
|
|
||||||
expect(resolveSessionsRoot('/tmp/explicit-dsh', { DSH_HOME: '~/env-dsh' }))
|
|
||||||
.toBe(join(resolve('/tmp/explicit-dsh'), 'sessions'))
|
|
||||||
expect(resolveSessionsRoot(undefined, { DSH_HOME: '~/env-dsh' }))
|
|
||||||
.toBe(join(homedir(), 'env-dsh', 'sessions'))
|
|
||||||
expect(resolveSessionsRoot(undefined, {})).toBe(join(defaultDshHome(), 'sessions'))
|
|
||||||
})
|
|
||||||
|
|
||||||
it('labels a resolved home by whether it is the default root', () => {
|
it('labels a resolved home by whether it is the default root', () => {
|
||||||
expect(dshHomeDisplay(resolve(defaultDshHome()))).toBe('~/.dsh')
|
expect(dshHomeDisplay(resolve(defaultDshHome()))).toBe('~/.dsh')
|
||||||
expect(dshHomeDisplay('/some/other/root')).toBe('$DSH_HOME')
|
expect(dshHomeDisplay('/some/other/root')).toBe('$DSH_HOME')
|
||||||
|
|||||||
Reference in New Issue
Block a user