Merge remote-tracking branch 'origin/master' into codex/agent-session-jsonl-location

# Conflicts:
#	docs/config-catalog.md
#	docs/module-graph.md
#	docs/rfc/INDEX.md
#	packages/README.md
#	packages/bash/bash-local/README.md
#	packages/bash/bash/README.md
#	packages/bash/tool-bash/README.md
#	packages/bash/tool-bash/tests/tools.spec.ts
#	packages/cordis/tool-cordis/src/api-catalog.ts
#	packages/examples/acp-demo/src/index.ts
#	packages/examples/acp-demo/tests/acp-agent.spec.ts
#	packages/examples/agent-spine-demo/README.md
#	packages/examples/agent-spine-demo/src/index.ts
#	packages/examples/agent-spine-demo/tests/agent-core.spec.ts
#	packages/examples/stdio-demo/src/index.ts
#	packages/examples/stdio-demo/tests/stdio-agent.spec.ts
#	packages/util/README.md
#	pnpm-lock.yaml
#	tsconfig.build.json
#	tsconfig.json
This commit is contained in:
Yichen Jiang
2026-07-17 18:35:48 +08:00
230 changed files with 13315 additions and 453 deletions
+2
View File
@@ -35,6 +35,7 @@
"@deepseek-ai/dsh-app-boot": "^0.0.1",
"@deepseek-ai/dsh-acp": "^0.0.1",
"@deepseek-ai/dsh-agent-spine-demo": "^0.0.1",
"@deepseek-ai/dsh-workspace-context": "^0.0.1",
"@deepseek-ai/dsh-session-persistence-jsonl": "^0.0.1",
"@deepseek-ai/dsh-tools": "^0.0.1",
"@deepseek-ai/dsh-user-interaction": "^0.0.1",
@@ -50,6 +51,7 @@
"@deepseek-ai/dsh-agent-spine-demo": "workspace:^",
"@deepseek-ai/dsh-tools": "workspace:^",
"@deepseek-ai/dsh-system-prompt": "workspace:^",
"@deepseek-ai/dsh-workspace-context": "workspace:^",
"@deepseek-ai/dsh-session-persistence-jsonl": "workspace:^",
"@deepseek-ai/dsh-user-interaction": "workspace:^",
"cordis": "^4.0.0-rc.7",
+5 -9
View File
@@ -13,6 +13,7 @@ import type { Context } from 'cordis'
import z from 'schemastery'
import * as acp from '@deepseek-ai/dsh-acp'
import * as agentCore from '@deepseek-ai/dsh-agent-spine-demo'
import * as workspaceContext from '@deepseek-ai/dsh-workspace-context'
import ToolRegistry, { type Config as ToolsConfig } from '@deepseek-ai/dsh-tools'
import SessionPersistenceJsonl from '@deepseek-ai/dsh-session-persistence-jsonl'
import UserInteractionService from '@deepseek-ai/dsh-user-interaction'
@@ -41,6 +42,8 @@ export interface Config {
dshHome?: string
/** Directory the JSONL session backend writes under. Defaults to `./.sessions`. */
persistenceRoot?: string
/** 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. */
skills?: agentCore.SkillConfig
/** Model-facing bash tool config forwarded through agent-core. */
@@ -64,6 +67,7 @@ export const Config: z<Config> = z.object({
// TODO(single-default-literal): share this schema default and the defensive
// apply() fallback through one named constant while retaining both boundaries.
persistenceRoot: z.string().default('./.sessions'),
workspaceContext: z.union([z.const(false), workspaceContext.Config]).required(),
skills: agentCore.SkillConfigSchema,
toolBash: agentCore.ToolBashConfigSchema,
toolTasks: agentCore.ToolTasksConfigSchema,
@@ -78,15 +82,7 @@ export const Config: z<Config> = z.object({
* from `model`. No logger, no `hmr` — stdout stays pure.
*/
export function apply(ctx: Context, config: Config): void {
ctx.plugin(agentCore, {
...config.persona !== undefined ? { persona: config.persona } : {},
...config.toolOrder !== undefined ? { toolOrder: config.toolOrder } : {},
...config.tools !== undefined ? { tools: config.tools } : {},
...config.dshHome !== undefined ? { dshHome: config.dshHome } : {},
...config.skills !== undefined ? { skills: config.skills } : {},
...config.toolBash !== undefined ? { toolBash: config.toolBash } : {},
...config.toolTasks !== undefined ? { toolTasks: config.toolTasks } : {},
})
ctx.plugin(agentCore, agentCore.pickSpineConfig(config))
ctx.plugin(UserInteractionService)
ctx.plugin(SessionPersistenceJsonl, { root: config.persistenceRoot ?? './.sessions' })
ctx.plugin(acp, { model: config.model })
@@ -11,7 +11,7 @@ import * as acpAgent from '../src/index.ts'
/**
* In-process unit coverage for the @deepseek-ai/dsh-acp-demo composition:
* mounting it brings up the agent-core spine + JSONL persistence + the ACP
* mounting it brings up the agent-spine-demo spine + JSONL persistence + the ACP
* bridge in one `ctx.plugin`. Unlike the stdio app, this one loads NO
* Loader-only plugin (no hmr), so it mounts in a plain Context.
*
@@ -70,7 +70,7 @@ async function withIsolatedSkillHomes<T>(run: () => Promise<T>): Promise<T> {
describe('dsh-acp-demo composition', () => {
it('brings up the spine + persistence + the ACP bridge', async () => {
const ctx = await mount({ model: 'mock', persona: 'hi', persistenceRoot: '/tmp/dsh-acp-demo-test', skills: await isolatedSkillsConfig() })
const ctx = await mount({ model: 'mock', persona: 'hi', persistenceRoot: '/tmp/dsh-acp-demo-test', skills: await isolatedSkillsConfig(), workspaceContext: false })
expect(ctx.get('agents')).toBeDefined()
expect(ctx.get('sessions')).toBeDefined()
expect(ctx.get('sessionPersistence')).toBeDefined()
@@ -89,16 +89,28 @@ describe('dsh-acp-demo composition', () => {
// persistenceRoot, so the runtime fallback is the one that fires.
const ctx = new Context()
// No persona: covers the omitted-persona forwarding branch too.
acpAgent.apply(ctx, { model: 'mock', skills: await isolatedSkillsConfig() })
acpAgent.apply(ctx, { model: 'mock', skills: await isolatedSkillsConfig(), workspaceContext: false })
await new Promise(resolve => setTimeout(resolve, 50))
expect(ctx.get('sessionPersistence')).toBeDefined()
await ctx.fiber.dispose()
})
it('forwards explicit project-instruction controls to the bundled spine', async () => {
const ctx = await mount({
model: 'mock',
persona: 'hi',
persistenceRoot: '/tmp/dsh-acp-demo-workspace-context',
workspaceContext: false,
})
expect(ctx.get('agents')).toBeDefined()
expect(ctx.get('agentLoop')).toBeDefined()
await ctx.fiber.dispose()
})
it('uses default skill config when apply is called directly without skills', async () => {
await withIsolatedSkillHomes(async () => {
const ctx = new Context()
acpAgent.apply(ctx, { model: 'mock' })
acpAgent.apply(ctx, { model: 'mock', workspaceContext: false })
await new Promise(resolve => setTimeout(resolve, 50))
expect(ctx.skills).toBeDefined()
expect(await ctx.skills.list()).toEqual([])
@@ -106,9 +118,9 @@ describe('dsh-acp-demo composition', () => {
})
})
it('forwards skill config into agent-core', async () => {
it('forwards skill config and dshHome into agent-spine-demo', async () => {
const skills = await isolatedSkillsConfig(6)
const ctx = await mount({ model: 'mock', persona: 'hi', dshHome: skills.local!.dshHome!, skills })
const ctx = await mount({ model: 'mock', persona: 'hi', dshHome: skills.local!.dshHome!, skills, workspaceContext: false })
ctx.skills.register({ name: 'acp-skill', description: 'ACP skill', source: 'runtime', content: 'body' })
expect(JSON.stringify(await composePrefix(ctx))).toContain('- `acp-skill`: ACP...')
await ctx.fiber.dispose()
@@ -117,6 +129,7 @@ describe('dsh-acp-demo composition', () => {
it('forwards bundled tool config into agent-core', async () => {
const ctx = await mount({
model: 'mock',
workspaceContext: false,
toolBash: { enableRunInBackground: false },
toolTasks: { waitTimeoutMs: 7, maxWaitTimeoutMs: 11 },
skills: await isolatedSkillsConfig(),
@@ -132,11 +145,12 @@ describe('dsh-acp-demo composition', () => {
expect(acpAgent.Config).toBeDefined()
})
it('forwards toolOrder through agent-core to the system-prompt assembly', async () => {
it('forwards toolOrder through agent-spine-demo to the system-prompt assembly', async () => {
const ctx = await mount({
model: 'mock',
toolOrder: ['zulu', TOOL_ORDER_REST],
persistenceRoot: '/tmp/dsh-acp-demo-test-tool-order',
workspaceContext: false,
})
// The bundle's own bash tools pend on the absent `ctx.bash` executor in
// this providerless mount, so register two plain tools to order.
@@ -30,9 +30,9 @@ const acpBin = join(repoRoot, 'packages/examples/acp-demo/lib/bin.js')
const dshPackages = [
'examples/agent-spine-demo', 'core/agent', 'core/session', 'core/system-prompt',
'core/tools', 'core/agent-loop', 'llm/llm', 'llm/llm-deepseek', 'bash/bash',
'bash/bash-local', 'bash/tool-bash', 'support/invariants', 'ui/app-boot',
'bash/bash-local', 'bash/tool-bash', 'context/workspace-context', 'support/invariants', 'ui/app-boot',
'session-persistence/session-persistence',
'session-persistence/session-persistence-jsonl', 'ui/acp', 'examples/acp-demo',
'session-persistence/session-persistence-jsonl', 'ui/acp', 'examples/acp-demo', 'util/paths',
]
const vendorPackages = [
'cordis', 'loader', 'include', 'timer', 'hmr', 'logger-console',
@@ -86,6 +86,7 @@ async function makeConsumer(): Promise<string> {
' config:',
' model: deepseek-v4-flash',
' persona: \'test agent\'',
' workspaceContext: false',
'',
].join('\n'))
return dir
@@ -44,6 +44,7 @@ const CORDIS_YML = `
config:
model: deepseek-v4-flash
persona: 'You are a test agent.'
workspaceContext: false
`
interface Spawned {
+3
View File
@@ -29,6 +29,9 @@
{
"path": "../agent-spine-demo"
},
{
"path": "../../context/workspace-context"
},
{
"path": "../../ui/user-interaction"
},