Merge branch 'master' into worktree/dsh-arg-parser
This commit is contained in:
@@ -9,9 +9,10 @@ ACP automation server app: the default agent spine, client-created agents throug
|
||||
| `@deepseek-ai/dsh-agent-spine-demo` | Providerless agent spine with no pre-created agents; `session/new` creates each agent. |
|
||||
| `@deepseek-ai/dsh-session-persistence-jsonl` | Durable session logs used by checkpointing, observability, and snapshot replay. |
|
||||
| `@deepseek-ai/dsh-session-checkpoint-policy` | Durability barriers before model calls and top-level tool effects, plus completed-step checkpoints. |
|
||||
| `@deepseek-ai/dsh-session-query-sqlite` | Derived exact/FTS session-query service, opened before the ACP transport so leaf consumers are ready for the first model request. |
|
||||
| `@deepseek-ai/dsh-acp` | Automation-only ACP transport over stdin/stdout. |
|
||||
|
||||
The app does not install commands, user interaction, session navigation, configuration pickers, or a stdout logger. It owns the four plugins through one ordered effect so ACP sessions quiesce before checkpointing and persistence detach. Leaf configurations supply LLM, executor, sandbox, approval, filesystem, and model-facing tool plugins.
|
||||
The app does not install commands, user interaction, session navigation, configuration pickers, or a stdout logger. It owns these plugins through one ordered effect so the query service is ready before ACP accepts work and ACP sessions quiesce before checkpointing and persistence detach. Leaf configurations supply LLM, executor, sandbox, approval, filesystem, and model-facing tool plugins.
|
||||
|
||||
## Config
|
||||
|
||||
@@ -25,7 +26,7 @@ The app does not install commands, user interaction, session navigation, configu
|
||||
| `tools` | `{ mode: 'native' }` | Native, Code Mode, or combined model tool transport. |
|
||||
| `dshHome` | `$DSH_HOME` or `~/.dsh` | Harness home shared by bash and local skill discovery. |
|
||||
| `sessionTitle` | spine example limits | Durable fallback-title limits; titles remain off the ACP wire. |
|
||||
| `persistenceRoot` | `./.sessions` | JSONL backend root. |
|
||||
| `persistenceRoot` | `./.sessions` | JSONL backend root and parent directory of the derived `session-query.db` index. |
|
||||
| `packChunks` | `false` | Pack consecutive delta-chunk events in storage. |
|
||||
| `persistenceCompression` | `zstd` | Checksummed Zstandard frames or raw `none`. |
|
||||
| `workspaceContext` | required | Workspace-instruction byte budget/config, or `false`. |
|
||||
@@ -35,7 +36,7 @@ The app does not install commands, user interaction, session navigation, configu
|
||||
| `goals` | owner defaults | Persisted same-session goal domain and model tools, or `false`. |
|
||||
| `llmRetry` | owner defaults | Bounded transient model-request retry policy. |
|
||||
|
||||
The shipped [`examples/acp-agent/cordis.yml`](../../../examples/acp-agent/cordis.yml) adds the DeepSeek adapter, sandboxed bash and filesystem providers, one-shot approval policy, compaction, subagents, workflows, hooks, and model-facing tools. Snapshot overlays replace only nondeterministic providers or policy values.
|
||||
The shipped [`examples/acp-agent/cordis.yml`](../../../examples/acp-agent/cordis.yml) adds the DeepSeek adapter, sandboxed bash and filesystem providers, one-shot approval policy, compaction, subagents, workflows, hooks, and model-facing tools. The app supplies the derived session-query index, while the model-facing query consumer remains an explicit leaf opt-in. Snapshot overlays replace only nondeterministic providers or policy values.
|
||||
|
||||
## Bin
|
||||
|
||||
|
||||
@@ -43,6 +43,8 @@
|
||||
"@deepseek-ai/dsh-invariants": "^0.0.1",
|
||||
"@deepseek-ai/dsh-session-checkpoint-policy": "^0.0.1",
|
||||
"@deepseek-ai/dsh-session-persistence-jsonl": "^0.0.1",
|
||||
"@deepseek-ai/dsh-session-query": "^0.0.1",
|
||||
"@deepseek-ai/dsh-session-query-sqlite": "^0.0.1",
|
||||
"@deepseek-ai/dsh-tools": "^0.0.1",
|
||||
"@deepseek-ai/dsh-workspace-context": "^0.0.1",
|
||||
"cordis": "^4.0.0-rc.7",
|
||||
@@ -58,6 +60,8 @@
|
||||
"@deepseek-ai/dsh-invariants": "workspace:^",
|
||||
"@deepseek-ai/dsh-session-checkpoint-policy": "workspace:^",
|
||||
"@deepseek-ai/dsh-session-persistence-jsonl": "workspace:^",
|
||||
"@deepseek-ai/dsh-session-query": "workspace:^",
|
||||
"@deepseek-ai/dsh-session-query-sqlite": "workspace:^",
|
||||
"@deepseek-ai/dsh-system-prompt": "workspace:^",
|
||||
"@deepseek-ai/dsh-tools": "workspace:^",
|
||||
"@deepseek-ai/dsh-workspace-context": "workspace:^",
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
*/
|
||||
|
||||
import type { Context } from 'cordis'
|
||||
import { join } from 'node:path'
|
||||
import z from 'schemastery'
|
||||
import * as acp from '@deepseek-ai/dsh-acp'
|
||||
import * as agentCore from '@deepseek-ai/dsh-agent-spine-demo'
|
||||
@@ -22,6 +23,7 @@ import SessionPersistenceJsonl, {
|
||||
type JsonlCompression,
|
||||
} from '@deepseek-ai/dsh-session-persistence-jsonl'
|
||||
import * as sessionCheckpointPolicy from '@deepseek-ai/dsh-session-checkpoint-policy'
|
||||
import SessionQuerySqlite from '@deepseek-ai/dsh-session-query-sqlite'
|
||||
|
||||
export const name = 'acp-demo'
|
||||
const DEFAULT_PERSISTENCE_ROOT = './.sessions'
|
||||
@@ -51,7 +53,7 @@ export interface Config {
|
||||
dshHome?: string
|
||||
/** Fallback session-title limits forwarded through agent-spine-demo. */
|
||||
sessionTitle?: NonNullable<agentCore.Config['sessionTitle']>
|
||||
/** Directory for JSONL sessions. Defaults to `./.sessions`. */
|
||||
/** Directory for JSONL sessions and the derived query index. Defaults to `./.sessions`. */
|
||||
persistenceRoot?: string
|
||||
/** Write delta-chunk runs as packed storage rows (the JSONL backend's `packChunks`). Defaults to `false`. */
|
||||
packChunks?: boolean
|
||||
@@ -101,28 +103,39 @@ export const Config: z<Config> = z.object({
|
||||
/**
|
||||
* Compose the spine with the ACP automation transport. The agent-spine-demo bundle pre-creates
|
||||
* NO agents (its `agents` list defaults to `[]`) and carries the deployment
|
||||
* `persona`; the JSONL backend persists under
|
||||
* `persona`; the JSONL backend and derived query index persist under
|
||||
* `persistenceRoot`; the ACP bridge owns stdout for JSON-RPC and creates one
|
||||
* agent per `session/new` from the provider/model pair. The composite effect
|
||||
* unloads in reverse order, keeping checkpoint and persistence listeners
|
||||
* attached until ACP agents have flushed their closing events. No logger, no
|
||||
* `hmr` — stdout stays pure.
|
||||
*/
|
||||
export function apply(ctx: Context, config: Config): void {
|
||||
export async function apply(ctx: Context, config: Config): Promise<void> {
|
||||
const goals = config.goals ?? {}
|
||||
const persistenceRoot = config.persistenceRoot ?? DEFAULT_PERSISTENCE_ROOT
|
||||
ctx.effect(function* () {
|
||||
yield ctx.plugin(agentCore, { ...agentCore.pickSpineConfig(config), goals }).dispose
|
||||
await ctx.effect(async function* () {
|
||||
const spine = ctx.plugin(agentCore, { ...agentCore.pickSpineConfig(config), goals })
|
||||
await spine
|
||||
yield spine.dispose
|
||||
// Same rationale as the Config schema above: each front door forwards its own
|
||||
// persistence passthroughs rather than sharing a facade with stdio-demo.
|
||||
/* jscpd:ignore-start */
|
||||
yield ctx.plugin(SessionPersistenceJsonl, {
|
||||
const persistence = ctx.plugin(SessionPersistenceJsonl, {
|
||||
root: persistenceRoot,
|
||||
...config.packChunks !== undefined ? { packChunks: config.packChunks } : {},
|
||||
...(config.persistenceCompression === undefined ? {} : { compression: config.persistenceCompression }),
|
||||
}).dispose
|
||||
})
|
||||
await persistence
|
||||
yield persistence.dispose
|
||||
/* jscpd:ignore-end */
|
||||
yield ctx.plugin(sessionCheckpointPolicy).dispose
|
||||
yield ctx.plugin(acp, { provider: config.provider, model: config.model }).dispose
|
||||
const checkpoint = ctx.plugin(sessionCheckpointPolicy)
|
||||
await checkpoint
|
||||
yield checkpoint.dispose
|
||||
const query = ctx.plugin(SessionQuerySqlite, { path: join(persistenceRoot, 'session-query.db') })
|
||||
await query
|
||||
yield query.dispose
|
||||
const transport = ctx.plugin(acp, { provider: config.provider, model: config.model })
|
||||
await transport
|
||||
yield transport.dispose
|
||||
}, 'acp-demo.composition')
|
||||
}
|
||||
|
||||
@@ -30,9 +30,6 @@ async function mount(config: acpAgent.Config, withBash = false): Promise<Context
|
||||
})
|
||||
}
|
||||
await ctx.plugin(acpAgent, config)
|
||||
// The bundle mounts its children inside apply() (not awaited there); let their
|
||||
// fibers settle so the spine services are ready.
|
||||
await new Promise(resolve => setTimeout(resolve, 50))
|
||||
return ctx
|
||||
}
|
||||
|
||||
@@ -89,7 +86,7 @@ describe('dsh-acp-demo composition', () => {
|
||||
expect(ctx.get('agents')).toBeDefined()
|
||||
expect(ctx.get('sessions')).toBeDefined()
|
||||
expect(ctx.get('sessionPersistence')).toBeDefined()
|
||||
expect(ctx.get('sessionQuery')).toBeUndefined()
|
||||
expect(ctx.get('sessionQuery')).toBeDefined()
|
||||
expect(ctx.get('sessionReferences')).toBeUndefined()
|
||||
expect((ctx.get('sessionPersistence') as unknown as { config: { compression?: string } }).config.compression).toBe('none')
|
||||
expect(ctx.get('agentLoop')).toBeDefined()
|
||||
@@ -122,8 +119,12 @@ 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, { provider: 'mock', model: 'mock', skills: await isolatedSkillsConfig(), workspaceContext: false })
|
||||
await new Promise(resolve => setTimeout(resolve, 50))
|
||||
await acpAgent.apply(ctx, {
|
||||
provider: 'mock',
|
||||
model: 'mock',
|
||||
skills: await isolatedSkillsConfig(),
|
||||
workspaceContext: false,
|
||||
})
|
||||
expect(ctx.get('sessionPersistence')).toBeDefined()
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
@@ -144,8 +145,7 @@ describe('dsh-acp-demo composition', () => {
|
||||
it('uses default skill config when apply is called directly without skills', async () => {
|
||||
await withIsolatedSkillHomes(async () => {
|
||||
const ctx = new Context()
|
||||
acpAgent.apply(ctx, { provider: 'mock', model: 'mock', workspaceContext: false })
|
||||
await new Promise(resolve => setTimeout(resolve, 50))
|
||||
await acpAgent.apply(ctx, { provider: 'mock', model: 'mock', workspaceContext: false })
|
||||
expect(ctx.skills).toBeDefined()
|
||||
expect(await ctx.skills.list()).toEqual([])
|
||||
await ctx.fiber.dispose()
|
||||
|
||||
@@ -28,8 +28,8 @@ const tsxLoader = fileURLToPath(import.meta.resolve('tsx'))
|
||||
// Repo root is four levels up from packages/examples/acp-demo/tests.
|
||||
const repoTsconfig = fileURLToPath(new URL('../../../../tsconfig.json', import.meta.url))
|
||||
|
||||
// A minimal leaf that loads this app + the two backends — the same shape as
|
||||
// examples/acp-agent/cordis.yml, inlined so the package test owns its fixture.
|
||||
// A minimal opt-in leaf that loads this app + the two backends and the optional
|
||||
// session-query consumer/policies, inlined so the package test owns its fixture.
|
||||
const CORDIS_YML = `
|
||||
- id: llm-deepseek
|
||||
name: '@deepseek-ai/dsh-llm-deepseek'
|
||||
@@ -44,6 +44,16 @@ const CORDIS_YML = `
|
||||
model: deepseek-v4-flash
|
||||
persona: 'You are a test agent.'
|
||||
workspaceContext: false
|
||||
- id: tool-session-query
|
||||
name: '@deepseek-ai/dsh-tool-session-query'
|
||||
- id: timeout-policy
|
||||
name: '@deepseek-ai/dsh-timeout-policy'
|
||||
- id: spill-local
|
||||
name: '@deepseek-ai/dsh-spill-local'
|
||||
- id: spill-policy
|
||||
name: '@deepseek-ai/dsh-spill-policy'
|
||||
config:
|
||||
maxInlineBytes: 50000
|
||||
`
|
||||
|
||||
interface Spawned {
|
||||
|
||||
@@ -26,6 +26,12 @@
|
||||
{
|
||||
"path": "../../core/agent"
|
||||
},
|
||||
{
|
||||
"path": "../../session-query/session-query"
|
||||
},
|
||||
{
|
||||
"path": "../../session-query/session-query-sqlite"
|
||||
},
|
||||
{
|
||||
"path": "../agent-spine-demo"
|
||||
},
|
||||
|
||||
@@ -13,7 +13,7 @@ Use [`@deepseek-ai/dsh-cli-demo`](../cli-demo/README.md) for pipes, scripts, and
|
||||
| `@deepseek-ai/dsh-command-goal` | Direct `/goal` status and mutation over the spine's persisted-goal stack |
|
||||
| `@deepseek-ai/dsh-session-persistence-jsonl` | Durable session log under `persistenceRoot` |
|
||||
| `@deepseek-ai/dsh-session-checkpoint-policy` | Semantic durability barriers before model requests and top-level tool effects, plus completed-step checkpoints |
|
||||
| `@deepseek-ai/dsh-session-query-sqlite` + `@deepseek-ai/dsh-session-reference` | Combined exact/FTS session queries and bounded `@session` snapshots consumed by the TUI |
|
||||
| `@deepseek-ai/dsh-session-query-sqlite` + `@deepseek-ai/dsh-session-reference` | Combined exact/FTS session queries and bounded `@session` snapshots consumed by the TUI; model-facing query tools remain a leaf opt-in |
|
||||
| `@deepseek-ai/dsh-user-interaction` | Provider-neutral human question service |
|
||||
| `@deepseek-ai/dsh-tui` | Full-screen transcript, editor, tool cards, plan, and question overlays |
|
||||
| `@deepseek-ai/dsh-tool-ask-user` | Model-facing `ask_user_question` tool |
|
||||
|
||||
Reference in New Issue
Block a user