@deepseek-ai/dsh-tool-pwsh 0.2.1-alpha.1 → 0.2.1-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.i18n.yaml CHANGED
@@ -15,8 +15,8 @@
15
15
  en: d152484eb41ac6b4
16
16
  zh: 09388d293f9be9cb
17
17
  /deepseek-ai-dsh-tool-pwsh/use-this-package:
18
- en: a49b7ebce02d3c94
19
- zh: 99c6cfbb42dbb6aa
18
+ en: 873b8b99a2018ed7
19
+ zh: c4481ffd20168071
20
20
  /deepseek-ai-dsh-tool-pwsh/use-this-package/when-to-choose-it:
21
21
  en: d274c33ae8a646f8
22
22
  zh: 99e3e64e61735e50
@@ -36,8 +36,8 @@
36
36
  en: c1a9cc1201ed3a89
37
37
  zh: a43a659d350e9d4a
38
38
  /deepseek-ai-dsh-tool-pwsh/understand-the-implementation:
39
- en: 8d94a7381bca3fa9
40
- zh: f4bd2866fff6fa8d
39
+ en: 165313776a851b51
40
+ zh: 828f48807036aff0
41
41
  /deepseek-ai-dsh-tool-pwsh/understand-the-implementation/design-philosophy:
42
42
  en: 86a8ad5203f7e239
43
43
  zh: 056c0d6d6ca5f36a
@@ -117,8 +117,8 @@
117
117
  en: f5d5f97a153f7c70
118
118
  zh: 31911b4f3506d6c8
119
119
  /deepseek-ai-dsh-tool-pwsh/known-limitations-and-deferred-work:
120
- en: 7d165285266e6639
121
- zh: 541e6daf5169e24c
120
+ en: 04c2102bcc5ebc6d
121
+ zh: d13640d3fddb8e9c
122
122
  /deepseek-ai-dsh-tool-pwsh/known-limitations-and-deferred-work/dev-note:
123
123
  en: e67f633dc6fe7773
124
124
  zh: 3bb0c20c93f16f98
package/README.md CHANGED
@@ -25,6 +25,8 @@ English | [中文](README.zh.md)
25
25
  <a id="use-this-package"></a>
26
26
  ## Use this package
27
27
 
28
+ New commands start in the Session's current working directory. Relative `workdir` values resolve from that directory; per-call overrides and background jobs do not change it.
29
+
28
30
  Load this plugin in any composition where the agent should run PowerShell commands — typically a Windows composition whose `ctx.shell` is backed by a PowerShell executor. It registers the `pwsh` tool once the executor provider and the `dsh-shell-env` registry are mounted.
29
31
 
30
32
  ### When to choose it
@@ -74,6 +76,8 @@ A composition with no PowerShell executor never activates the tool, and the inje
74
76
  <details>
75
77
  <summary>Implementation internals — click to expand</summary>
76
78
 
79
+ Structured foreground and background results expose `cwd`, the executor-resolved launch directory. Persisted presentation metadata carries the same value so settled cards retain the directory used by that call. A command’s own `cd` does not change this launch value; native result text remains stdout, stderr, and status markers.
80
+
77
81
  This section explains the design decisions behind the tool and points at the code that realizes them; the observable behavior is fully covered in [Use this package](#use-this-package).
78
82
 
79
83
  ### Design philosophy
@@ -204,7 +208,6 @@ These limits define when the tool is a poor fit or needs special care. They are
204
208
  - **Language mode and named-pipe capture under the Windows sandbox** — under the [Windows ACL sandbox](../../sandbox/sandbox-windows-acl/README.md), read-only pwsh starts in ConstrainedLanguage because its temp write denial makes PowerShell's AppLocker probe fail closed: `Add-Type`, non-core .NET statics (`[System.IO.*]::`, `[math]::`), COM objects, and reflection fail with "only core types" errors, and the mode cannot be lifted from inside. Workspace-write's private temp lets the probe complete, so it stays in FullLanguage unless host policy says otherwise. Both confined modes deny named-pipe opens, so a piped-stdio spawn inside a confined command fails with EPERM. The tool description teaches both contracts to the model; the backend README owns the full limitations.
205
209
  - **No persistent shell** — every call starts a fresh `pwsh -Command`; the persistent-shell counterpart is [`@deepseek-ai/dsh-tool-pwsh-persistent`](../tool-pwsh-persistent/README.md), which keeps one owner-scoped pwsh alive across calls.
206
210
  - **PowerShell-dialect contract** — the model must write PowerShell (native paths, `$env:` variables), not bash; there is no dialect translation.
207
- - **Session-cwd identity is not canonicalized** — the workdir base is the session header cwd as-is, unlike the bash tool's sandbox-root-canonicalized identity. Under a confining executor the policy's workspace root IS canonicalized (by the shared policy service), so the workdir and the confinement root can diverge when the raw session cwd differs from its canonical form — a parity gap deferred to the shared shell-tool base extraction.
208
211
 
209
212
  <a id="dev-note"></a>
210
213
  ### Dev Note
package/README.zh.md CHANGED
@@ -25,6 +25,8 @@ kind: "package-reference"
25
25
  <a id="use-this-package"></a>
26
26
  ## 使用本包
27
27
 
28
+ 新命令从 Session 的当前工作目录启动。相对 `workdir` 从该目录解析;单次调用的覆盖值与后台任务不会改变该目录。
29
+
28
30
  在 agent 需要运行 PowerShell 命令的任何组合中加载本插件——通常是 `ctx.shell` 由 PowerShell 执行器支撑的 Windows 组合。一旦挂载执行器提供方与 `dsh-shell-env` 注册表,它就注册 `pwsh` 工具。
29
31
 
30
32
  ### 何时选择
@@ -74,6 +76,8 @@ kind: "package-reference"
74
76
  <details>
75
77
  <summary>实现细节——点击展开</summary>
76
78
 
79
+ 前台与后台的结构化结果均包含 `cwd`,即执行器解析后的启动目录。持久化展示元数据携带同一值,使已结束的调用卡片保留该次调用使用的目录。命令内部的 `cd` 不会改变这个启动值;原生结果文本仍由 stdout、stderr 与状态标记组成。
80
+
77
81
  本节解释工具背后的设计决策,并指出实现它们的代码位置;可观察行为已在[使用本包](#use-this-package)中完整说明。
78
82
 
79
83
  ### 设计理念
@@ -204,7 +208,6 @@ Non-zero exits are reported as `[exit code: N]` markers; investigate failures be
204
208
  - **Windows 沙箱下的语言模式与命名管道捕获**——在 [Windows ACL 沙箱](../../sandbox/sandbox-windows-acl/README.zh.md)下,只读 pwsh 以 ConstrainedLanguage 启动,因为其临时目录写入被拒绝,导致 PowerShell 的 AppLocker 探测失败并按拒绝处理:`Add-Type`、非核心 .NET 静态调用(`[System.IO.*]::`、`[math]::`)、COM 对象与反射会以 "only core types" 错误失败,且该模式无法从内部解除。workspace-write 的私有临时目录让探测完成,因此除非宿主策略另有规定,它保持 FullLanguage。两种受限模式都拒绝命名管道打开,因此受限命令内部的管道 stdio spawn 会以 EPERM 失败。工具描述把两条约定都教给模型;完整限制以后端 README 为准。
205
209
  - **没有持久 shell**——每次调用都启动全新的 `pwsh -Command`;持久 shell 对应物是 [`@deepseek-ai/dsh-tool-pwsh-persistent`](../tool-pwsh-persistent/README.zh.md),它跨调用保持一个按所有者隔离的 pwsh 存活。
206
210
  - **PowerShell 方言约定**——模型必须编写 PowerShell(原生路径、`$env:` 变量),而不是 bash;没有方言翻译。
207
- - **会话 cwd 身份未规范化**——workdir 基准就是会话头部 cwd 原样,不像 bash 工具那样以沙箱根规范化身份为准。在约束执行器下,策略的 workspace root 确实被规范化(由共享策略服务完成),因此当原始会话 cwd 与其规范形式不同时,workdir 与约束根可能分叉——这是推迟到共享 shell 工具基座抽取的对齐差距。
208
211
 
209
212
  <a id="dev-note"></a>
210
213
  ### 开发备注
package/lib/index.js CHANGED
@@ -238,7 +238,8 @@ const inject = [
238
238
  "tools",
239
239
  "shell",
240
240
  "systemPrompt",
241
- "shellEnv"
241
+ "shellEnv",
242
+ "workingDirectory"
242
243
  ];
243
244
  /** Runtime configuration schema for the pwsh tool plugin. */
244
245
  const Config = z.object({
@@ -256,24 +257,21 @@ function pwshDescription(windowsSandbox) {
256
257
  if (!windowsSandbox) return base;
257
258
  return "Execute a PowerShell command (`pwsh -Command`) and return its stdout/stderr. Each call runs in a fresh pwsh process; pass `workdir` instead of using `cd`. Paths use native Windows form (`C:\\...`); read environment variables with `$env:NAME`. Managed `$env:DSH_*` variables expose current harness environment facts. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. On Windows a force-killed command settles as `[exit code: 1]` without a signal marker — treat it as an interruption, not a command failure. Provide `description` before `command` in the arguments. Before any delete or move, verify that the resolved absolute target path is the intended one; never run it against a computed path you have not checked. Do not assign to automatic variables such as `$HOME`; variable names are case-insensitive, so `$home` is the same read-only variable. Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under <mode> mode]`, a policy denial: do not retry another way. Under the Windows sandbox, read-only pwsh runs in PowerShell ConstrainedLanguage mode, while workspace-write stays in FullLanguage unless host policy says otherwise. In read-only, prefer cmdlets and core types (`[string]`, `[datetime]`, `[regex]`, `[guid]`); .NET static calls (`[System.IO.*]::`, `[math]::`), `Add-Type`, COM objects, and reflection fail with \"only core types\" errors. `-f` formatting, property access, and core cmdlets work. In both confined modes, programs cannot open named pipes, so a command that captures another program's output through piped stdio (Node.js `child_process.spawn`/`exec` with the default `stdio: 'pipe'`) fails with EPERM, while `stdio: 'inherit'` and `stdio: 'ignore'` spawns work and PowerShell's own pipelines are unaffected. That EPERM is the documented boundary: do not retry the command another way — escalate the exact command once or restructure it to avoid capturing output.";
258
259
  }
259
- /**
260
- * Resolve an explicit workdir first, making a relative one session-workspace-relative;
261
- * otherwise use the session header cwd and leave executor defaulting as the fallback.
262
- */
263
- function resolveWorkdir(modelWorkdir, exec) {
264
- const headerCwd = exec.agent?.session.header.cwd;
265
- if (modelWorkdir === void 0) return headerCwd;
266
- if (headerCwd !== void 0 && !isAbsolute(modelWorkdir)) return resolve(headerCwd, modelWorkdir);
260
+ /** Resolve a relative per-call override against the Session's current directory. */
261
+ function resolveWorkdir(modelWorkdir, sessionCwd) {
262
+ if (modelWorkdir === void 0) return sessionCwd;
263
+ if (sessionCwd !== void 0 && !isAbsolute(modelWorkdir)) return resolve(sessionCwd, modelWorkdir);
267
264
  return modelWorkdir;
268
265
  }
269
266
  /** Detach the executor DTO from readonly Service Definition types into plain JSON data. */
270
- function canonicalPwshResult(result) {
267
+ function canonicalPwshResult(result, cwd) {
271
268
  const output = (stream) => ({
272
269
  text: stream.text,
273
270
  truncated: stream.truncated,
274
271
  ...stream.spillPath !== void 0 ? { spillPath: stream.spillPath } : {}
275
272
  });
276
273
  return {
274
+ cwd,
277
275
  kind: "foreground",
278
276
  exitCode: result.exitCode,
279
277
  signal: result.signal,
@@ -306,6 +304,10 @@ const BACKGROUND_OUTPUT_PROPERTIES = {
306
304
  jobId: {
307
305
  type: "string",
308
306
  required: true
307
+ },
308
+ cwd: {
309
+ type: "string",
310
+ required: true
309
311
  }
310
312
  };
311
313
  function apply(ctx, config = {}) {
@@ -366,6 +368,10 @@ function apply(ctx, config = {}) {
366
368
  id: registry.start({
367
369
  kind: "pwsh",
368
370
  label: args.command,
371
+ toolCall: {
372
+ callId: exec.callId,
373
+ rootCallId: exec.rootCallId
374
+ },
369
375
  ...exec.agent ? { owner: exec.agent.id } : {},
370
376
  output: processSources(() => proc),
371
377
  run: () => {
@@ -416,6 +422,7 @@ function apply(ctx, config = {}) {
416
422
  await stop("timed out during preparation");
417
423
  return {
418
424
  kind: "foreground",
425
+ cwd: spec.workdir,
419
426
  exitCode: null,
420
427
  signal: null,
421
428
  timedOut: true,
@@ -439,6 +446,7 @@ function apply(ctx, config = {}) {
439
446
  const read = registry.read(attached.id, owner);
440
447
  return {
441
448
  kind: "promoted",
449
+ cwd: spec.workdir,
442
450
  jobId: attached.id,
443
451
  timeoutMs,
444
452
  output: renderPwshJobRead(ringDelta(read.chunks), read.lossy, read.job.output.spillPaths ?? [], attached.process()?.sandbox, escalationModes)
@@ -450,7 +458,7 @@ function apply(ctx, config = {}) {
450
458
  const result = await process.result();
451
459
  const stopped = attached.stopped();
452
460
  return {
453
- ...canonicalPwshResult(result),
461
+ ...canonicalPwshResult(result, spec.workdir),
454
462
  ...stopped !== void 0 ? { stopped } : {}
455
463
  };
456
464
  };
@@ -508,6 +516,10 @@ function apply(ctx, config = {}) {
508
516
  required: true,
509
517
  const: "promoted"
510
518
  },
519
+ cwd: {
520
+ type: "string",
521
+ required: true
522
+ },
511
523
  jobId: {
512
524
  type: "string",
513
525
  required: true
@@ -531,6 +543,10 @@ function apply(ctx, config = {}) {
531
543
  required: true,
532
544
  const: "foreground"
533
545
  },
546
+ cwd: {
547
+ type: "string",
548
+ required: true
549
+ },
534
550
  exitCode: {
535
551
  required: true,
536
552
  oneOf: [{ type: "integer" }, { type: "null" }]
@@ -606,7 +622,8 @@ function apply(ctx, config = {}) {
606
622
  render: (_args, value) => [{
607
623
  type: "text",
608
624
  text: value.kind === "background" ? `started background job ${value.jobId}` : value.kind === "promoted" ? renderPwshPromoted(value) : renderPwshResult(value, escalationModes)
609
- }]
625
+ }],
626
+ presentationMeta: (_args, value) => ({ cwd: value.cwd })
610
627
  },
611
628
  async execute(args, exec) {
612
629
  validatePwshArgs(args);
@@ -616,7 +633,8 @@ function apply(ctx, config = {}) {
616
633
  ...standingPolicy,
617
634
  mode: approvedMode
618
635
  };
619
- const workdir = resolveWorkdir(args.workdir, exec);
636
+ const cwd = exec.agent === void 0 ? void 0 : await ctx.workingDirectory.ensure(exec.agent, exec.signal);
637
+ const workdir = resolveWorkdir(args.workdir, cwd);
620
638
  const request = {
621
639
  command: args.command,
622
640
  ...workdir !== void 0 ? { workdir } : {},
@@ -628,12 +646,14 @@ function apply(ctx, config = {}) {
628
646
  if (!backgroundEnabled) throw new Error("run_in_background is disabled for this deployment (enableRunInBackground: false)");
629
647
  if (jobs === void 0) throw new Error("background jobs unavailable: load @deepseek-ai/dsh-jobs and @deepseek-ai/dsh-tool-jobs");
630
648
  if (exec.signal.aborted) throw toolAborted();
649
+ const spec = ctx.shell.resolve({
650
+ ...request,
651
+ onExpiry: "none"
652
+ });
631
653
  return {
632
654
  kind: "background",
633
- jobId: startJob(jobs, args, exec, ctx.shell.resolve({
634
- ...request,
635
- onExpiry: "none"
636
- })).id
655
+ jobId: startJob(jobs, args, exec, spec).id,
656
+ cwd: spec.workdir
637
657
  };
638
658
  }
639
659
  if (jobs !== void 0 && promote) {
@@ -649,12 +669,13 @@ function apply(ctx, config = {}) {
649
669
  }
650
670
  if (attached !== void 0) return waitOnJob(jobs, attached, exec, spec);
651
671
  }
652
- const result = await (await ctx.shell.execute(ctx.shell.resolve({
672
+ const spec = ctx.shell.resolve({
653
673
  ...request,
654
674
  signal: exec.signal
655
- }))).result();
675
+ });
676
+ const result = await (await ctx.shell.execute(spec)).result();
656
677
  if (result.aborted) throw toolAborted();
657
- return canonicalPwshResult(result);
678
+ return canonicalPwshResult(result, spec.workdir);
658
679
  },
659
680
  presentCall: (args) => {
660
681
  if (args.run_in_background === true) return {
package/locale/en.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "meta": {
3
+ "title": "tool-pwsh"
4
+ }
5
+ }
package/locale/zh.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "PowerShell 命令工具",
4
+ "description": "让 AI 执行 PowerShell 命令。"
5
+ }
6
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-tool-pwsh",
3
3
  "description": "Model-facing pwsh tool over the bash executor seam",
4
- "version": "0.2.1-alpha.1",
4
+ "version": "0.2.1-alpha.3",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -19,49 +19,54 @@
19
19
  "default": "./lib/index.js"
20
20
  },
21
21
  "./src/*": "./src/*",
22
- "./package.json": "./package.json"
22
+ "./package.json": "./package.json",
23
+ "./locale/*.json": "./locale/*.json"
23
24
  },
24
25
  "files": [
26
+ "locale/*.json",
25
27
  "lib/index.js",
26
28
  "lib/types/**/*.d.ts"
27
29
  ],
28
30
  "license": "MIT",
29
31
  "peerDependencies": {
30
- "@deepseek-ai/cordis": "~4.0.5-alpha.1",
31
- "@deepseek-ai/dsh-agent": "0.2.1-alpha.1",
32
- "@deepseek-ai/dsh-jobs": "0.2.1-alpha.1",
33
- "@deepseek-ai/dsh-llm": "0.2.1-alpha.1",
34
- "@deepseek-ai/dsh-sandbox": "0.2.1-alpha.1",
35
- "@deepseek-ai/dsh-sandbox-policy": "0.2.1-alpha.1",
36
- "@deepseek-ai/dsh-shell": "0.2.1-alpha.1",
37
- "@deepseek-ai/dsh-shell-env": "0.2.1-alpha.1",
38
- "@deepseek-ai/dsh-system-prompt": "0.2.1-alpha.1",
39
- "@deepseek-ai/dsh-tools": "0.2.1-alpha.1",
40
- "@deepseek-ai/dsh-user-approval": "0.2.1-alpha.1"
32
+ "@deepseek-ai/cordis": "~4.0.5-alpha.2",
33
+ "@deepseek-ai/dsh-agent": "0.2.1-alpha.3",
34
+ "@deepseek-ai/dsh-jobs": "0.2.1-alpha.3",
35
+ "@deepseek-ai/dsh-llm": "0.2.1-alpha.3",
36
+ "@deepseek-ai/dsh-sandbox": "0.2.1-alpha.3",
37
+ "@deepseek-ai/dsh-sandbox-policy": "0.2.1-alpha.3",
38
+ "@deepseek-ai/dsh-shell": "0.2.1-alpha.3",
39
+ "@deepseek-ai/dsh-shell-env": "0.2.1-alpha.3",
40
+ "@deepseek-ai/dsh-system-prompt": "0.2.1-alpha.3",
41
+ "@deepseek-ai/dsh-tools": "0.2.1-alpha.3",
42
+ "@deepseek-ai/dsh-user-approval": "0.2.1-alpha.3",
43
+ "@deepseek-ai/dsh-working-directory": "0.2.1-alpha.3"
41
44
  },
42
45
  "dependencies": {
43
- "@deepseek-ai/schemastery": "~3.18.5-alpha.1"
46
+ "@deepseek-ai/schemastery": "~3.18.5-alpha.2"
44
47
  },
45
48
  "devDependencies": {
46
- "@deepseek-ai/cordis": "~4.0.5-alpha.1",
47
- "@deepseek-ai/dsh-agent": "0.2.1-alpha.1",
48
- "@deepseek-ai/dsh-agent-loop": "0.2.1-alpha.1",
49
- "@deepseek-ai/dsh-agent-loop-testkit": "0.2.1-alpha.1",
50
- "@deepseek-ai/dsh-app-boot": "0.2.1-alpha.1",
51
- "@deepseek-ai/dsh-jobs-local": "0.2.1-alpha.1",
52
- "@deepseek-ai/dsh-llm": "0.2.1-alpha.1",
53
- "@deepseek-ai/dsh-jobs": "0.2.1-alpha.1",
54
- "@deepseek-ai/dsh-loader-smoke": "0.2.1-alpha.1",
55
- "@deepseek-ai/dsh-pwsh-local": "0.2.1-alpha.1",
56
- "@deepseek-ai/dsh-sandbox": "0.2.1-alpha.1",
57
- "@deepseek-ai/dsh-sandbox-policy": "0.2.1-alpha.1",
58
- "@deepseek-ai/dsh-session-projection": "0.2.1-alpha.1",
59
- "@deepseek-ai/dsh-shell": "0.2.1-alpha.1",
60
- "@deepseek-ai/dsh-shell-env": "0.2.1-alpha.1",
61
- "@deepseek-ai/dsh-subprocess-local": "0.2.1-alpha.1",
62
- "@deepseek-ai/dsh-system-prompt": "0.2.1-alpha.1",
63
- "@deepseek-ai/dsh-tools": "0.2.1-alpha.1",
64
- "@deepseek-ai/dsh-tool-jobs": "0.2.1-alpha.1",
65
- "@deepseek-ai/dsh-user-approval": "0.2.1-alpha.1"
49
+ "@deepseek-ai/cordis": "~4.0.5-alpha.2",
50
+ "@deepseek-ai/dsh-agent": "0.2.1-alpha.3",
51
+ "@deepseek-ai/dsh-agent-loop": "0.2.1-alpha.3",
52
+ "@deepseek-ai/dsh-agent-loop-testkit": "0.2.1-alpha.3",
53
+ "@deepseek-ai/dsh-app-boot": "0.2.1-alpha.3",
54
+ "@deepseek-ai/dsh-jobs": "0.2.1-alpha.3",
55
+ "@deepseek-ai/dsh-jobs-local": "0.2.1-alpha.3",
56
+ "@deepseek-ai/dsh-llm": "0.2.1-alpha.3",
57
+ "@deepseek-ai/dsh-loader-smoke": "0.2.1-alpha.3",
58
+ "@deepseek-ai/dsh-pwsh-local": "0.2.1-alpha.3",
59
+ "@deepseek-ai/dsh-sandbox": "0.2.1-alpha.3",
60
+ "@deepseek-ai/dsh-sandbox-policy": "0.2.1-alpha.3",
61
+ "@deepseek-ai/dsh-session-projection": "0.2.1-alpha.3",
62
+ "@deepseek-ai/dsh-shell": "0.2.1-alpha.3",
63
+ "@deepseek-ai/dsh-shell-env": "0.2.1-alpha.3",
64
+ "@deepseek-ai/dsh-subprocess-local": "0.2.1-alpha.3",
65
+ "@deepseek-ai/dsh-system-prompt": "0.2.1-alpha.3",
66
+ "@deepseek-ai/dsh-tool-jobs": "0.2.1-alpha.3",
67
+ "@deepseek-ai/dsh-tools": "0.2.1-alpha.3",
68
+ "@deepseek-ai/dsh-user-approval": "0.2.1-alpha.3",
69
+ "@deepseek-ai/dsh-working-directory": "0.2.1-alpha.3",
70
+ "@deepseek-ai/dsh-fs-local": "0.2.1-alpha.3"
66
71
  }
67
72
  }