feat(session): centralize working directories and directory context

This commit is contained in:
Tianyi Cui committed 2026-10-06 01:27:54 +08:00
1 parent 3d9cd9fa95
commit 79bd3d8da7
497 files changed
+5670 -1187

No files matched your search

+1
View File
@@ -110,6 +110,7 @@ export const SERVICE_PAGE: Record<string, string> = {
sessionReferenceResolver: 'session-reference.md',
sessionProjectionCache: 'session-projection.md',
sessionProjections: 'session-projection.md',
workingDirectory: 'working-directory.md',
sessionController: 'session.md',
sessionSkillCatalog: 'skills.md',
sessions: 'session.md',
+9
View File
@@ -154,6 +154,15 @@ const SERVICE_ROLES: ServiceRole[] = [
consumers: ['api-gateway', 'host-frontend-static'],
note: 'Owns browser authentication and shared HTTP request dispatch; API adapters register endpoints and streams.',
},
{
key: 'workingDirectory',
pkg: 'working-directory',
title: 'Session working directory',
mode: 'seam',
implementations: ['working-directory'],
consumers: ['tool-working-directory', 'tool-fs', 'tool-bash', 'tool-pwsh', 'subagent', 'sdk-jsonrpc-server'],
note: 'One Session projection owns the effective execution directory. The filesystem validates changes; user context reports them while original metadata and write grants stay fixed.',
},
{
key: 'mcpResources',
pkg: 'mcp-resources',
+28 -17
View File
@@ -25,6 +25,8 @@ import * as BashEnvPlugin from '@deepseek-ai/dsh-shell-env'
import { PwshLocalExecutor } from '@deepseek-ai/dsh-pwsh-local'
import LocalSubprocessRuntime from '@deepseek-ai/dsh-subprocess-local'
import LocalFileSystem from '@deepseek-ai/dsh-fs-local'
import WorkingDirectory from '@deepseek-ai/dsh-working-directory'
import * as ToolWorkingDirectory from '@deepseek-ai/dsh-tool-working-directory'
import { AttachmentStore } from '@deepseek-ai/dsh-attachment'
import type { ImageAttachmentLimits, ImageAttachmentRef, SaveImageAttachment, StoredImageAttachment } from '@deepseek-ai/dsh-attachment'
import UserQuestionService from '@deepseek-ai/dsh-user-questions'
@@ -136,7 +138,7 @@ const catalogChildScopes = new WeakMap<Context, Agent>()
/**
* Tool package plus its hand-maintained boot recipe. The caller mounts the
* prompt and registry; each recipe supplies only package-specific seams and
* prompt, directory services, and registry; each recipe supplies only package-specific seams and
* config, while `dir` participates in the completeness check.
*/
export interface ToolPackage {
@@ -241,7 +243,7 @@ const TOOL_PACKAGES: ToolPackage[] = [
pkg: '@deepseek-ai/dsh-tools',
dir: 'tools',
source: 'packages/core/tools/src/ptc.ts',
requires: ['ctx.tools', 'ctx.ptcRuntime (execution time)', 'ctx.systemPrompt'],
requires: ['ctx.workingDirectory (Agent executions)', 'ctx.tools', 'ctx.ptcRuntime (execution time)', 'ctx.systemPrompt'],
writes: ['tool/call', 'one tool/ptc-dispatch-start + tool/ptc-dispatch pair per bridged sub-call', 'tool/result'],
// The registry's OWN tool: run_code exists only under a non-native mode
// (the registry registers it in its constructor; the PTC runtime is read
@@ -267,7 +269,7 @@ const TOOL_PACKAGES: ToolPackage[] = [
pkg: '@deepseek-ai/dsh-tool-bash',
dir: 'tool-bash',
source: 'packages/shell/tool-bash/src/index.ts',
requires: ['ctx.tools', 'ctx.shell', 'ctx.systemPrompt', 'ctx.shellEnv', 'ctx.jobs for run_in_background and the job-backed foreground path'],
requires: ['ctx.workingDirectory', 'ctx.tools', 'ctx.shell', 'ctx.systemPrompt', 'ctx.shellEnv', 'ctx.jobs for run_in_background and the job-backed foreground path'],
writes: ['tool/call', 'tool/result'],
async mount(ctx) {
await ctx.plugin(LocalSubprocessRuntime)
@@ -285,10 +287,9 @@ const TOOL_PACKAGES: ToolPackage[] = [
pkg: '@deepseek-ai/dsh-tool-present',
dir: 'tool-present',
source: 'packages/deliverables/tool-present/src/index.ts',
requires: ['ctx.tools', 'ctx.fs', 'ctx.sessionProjections'],
requires: ['ctx.workingDirectory', 'ctx.tools', 'ctx.fs', 'ctx.sessionProjections'],
writes: ['tool/call', 'deliverables/presented after a successful final result', 'tool/result'],
async mount(ctx) {
await ctx.plugin(LocalFileSystem)
await ctx.plugin(ToolPresent)
},
note: 'Deliveries belong to the calling Session; Web ui-deliverables supplies source-file opening and cards.',
@@ -297,7 +298,7 @@ const TOOL_PACKAGES: ToolPackage[] = [
pkg: '@deepseek-ai/dsh-tool-pwsh',
dir: 'tool-pwsh',
source: 'packages/shell/tool-pwsh/src/index.ts',
requires: ['ctx.tools', 'ctx.shell', 'ctx.systemPrompt', 'ctx.shellEnv', 'ctx.jobs for run_in_background and the job-backed foreground path'],
requires: ['ctx.workingDirectory', 'ctx.tools', 'ctx.shell', 'ctx.systemPrompt', 'ctx.shellEnv', 'ctx.jobs for run_in_background and the job-backed foreground path'],
writes: ['tool/call', 'tool/result'],
async mount(ctx) {
// The pwsh tool consumes the bash executor seam; the schema harvest
@@ -330,7 +331,7 @@ const TOOL_PACKAGES: ToolPackage[] = [
pkg: '@deepseek-ai/dsh-tool-bash-persistent',
dir: 'tool-bash-persistent',
source: 'packages/shell/tool-bash-persistent/src/index.ts',
requires: ['ctx.tools', 'ctx.terminals', 'an owning Agent at execution time'],
requires: ['ctx.workingDirectory', 'ctx.tools', 'ctx.terminals', 'an owning Agent at execution time'],
writes: ['tool/call', 'PTY shell state', 'tool/result'],
async mount(ctx) {
await ctx.plugin(TerminalSessionService)
@@ -343,7 +344,7 @@ const TOOL_PACKAGES: ToolPackage[] = [
pkg: '@deepseek-ai/dsh-tool-pwsh-persistent',
dir: 'tool-pwsh-persistent',
source: 'packages/shell/tool-pwsh-persistent/src/index.ts',
requires: ['ctx.tools', 'ctx.terminals', 'an owning Agent at execution time'],
requires: ['ctx.workingDirectory', 'ctx.tools', 'ctx.terminals', 'an owning Agent at execution time'],
writes: ['tool/call', 'PTY shell state', 'tool/result'],
async mount(ctx) {
await ctx.plugin(TerminalSessionService)
@@ -359,7 +360,6 @@ const TOOL_PACKAGES: ToolPackage[] = [
requires: ['ctx.tools', 'ctx.fs'],
writes: ['tool/call', 'fs/observed after view presence/absence, edit absence, or successful mutation', 'tool/result'],
async mount(ctx) {
await ctx.plugin(LocalFileSystem)
await ctx.plugin(ToolStrReplaceEditor)
},
note:
@@ -369,13 +369,11 @@ const TOOL_PACKAGES: ToolPackage[] = [
pkg: '@deepseek-ai/dsh-tool-fs',
dir: 'tool-fs',
source: 'packages/fs/tool-fs/src/index.ts',
requires: ['ctx.tools', 'ctx.fs', 'ctx.systemPrompt', 'ctx.attachments (image-tool registration)', 'ctx.llm + an image-capable route (image-tool execution)'],
requires: ['ctx.workingDirectory', 'ctx.tools', 'ctx.fs', 'ctx.systemPrompt', 'ctx.attachments (image-tool registration)', 'ctx.llm + an image-capable route (image-tool execution)'],
writes: ['tool/call', 'fs/write-intent or fs/edit-intent for mutations', 'fs/observed after read presence/absence or successful file operation', 'durable attachment (read_image)', 'tool/result'],
async mount(ctx) {
// The tool needs `fs`; the bare provider is sufficient because policy
// changes behavior, not schema shape. The catalog seam marker opts into
// The catalog seam marker opts into
// the attachments-conditional image schema without attachment I/O.
await ctx.plugin(LocalFileSystem)
await ctx.plugin(CatalogAttachmentStore)
await ctx.plugin(ToolFs)
},
@@ -386,7 +384,7 @@ const TOOL_PACKAGES: ToolPackage[] = [
pkg: '@deepseek-ai/dsh-tool-fs-search',
dir: 'tool-fs-search',
source: 'packages/fs/tool-fs-search/src/index.ts',
requires: ['ctx.tools', 'ctx.subprocess', 'ctx.systemPrompt'],
requires: ['ctx.workingDirectory', 'ctx.tools', 'ctx.subprocess', 'ctx.systemPrompt'],
writes: ['tool/call', 'tool/result'],
async mount(ctx) {
// The tools inject `subprocess` (search spawns the packaged ripgrep
@@ -404,7 +402,7 @@ const TOOL_PACKAGES: ToolPackage[] = [
pkg: '@deepseek-ai/dsh-tool-terminal',
dir: 'tool-terminal',
source: 'packages/terminal/tool-terminal/src/index.ts',
requires: ['ctx.tools', 'ctx.terminals', 'ctx.systemPrompt', 'ctx.jobs at call time for run_in_background'],
requires: ['ctx.workingDirectory', 'ctx.tools', 'ctx.terminals', 'ctx.systemPrompt', 'ctx.jobs at call time for run_in_background'],
writes: ['tool/call', 'tool/result'],
async mount(ctx) {
await ctx.plugin(TerminalSessionService)
@@ -450,7 +448,7 @@ const TOOL_PACKAGES: ToolPackage[] = [
pkg: '@deepseek-ai/dsh-tool-lsp',
dir: 'tool-lsp',
source: 'packages/lsp/tool-lsp/src/index.ts',
requires: ['ctx.tools', 'ctx.lsp', 'ctx.systemPrompt'],
requires: ['ctx.workingDirectory', 'ctx.tools', 'ctx.lsp', 'ctx.systemPrompt'],
writes: ['tool/call', 'tool/result'],
async mount(ctx) {
// The tool registers from the seam alone; the schema does not depend on any provider.
@@ -479,7 +477,7 @@ const TOOL_PACKAGES: ToolPackage[] = [
pkg: '@deepseek-ai/dsh-tool-skill',
dir: 'tool-skill',
source: 'packages/skill/tool-skill/src/index.ts',
requires: ['ctx.tools', 'ctx.agents', 'ctx.skills'],
requires: ['ctx.workingDirectory', 'ctx.tools', 'ctx.agents', 'ctx.skills'],
writes: ['tool/call', 'tool/result', 'user/message replacement catalogs via agent.inject()'],
async mount(ctx) {
await ctx.plugin(AgentRegistry)
@@ -609,6 +607,17 @@ const TOOL_PACKAGES: ToolPackage[] = [
note:
'todo_write is session-owned state; UIs render the latest todo/write event as a checklist. `allowParallelInProgress` is required with no default, so the catalog states its choice: `true`, whose description invites several `in_progress` items. A deployment choosing `false` receives the same tool with a description asking for exactly one active task.',
},
{
pkg: '@deepseek-ai/dsh-tool-working-directory',
dir: 'tool-working-directory',
source: 'packages/session/tool-working-directory/src/index.ts',
requires: ['ctx.tools', 'ctx.workingDirectory'],
writes: ['tool/call', 'working-directory/change', 'user context for directory changes', 'tool/result'],
async mount(ctx) {
await ctx.plugin(ToolWorkingDirectory)
},
note: 'Reads or changes the calling Session directory. Existing shells and processes retain their own directories; origin metadata and permission roots stay fixed.',
},
{
pkg: '@deepseek-ai/dsh-tool-workflow',
dir: 'tool-workflow',
@@ -729,6 +738,8 @@ export async function collectToolCatalog(packages: ToolPackage[] = TOOL_PACKAGES
try {
await ctx.plugin(SessionProjectionRegistry)
await ctx.plugin(SystemPrompt)
await ctx.plugin(LocalFileSystem)
await ctx.plugin(WorkingDirectory)
await ctx.plugin(ToolRuntime, entry.toolsConfig ?? {})
await entry.mount(ctx)
const schemas = ctx.tools.schemas(entry.scope?.(ctx)).sort((a, b) => a.name.localeCompare(b.name))
@@ -57,7 +57,7 @@ describe('advanced Python snapshot workflow ordering', () => {
const assembly = ctx.plugin({
name: 'workflow-order-driver-test',
async apply(inner: Context) {
await mountAgentLoopTestDependencies(inner)
await mountAgentLoopTestDependencies(inner, { workingDirectory: true })
await inner.plugin(AgentLoop, { agents: [] })
await inner.plugin(SubagentRuntime)
await inner.plugin(spawn, { providerName: 'spawn' })
+2 -2
View File
@@ -2277,8 +2277,8 @@ def build_minimal_snapshot_files(
kept verbatim: they carry what the deployment actually shows the model, so a plugin
that contributes an unintended system section or user message cannot pass unnoticed.
Assistant and tool payloads keep only their call identity because their text differs
across the platforms this expected output must replay on. The shipped profile omits
dynamic runtime context, so every message it emits is compared.
across the platforms this expected output must replay on. Required working-directory
context remains visible even when the profile disables optional runtime context.
"""
snapshot = []
for body in requests:
@@ -17,6 +17,19 @@
"command"
]
}
},
{
"name": "working_directory",
"description": "Read the current working directory, or change it with cd. Relative paths use the current directory. Existing shells and running processes keep their own directories.",
"input_schema": {
"type": "object",
"properties": {
"cd": {
"type": "string",
"description": "Existing directory to enter. Omit to read the current directory."
}
}
}
}
],
"messages": [
@@ -26,6 +39,10 @@
{
"type": "text",
"text": "Exercise the packaged minimal agent's persistent shell."
},
{
"type": "text",
"text": "Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\"."
}
]
}
@@ -49,6 +66,19 @@
"command"
]
}
},
{
"name": "working_directory",
"description": "Read the current working directory, or change it with cd. Relative paths use the current directory. Existing shells and running processes keep their own directories.",
"input_schema": {
"type": "object",
"properties": {
"cd": {
"type": "string",
"description": "Existing directory to enter. Omit to read the current directory."
}
}
}
}
],
"messages": [
@@ -58,6 +88,10 @@
{
"type": "text",
"text": "Exercise the packaged minimal agent's persistent shell."
},
{
"type": "text",
"text": "Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\"."
}
]
},
@@ -100,6 +134,19 @@
"command"
]
}
},
{
"name": "working_directory",
"description": "Read the current working directory, or change it with cd. Relative paths use the current directory. Existing shells and running processes keep their own directories.",
"input_schema": {
"type": "object",
"properties": {
"cd": {
"type": "string",
"description": "Existing directory to enter. Omit to read the current directory."
}
}
}
}
],
"messages": [
@@ -109,6 +156,10 @@
{
"type": "text",
"text": "Exercise the packaged minimal agent's persistent shell."
},
{
"type": "text",
"text": "Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\"."
}
]
},
@@ -17,6 +17,19 @@
"command"
]
}
},
{
"name": "working_directory",
"description": "Read the current working directory, or change it with cd. Relative paths use the current directory. Existing shells and running processes keep their own directories.",
"input_schema": {
"type": "object",
"properties": {
"cd": {
"type": "string",
"description": "Existing directory to enter. Omit to read the current directory."
}
}
}
}
],
"messages": [
@@ -26,6 +39,10 @@
{
"type": "text",
"text": "Exercise the packaged minimal agent's persistent shell."
},
{
"type": "text",
"text": "Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\"."
}
]
}
@@ -49,6 +66,19 @@
"command"
]
}
},
{
"name": "working_directory",
"description": "Read the current working directory, or change it with cd. Relative paths use the current directory. Existing shells and running processes keep their own directories.",
"input_schema": {
"type": "object",
"properties": {
"cd": {
"type": "string",
"description": "Existing directory to enter. Omit to read the current directory."
}
}
}
}
],
"messages": [
@@ -58,6 +88,10 @@
{
"type": "text",
"text": "Exercise the packaged minimal agent's persistent shell."
},
{
"type": "text",
"text": "Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\"."
}
]
},
@@ -100,6 +134,19 @@
"command"
]
}
},
{
"name": "working_directory",
"description": "Read the current working directory, or change it with cd. Relative paths use the current directory. Existing shells and running processes keep their own directories.",
"input_schema": {
"type": "object",
"properties": {
"cd": {
"type": "string",
"description": "Existing directory to enter. Omit to read the current directory."
}
}
}
}
],
"messages": [
@@ -109,6 +156,10 @@
{
"type": "text",
"text": "Exercise the packaged minimal agent's persistent shell."
},
{
"type": "text",
"text": "Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\"."
}
]
},
@@ -12,7 +12,7 @@
},
{
"type": "text",
"text": "Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."
"text": "Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."
}
]
}
@@ -25,7 +25,8 @@
"job_output",
"run_code",
"subagent",
"workflow"
"workflow",
"working_directory"
]
},
{
@@ -41,7 +42,7 @@
},
{
"type": "text",
"text": "Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."
"text": "Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."
}
]
}
@@ -54,7 +55,8 @@
"job_output",
"run_code",
"subagent",
"workflow"
"workflow",
"working_directory"
]
}
]
+5
View File
@@ -497,6 +497,11 @@
"symbol": "PromptContext",
"source": "packages/core/system-prompt/src/index.ts"
},
{
"doc": "docs/subsystems/system-prompt.md",
"symbol": "AssembledContext",
"source": "packages/core/system-prompt/src/index.ts"
},
{
"doc": "docs/subsystems/system-prompt.md",
"symbol": "PromptSection",