瀏覽代碼

fix(pwsh): pin CI expectations for the mirror design

- jscpd: the executor/tool mirror dsh-bash-local/dsh-tool-bash by design
  (Agent Note), so the mirrored regions carry explicit ignore markers with
  reasons instead of being flagged as duplication.
- pwsh-local: a self-terminated process reports SIGTERM or SIGKILL on
  POSIX (PowerShell's Stop-Process choice), not only SIGTERM.
- gen-tool-catalog.spec: the shipped-tool completeness list gains 'pwsh'.
Huanqi Cao 2 月之前
父節點
當前提交
30c421ed75

+ 2 - 0
packages/bash/pwsh-local/src/index.ts

@@ -22,6 +22,7 @@ import type { BashExecRequest, BashExecSpec, BashProcess, BashProcessRead, BashR
 import type { SubprocessCollect, SubprocessHandle, SubprocessOutputReader, SubprocessSpawnSpec } from '@deepseek-ai/dsh-subprocess'
 import { clampTimeout, deadline, timeoutOf } from '@deepseek-ai/dsh-timeout'
 
+/* jscpd:ignore-start -- deliberate call-for-call mirror of dsh-bash-local (Agent Note: pwsh-tool-and-executor). */
 /**
  * Model-friendly environment overrides for PowerShell: disable colors and
  * pagers that would garble tool output. `TERM=dumb` is a POSIX concept and is
@@ -312,5 +313,6 @@ export class PwshLocalExecutor extends BashExecutor {
    */
   protected onProcessDone(_proc: BashProcess, _stderr: string): void {}
 }
+/* jscpd:ignore-end */
 
 export default PwshLocalExecutor

+ 5 - 3
packages/bash/pwsh-local/tests/executor.spec.ts

@@ -197,11 +197,12 @@ describe.skipIf(!hasPwsh)('PwshLocalExecutor.run', () => {
     const result = await bash.run(bash.resolve({ command: 'Stop-Process -Id $PID' }))
     expect(result.timedOut).toBe(false)
     expect(result.aborted).toBe(false)
-    // Windows reports a forced termination without a signal; POSIX reports SIGTERM.
+    // Windows reports a forced termination without a signal; POSIX reports the
+    // terminating signal PowerShell chose (SIGTERM, or SIGKILL for the hard kill).
     if (process.platform === 'win32') {
       expect(result.signal).toBeNull()
     } else {
-      expect(result.signal).toBe('SIGTERM')
+      expect(['SIGTERM', 'SIGKILL']).toContain(result.signal)
     }
   })
 
@@ -347,7 +348,8 @@ describe.skipIf(!hasPwsh)('PwshLocalExecutor.start (background process handles)'
     await proc.done
     expect(proc.status).toBe('killed')
     expect(proc.exitCode).toBeNull()
-    expect(proc.signal).toBe('SIGTERM')
+    // PowerShell picks SIGTERM for Stop-Process, SIGKILL for the hard kill.
+    expect(['SIGTERM', 'SIGKILL']).toContain(proc.signal)
   })
 
   it('a background spawn failure settles as killed with the error readable on stderr', async () => {

+ 9 - 0
packages/bash/tool-pwsh/src/index.ts

@@ -58,6 +58,7 @@ interface PwshForegroundResult {
   stderr: { text: string; truncated: boolean; spillPath?: string }
 }
 
+/* jscpd:ignore-start -- minimal mirror of dsh-tool-bash's validation and execute plumbing (Agent Note). */
 function validatePwshArgs(args: PwshToolArgs): void {
   if (args.command.trim().length === 0) {
     throw new Error('invalid command: expected a non-empty string')
@@ -69,6 +70,7 @@ function validatePwshArgs(args: PwshToolArgs): void {
     throw new Error(`invalid timeoutMs: expected a positive number, got ${JSON.stringify(args.timeoutMs)}`)
   }
 }
+/* jscpd:ignore-end */
 
 function pwshDescription(): string {
   return 'Execute a PowerShell command (`pwsh -Command`) and return its stdout/stderr. '
@@ -185,6 +187,10 @@ export function apply(ctx: Context, config: Config = {}): void {
       workdir: { type: 'string', description: 'Working directory for this command. Defaults to the session workspace; a relative path is resolved against it.' },
     },
     output: {
+      // The foreground result wire shape mirrors dsh-tool-bash's by contract —
+      // consumers of one must accept the other (see the pwsh-tool-and-executor
+      // Agent Note).
+      /* jscpd:ignore-start -- deliberate foreground-result schema symmetry with dsh-tool-bash. */
       schema: {
         type: 'object',
         additionalProperties: false,
@@ -217,11 +223,13 @@ export function apply(ctx: Context, config: Config = {}): void {
           },
         },
       },
+      /* jscpd:ignore-end */
       render: (_args, value) => [{
         type: 'text',
         text: renderPwshOutput(value),
       }],
     },
+    /* jscpd:ignore-start -- the foreground execute path mirrors dsh-tool-bash's by design (see the pwsh-tool-and-executor Agent Note). */
     async execute(args: PwshToolArgs, exec) {
       validatePwshArgs(args)
       const workdir = resolveWorkdir(args.workdir, exec)
@@ -239,6 +247,7 @@ export function apply(ctx: Context, config: Config = {}): void {
       }
       return canonicalPwshResult(result)
     },
+    /* jscpd:ignore-end */
     presentCall: (args: PwshToolArgs): TerminalCallView => ({
       card: 'terminal',
       title: args.command,

+ 1 - 1
packages/core/tools/tests/gen-tool-catalog.spec.ts

@@ -23,7 +23,7 @@ describe('gen-tool-catalog collectToolCatalog', () => {
   it('boots every shipped tool package and harvests its model-facing schemas', async () => {
     const catalog = await collectToolCatalog()
     const names = catalog.flatMap(entry => entry.schemas.map(s => s.name)).sort()
-    expect(names).toEqual(['ask_user_question', 'bash', 'bash', 'cordis_inspect', 'cordis_mount', 'cordis_unmount', 'create_goal', 'edit', 'exit_plan_mode', 'get_goal', 'glob', 'grep', 'lsp', 'ralph', 'read', 'run_code', 'session_event_read', 'session_event_search', 'session_event_trace', 'session_search', 'session_trace', 'skill', 'str_replace_editor', 'subagent', 'task_kill', 'task_list', 'task_output', 'terminal_close', 'terminal_list', 'terminal_open', 'terminal_read', 'terminal_send', 'terminal_signal', 'todo_write', 'update_goal', 'web_fetch', 'web_search', 'workflow', 'write'])
+    expect(names).toEqual(['ask_user_question', 'bash', 'bash', 'cordis_inspect', 'cordis_mount', 'cordis_unmount', 'create_goal', 'edit', 'exit_plan_mode', 'get_goal', 'glob', 'grep', 'lsp', 'pwsh', 'ralph', 'read', 'run_code', 'session_event_read', 'session_event_search', 'session_event_trace', 'session_search', 'session_trace', 'skill', 'str_replace_editor', 'subagent', 'task_kill', 'task_list', 'task_output', 'terminal_close', 'terminal_list', 'terminal_open', 'terminal_read', 'terminal_send', 'terminal_signal', 'todo_write', 'update_goal', 'web_fetch', 'web_search', 'workflow', 'write'])
     // Every tool carries a JSON-Schema `parameters` object (what the model sees).
     for (const entry of catalog) {
       for (const schema of entry.schemas) {