浏览代码

chore(compositions): mount bash-env and the pwsh tool in shipped and demo compositions

Huanqi Cao 1 月之前
父节点
当前提交
96cf8a2fbc

+ 3 - 0
apps/cli/composition.md

@@ -54,6 +54,8 @@ flowchart LR
   cfg --> plugin_tui_approval
   plugin_tui_permission["permission<br/>@deepseek-ai/dsh-permission"]
   cfg --> plugin_tui_permission
+  plugin_tui_bash_env["bash-env<br/>@deepseek-ai/dsh-bash-env"]
+  cfg --> plugin_tui_bash_env
   plugin_tui_tool_bash["tool-bash<br/>@deepseek-ai/dsh-tool-bash"]
   cfg --> plugin_tui_tool_bash
   plugin_tui_tool_tasks["tool-tasks<br/>@deepseek-ai/dsh-tool-tasks"]
@@ -165,6 +167,7 @@ flowchart LR
 | `bash-sandbox` | `@deepseek-ai/dsh-bash-sandbox` |
 | `approval` | `@deepseek-ai/dsh-user-approval` |
 | `permission` | `@deepseek-ai/dsh-permission` |
+| `bash-env` | `@deepseek-ai/dsh-bash-env` |
 | `tool-bash` | `@deepseek-ai/dsh-tool-bash` |
 | `tool-tasks` | `@deepseek-ai/dsh-tool-tasks` |
 | `fs-policy` | `@deepseek-ai/dsh-fs-policy` |

+ 3 - 0
apps/cli/config/base.cordis.yml

@@ -174,6 +174,9 @@
         sandbox: danger-full-access
         approval: never
 
+- id: bash-env
+  name: '@deepseek-ai/dsh-bash-env'
+
 - id: tool-bash
   name: '@deepseek-ai/dsh-tool-bash'
 

+ 1 - 0
apps/cli/package.json

@@ -23,6 +23,7 @@
     "@deepseek-ai/dsh-agent-loop": "workspace:^",
     "@deepseek-ai/dsh-app-boot": "workspace:^",
     "@deepseek-ai/dsh-bash-local": "workspace:^",
+    "@deepseek-ai/dsh-bash-env": "workspace:^",
     "@deepseek-ai/dsh-bash-sandbox": "workspace:^",
     "@deepseek-ai/dsh-client-connection": "workspace:^",
     "@deepseek-ai/dsh-client-hmr": "workspace:^",

+ 1 - 1
apps/cli/src/web.ts

@@ -11,7 +11,7 @@ import type { Context } from 'cordis'
 import { addHarnessSourceSection, resolveConfigPath } from '@deepseek-ai/dsh-app-boot'
 import type {} from '@deepseek-ai/dsh-host-webserver'
 import type {} from '@deepseek-ai/dsh-system-prompt'
-import type {} from '@deepseek-ai/dsh-tool-bash'
+import type {} from '@deepseek-ai/dsh-bash-env'
 import { AppCLIEntry } from './app-cli-entry.ts'
 
 // The shared core every `dsh` surface mounts, plus this surface's overlay over it.

+ 3 - 0
apps/cli/tsconfig.json

@@ -23,6 +23,9 @@
     {
       "path": "../../packages/ui/app-boot"
     },
+    {
+      "path": "../../packages/bash/bash-env"
+    },
     {
       "path": "../../packages/bash/tool-bash"
     },

+ 6 - 0
examples/acp-agent/tests/acp.snapshot.ts

@@ -154,6 +154,12 @@ const SCENARIOS: Scenario[] = [
     configPath: PTY_CONFIG,
   },
   { name: 'bash-tool-turn', hasModelTurn: true, recorded: true },
+  // The pwsh-tool-turn scenario is NOT registered yet: its overlay
+  // (pwsh.cordis.yml / pwsh.cordis.snapshot.yml) swaps the bundle's bash tool
+  // for the PowerShell twin, so its header class needs its own prompt/tool
+  // sidecars and a recorded transcript. Both require a keyed environment
+  // (`test:snapshot:record`); the composition ships so the scenario can be
+  // registered and recorded in one keyed pass.
   { name: 'todo-write', hasModelTurn: true, recorded: true },
   {
     name: 'skill-load',

+ 27 - 0
examples/acp-agent/tests/fixtures/bash/tool-pwsh/cordis.yml

@@ -0,0 +1,27 @@
+# Minimal tool-pwsh composition: real app boot path, real pwsh executor, real
+# foreground + background tool calls; driven by the package's loader.spec.ts.
+- id: system-prompt
+  name: '@deepseek-ai/dsh-system-prompt'
+
+- id: tools
+  name: '@deepseek-ai/dsh-tools'
+
+- id: subprocess
+  name: '@deepseek-ai/dsh-subprocess-local'
+
+- id: bash
+  name: '@deepseek-ai/dsh-pwsh-local'
+  config:
+    graceMs: 200
+
+- id: bash-env
+  name: '@deepseek-ai/dsh-bash-env'
+
+- id: tasks
+  name: '@deepseek-ai/dsh-tasks-local'
+
+- id: tool-tasks
+  name: '@deepseek-ai/dsh-tool-tasks'
+
+- id: tool-pwsh
+  name: '@deepseek-ai/dsh-tool-pwsh'

+ 67 - 0
examples/acp-agent/tests/fixtures/bash/tool-pwsh/driver.ts

@@ -0,0 +1,67 @@
+#!/usr/bin/env node
+/**
+ * Test driver: boot the tool-pwsh Loader composition, execute one real
+ * foreground and one real background pwsh command through the tool registry,
+ * and persist the observed model-visible output to `./pwsh-loader-report.json`
+ * for the package spec's inspect step.
+ */
+
+import { writeFile } from 'node:fs/promises'
+import { boot, resolveConfigPath } from '@deepseek-ai/dsh-app-boot'
+import { CallId } from '@deepseek-ai/dsh-llm'
+
+const configPath = process.argv[2]
+if (configPath === undefined) throw new Error('tool-pwsh driver requires a config path')
+
+const ctx = await boot('tool-pwsh-loader-smoke', resolveConfigPath(configPath, undefined))
+try {
+  const schema = ctx.tools.schemas().find(tool => tool.name === 'pwsh')
+  if (schema === undefined) throw new Error('pwsh tool not registered by the composition')
+  const prompt = (await ctx.systemPrompt.assemble()).sections.find(section => section.name === 'tool:pwsh')
+
+  const foreground = await ctx.tools.execute({
+    signal: new AbortController().signal,
+    callId: CallId('loader-fg'),
+    name: 'pwsh',
+    arguments: { command: 'Write-Output loader-ok', description: 'loader foreground' },
+  })
+  const foregroundText = foreground.content.filter(block => block.type === 'text').map(block => block.text).join('')
+
+  const background = await ctx.tools.execute({
+    signal: new AbortController().signal,
+    callId: CallId('loader-bg'),
+    name: 'pwsh',
+    arguments: {
+      command: 'Start-Sleep -Milliseconds 200; Write-Output loader-bg-ok',
+      description: 'loader background',
+      run_in_background: true,
+    },
+  })
+  const taskId = (background.value as { taskId: string }).taskId
+
+  // The output delta and the terminal status can land in separate reads
+  // (Windows flushes the child pipe at exit), so accumulate both.
+  let backgroundText = ''
+  const deadline = Date.now() + 10_000
+  while (Date.now() < deadline) {
+    const read = await ctx.tools.execute({
+      signal: new AbortController().signal,
+      callId: CallId('loader-bg-read'),
+      name: 'task_output',
+      arguments: { task_id: taskId },
+    })
+    backgroundText += read.content.filter(block => block.type === 'text').map(block => block.text).join('')
+    if (backgroundText.includes('loader-bg-ok') && backgroundText.includes('[status: completed')) break
+    await new Promise(resolve => setTimeout(resolve, 50))
+  }
+
+  await writeFile('./pwsh-loader-report.json', JSON.stringify({
+    schemaHasRunInBackground: Object.hasOwn(schema.parameters.properties as object, 'run_in_background'),
+    promptHasMarkerSection: prompt?.text.includes('Non-zero exits are reported as `[exit code: N]` markers') === true,
+    // Normalize PowerShell's platform line endings (CRLF on Windows, LF elsewhere).
+    foregroundText: foregroundText.replace(/\r\n/g, '\n'),
+    backgroundText: backgroundText.replace(/\r\n/g, '\n'),
+  }))
+} finally {
+  await ctx.fiber.dispose()
+}

+ 37 - 0
examples/acp-agent/tests/pwsh.cordis.snapshot.yml

@@ -0,0 +1,37 @@
+# Minimal keyless composition: real app, pwsh executor, and pwsh tool; replayed model.
+- id: llm-replay
+  name: '@deepseek-ai/dsh-llm-replay'
+  config:
+    providers:
+      - id: deepseek-official
+        name: DeepSeek
+        models:
+          - id: deepseek-v4-pro
+
+- id: subprocess
+  name: '@deepseek-ai/dsh-subprocess-local'
+
+- id: bash
+  name: '@deepseek-ai/dsh-pwsh-local'
+
+- id: bash-env
+  name: '@deepseek-ai/dsh-bash-env'
+
+- id: acp-agent
+  name: '@deepseek-ai/dsh-acp-demo'
+  config:
+    provider: deepseek-official
+    model: deepseek-v4-pro
+    persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
+    persistenceCompression: none
+    workspaceContext: false
+    skills:
+      enabled: false
+    toolTasks: false
+    goals: false
+    # The pwsh tool replaces the bundle's bash tool in this composition.
+    toolBash: false
+    persona: You are a concise snapshot agent working in {{cwd}}.
+
+- id: tool-pwsh
+  name: '@deepseek-ai/dsh-tool-pwsh'

+ 36 - 0
examples/acp-agent/tests/pwsh.cordis.yml

@@ -0,0 +1,36 @@
+# Minimal live counterpart for the pwsh-tool-turn snapshot composition.
+- id: llm-deepseek
+  name: '@deepseek-ai/dsh-llm-deepseek'
+  config:
+    apiKey: !!js process.env.DEEPSEEK_API_KEY
+    baseURL: !!js process.env.DEEPSEEK_BASE_URL
+    models:
+      - id: deepseek-v4-pro
+
+- id: subprocess
+  name: '@deepseek-ai/dsh-subprocess-local'
+
+- id: bash
+  name: '@deepseek-ai/dsh-pwsh-local'
+
+- id: bash-env
+  name: '@deepseek-ai/dsh-bash-env'
+
+- id: acp-agent
+  name: '@deepseek-ai/dsh-acp-demo'
+  config:
+    provider: deepseek-official
+    model: deepseek-v4-pro
+    persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions'
+    persistenceCompression: !!js "process.env.DSH_SNAPSHOT === undefined ? 'zstd' : 'none'"
+    workspaceContext: false
+    skills:
+      enabled: false
+    toolTasks: false
+    goals: false
+    # The pwsh tool replaces the bundle's bash tool in this composition.
+    toolBash: false
+    persona: You are a concise snapshot agent working in {{cwd}}.
+
+- id: tool-pwsh
+  name: '@deepseek-ai/dsh-tool-pwsh'

+ 3 - 0
examples/package.json

@@ -14,6 +14,7 @@
     "@deepseek-ai/dsh-agent-spine-demo": "workspace:*",
     "@deepseek-ai/dsh-app-boot": "workspace:*",
     "@deepseek-ai/dsh-bash": "workspace:*",
+    "@deepseek-ai/dsh-bash-env": "workspace:*",
     "@deepseek-ai/dsh-bash-local": "workspace:*",
     "@deepseek-ai/dsh-bash-sandbox": "workspace:*",
     "@deepseek-ai/dsh-cli-demo": "workspace:*",
@@ -42,6 +43,7 @@
     "@deepseek-ai/dsh-plan-mode": "workspace:*",
     "@deepseek-ai/dsh-pty": "workspace:*",
     "@deepseek-ai/dsh-pty-local": "workspace:*",
+    "@deepseek-ai/dsh-pwsh-local": "workspace:*",
     "@deepseek-ai/dsh-repeat-tool-guard": "workspace:*",
     "@deepseek-ai/dsh-repository-plugin": "workspace:*",
     "@deepseek-ai/dsh-sandbox-local": "workspace:*",
@@ -81,6 +83,7 @@
     "@deepseek-ai/dsh-tool-goal": "workspace:*",
     "@deepseek-ai/dsh-tool-lsp": "workspace:*",
     "@deepseek-ai/dsh-tool-pty": "workspace:*",
+    "@deepseek-ai/dsh-tool-pwsh": "workspace:*",
     "@deepseek-ai/dsh-tool-ralph": "workspace:*",
     "@deepseek-ai/dsh-tool-session-query": "workspace:*",
     "@deepseek-ai/dsh-tool-skill": "workspace:*",

+ 2 - 2
packages/examples/agent-spine-demo/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write packages/examples/agent-spine-demo/README.md
-README.md: 34d68b0791746c28528124853a4d8ea82b68138d
-README.zh.md: 1b0a644595e35d8703d0600812268b0950b79182
+README.md: 7ea2f4afbe5d0bea62cf0fc2c7ecdd1496d20aef
+README.zh.md: cb4202ac0e72db7c85967425671144e743f31b62

文件差异内容过多而无法显示
+ 0 - 0
packages/examples/agent-spine-demo/README.md


文件差异内容过多而无法显示
+ 0 - 0
packages/examples/agent-spine-demo/README.zh.md


+ 3 - 0
packages/examples/agent-spine-demo/package.json

@@ -43,6 +43,7 @@
     "@deepseek-ai/dsh-skill-local": "^0.0.1",
     "@deepseek-ai/dsh-system-prompt": "^0.0.1",
     "@deepseek-ai/dsh-tasks-local": "^0.0.1",
+    "@deepseek-ai/dsh-bash-env": "^0.0.1",
     "@deepseek-ai/dsh-tool-bash": "^0.0.1",
     "@deepseek-ai/dsh-tool-goal": "^0.0.1",
     "@deepseek-ai/dsh-tool-skill": "^0.0.1",
@@ -55,6 +56,7 @@
     "@cordisjs/plugin-timer": "workspace:^",
     "@deepseek-ai/dsh-agent": "workspace:^",
     "@deepseek-ai/dsh-agent-loop": "workspace:^",
+    "@deepseek-ai/dsh-bash-env": "workspace:^",
     "@deepseek-ai/dsh-bash-local": "workspace:^",
     "@deepseek-ai/dsh-bash-sandbox": "workspace:^",
     "@deepseek-ai/dsh-fs-local": "workspace:^",
@@ -77,6 +79,7 @@
     "@deepseek-ai/dsh-system-prompt": "workspace:^",
     "@deepseek-ai/dsh-tasks": "workspace:^",
     "@deepseek-ai/dsh-tasks-local": "workspace:^",
+    "@deepseek-ai/dsh-bash-env": "workspace:^",
     "@deepseek-ai/dsh-tool-bash": "workspace:^",
     "@deepseek-ai/dsh-tool-fs": "workspace:^",
     "@deepseek-ai/dsh-tool-goal": "workspace:^",

+ 3 - 1
packages/examples/agent-spine-demo/src/index.ts

@@ -29,6 +29,7 @@ import * as agentInvariant from '@deepseek-ai/dsh-agent/invariant'
 import * as scopeInvariant from '@deepseek-ai/dsh-scope/invariant'
 import * as agentLoopInvariant from '@deepseek-ai/dsh-agent-loop/invariant'
 import * as toolBash from '@deepseek-ai/dsh-tool-bash'
+import * as bashEnv from '@deepseek-ai/dsh-bash-env'
 import * as workspaceContext from '@deepseek-ai/dsh-workspace-context'
 import * as toolSkill from '@deepseek-ai/dsh-tool-skill'
 import * as toolTasks from '@deepseek-ai/dsh-tool-tasks'
@@ -234,7 +235,8 @@ export function apply(ctx: Context, config: Config): void {
   ctx.plugin(scopeInvariant)
   ctx.plugin(agentLoopInvariant)
   if (config.toolBash !== false) {
-    ctx.plugin(toolBash, Object.assign({}, config.toolBash, { dshHome }))
+    ctx.plugin(bashEnv, { dshHome })
+    ctx.plugin(toolBash, config.toolBash ?? {})
   }
   if (config.workspaceContext !== false) {
     ctx.plugin(workspaceContext, config.workspaceContext)

+ 3 - 0
packages/examples/agent-spine-demo/tsconfig.json

@@ -68,6 +68,9 @@
     {
       "path": "../../util/paths"
     },
+    {
+      "path": "../../bash/bash-env"
+    },
     {
       "path": "../../bash/tool-bash"
     },

部分文件因为文件数量过多而无法显示