fix(session): harden cross-session references
This commit is contained in:
@@ -23,7 +23,7 @@ import SessionPersistenceJsonl, {
|
||||
} from '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
import UserInteractionService from '@deepseek-ai/dsh-user-interaction'
|
||||
import SessionQueryService from '@deepseek-ai/dsh-session-query'
|
||||
import SessionReferenceService from '@deepseek-ai/dsh-session-reference'
|
||||
import SessionReferenceService, { type Config as SessionReferenceConfig } from '@deepseek-ai/dsh-session-reference'
|
||||
|
||||
export const name = 'acp-demo'
|
||||
const DEFAULT_PERSISTENCE_ROOT = './.sessions'
|
||||
@@ -56,6 +56,8 @@ export interface Config {
|
||||
persistenceRoot?: string
|
||||
/** JSONL artifact encoding; defaults to checksummed Zstandard frames. */
|
||||
persistenceCompression?: JsonlCompression
|
||||
/** Cross-session reference discovery and snapshot byte budgets. */
|
||||
sessionReferences?: SessionReferenceConfig
|
||||
/** Controls automatic AGENTS.md/CLAUDE.md loading; configure a byte budget or set `false`. */
|
||||
workspaceContext: agentCore.Config['workspaceContext']
|
||||
/** Skill registry, local-provider, and model-facing consumer config forwarded to agent-spine-demo. */
|
||||
@@ -86,6 +88,7 @@ export const Config: z<Config> = z.object({
|
||||
dshHome: z.string(),
|
||||
persistenceRoot: z.string().default(DEFAULT_PERSISTENCE_ROOT),
|
||||
persistenceCompression: JsonlCompressionSchema,
|
||||
sessionReferences: SessionReferenceService.Config,
|
||||
workspaceContext: z.union([z.const(false), workspaceContext.Config]).required(),
|
||||
skills: agentCore.SkillConfigSchema,
|
||||
toolBash: agentCore.ToolBashConfigSchema,
|
||||
@@ -107,12 +110,16 @@ export function apply(ctx: Context, config: Config): void {
|
||||
ctx.plugin(CommandService)
|
||||
if (goals !== false) ctx.plugin(commandGoal)
|
||||
ctx.plugin(agentCore, { ...agentCore.pickSpineConfig(config), goals })
|
||||
// This front door owns the same persistence/reference cluster as the TUI;
|
||||
// extracting these few calls would introduce a shared app-composition facade.
|
||||
/* jscpd:ignore-start */
|
||||
ctx.plugin(UserInteractionService)
|
||||
ctx.plugin(SessionPersistenceJsonl, {
|
||||
root: config.persistenceRoot ?? DEFAULT_PERSISTENCE_ROOT,
|
||||
...(config.persistenceCompression === undefined ? {} : { compression: config.persistenceCompression }),
|
||||
})
|
||||
ctx.plugin(SessionQueryService)
|
||||
ctx.plugin(SessionReferenceService)
|
||||
ctx.plugin(SessionReferenceService, config.sessionReferences ?? {})
|
||||
/* jscpd:ignore-end */
|
||||
ctx.plugin(acp, { provider: config.provider, model: config.model })
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { tmpdir } from 'node:os'
|
||||
import { Context } from 'cordis'
|
||||
import Loader from '@cordisjs/plugin-loader'
|
||||
import { agentEvents, type Agent } from '@deepseek-ai/dsh-agent'
|
||||
import { SessionId } from '@deepseek-ai/dsh-session'
|
||||
import { TOOL_ORDER_REST } from '@deepseek-ai/dsh-system-prompt'
|
||||
import type { Message } from '@deepseek-ai/dsh-llm'
|
||||
import * as acpAgent from '../src/index.ts'
|
||||
@@ -76,6 +77,7 @@ describe('dsh-acp-demo composition', () => {
|
||||
persona: 'hi',
|
||||
persistenceRoot: '/tmp/dsh-acp-demo-test',
|
||||
persistenceCompression: 'none',
|
||||
sessionReferences: { candidateLimit: 1 },
|
||||
skills: await isolatedSkillsConfig(),
|
||||
workspaceContext: false,
|
||||
})
|
||||
@@ -90,6 +92,11 @@ describe('dsh-acp-demo composition', () => {
|
||||
expect(ctx.get('tools')?.get('ask_user_question')).toBeUndefined()
|
||||
expect(ctx.get('goals')).toBeDefined()
|
||||
expect(ctx.get('tools')?.get('get_goal')).toBeDefined()
|
||||
const target = ctx.sessions.create(SessionId('candidate-target'))
|
||||
ctx.sessions.create(SessionId('candidate-one'))
|
||||
ctx.sessions.create(SessionId('candidate-two'))
|
||||
await expect(ctx.sessionReferences.listCandidates({ id: target.id, session: target } as Agent))
|
||||
.resolves.toHaveLength(1)
|
||||
// No pre-created agents — ACP session/new creates them on demand.
|
||||
expect(ctx.get('agents')!.list()).toHaveLength(0)
|
||||
await ctx.fiber.dispose()
|
||||
|
||||
@@ -23,7 +23,7 @@ import SessionPersistenceJsonl, {
|
||||
} from '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
import UserInteractionService from '@deepseek-ai/dsh-user-interaction'
|
||||
import SessionQueryService from '@deepseek-ai/dsh-session-query'
|
||||
import SessionReferenceService from '@deepseek-ai/dsh-session-reference'
|
||||
import SessionReferenceService, { type Config as SessionReferenceConfig } from '@deepseek-ai/dsh-session-reference'
|
||||
import * as toolAskUser from '@deepseek-ai/dsh-tool-ask-user'
|
||||
import * as uiTui from '@deepseek-ai/dsh-tui'
|
||||
|
||||
@@ -51,6 +51,8 @@ export interface Config {
|
||||
persistenceRoot?: string
|
||||
/** JSONL artifact encoding; defaults to checksummed Zstandard frames. */
|
||||
persistenceCompression?: JsonlCompression
|
||||
/** Cross-session reference discovery and snapshot byte budgets. */
|
||||
sessionReferences?: SessionReferenceConfig
|
||||
/** TUI subtitle rendered on start. Defaults to `ready.`. */
|
||||
welcome?: string
|
||||
/** Full-screen TUI presentation settings. */
|
||||
@@ -83,6 +85,7 @@ export const Config: z<Config> = z.object({
|
||||
dshHome: z.string(),
|
||||
persistenceRoot: z.string().default(DEFAULT_PERSISTENCE_ROOT),
|
||||
persistenceCompression: JsonlCompressionSchema,
|
||||
sessionReferences: SessionReferenceService.Config,
|
||||
welcome: z.string().default(DEFAULT_WELCOME),
|
||||
ui: uiTui.TuiConfigSchema,
|
||||
skills: agentCore.SkillConfigSchema,
|
||||
@@ -112,7 +115,7 @@ export function composeTuiApp(ctx: Context, config: Config): void {
|
||||
...(config.persistenceCompression === undefined ? {} : { compression: config.persistenceCompression }),
|
||||
})
|
||||
ctx.plugin(SessionQueryService)
|
||||
ctx.plugin(SessionReferenceService)
|
||||
ctx.plugin(SessionReferenceService, config.sessionReferences ?? {})
|
||||
ctx.plugin(UserInteractionService)
|
||||
ctx.plugin(uiTui, {
|
||||
...config.ui,
|
||||
|
||||
@@ -32,6 +32,12 @@ describe('dsh-tui-demo app', () => {
|
||||
dshHome: '/tmp/dsh-home',
|
||||
persistenceRoot: '/tmp/tui-sessions',
|
||||
persistenceCompression: 'none',
|
||||
sessionReferences: {
|
||||
maxReferences: 2,
|
||||
candidateLimit: 7,
|
||||
maxReferenceBytes: 1234,
|
||||
maxTotalBytes: 2345,
|
||||
},
|
||||
welcome: 'TUI ready',
|
||||
ui: { color: false, maxToolOutputLines: 3 },
|
||||
skills: { tool: { catalogDescriptionMaxLength: 8 } },
|
||||
@@ -53,6 +59,12 @@ describe('dsh-tui-demo app', () => {
|
||||
])
|
||||
expect(calls[0]?.config).toBeUndefined()
|
||||
expect(calls[2]?.config).toEqual({ root: '/tmp/tui-sessions', compression: 'none' })
|
||||
expect(calls[4]?.config).toEqual({
|
||||
maxReferences: 2,
|
||||
candidateLimit: 7,
|
||||
maxReferenceBytes: 1234,
|
||||
maxTotalBytes: 2345,
|
||||
})
|
||||
const tuiConfig = calls[6]?.config as { sessionId: string }
|
||||
expect(tuiConfig).toMatchObject({ welcome: 'TUI ready', color: false, maxToolOutputLines: 3 })
|
||||
expect(tuiConfig.sessionId).toMatch(/^main-session-[0-9a-f-]{36}$/)
|
||||
@@ -90,6 +102,7 @@ describe('dsh-tui-demo app', () => {
|
||||
})
|
||||
|
||||
expect(calls[2]?.config).toEqual({ root: './.sessions' })
|
||||
expect(calls[4]?.config).toEqual({})
|
||||
expect(calls[6]?.config).toEqual({ welcome: 'ready.', sessionId: 'persisted-session' })
|
||||
expect((calls[7]?.config as { agents: Array<Record<string, unknown>> }).agents[0]).toMatchObject({
|
||||
id: 'main',
|
||||
|
||||
Reference in New Issue
Block a user