Skip to content

fix(dsh): call ShellExecutor.execute() on dsh 0.2.x, keep run() for 0.1.x - #330

Merged
Yifan Yang (Yif-Yang) merged 1 commit into
microsoft:mainfrom
WODE25500:fix/dsh-shell-execute-seam
Oct 23, 2026
Merged

Yifan Yang (Yif-Yang) merged 1 commit into
microsoft:mainfrom
WODE25500:fix/dsh-shell-execute-seam

Conversation

@WODE25500

Copy link
Copy Markdown
Contributor

Problem

plugins/dsh/src/index.js runs every skillopt_* tool through shell.run(spec)
(line 373 on current main). dsh 0.2.x removed that method: ShellExecutor
now declares a single abstract execute(spec): Promise<ShellExecution>, and the
foreground outcome comes from await handle.result(). Under dsh 0.2.x every
tool call therefore fails with shell.run is not a function ??the tool never
reaches the engine.

#311 widened the @deepseek-ai/dsh-tools range but the call site was not part
of that diff, so the range it declares
(>=0.1.1-rc.2 <0.2.0 || >=0.2.0-rc.1 <0.3.0) currently admits a generation
that the code cannot call.

Why not a hard switch

The same range still admits 0.1.x, where shell.run() is the only method and
execute() does not exist. Switching the call outright trades a 0.2-only bug
for a 0.1-only one. The file already feature-detects the neighbouring seam
(typeof shell.resolve === 'function' on the line above), so this follows the
same idiom.

Change

export async function runShell(shell, spec) {
  if (typeof shell.execute === 'function') return (await shell.execute(spec)).result()
  return shell.run(spec)
}

+16/?? in src/index.js (helper + one call site).

Verification

scripts/test-shell-seam.mjs covers the three executor shapes:

  1. 0.2.x ??execute() returns a ShellExecution; outcome read from result()
  2. 0.1.x ??run() returns the outcome directly; execute absent
  3. transitional ??both present; execute() must win
$ node scripts/test-shell-seam.mjs
1. dsh 0.2.x: execute() handle, outcome from result()   ?????2. dsh 0.1.x: run() returns the outcome directly        ???
3. transitional: both present, execute() wins           ???
ALL SHELL SEAM CHECKS PASSED

Negative control: with the body hard-switched back to shell.run(spec), case 1
dies with TypeError: shell.run is not a function ??the same error users hit on
0.2.x, so the test fails exactly where the regression is.

Existing scripts/test-quoting-bash.mjs is unaffected (needs a POSIX bash; on
Windows quoteArgv() prepends the PowerShell call operator, which is a
pre-existing condition of that script, not of this change).

Scope

Only the shell seam. The CollectedOutput handling below it (fmt()) and the
timedOut / exitCode classification are unchanged ??ShellRunResult keeps
those fields.

….1.x

dsh 0.2.x replaced ShellExecutor.run() with execute(), which returns a
ShellExecution whose foreground outcome is handle.result(); 0.1.x has only
run(). plugins/dsh declares @deepseek-ai/dsh-tools for BOTH generations
(>=0.1.1-rc.2 <0.2.0 || >=0.2.0-rc.1 <0.3.0), so under 0.2.x every
skillopt_* tool call failed with "shell.run is not a function".

Feature-detect the seam instead of switching it: execute() wins when present,
run() stays the 0.1.x path. The test covers all three shapes (0.2.x handle,
0.1.x direct, transitional both-present) and dies on the pre-fix body with
the same production error.
@Yif-Yang
Yifan Yang (Yif-Yang) merged commit f550b9f into microsoft:main Oct 23, 2026
7 checks passed
@Yif-Yang

Copy link
Copy Markdown
Contributor

Thanks for the contribution. Merged as f550b9fcb583 after successful exact-head official CI and exact-tree/author-credit readback. The resulting main passes 1838 tests with 13 skips and strict docs.

Reviewed db54b927083f6e2226c5d67205b0a2286f762697 against the published dsh-shell contracts. This repairs the invocation gap left after #311: the 0.2 interface uses execute(spec).result(), while the legacy interface uses run(spec). The implementation also prefers execute when both exist.

Independent checks exercised all seven registered tools through the actual packed and installed plugin with both supported dependency generations. They cover resolved specs, literal shell arguments, output/truncation, nonzero exits, timeout, cancellation and preparation/result errors. All 138 checks passed per generation, including on the pre-merge review integration; the corresponding execute-only baseline fails. The current-main merge preserves #311's package support and passes the full regression suite. The final published main also passes a separate 109 registered-plugin checks per supported dependency generation. This is a concrete invocation compatibility fix. It does not address the separate DSH transcript-format work in #266/#225.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants