dsh-ops 0.2.3 → 0.2.4

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/CHANGELOG.md CHANGED
@@ -5,6 +5,15 @@ All notable changes to this package are recorded here.
5
5
  The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
6
6
  this package adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.2.4] — Independently toggled components
9
+
10
+ - Split the market bundle into shell (on), file (on), and background (off) rows with exported per-component titles, descriptions, icons and locales.
11
+ - Publish five tools by default, nine with background enabled. Stop publishing duplicate foreground ops_run. Preserve the bash/PowerShell 7 routing and the object-root bash schema.
12
+ - Own separate prompt sections for each component; visibility and shutdown withdraw only the matching guidance/tools. Do not append to AGENTS.md.
13
+ - Share one ref-counted plugin-owned FastCtx connection for file/background. Drain in-flight calls and attempt to kill only remembered owned jobs when background stops; report cleanup failures.
14
+ - Replace the unconditional bundle pwshPath override with a runtime-only official config lifecycle overlay; shell OFF recomputes the executor's current owning raw config without persisting changes.
15
+ - Document release-age version selection and the limits of token savings. No host/vendor implementation, old regression suite, or Actions changes.
16
+
8
17
  ## [0.2.3] — Strict-argument guidance and display metadata
9
18
 
10
19
  - Add one routing-section reminder: `ops_* arguments are strict: pass only declared fields.` Keep FastCtx's portable schema subset and runtime guards unchanged.
package/PROVENANCE.md CHANGED
@@ -3,6 +3,30 @@
3
3
  What this repository is, where its parts came from, what was verified, and what
4
4
  was not.
5
5
 
6
+ ## 0.2.4 Component lifecycle
7
+
8
+ Three exported subpath plugins replace the single bundle row: shell/file on by
9
+ default, background off. Each owns its registrations and system-prompt section;
10
+ file and background lease one plugin-owned MCP connection scoped to the Cordis root.
11
+ No global host registry or service methods are replaced. Foreground ops_run is no
12
+ longer published. Background shutdown drains tracked calls, attempts to kill only
13
+ remembered job IDs, then releases its connection lease; disconnect can still lose
14
+ ownership, so cleanup is not guaranteed for unknown durable jobs.
15
+
16
+ The unconditional pwsh-sandbox patch is removed. Shell mounts an internal/config
17
+ waterfall hook targeted at the official pwsh-sandbox row, then uses public
18
+ Fiber.update(rawConfig, true) to apply/revoke the temporary path via the official
19
+ config lifecycle (noSave). Removing the hook recomputes from the latest owning raw
20
+ config, not a stale saved copy. It does not mutate a service/config reference, write
21
+ profile files, change OS defaults or cancel existing host shell processes. An
22
+ isolated real Cordis/ToolRuntime inspection verified independent tool/prompt
23
+ withdrawal, permission-gated commands, shared-client leases, and config restoration
24
+ with a stand-in executor. Real desktop marketplace toggles and the real sandbox
25
+ executor require user validation; no model requests/old suites were run.
26
+
27
+ Default schema bytes drop from 10,700 (0.2.3 ten tools) to 7,685 (five tools).
28
+ This is a byte metric, not a tokenizer or proof of lower whole-task usage.
29
+
6
30
  ## 0.2.3 Prompt and display update
7
31
 
8
32
  Add one model-facing sentence in `lib/policy.js`: `ops_* arguments are strict:
package/README.en.md CHANGED
@@ -41,10 +41,12 @@ dsh plugin --profile dsh-tui add dsh-ops
41
41
  Update by repeating the npx install command, or official `add dsh-ops@latest`.
42
42
  Reload/restart as the application requests; replacing loaded code may require restart.
43
43
 
44
- The current version is `0.2.3`: it reminds the model to pass only declared `ops_*`
45
- fields and updates the display title, descriptions, and icon. It retains the 0.2.2
46
- bash parameter schema fix. Runtime dependencies remain pinned to `0.2.1`; their
47
- original release record is in [docs/release-0.2.1.md](docs/release-0.2.1.md).
44
+ Version `0.2.4` adds three independently toggled components and retains the bash
45
+ object-root schema fix and custom icon. Runtime dependencies remain at `0.2.1` and
46
+ install automatically. Desktop pnpm's 24-hour release-age policy may show new metadata
47
+ but select an older package for a bare name: specify `dsh-ops@0.2.4` from the official
48
+ npm registry, confirm the installed version, then fully quit/restart. See the original
49
+ [payload release record](docs/release-0.2.1.md).
48
50
 
49
51
  ```console
50
52
  npx --yes dsh-ops@latest status --profile desktop
@@ -56,11 +58,32 @@ also remove profile dependency references. Runtime files are package dependencie
56
58
  not a new shared provisioned directory. System shells, other profiles and package
57
59
  manager caches are never manually deleted. Clean legacy provisioned files separately
58
60
  with `npx --yes dsh-ops@latest uninstall --yes`; `~/.fastctx/` is kept unless
59
- `--purge-fastctx` is explicit. Unload/downgrade does not prove durable jobs stopped.
61
+ `--purge-fastctx` is explicit. Background OFF drains in-flight calls and attempts to
62
+ kill only jobs remembered by this component on its current connection; failures warn.
63
+ Lost ownership after disconnect and permission downgrade do not guarantee termination.
64
+
65
+ ## Components and prompts
66
+
67
+ | Component | Default | Capability |
68
+ | --- | --- | --- |
69
+ | dsh-ops-bash & powershell 7 | On | Prefer ops_bash for general commands; bundled PowerShell 7 for Windows-native work |
70
+ | dsh-ops-file | On | Rust batch reads, search, path discovery and replacement |
71
+ | dsh-ops-background | Off | Scientific simulations, model training and other long-running tasks |
72
+
73
+ Each row owns its tools and runtime prompt section; OFF withdraws both. Nothing is
74
+ appended to AGENTS.md. Shell ON adds a runtime-only configuration overlay for the
75
+ stock pwsh-sandbox path; OFF recomputes its latest owning raw configuration via the
76
+ official lifecycle, without writing profile files. It does not change the OS default
77
+ terminal or cancel already-running host commands. File/background share one owned
78
+ FastCtx connection; turning one off does not stop the other. Default tool count is
79
+ five; duplicate ops_run is no longer published. Reduced fixed schema cost is not a
80
+ promise of lower task-total tokens. Minimal's complete persona excludes extra sections;
81
+ PTC invokes the tools through its generated SDK with unchanged permission checks.
60
82
 
61
83
  ## Configuration and tools
62
84
 
63
- Edit the dsh-ops row's `config` in the DSH configuration editor. Unknown keys fail.
85
+ Edit the corresponding dsh-ops/shell, dsh-ops/file or dsh-ops/background row's config.
86
+ Unknown keys fail; normal component enablement is available in the marketplace.
64
87
 
65
88
  ```yaml
66
89
  config:
@@ -75,8 +98,8 @@ config:
75
98
  # bashPath: 'C:\tools\bash.exe'
76
99
  ```
77
100
 
78
- `enableShellTools` controls the FastCtx command/job group; `publishBashTool` controls
79
- bash. Both additionally require authoritative session `danger-full-access`; bash
101
+ `enableShellTools` controls the background component's four run/job tools; file never
102
+ publishes commands. `publishBashTool` controls bash. Both additionally require authoritative session `danger-full-access`; bash
80
103
  also requires the host subprocess service. `promptPolicy` controls compact routing;
81
104
  `extraGuidance` appends text. RPC wait timeout does not prove server termination.
82
105
  `required` fails activation on unavailable runtime. Explicit binary/bash paths are
@@ -90,7 +113,6 @@ authoritative. `shellPolicy: deny-host-shell` blocks `deniedHostTools` (default
90
113
  | ops_glob | Multiple path patterns and exclusions | No command gate¹ |
91
114
  | ops_replace | Mechanical cross-file replacement; host edit for precise edits | No command gate¹ |
92
115
  | ops_bash | Preferred general bash executor | Required |
93
- | ops_run | Bounded bash result | Required |
94
116
  | ops_run_background | Start background jobs | Required |
95
117
  | ops_job_output / ops_job_list / ops_job_kill | Operate on this session's owned jobs | Required |
96
118
  | Host pwsh | Bundled PowerShell 7 Windows-native operations | Host policy |
package/README.md CHANGED
@@ -40,7 +40,7 @@ dsh plugin --profile dsh-tui add dsh-ops
40
40
 
41
41
  **更新**:重新运行相同的 `npx --yes dsh-ops@latest install --profile ...`,或用官方 CLI `add dsh-ops@latest`。按应用提示重载;替换已加载代码时重启应用。
42
42
 
43
- 当前 npm 版本为 `0.2.3`:提示词提醒 `ops_*` 只传声明字段,标题统一为 `dsh-ops`,并更新中英文介绍和图标;保留 `0.2.2` 的 bash schema 修复,三个运行包仍使用 `0.2.1`。原始载荷发布记录见 [docs/release-0.2.1.md](docs/release-0.2.1.md)。
43
+ 当前版本 `0.2.4` 支持三个独立组件;bash 参数 schema 修复和自定义图标保留。三个二进制依赖继续使用 `0.2.1`,它们会自动安装,不影响主插件版本。新发布版本可能受桌面 pnpm 的 24 小时安全冷却策略影响:如果市场分析版号与安装结果不一致,指定 `dsh-ops@0.2.4` 并选择 npm 官方源,确认实际版本后完全退出并重启。原始载荷记录见 [docs/release-0.2.1.md](docs/release-0.2.1.md)。
44
44
 
45
45
  **卸载与查看状态**:
46
46
 
@@ -51,14 +51,29 @@ npx --yes dsh-ops@latest uninstall --profile desktop
51
51
 
52
52
  把 `desktop` 换成 `web` 或 `tui` 即可。也可在市场卸载,或执行官方 `dsh plugin --profile ... remove dsh-ops`。二进制随 profile 的插件依赖管理;卸载移除依赖引用,不误删系统 shell、其它 profile 的副本或包管理器共享缓存。
53
53
 
54
- 旧版显式 provision 的 `<DSH_HOME>/dsh-ops/` 文件需单独清理:`npx --yes dsh-ops@latest uninstall --yes`。默认不删 `~/.fastctx/` 持久状态;需要时显式加 `--purge-fastctx`。卸载/降权不保证已经启动的持久后台任务终止,先处理自己的任务。
54
+ 旧版显式 provision 的 `<DSH_HOME>/dsh-ops/` 文件需单独清理:`npx --yes dsh-ops@latest uninstall --yes`。默认不删 `~/.fastctx/` 持久状态;需要时显式加 `--purge-fastctx`。关闭后台组件会等待已发出的调用,然后尝试终止该组件当前连接记住的自有任务;失败会告警。断线丢失归属的旧任务及单纯降权不保证终止,先处理自己的任务。
55
+
56
+ ## 组件开关与提示词
57
+
58
+ 官方市场显示三个组件,分别启停,首次安装默认开启前两个:
59
+
60
+ | 组件 | 介绍 | 默认 | 发布的工具 |
61
+ | --- | --- | --- | --- |
62
+ | `dsh-ops-bash & powershell 7` | 将默认终端改为bash,以及bash覆盖不到时提供powershell7 | 开 | `ops_bash`;宿主 `pwsh` 使用随包 PowerShell 7 |
63
+ | `dsh-ops-file` | 更快、更高性能、输出更精简更省token的rust文件检索 | 开 | 四个文件工具 |
64
+ | `dsh-ops-background` | 为科研仿真、模型训练及其他长进程后台任务提供托管 | 关 | 启动、输出、列出、终止后台任务 |
65
+
66
+ “默认终端改为 bash”指模型提示优先使用 `ops_bash`,**不修改系统默认终端、PATH 或宿主原有 shell 工具名称**。Shell 组件开时,通过官方配置生命周期临时指定 `pwsh-sandbox` 的 PowerShell 7 路径;关时按执行器最新原配置恢复,不写 profile 配置。执行器配置切换不代表取消已在运行的宿主命令。
67
+
68
+ 提示词注册为独立运行时段,**不写 AGENTS.md**:Shell 关就撤销 bash/pwsh 路由;文件关就撤销检索说明;后台关就不发布后台工具及自有 job 指导。文件与后台共享一个插件拥有的 FastCtx 连接,关闭其中一个不关闭另一个。默认仅新增五个工具,不发布重复前台 `ops_run`。这些改变减少固定声明成本,**不保证任意任务总 token 降低**。极简预设会覆盖附加 system sections;PTC 模式经 SDK 调用底层工具,权限门不变。
55
69
 
56
70
  ## 配置方式与工具列表
57
71
 
58
- 在 DSH 的配置编辑器中找到 `dsh-ops` 行,调整 `config` 后保存;未知配置键会点名报错。主要默认值:
72
+ 在 DSH 的配置编辑器中找到对应的 `dsh-ops/shell`、`dsh-ops/file` 或 `dsh-ops/background` 行,调整 `config` 后保存;未知配置键会点名报错。也可直接在市场切换组件。主要配置:
59
73
 
60
74
  ```yaml
61
75
  config:
76
+ # background component only; its market row is disabled by default
62
77
  enableShellTools: true
63
78
  publishBashTool: true
64
79
  promptPolicy: true
@@ -70,9 +85,9 @@ config:
70
85
  # bashPath: 'C:\tools\bash.exe'
71
86
  ```
72
87
 
73
- - `enableShellTools`:部署开关,开启仍须会话 `danger-full-access` 才发布 FastCtx 命令/job 组。
88
+ - `enableShellTools`:后台组件的部署开关,开启仍须会话 `danger-full-access` 才发布四个后台工具;文件组件不发布命令工具。
74
89
  - `publishBashTool`:是否发布 `ops_bash`,同样要求完全权限和宿主 subprocess 服务。
75
- - `promptPolicy`:是否注入紧凑三层路由说明;可用 `extraGuidance` 追加指导。
90
+ - `promptPolicy`:是否注入该组件的运行时说明;可用 `extraGuidance` 追加指导。
76
91
  - `toolCallTimeoutMs`:FastCtx RPC 等待超时,不代表服务端工作已终止。
77
92
  - `required`:运行时不可用时是否拒绝激活。
78
93
  - `shellPolicy`:默认 `advise`;`deny-host-shell` 拒绝 `deniedHostTools` 列表中的宿主 shell(默认 `[pwsh, bash, pwsh_persistent]`)。这也会禁用第三层 pwsh,谨慎开启。
@@ -85,7 +100,6 @@ config:
85
100
  | `ops_glob` | 多模式找路径,支持排除 | 无命令权限门¹ |
86
101
  | `ops_replace` | 跨文件批量替换;精确编辑仍用宿主 edit | 无命令权限门¹ |
87
102
  | `ops_bash` | 优先的通用 bash 命令执行器 | 是 |
88
- | `ops_run` | 有界 bash 命令结果 | 是 |
89
103
  | `ops_run_background` | 启动后台任务 | 是 |
90
104
  | `ops_job_output` / `ops_job_list` / `ops_job_kill` | 查看、列出、停止当前会话启动的任务 | 是 |
91
105
  | 宿主 `pwsh` | 使用随包 PowerShell 7 的 Windows 原生操作 | 沿用宿主策略 |
package/README.zh.md CHANGED
@@ -40,7 +40,7 @@ dsh plugin --profile dsh-tui add dsh-ops
40
40
 
41
41
  **更新**:重新运行相同的 `npx --yes dsh-ops@latest install --profile ...`,或用官方 CLI `add dsh-ops@latest`。按应用提示重载;替换已加载代码时重启应用。
42
42
 
43
- 当前 npm 版本为 `0.2.3`:提示词提醒 `ops_*` 只传声明字段,标题统一为 `dsh-ops`,并更新中英文介绍和图标;保留 `0.2.2` 的 bash schema 修复,三个运行包仍使用 `0.2.1`。原始载荷发布记录见 [docs/release-0.2.1.md](docs/release-0.2.1.md)。
43
+ 当前版本 `0.2.4` 支持三个独立组件;bash 参数 schema 修复和自定义图标保留。三个二进制依赖继续使用 `0.2.1`,它们会自动安装,不影响主插件版本。新发布版本可能受桌面 pnpm 的 24 小时安全冷却策略影响:如果市场分析版号与安装结果不一致,指定 `dsh-ops@0.2.4` 并选择 npm 官方源,确认实际版本后完全退出并重启。原始载荷记录见 [docs/release-0.2.1.md](docs/release-0.2.1.md)。
44
44
 
45
45
  **卸载与查看状态**:
46
46
 
@@ -51,14 +51,29 @@ npx --yes dsh-ops@latest uninstall --profile desktop
51
51
 
52
52
  把 `desktop` 换成 `web` 或 `tui` 即可。也可在市场卸载,或执行官方 `dsh plugin --profile ... remove dsh-ops`。二进制随 profile 的插件依赖管理;卸载移除依赖引用,不误删系统 shell、其它 profile 的副本或包管理器共享缓存。
53
53
 
54
- 旧版显式 provision 的 `<DSH_HOME>/dsh-ops/` 文件需单独清理:`npx --yes dsh-ops@latest uninstall --yes`。默认不删 `~/.fastctx/` 持久状态;需要时显式加 `--purge-fastctx`。卸载/降权不保证已经启动的持久后台任务终止,先处理自己的任务。
54
+ 旧版显式 provision 的 `<DSH_HOME>/dsh-ops/` 文件需单独清理:`npx --yes dsh-ops@latest uninstall --yes`。默认不删 `~/.fastctx/` 持久状态;需要时显式加 `--purge-fastctx`。关闭后台组件会等待已发出的调用,然后尝试终止该组件当前连接记住的自有任务;失败会告警。断线丢失归属的旧任务及单纯降权不保证终止,先处理自己的任务。
55
+
56
+ ## 组件开关与提示词
57
+
58
+ 官方市场显示三个组件,分别启停,首次安装默认开启前两个:
59
+
60
+ | 组件 | 介绍 | 默认 | 发布的工具 |
61
+ | --- | --- | --- | --- |
62
+ | `dsh-ops-bash & powershell 7` | 将默认终端改为bash,以及bash覆盖不到时提供powershell7 | 开 | `ops_bash`;宿主 `pwsh` 使用随包 PowerShell 7 |
63
+ | `dsh-ops-file` | 更快、更高性能、输出更精简更省token的rust文件检索 | 开 | 四个文件工具 |
64
+ | `dsh-ops-background` | 为科研仿真、模型训练及其他长进程后台任务提供托管 | 关 | 启动、输出、列出、终止后台任务 |
65
+
66
+ “默认终端改为 bash”指模型提示优先使用 `ops_bash`,**不修改系统默认终端、PATH 或宿主原有 shell 工具名称**。Shell 组件开时,通过官方配置生命周期临时指定 `pwsh-sandbox` 的 PowerShell 7 路径;关时按执行器最新原配置恢复,不写 profile 配置。执行器配置切换不代表取消已在运行的宿主命令。
67
+
68
+ 提示词注册为独立运行时段,**不写 AGENTS.md**:Shell 关就撤销 bash/pwsh 路由;文件关就撤销检索说明;后台关就不发布后台工具及自有 job 指导。文件与后台共享一个插件拥有的 FastCtx 连接,关闭其中一个不关闭另一个。默认仅新增五个工具,不发布重复前台 `ops_run`。这些改变减少固定声明成本,**不保证任意任务总 token 降低**。极简预设会覆盖附加 system sections;PTC 模式经 SDK 调用底层工具,权限门不变。
55
69
 
56
70
  ## 配置方式与工具列表
57
71
 
58
- 在 DSH 的配置编辑器中找到 `dsh-ops` 行,调整 `config` 后保存;未知配置键会点名报错。主要默认值:
72
+ 在 DSH 的配置编辑器中找到对应的 `dsh-ops/shell`、`dsh-ops/file` 或 `dsh-ops/background` 行,调整 `config` 后保存;未知配置键会点名报错。也可直接在市场切换组件。主要配置:
59
73
 
60
74
  ```yaml
61
75
  config:
76
+ # background component only; its market row is disabled by default
62
77
  enableShellTools: true
63
78
  publishBashTool: true
64
79
  promptPolicy: true
@@ -70,9 +85,9 @@ config:
70
85
  # bashPath: 'C:\tools\bash.exe'
71
86
  ```
72
87
 
73
- - `enableShellTools`:部署开关,开启仍须会话 `danger-full-access` 才发布 FastCtx 命令/job 组。
88
+ - `enableShellTools`:后台组件的部署开关,开启仍须会话 `danger-full-access` 才发布四个后台工具;文件组件不发布命令工具。
74
89
  - `publishBashTool`:是否发布 `ops_bash`,同样要求完全权限和宿主 subprocess 服务。
75
- - `promptPolicy`:是否注入紧凑三层路由说明;可用 `extraGuidance` 追加指导。
90
+ - `promptPolicy`:是否注入该组件的运行时说明;可用 `extraGuidance` 追加指导。
76
91
  - `toolCallTimeoutMs`:FastCtx RPC 等待超时,不代表服务端工作已终止。
77
92
  - `required`:运行时不可用时是否拒绝激活。
78
93
  - `shellPolicy`:默认 `advise`;`deny-host-shell` 拒绝 `deniedHostTools` 列表中的宿主 shell(默认 `[pwsh, bash, pwsh_persistent]`)。这也会禁用第三层 pwsh,谨慎开启。
@@ -85,7 +100,6 @@ config:
85
100
  | `ops_glob` | 多模式找路径,支持排除 | 无命令权限门¹ |
86
101
  | `ops_replace` | 跨文件批量替换;精确编辑仍用宿主 edit | 无命令权限门¹ |
87
102
  | `ops_bash` | 优先的通用 bash 命令执行器 | 是 |
88
- | `ops_run` | 有界 bash 命令结果 | 是 |
89
103
  | `ops_run_background` | 启动后台任务 | 是 |
90
104
  | `ops_job_output` / `ops_job_list` / `ops_job_kill` | 查看、列出、停止当前会话启动的任务 | 是 |
91
105
  | 宿主 `pwsh` | 使用随包 PowerShell 7 的 Windows 原生操作 | 沿用宿主策略 |
package/cordis.patch.yml CHANGED
@@ -1,160 +1,25 @@
1
- # ============================================================================
2
- # dsh-ops — DSH bundle patch
3
- # ============================================================================
4
- # This is the plugin's "mount declaration". DSH applies every bundle patch in
5
- # the profile's `dsh.profile.bundles` order at startup; this file inserts the
6
- # dsh-ops row into that tree.
7
- #
8
- # Install:
9
- # dsh plugin --profile desktop add <path to this package>
10
- # dsh plugin --profile web add <path to this package>
11
- #
12
- # The row's config is validated by lib/config.js; every key below is optional
13
- # and the values shown are the defaults. An unknown key fails the plugin at load
14
- # rather than being ignored.
15
- # ----------------------------------------------------------------------------
16
-
1
+ # Three independent market components; no unconditional host executor override.
2
+ # Shell and file are enabled by default. Background is explicitly opt-in.
17
3
  - insert:
18
- - id: dsh-ops
19
- name: dsh-ops
4
+ - id: dsh-ops-shell
5
+ name: dsh-ops/shell
20
6
  config:
21
- # Absolute path to the FastCtx executable. When set, the plugin hosts
22
- # exactly this file and fails loud if it is unusable.
23
- # binaryPath: 'C:\path\to\fastctx.exe'
24
-
25
- # The FastCtx server's own name, used to attribute the server
26
- # instructions this plugin publishes as a prompt section. The model
27
- # never sees it: this plugin speaks MCP to the server it spawns itself
28
- # and registers every tool under its own ops_<tool> name.
29
- serverName: fastctx
30
-
31
- # Deployment opt-in only. Command/job tools are published per session
32
- # only when sandboxPolicy.resolve() reports danger-full-access.
33
- enableShellTools: true
34
-
35
- # Per-call deadline for one FastCtx tool call, in milliseconds.
36
- toolCallTimeoutMs: 300000
37
-
38
- # Fail plugin activation when the FastCtx server cannot start. Keep
39
- # false so a missing runtime degrades to "prompt policy only" instead of
40
- # taking the plugin down.
41
- required: false
42
-
43
- # 'advise' — inject the prompt policy only.
44
- # 'deny-host-shell' — additionally refuse the host shell tools below at
45
- # the tools/pre-execute gate.
46
- shellPolicy: advise
47
-
48
- # Host tool names refused under 'deny-host-shell'.
49
- deniedHostTools:
50
- - pwsh
51
- - bash
52
- - pwsh_persistent
53
-
54
- # Inject the repository-tooling prompt section.
55
7
  promptPolicy: true
56
-
57
- # ---- L2: the plugin's own bash -------------------------------------
58
- # Absolute path to the bash this plugin runs `ops_bash` through. When
59
- # set, the plugin runs exactly this file and refuses the rung when it is
60
- # unusable; leave it unset to search the plugin's own bundled copy
61
- # first, then PATH, then the well-known Git for Windows locations.
62
- # bashPath: 'C:\Program Files\Git\bin\bash.exe'
63
-
64
- # Layer 2: preferred executor for general commands, pipelines and scripts.
65
- # Requires a resolved bash, host subprocess and session full access.
66
8
  publishBashTool: true
67
-
68
- # Fall back to a system-installed bash when the plugin carries no copy
69
- # of its own. Set false to confine the bash rung to plugin-provided
70
- # shells. (L3 needs no such switch: whether the host's pwsh row runs
71
- # the plugin's own pwsh is decided by the executable's presence — see
72
- # the L3 override below.)
9
+ shellPolicy: advise
73
10
  allowSystemShellFallback: true
74
-
75
- # ----------------------------------------------------------------------------
76
- # L3: run the host's pwsh tool on the plugin's own PowerShell 7.
77
- #
78
- # WHY ONE LINE IS ENOUGH. `pwsh-local`'s Config owns a `pwshPath` field
79
- # ("Explicit pwsh executable", packages/shell/pwsh-local/src/index.ts:71-77),
80
- # and `pwsh-sandbox` inherits it verbatim because it declares
81
- # `type Config = LocalConfig` (packages/shell/pwsh-sandbox/src/index.ts:40).
82
- # Pointing this row at the plugin's executable therefore gets the bundled pwsh
83
- # 7 under the host's sandbox, credential scrub, and output governance with no
84
- # custom executor and no patched internals.
85
- #
86
- # WHY IT CAN EVALUATE TO `undefined` — and that is the safe answer. The row's
87
- # `pwshPath` default is `undefined`, and `undefined` means "resolve a pwsh the
88
- # official way" (PowerShell 7 install, PATH, Windows PowerShell 5.1;
89
- # packages/shell/pwsh-local/src/resolve.ts:67-79). The expression keeps exactly
90
- # four guards:
91
- #
92
- # 1. the platform gate, matching the row's own `disabled` gate: this override
93
- # is meaningless off Windows, where `pwsh-sandbox` is not mounted;
94
- # 2. an existence check over the SAME THREE PLACES, IN THE SAME ORDER, as the
95
- # plugin's own probe in `lib/shells.js` (`PWSH_LAYOUT_ORDER`): first the
96
- # provisioned store, `<DSH_HOME>/dsh-ops/shells/pwsh/<version>/bin/pwsh.exe`
97
- # (what `dsh-ops provision-shells` installs, and the copy the plugin
98
- # prefers), then `@dsh-ops/pwsh-<platform>-<arch>` with the shell at
99
- # `bin/pwsh.exe` (a deployment that installed the platform package), then
100
- # the vendored `vendor/pwsh/<platform>-<arch>/pwsh.exe` (a checkout that
101
- # carries its own copy). Keeping the order identical is what makes the
102
- # ladder's L3 and the host row the same fact rather than two guesses;
103
- # 3. a `lstat`-based usability test, so a Windows Store execution alias (a
104
- # symlink whose target is not statable) counts, while a directory named
105
- # `pwsh.exe` does not — the same rule `resolveShells` applies;
106
- # 4. a wrapping `try`/`catch` around the whole body, plus a `try` each for the
107
- # store scan, the plugin-package resolution, and the platform-package
108
- # resolution: an absent store, an unresolvable package, and a malformed
109
- # manifest alike may not fail the boot. The store scan sits outside the
110
- # plugin resolution on purpose — a provisioned copy is an absolute path
111
- # under `DSH_HOME`, so a profile whose `baseUrl` cannot resolve `dsh-ops`
112
- # still gets it rather than losing the rung.
113
- #
114
- # The store scan picks the greatest version name that holds the executable —
115
- # the identical comparison `provisionedShell` makes in `lib/shells.js`, which is
116
- # why it is one expression rather than a version list repeated here. The store's
117
- # root mirrors `dshHome` there too: `DSH_HOME` when the deployment names one,
118
- # `~/.dsh` otherwise.
119
- #
120
- # Until one of those three places actually carries the executable, the
121
- # expression yields `undefined` and the host row is left completely untouched.
122
- #
123
- # "Installed means used" is the point of the rung: a deployment that carries
124
- # the plugin's pwsh 7 wants the host's pwsh tool to run it, not the machine's.
125
- # A deployment that does NOT want this rewrite removes or edits this patch
126
- # entry in its OWN profile `cordis.patch.yml` — that layer applies after every
127
- # bundle layer (docs/user/develop/basic/publish.md:118-130), so it has the last
128
- # word. There is deliberately no plugin config key for it: a bundle patch is
129
- # evaluated before any dsh-ops row is mounted (the Loader defers a row's `!!js`
130
- # interpolation until that row's own injections are active), so a config key
131
- # could not reach this expression, and a key that pretends to is a ladder that
132
- # lies about the row it never changed.
133
- #
134
- # `!!js` SCOPE, VERIFIED IN THE VENDORED LOADER. The Loader evaluates these
135
- # expressions as `with (ctx) { return eval(expr) }`
136
- # (vendor/loader/src/config/utils.ts:5-9), so bare `baseUrl` (the owning config
137
- # tree's base URL) and `ctx` are both in scope — the same convention the shipped
138
- # bundle uses at packages/bundle/web-app/presets/cordis.patch.yml:147.
139
- # `dsh-ops` is a dependency of the profile, so
140
- # `createRequire(baseUrl).resolve('dsh-ops/package.json')` finds the package
141
- # whether it was installed as a link or from the registry.
142
- #
143
- # WHOLE-OBJECT REPLACEMENT — AUDITED, NOT ASSUMED. A patch replaces the target
144
- # row's ENTIRE `config` object rather than deep-merging it
145
- # (docs/user/develop/basic/publish.md:129), so every non-default field of the
146
- # target must be restated here. The base row this targets is
147
- # packages/bundle/base/cordis.patch.yml:241-243:
148
- #
149
- # - id: pwsh-sandbox
150
- # name: '@deepseek-ai/dsh-pwsh-sandbox'
151
- # disabled: !!js process.platform !== 'win32'
152
- #
153
- # It declares NO `config` at all, and this patch writes only `config` (the
154
- # row's `name` and its platform `disabled` gate are untouched), so there is no
155
- # non-default field to restate and nothing is silently dropped. If the base row
156
- # ever gains one, it must be restated here.
157
- # ----------------------------------------------------------------------------
158
- - id: pwsh-sandbox
159
- config:
160
- pwshPath: !!js "(() => { if (process.platform !== 'win32') return undefined; try { const builtin = (name) => process.getBuiltinModule(name); const fs = builtin('node:fs'); const nodePath = builtin('node:path'); const win = process.platform + '-' + process.arch; const usable = (file) => { try { const stat = fs.lstatSync(file); return stat.isFile() || stat.isSymbolicLink(); } catch { return false; } }; const named = process.env.DSH_HOME; const home = typeof named === 'string' && named.trim() !== '' ? nodePath.resolve(named) : nodePath.join(builtin('node:os').homedir(), '.dsh'); const layouts = []; try { const store = nodePath.join(home, 'dsh-ops', 'shells', 'pwsh'); const versions = fs.readdirSync(store, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort().reverse(); for (const version of versions) layouts.push(nodePath.join(store, version, 'bin', 'pwsh.exe')); } catch {} try { const requires = builtin('node:module').createRequire(String(baseUrl).replace(/[^/]*$/, 'noop.json')); const dir = nodePath.dirname(requires.resolve('dsh-ops/package.json')); try { const pluginRequire = builtin('node:module').createRequire(nodePath.join(dir, 'package.json')); const pkg = nodePath.dirname(pluginRequire.resolve('@dsh-ops/pwsh-' + win + '/package.json')); layouts.push(nodePath.join(pkg, 'bin', 'pwsh.exe')); } catch {} layouts.push(nodePath.join(dir, 'vendor', 'pwsh', win, 'pwsh.exe')); } catch {} for (const candidate of layouts) { if (usable(candidate)) return candidate; } return undefined; } catch { return undefined; } })()"
11
+ - id: dsh-ops-file
12
+ name: dsh-ops/file
13
+ config:
14
+ promptPolicy: true
15
+ enableShellTools: false
16
+ required: false
17
+ toolCallTimeoutMs: 300000
18
+ - id: dsh-ops-background
19
+ name: dsh-ops/background
20
+ disabled: true
21
+ config:
22
+ promptPolicy: true
23
+ enableShellTools: true
24
+ required: false
25
+ toolCallTimeoutMs: 300000
@@ -4,13 +4,17 @@
4
4
 
5
5
  ## 工具发布与提示词
6
6
 
7
- - `workspace-write`、`read-only`:目录只有四个 ops 文件工具;没有任何 `ops_run` / `ops_job_*`,提示词也不出现这些名字。
8
- - `danger-full-access` 且 `enableShellTools: true`:五个命令/job 工具一起出现。
9
- - `enableShellTools: false`、无 sandboxPolicy 服务、无会话:命令组不出现。
7
+ - 组件列表恰为三个:shell/file 默认开启,background 默认关闭。分别核验组件名称、中文介绍、图标,以及实际版本。
8
+ - `workspace-write`、`read-only`:只有开启的四个 ops 文件工具;没有 ops_bash/后台工具,提示词不广告不存在的命令。
9
+ - `danger-full-access`:默认四个文件工具 + ops_bash;仅后台组件开且 enableShellTools=true 时增加四个后台工具。任何组合都不发布 ops_run。
10
+ - 文件与后台共享一个 FastCtx;关文件仍能管理后台,关后台仍能读搜改,重开不积累注册。
11
+ - `enableShellTools: false`、无 sandboxPolicy 服务、无会话:后台组不出现。
10
12
  - 同时开受限会话与完全权限会话:两者目录互不污染。受限子 agent 不继承祖先的命令组。
11
13
  - 同一会话切换受限 → 完全权限 → 受限:每次下一请求目录/提示词跟随变化,不重连 FastCtx;旧命令句柄不能继续调用。
12
14
  - 受限会话不发布 `ops_bash`;完全权限 + publishBashTool=true + bash 可解析 + subprocess 可用时出现,通用命令优先走它。FastCtx 缺失或 enableShellTools=false 时 bash 层仍独立可用。
13
- - 提示词仅一段 `dsh-ops:repository-tooling`:工具包 → bash → pwsh7;不混语法,不因 bash 报错切 PowerShell。Windows 原生操作才用 pwsh。没有旧 host-shell 段或 `mcp:fastctx`。
15
+ - 提示词按组件分段 `dsh-ops:repository-tooling:{file,shell,background}`:开关只移除对应段;bash/pwsh 路由仅 shell 开时出现,后台关闭不出现后台指导。不写 AGENTS.md,没有旧 host-shell 段或 mcp:fastctx。
16
+ - shell 开时宿主 pwsh-sandbox 采用随包 pwsh7,关时恢复最新宿主原配置;profile 文件不得被改动。开关期间用户更改执行器原配置后也应恢复新值。
17
+ - 极简预设 complete persona 可排除附加提示;PTC 通过 SDK 使用同一工具层,权限/组件开关仍应生效。
14
18
  - publishBashTool=false 隐去 bash 这一层;bash 权限动态切换、受限子 agent 继承过滤和卸载清理一起验证。
15
19
  - `promptPolicy: false`:不发布路由提示词。
16
20
  - `deny-host-shell`:有无 ops 命令组都应拒绝配置的宿主 shell;若工具来自继承层,目录也应遮蔽。无可用执行器时不推荐不可调用的 ops 命令。
@@ -35,6 +39,7 @@
35
39
  - 完成/kill 后仍能读取自己已有 ID 的日志。
36
40
  - job_list status/limit/offset 是自己的分页,不暴露全局总数/offset;空列表明确为空。
37
41
  - 大全局 job 存储扫描不完整时必须报告 Partial,不冒充完整;原工具结果的 Complete/Partial 续页提示不能丢。
42
+ - 关闭后台组件/卸载:等待已发调用,再尝试终止当前组件已知的自有 ID;不能杀别的会话或组件任务。清理失败有告警。断线后已丢归属的任务不保证终止。
38
43
  - 重连后不认领旧 ID;操作者可通过上游工具清理持久 job。权限降级不自动杀已运行任务,须操作者管理。
39
44
 
40
45
  ## 生命周期与边界
@@ -42,7 +47,7 @@
42
47
  - 服务端断线立即撤下工具与对应提示词项,恢复后重新出现。
43
48
  - 卸载后工具/提示词/限制全部消失;共享注册表方法未改写,外来同名工具仍可按自己的 scope 注册。
44
49
  - 未知配置键报错点名;required=true 的缺失/不可用二进制拒绝激活。
45
- - 取消/传输超时只表明停止等待,不把它误读为命令已停止。ops_run 的 timeout_ms 才是上游进程树超时。
50
+ - 取消/传输超时只表明停止等待,不把它误读为命令已停止。ops_bash 的宿主执行器截止时间独立于 MCP 等待超时。
46
51
 
47
52
  ## 数字复核
48
53
 
@@ -38,11 +38,6 @@
38
38
  "bytes": 1511,
39
39
  "approximateTokens": 378
40
40
  },
41
- {
42
- "name": "ops_run",
43
- "bytes": 751,
44
- "approximateTokens": 188
45
- },
46
41
  {
47
42
  "name": "ops_run_background",
48
43
  "bytes": 635,
@@ -50,28 +45,40 @@
50
45
  }
51
46
  ],
52
47
  "total": {
53
- "bytes": 9739,
54
- "approximateTokens": 2435
48
+ "bytes": 8988,
49
+ "approximateTokens": 2247
55
50
  },
56
51
  "fileOnly": {
57
52
  "bytes": 6724,
58
53
  "approximateTokens": 1681
59
54
  },
60
55
  "fileOnlyPrompt": {
61
- "bytes": 527,
62
- "approximateTokens": 132
56
+ "bytes": 498,
57
+ "approximateTokens": 125
63
58
  },
64
59
  "bashSchema": {
65
60
  "bytes": 961,
66
61
  "approximateTokens": 241
67
62
  },
63
+ "defaultWithBash": {
64
+ "bytes": 7685,
65
+ "approximateTokens": 1922
66
+ },
68
67
  "fullWithBash": {
69
- "bytes": 10700,
70
- "approximateTokens": 2675
68
+ "bytes": 9949,
69
+ "approximateTokens": 2488
70
+ },
71
+ "shellPrompt": {
72
+ "bytes": 492,
73
+ "approximateTokens": 123
74
+ },
75
+ "backgroundPrompt": {
76
+ "bytes": 269,
77
+ "approximateTokens": 68
71
78
  },
72
79
  "toolingPrompt": {
73
- "bytes": 1110,
74
- "approximateTokens": 278
80
+ "bytes": 878,
81
+ "approximateTokens": 220
75
82
  },
76
83
  "publishedServerInstructions": {
77
84
  "bytes": 0,
@@ -1,17 +1,37 @@
1
1
  # Schema measurement
2
2
 
3
- Manual measurement only; not CI, regression coverage, a tokenizer, or tool-use telemetry.
3
+ Manual measurement only; not a tokenizer, CI, or proof of whole-task savings.
4
+ Metric: compact UTF-8 JSON `{name,description,parameters}` bytes, tokens ≈ ceil(bytes/4).
5
+ Runtime is FastCtx 0.2.6; only plugin publication/presentation changes.
4
6
 
5
- Baseline: plugin `dee3c57`, FastCtx 0.2.6, before presentation changes. Current: same runtime, plugin-only schema projections. Metric: sum of compact UTF-8 JSON `{name,description,parameters}` sizes, no whitespace formatting; tokens ≈ ceil(bytes/4).
7
+ ## 0.2.4 component surface
6
8
 
7
- | Surface | Baseline bytes | Current bytes | Reduction |
9
+ | Enabled components | Tools | Schema bytes | Approximate tokens |
8
10
  | --- | ---: | ---: | ---: |
9
- | Nine FastCtx schemas (full-access session) | 18,889 | 9,739 | 48.4% |
10
- | Four file schemas | 12,807 | 6,724 | 47.5% |
11
- | Routing section (original full ladder vs compact three-layer table with strict-argument reminder) | 3,373 | 1,110 | 67.1% |
11
+ | File only | 4 | 6,724 | 1,681 |
12
+ | Shell + file (default, full access) | 5 | 7,685 | 1,922 |
13
+ | Shell + file + background (full access) | 9 | 9,949 | 2,488 |
14
+ | 0.2.3 full surface (historical) | 10 | 10,700 | 2,675 |
12
15
 
13
- Full-access FastCtx schema estimate: 4,723 → 2,435 tokens. The corrected object-root ops_bash schema adds 961 bytes (~241 tokens); full FastCtx+bash surface is 10,700 bytes (~2,675 tokens). Restricted file-only surface: about 1,681 tokens; its routing table is 527 bytes (~132 tokens). No server instructions are published (the original raw server instructions were 246 bytes); the old host-shell section is also removed. Baseline excludes ops_bash, so compare nine FastCtx tools like-for-like rather than treating restoration as a regression of those numbers. Total request savings depend on host presentation; these are not API usage/billing measurements.
16
+ Default reduces plugin schema bytes by 28.2% versus 0.2.3. Background contributes
17
+ 2,264 bytes only when enabled; duplicate ops_run (751 bytes) is no longer published.
18
+ The host tool declarations remain additive and are not included in this table.
19
+ Restricted sessions have no ops_bash or background command tools.
14
20
 
15
- `ops_grep` went from 3,766 → 2,019 bytes; `ops_inspect_local_file` from 4,375 → 1,968. `ops_replace` is retained: schema cost cannot establish invocation frequency, and no usage-frequency measurement was collected.
21
+ Prompt sections are component-owned and independently removed: file 498 bytes,
22
+ shell 492 bytes, background 269 bytes. These sum when multiple components are on;
23
+ `toolingPrompt` in the snapshot is the legacy combined CLI renderer, not the actual
24
+ sum of separately registered sections. Minimal's complete persona can exclude them.
25
+ No raw server instructions or old host-shell policy segment is published.
16
26
 
17
- Reproduce the current snapshot with `node scripts/measure-schemas.mjs docs/schema-current.json`. It only starts FastCtx, initializes, lists schemas, then closes it; it executes no tools. Keep the baseline unchanged; a different runtime schema requires an explicitly labeled comparison.
27
+ ## Historical comparison
28
+
29
+ The unchanged baseline is dee3c57 before description compression: nine FastCtx
30
+ schemas 18,889 bytes; four file schemas 12,807. File descriptions remain 6,724 bytes
31
+ (47.5% reduction). ops_grep: 3,766 → 2,019; inspect: 4,375 → 1,968. These are plugin
32
+ old/new comparisons, NOT official-host versus plugin comparisons.
33
+
34
+ Reproduce with `node scripts/measure-schemas.mjs docs/schema-current.json`.
35
+ It initializes and lists schemas without tool execution. Preserve baseline JSON.
36
+ Actual request count, cache pricing, tool output and repeated history can outweigh
37
+ fixed schema savings; no percentage here predicts task-total provider tokens.
package/dsh-plugin.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "manifestVersion": "0.15",
4
4
  "id": "dsh-ops.fastctx",
5
5
  "name": "dsh-ops",
6
- "version": "0.2.3",
6
+ "version": "0.2.4",
7
7
  "license": "MIT AND Apache-2.0",
8
8
  "facets": {
9
9
  "host": {
@@ -50,9 +50,19 @@
50
50
  ],
51
51
  "x-prompt-sections": [
52
52
  {
53
- "name": "dsh-ops:repository-tooling",
53
+ "name": "dsh-ops:repository-tooling:file",
54
54
  "order": 3250,
55
- "summary": "Compact tools-to-bash-to-PowerShell-7 routing; prefer bash except for Windows-native operations."
55
+ "summary": "File operations only; follows the file component visibility."
56
+ },
57
+ {
58
+ "name": "dsh-ops:repository-tooling:shell",
59
+ "order": 3251,
60
+ "summary": "Prefer bash, use bundled PowerShell 7 for Windows-native functionality; removed when shell component is off."
61
+ },
62
+ {
63
+ "name": "dsh-ops:repository-tooling:background",
64
+ "order": 3252,
65
+ "summary": "Owned background tasks only; absent by default."
56
66
  }
57
67
  ],
58
68
  "x-tool-namespaces": [
@@ -66,21 +76,39 @@
66
76
  "grep",
67
77
  "glob",
68
78
  "replace",
69
- "run",
70
79
  "run_background",
71
80
  "job_output",
72
81
  "job_list",
73
82
  "job_kill"
74
83
  ],
75
- "shellPermission": "enableShellTools AND per-session sandboxPolicy danger-full-access"
84
+ "shellPermission": "background component enabled AND enableShellTools AND per-session sandboxPolicy danger-full-access"
76
85
  },
77
86
  {
78
87
  "serverName": "dsh-ops",
79
88
  "transport": "in-process",
80
89
  "source": "the plugin's bash layer via host subprocess",
81
90
  "publicPrefix": "ops_",
82
- "tools": ["bash"],
83
- "shellPermission": "publishBashTool AND per-session sandboxPolicy danger-full-access"
91
+ "tools": [
92
+ "bash"
93
+ ],
94
+ "shellPermission": "shell component enabled AND publishBashTool AND per-session sandboxPolicy danger-full-access"
95
+ }
96
+ ],
97
+ "x-components": [
98
+ {
99
+ "id": "dsh-ops-shell",
100
+ "entry": "dsh-ops/shell",
101
+ "defaultEnabled": true
102
+ },
103
+ {
104
+ "id": "dsh-ops-file",
105
+ "entry": "dsh-ops/file",
106
+ "defaultEnabled": true
107
+ },
108
+ {
109
+ "id": "dsh-ops-background",
110
+ "entry": "dsh-ops/background",
111
+ "defaultEnabled": false
84
112
  }
85
113
  ]
86
114
  },
@@ -0,0 +1,3 @@
1
+ import { applyComponent } from "../../index.js"
2
+ export const name = "dsh-ops-background"
3
+ export function apply(ctx, config) { return applyComponent(ctx, config, "background") }
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "dsh-ops-background",
4
+ "description": "Manage scientific simulations, model training, and other long-running background tasks."
5
+ }
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "dsh-ops-background",
4
+ "description": "为科研仿真、模型训练及其他长进程后台任务提供托管"
5
+ }
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "dsh-ops-background",
3
+ "description": "Manage scientific simulations, model training, and other long-running background tasks.",
4
+ "icon": "./icon.png",
5
+ "type": "module"
6
+ }
Binary file
@@ -0,0 +1,3 @@
1
+ import { applyComponent } from "../../index.js"
2
+ export const name = "dsh-ops-file"
3
+ export function apply(ctx, config) { return applyComponent(ctx, config, "file") }
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "dsh-ops-file",
4
+ "description": "High-performance Rust file search with faster execution and compact output for token efficiency."
5
+ }
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "dsh-ops-file",
4
+ "description": "更快、更高性能、输出更精简更省token的rust文件检索"
5
+ }
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "dsh-ops-file",
3
+ "description": "High-performance Rust file search with faster execution and compact output for token efficiency.",
4
+ "icon": "./icon.png",
5
+ "type": "module"
6
+ }
Binary file
@@ -0,0 +1,3 @@
1
+ import { applyComponent } from "../../index.js"
2
+ export const name = "dsh-ops-bash & powershell 7"
3
+ export function apply(ctx, config) { return applyComponent(ctx, config, "shell") }
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "dsh-ops-bash & powershell 7",
4
+ "description": "Prefer bash for general commands and provide PowerShell 7 when Windows-native functionality is needed."
5
+ }
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "dsh-ops-bash & powershell 7",
4
+ "description": "将默认终端改为bash,以及bash覆盖不到时提供powershell7"
5
+ }
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "dsh-ops-bash & powershell 7",
3
+ "description": "Prefer bash for general commands and provide PowerShell 7 when Windows-native functionality is needed.",
4
+ "icon": "./icon.png",
5
+ "type": "module"
6
+ }
package/lib/index.js CHANGED
@@ -1,9 +1,10 @@
1
1
  /** dsh-ops host half. Own registrations only; no @deepseek-ai value imports. */
2
2
  import { PACKAGE_ROOT, probeBinary, resolveBinary } from './binary.js'
3
3
  import { resolveConfig } from './config.js'
4
- import { SECTION_ORDERS, TOOLING_SECTION, hostShellRefusal, renderToolingPolicy } from './policy.js'
4
+ import { SECTION_ORDERS, TOOLING_SECTION, BACKGROUND_TOOLS, FILE_TOOLS, BASH_TOOL, publicToolName, hostShellRefusal, renderToolingPolicy } from './policy.js'
5
5
  import { resolveShells } from './shells.js'
6
6
  import { mountSessionBash } from './session-shells.js'
7
+ import { mountBundledPwsh } from './session-pwsh.js'
7
8
  import { FastCtxTools } from './tools.js'
8
9
 
9
10
  export const name = 'dsh-ops'
@@ -54,30 +55,34 @@ function installHostShellDenial(ctx, config) {
54
55
  }
55
56
  }
56
57
 
57
- async function mountServer(ctx, config, runtime) {
58
+ async function mountServer(ctx, config, runtime, component) {
58
59
  if (!runtime) return
59
- const surface = new FastCtxTools({ ctx, config, runtime, report: (message, level) => report(ctx, message, level) })
60
+ const background = component === 'background'
61
+ const surface = new FastCtxTools({ ctx, config, runtime,
62
+ fileTools: !background, commandTools: background ? BACKGROUND_TOOLS : [], shared: true,
63
+ report: (message, level) => report(ctx, message, level) })
60
64
  ctx.effect(() => () => surface.stop(), 'dsh-ops: fastctx server')
61
- ctx.on('agent/created', ({ agent }) => surface.attachAgent(agent))
62
- ctx.on('agent/disposed', ({ agent }) => surface.detachAgent(agent))
63
- ctx.on('session/event', (session, event) => {
64
- if (event.type === 'sandbox/mode') surface.refreshAgents(session)
65
- })
66
- // Authority may mount after the tools service or be hot-reloaded. Losing it
67
- // immediately retires every command layer; it never falls back to env state.
68
- surface.authorityAvailable = false
69
- ctx.inject(['sandboxPolicy'], authorityCtx => {
70
- surface.authorityAvailable = true
71
- surface.refreshAgents()
72
- authorityCtx.effect(() => () => {
73
- surface.authorityAvailable = false
65
+ if (background) {
66
+ ctx.on('agent/created', ({ agent }) => surface.attachAgent(agent))
67
+ ctx.on('agent/disposed', ({ agent }) => surface.detachAgent(agent))
68
+ ctx.on('session/event', (_session, event) => {
69
+ if (event.type === 'sandbox/mode') surface.refreshAgents()
70
+ })
71
+ // Background command layers follow authoritative per-session permission.
72
+ surface.authorityAvailable = false
73
+ ctx.inject(['sandboxPolicy'], authorityCtx => {
74
+ surface.authorityAvailable = true
74
75
  surface.refreshAgents()
75
- }, 'dsh-ops: permission authority')
76
- })
77
- for (const agent of ctx.get('agents')?.list?.() ?? []) surface.attachAgent(agent)
76
+ authorityCtx.effect(() => () => {
77
+ surface.authorityAvailable = false
78
+ surface.refreshAgents()
79
+ }, 'dsh-ops: permission authority')
80
+ })
81
+ for (const agent of ctx.get('agents')?.list?.() ?? []) surface.attachAgent(agent)
82
+ }
78
83
  try {
79
84
  const tools = await surface.start()
80
- report(ctx, `dsh-ops: FastCtx ${runtime.version} ready; ${tools.length} file tools, session-authorized command layers.`, 'info')
85
+ report(ctx, `dsh-ops: FastCtx ${runtime.version} ready; ${component} component, ${tools.length} global tools.`, 'info')
81
86
  } catch (error) {
82
87
  report(ctx, `dsh-ops: FastCtx unavailable: ${error.message}`)
83
88
  if (config.required) throw error
@@ -85,24 +90,33 @@ async function mountServer(ctx, config, runtime) {
85
90
  }
86
91
  }
87
92
 
88
- export function apply(root, rawConfig) {
93
+ export function applyComponent(root, rawConfig, component) {
89
94
  const config = resolveConfig(rawConfig)
90
- const runtime = prepareRuntime(root, config)
91
- const shells = resolveShells(config)
95
+ const runtime = component === 'shell' ? undefined : prepareRuntime(root, config)
96
+ const shells = component === 'shell' ? resolveShells(config) : undefined
97
+ const owned = component === 'shell' ? [BASH_TOOL, 'pwsh']
98
+ : component === 'file' ? FILE_TOOLS.map(publicToolName) : BACKGROUND_TOOLS.map(publicToolName)
99
+ if (component === 'file') owned.push('read_image')
92
100
  if (config.promptPolicy) root.inject(['systemPrompt'], ctx => {
93
101
  ctx.effect(() => ctx.systemPrompt.section({
94
- name: TOOLING_SECTION,
95
- order: SECTION_ORDERS[TOOLING_SECTION],
102
+ name: `${TOOLING_SECTION}:${component}`,
103
+ order: SECTION_ORDERS[TOOLING_SECTION] + (component === 'shell' ? 1 : component === 'background' ? 2 : 0),
96
104
  interpolate: false,
97
105
  text: context => renderToolingPolicy({
98
- published: visibleTools(ctx, context.agent), pwsh7: shells.pwsh.available,
106
+ published: visibleTools(ctx, context.agent).filter(name => owned.includes(name)),
107
+ shellComponent: component === 'shell', pwsh7: shells?.pwsh.available ?? false,
99
108
  extraGuidance: config.extraGuidance,
100
109
  }),
101
- }), 'dsh-ops: repository tooling')
102
- })
103
- root.inject(['tools'], ctx => {
104
- mountSessionBash(ctx, config, shells)
105
- if (config.shellPolicy === 'deny-host-shell') installHostShellDenial(ctx, config)
106
- return mountServer(ctx, config, runtime)
110
+ }), `dsh-ops: ${component} tooling`)
107
111
  })
112
+ if (component === 'shell') {
113
+ mountBundledPwsh(root, shells)
114
+ root.inject(['tools'], ctx => {
115
+ mountSessionBash(ctx, config, shells)
116
+ if (config.shellPolicy === 'deny-host-shell') installHostShellDenial(ctx, config)
117
+ })
118
+ } else root.inject(['tools'], ctx => mountServer(ctx, config, runtime, component))
108
119
  }
120
+
121
+ // Backward-compatible module entry. The bundle uses independently toggled rows.
122
+ export function apply(root, rawConfig) { return applyComponent(root, rawConfig, 'file') }
package/lib/policy.js CHANGED
@@ -5,7 +5,8 @@ export const HOST_SHELL_SECTION = 'dsh-ops:host-shell-policy'
5
5
  export const SECTION_ORDERS = Object.freeze({ [TOOLING_SECTION]: 3250 })
6
6
  export const TOOL_PREFIX = 'ops_'
7
7
  export const FILE_TOOLS = Object.freeze(['inspect_local_file', 'grep', 'glob', 'replace'])
8
- export const SHELL_TOOLS = Object.freeze(['run', 'run_background', 'job_output', 'job_list', 'job_kill'])
8
+ export const BACKGROUND_TOOLS = Object.freeze(['run_background', 'job_output', 'job_list', 'job_kill'])
9
+ export const SHELL_TOOLS = Object.freeze(['run', ...BACKGROUND_TOOLS])
9
10
  // The plugin's second layer executes POSIX commands; pwsh7 uses the host tool.
10
11
  export const IN_PROCESS_TOOLS = Object.freeze(['bash'])
11
12
  export function publicToolName(rawName) { return `${TOOL_PREFIX}${rawName}` }
@@ -23,17 +24,24 @@ export function ladderLevels({ published, pwsh = false }) {
23
24
  }
24
25
 
25
26
  /** A single compact table; absent tools never get advertised. */
26
- export function renderToolingPolicy({ published, enableShellTools, mounted, levels, pwsh7 = false, extraGuidance = '' }) {
27
+ export function renderToolingPolicy({ published, enableShellTools, mounted, levels, pwsh7 = false, shellComponent = true, extraGuidance = '' }) {
27
28
  const names = new Set(published ?? (mounted || levels?.fastctx
28
29
  ? [...FILE_TOOLS, ...(enableShellTools ? SHELL_TOOLS : []), ...(levels?.bash ? IN_PROCESS_TOOLS : [])].map(publicToolName) : []))
29
30
  if (levels?.pwsh) names.add('pwsh')
30
- if (!FILE_TOOLS.some(name => names.has(publicToolName(name))) && !names.has(BASH_TOOL)) return ''
31
- const lines = ['# Repository operations: tools → bash → PowerShell 7', '', '| Task | Tool |', '| --- | --- |']
31
+ if (!shellComponent) names.delete('pwsh')
32
+ const hasFiles = FILE_TOOLS.some(name => names.has(publicToolName(name)))
33
+ const hasBash = names.has(BASH_TOOL)
34
+ const hasBackground = names.has(publicToolName('run_background'))
35
+ const hasPwsh = shellComponent && names.has('pwsh')
36
+ if (!hasFiles && !hasBash && !hasBackground && !hasPwsh) return ''
37
+ const heading = hasBash ? '# Shell routing: bash → PowerShell 7'
38
+ : hasFiles ? '# Repository file tools' : hasBackground ? '# Background task tools' : '# Windows-native shell'
39
+ const lines = [heading, '', '| Task | Tool |', '| --- | --- |']
32
40
  const row = (raw, task) => {
33
41
  if (names.has(publicToolName(raw))) lines.push(`| ${task} | ${publicToolName(raw)} |`)
34
42
  }
35
43
  row('inspect_local_file', 'Text / multiple ranges / encoding / PDF text / hex')
36
- lines.push('| Images | read_image (host) |')
44
+ if (hasFiles && names.has('read_image')) lines.push('| Images | read_image (host) |')
37
45
  row('grep', 'Search contents')
38
46
  row('glob', 'Find paths')
39
47
  row('replace', 'Batch replacement; use host edit for precise edits')
@@ -41,10 +49,10 @@ export function renderToolingPolicy({ published, enableShellTools, mounted, leve
41
49
  if (names.has('pwsh')) lines.push(`| Windows-native cmdlets / registry / services only | pwsh (${pwsh7 || levels?.pwsh ? 'bundled PowerShell 7 via host' : 'host PowerShell'}) |`)
42
50
  row('run', 'Bounded bash command result (full access only)')
43
51
  if (names.has(publicToolName('run_background'))) {
44
- lines.push(`| Background commands / owned jobs | ${SHELL_TOOLS.slice(1).map(publicToolName).join(', ')} |`)
52
+ lines.push(`| Background commands / owned jobs | ${BACKGROUND_TOOLS.map(publicToolName).filter(name => names.has(name)).join(', ')} |`)
45
53
  }
46
- lines.push('', '- ops_* arguments are strict: pass only declared fields.',
47
- '- Do not build shell command lines for file operations.',
54
+ lines.push('', '- ops_* arguments are strict: pass only declared fields.')
55
+ if (hasFiles) lines.push('- Use file tools for file operations; use count for totals and batch independent requests.',
48
56
  '- On a tool error, correct the arguments and retry; do not switch to a shell workaround.')
49
57
  if (names.has(BASH_TOOL)) lines.push('- Prefer bash for command execution. Use PowerShell only when Windows-native functionality is necessary; fix bash errors in bash, do not switch shells or mix their syntax.')
50
58
  if (names.has(publicToolName('run_background'))) lines.push('- Only operate on job IDs you started in this session.')
@@ -0,0 +1,42 @@
1
+ /** One plugin-owned FastCtx connection shared by independently toggled components. */
2
+ import { McpStdioClient } from './handshake.js'
3
+ const pools = new WeakMap()
4
+ export async function acquireRuntime(ctx, runtime, env) {
5
+ const root = ctx.root
6
+ let pool = pools.get(root)
7
+ if (!pool) { pool = { clients: new Map(), closing: Promise.resolve() }; pools.set(root, pool) }
8
+ await pool.closing
9
+ let entry = pool.clients.get(runtime.file)
10
+ if (!entry) {
11
+ const client = McpStdioClient.start({ file: runtime.file, args: ['serve', '--enable-shell'], env, timeoutMs: 60000 })
12
+ entry = { client, refs: 0, ready: undefined }
13
+ pool.clients.set(runtime.file, entry)
14
+ entry.ready = (async () => {
15
+ try {
16
+ const handshake = await client.initialize('dsh-ops-components')
17
+ const tools = await client.listTools()
18
+ return { client, handshake, tools }
19
+ } catch (error) {
20
+ if (pool.clients.get(runtime.file) === entry) pool.clients.delete(runtime.file)
21
+ await client.close()
22
+ throw error
23
+ }
24
+ })()
25
+ void client.exited.then(() => { if (pool.clients.get(runtime.file) === entry) pool.clients.delete(runtime.file) })
26
+ }
27
+ entry.refs += 1
28
+ let released = false
29
+ const release = async () => {
30
+ if (released) return
31
+ released = true
32
+ entry.refs -= 1
33
+ if (!entry.refs) {
34
+ if (pool.clients.get(runtime.file) === entry) pool.clients.delete(runtime.file)
35
+ const closing = pool.closing.then(() => entry.client.close())
36
+ pool.closing = closing.catch(() => {})
37
+ await closing
38
+ }
39
+ }
40
+ try { return { ...await entry.ready, release } }
41
+ catch (error) { await release(); throw error }
42
+ }
@@ -0,0 +1,31 @@
1
+ /** A reversible runtime-only config overlay; never edits profile files or services. */
2
+ export function mountBundledPwsh(ctx, shells) {
3
+ if (!shells.pwsh.available || process.platform !== 'win32') return
4
+ ctx.inject(['loader'], loaderCtx => {
5
+ const loader = loaderCtx.get('loader')
6
+ const matches = entry => entry?.options?.id === 'pwsh-sandbox'
7
+ && entry.options.name === '@deepseek-ai/dsh-pwsh-sandbox'
8
+ const refresh = () => {
9
+ for (const entry of loader.entries()) {
10
+ if (matches(entry) && entry.fiber?.state === 2) {
11
+ // Public Fiber.update with noSave recomputes from the owner's latest
12
+ // raw config. No stored config, volatile reference, or method is changed.
13
+ entry.fiber.update(entry.options.config, true)
14
+ }
15
+ }
16
+ }
17
+ const unhook = loaderCtx.on('internal/config', function (_raw, next) {
18
+ const config = next()
19
+ if (!matches(this.entry) || this.parent?.fiber?.entry === this.entry) return config
20
+ return { ...config, pwshPath: shells.pwsh.file }
21
+ }, { global: true, prepend: true })
22
+ let stopped = false
23
+ loaderCtx.effect(() => () => {
24
+ if (stopped) return
25
+ stopped = true
26
+ unhook()
27
+ refresh()
28
+ }, 'dsh-ops: restore owning PowerShell executor config')
29
+ refresh()
30
+ })
31
+ }
package/lib/tools.js CHANGED
@@ -23,6 +23,8 @@ import { McpStdioClient } from './handshake.js'
23
23
  import { FILE_TOOLS, SHELL_TOOLS, publicToolName } from './policy.js'
24
24
  import { projectTool } from './presentation.js'
25
25
  import { cleanBackground, jobEntries, startedJob } from './jobs.js'
26
+ import { acquireRuntime } from './runtime-pool.js'
27
+ import { resolveShells } from './shells.js'
26
28
 
27
29
  /** Deadline for one connection attempt: spawn, initialize, and `tools/list`. */
28
30
  const CONNECT_TIMEOUT_MS = 60_000
@@ -216,10 +218,16 @@ export class FastCtxTools {
216
218
  * @param {{file: string, source: string, version: string}} options.runtime - the resolved FastCtx executable.
217
219
  * @param {(message: string, level?: string) => void} [options.report] - where asynchronous events are reported.
218
220
  */
219
- constructor({ ctx, config, runtime, report }) {
221
+ constructor({ ctx, config, runtime, report, fileTools = true, commandTools = SHELL_TOOLS, shared = false }) {
220
222
  this.ctx = ctx
221
223
  this.config = config
222
224
  this.runtime = runtime
225
+ this.fileTools = fileTools
226
+ this.commandTools = commandTools
227
+ this.shared = shared
228
+ this.leases = new Map()
229
+ this.pendingCalls = new Set()
230
+ this.shuttingDown = false
223
231
  this.report = report ?? (() => {})
224
232
  /** The live connection, or undefined while the surface is down. */
225
233
  this.client = undefined
@@ -282,7 +290,7 @@ export class FastCtxTools {
282
290
  if (!entry?.ctx) return
283
291
  for (const dispose of entry.disposers) dispose()
284
292
  entry.disposers = []
285
- if (this.stopped) return
293
+ if (this.stopped || this.shuttingDown) return
286
294
  if (!this.shellAllowed(agent.session)) {
287
295
  // A child must not inherit an authorized ancestor's command surface.
288
296
  const names = (this.ctx.get('tools')?.schemas(agent) ?? []).map(tool => tool.name)
@@ -293,8 +301,8 @@ export class FastCtxTools {
293
301
  }
294
302
  return
295
303
  }
296
- const commandTools = this.tools.filter(tool => SHELL_TOOLS.includes(tool.name))
297
- if (commandTools.length !== SHELL_TOOLS.length) return
304
+ const commandTools = this.tools.filter(tool => this.commandTools.includes(tool.name))
305
+ if (commandTools.length !== this.commandTools.length) return
298
306
  try {
299
307
  for (const tool of commandTools) {
300
308
  entry.disposers.push(entry.ctx.tools.register(toolDefinition({
@@ -367,6 +375,20 @@ export class FastCtxTools {
367
375
  * @returns {Promise<void>} resolves once the child is gone.
368
376
  */
369
377
  async stop() {
378
+ this.shuttingDown = true
379
+ this.refreshAgents()
380
+ // Drain launches before killing only this component's owned jobs. Closing
381
+ // FastCtx alone does not stop its durable supervisors.
382
+ await Promise.allSettled([...this.pendingCalls])
383
+ const cleanupClient = this.client
384
+ if (cleanupClient) {
385
+ for (const ids of this.ownedJobs.values()) for (const job_id of ids) {
386
+ try {
387
+ const result = await cleanupClient.callTool('job_kill', { job_id }, { timeoutMs: 15000 })
388
+ if (result?.isError) this.report(`dsh-ops: owned job ${job_id} could not be stopped during component shutdown.`, 'warn')
389
+ } catch (error) { this.report(`dsh-ops: owned job ${job_id} shutdown failed: ${messageOf(error)}`, 'warn') }
390
+ }
391
+ }
370
392
  this.stopped = true
371
393
  if (this.timer !== undefined) {
372
394
  clearTimeout(this.timer)
@@ -380,7 +402,14 @@ export class FastCtxTools {
380
402
  this.tools = []
381
403
  this.ownedJobs.clear()
382
404
  await Promise.all([...this.agents.keys()].map(agent => this.detachAgent(agent)))
383
- if (client !== undefined) await client.close()
405
+ if (client !== undefined) await this.#release(client)
406
+ }
407
+
408
+ async #release(client) {
409
+ const lease = this.leases.get(client)
410
+ this.leases.delete(client)
411
+ if (lease) await lease.release()
412
+ else if (!this.shared) await client.close()
384
413
  }
385
414
 
386
415
  /**
@@ -388,7 +417,16 @@ export class FastCtxTools {
388
417
  * @returns {Promise<void>} resolves once the generation is live.
389
418
  */
390
419
  async #connect() {
391
- const client = McpStdioClient.start({
420
+ let lease
421
+ if (this.shared) {
422
+ const env = childEnv()
423
+ const bash = resolveShells(this.config).bash
424
+ if (bash.available) env.FASTCTX_BASH = bash.file
425
+ lease = await acquireRuntime(this.ctx, this.runtime, env)
426
+ if (this.shuttingDown || this.stopped) { await lease.release(); throw new StoppedError() }
427
+ this.leases.set(lease.client, lease)
428
+ }
429
+ const client = lease?.client ?? McpStdioClient.start({
392
430
  file: this.runtime.file,
393
431
  args: serverArgs(this.config.enableShellTools),
394
432
  env: childEnv(),
@@ -401,9 +439,9 @@ export class FastCtxTools {
401
439
  // already replaced or abandoned.
402
440
  void client.exited.then(({ code, signal, error }) => this.#onExit(client, code, signal, error))
403
441
  try {
404
- const handshake = await client.initialize('dsh-ops')
405
- const tools = await client.listTools()
406
- if (this.stopped || client !== this.client) throw new StoppedError()
442
+ const handshake = lease?.handshake ?? await client.initialize('dsh-ops')
443
+ const tools = lease?.tools ?? await client.listTools()
444
+ if (this.stopped || this.shuttingDown || client !== this.client) throw new StoppedError()
407
445
  this.ownedJobs.clear()
408
446
  this.#publish(tools)
409
447
  this.serverInfo = handshake?.serverInfo
@@ -411,7 +449,7 @@ export class FastCtxTools {
411
449
  this.serverInstructions = ''
412
450
  this.connectedAt = Date.now()
413
451
  } catch (error) {
414
- await client.close()
452
+ await this.#release(client)
415
453
  if (this.client === client) this.client = undefined
416
454
  throw error
417
455
  }
@@ -426,7 +464,8 @@ export class FastCtxTools {
426
464
  * @returns {void}
427
465
  */
428
466
  #onExit(client, code, signal, error) {
429
- if (this.stopped || client !== this.client) return
467
+ if (this.stopped || this.shuttingDown || client !== this.client) return
468
+ void this.#release(client)
430
469
  this.client = undefined
431
470
  this.ownedJobs.clear()
432
471
  this.tools = []
@@ -449,7 +488,7 @@ export class FastCtxTools {
449
488
  #scheduleReconnect() {
450
489
  // One pending timer at a time: a crash and a failed first connect can both
451
490
  // land here, and two loops would double every later attempt.
452
- if (this.stopped || this.timer !== undefined) return
491
+ if (this.stopped || this.shuttingDown || this.timer !== undefined) return
453
492
  // A connection that stayed up long enough closed the previous outage.
454
493
  if (this.connectedAt !== undefined && Date.now() - this.connectedAt >= RECONNECT.maxDelayMs) {
455
494
  this.attempts = 0
@@ -486,12 +525,12 @@ export class FastCtxTools {
486
525
  * @returns {Promise<void>} resolves once this attempt settled.
487
526
  */
488
527
  async #reconnect() {
489
- if (this.stopped) return
528
+ if (this.stopped || this.shuttingDown) return
490
529
  try {
491
530
  await this.#connect()
492
531
  this.report(`dsh-ops: ${this.#describe()} reconnected with ${this.names().length} tool(s).`)
493
532
  } catch (error) {
494
- if (this.stopped) return
533
+ if (this.stopped || this.shuttingDown) return
495
534
  this.report(
496
535
  `dsh-ops: FastCtx reconnect attempt ${this.attempts}/${RECONNECT.maxAttempts} `
497
536
  + `failed (${messageOf(error)}).`,
@@ -521,7 +560,7 @@ export class FastCtxTools {
521
560
  throw new Error(`${this.#describe()} listed the tool "${String(tool?.name)}" more than once`)
522
561
  }
523
562
  seen.add(name)
524
- if (!FILE_TOOLS.includes(tool.name)) continue
563
+ if (!this.fileTools || !FILE_TOOLS.includes(tool.name)) continue
525
564
  definitions.push(toolDefinition({
526
565
  tool,
527
566
  call: (args, exec) => this.#call(tool.name, args, exec),
@@ -556,7 +595,15 @@ export class FastCtxTools {
556
595
  * @param {{signal?: AbortSignal, agent?: {session?: object}}} [exec] - the host execution context.
557
596
  * @returns {Promise<{content: unknown[], structuredContent?: unknown}>} the canonical value.
558
597
  */
559
- async #call(rawName, args, exec) {
598
+ #call(rawName, args, exec) {
599
+ if (this.shuttingDown || this.stopped) return Promise.reject(new Error('This dsh-ops component is shutting down.'))
600
+ const call = this.#callActive(rawName, args, exec)
601
+ this.pendingCalls.add(call)
602
+ void call.then(() => this.pendingCalls.delete(call), () => this.pendingCalls.delete(call))
603
+ return call
604
+ }
605
+
606
+ async #callActive(rawName, args, exec) {
560
607
  const label = publicToolName(rawName)
561
608
  const session = exec?.agent?.session
562
609
  if (SHELL_TOOLS.includes(rawName) && !this.shellAllowed(session)) {
@@ -569,6 +616,7 @@ export class FastCtxTools {
569
616
  throw new Error('This is an image; use host read_image.')
570
617
  }
571
618
  }
619
+ if (rawName === 'run_background') args = { ...args, cwd: args?.cwd ?? session?.header?.cwd }
572
620
  const client = this.client
573
621
  if (client === undefined) {
574
622
  // A dead server must be an immediate, explicit failure rather than a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-ops",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -39,7 +39,22 @@
39
39
  "default": "./lib/policy.js"
40
40
  },
41
41
  "./package.json": "./package.json",
42
- "./locale/*.json": "./locale/*.json"
42
+ "./locale/*.json": "./locale/*.json",
43
+ "./shell": {
44
+ "default": "./lib/components/shell/index.js"
45
+ },
46
+ "./shell/package.json": "./lib/components/shell/package.json",
47
+ "./shell/locale/*.json": "./lib/components/shell/locale/*.json",
48
+ "./file": {
49
+ "default": "./lib/components/file/index.js"
50
+ },
51
+ "./file/package.json": "./lib/components/file/package.json",
52
+ "./file/locale/*.json": "./lib/components/file/locale/*.json",
53
+ "./background": {
54
+ "default": "./lib/components/background/index.js"
55
+ },
56
+ "./background/package.json": "./lib/components/background/package.json",
57
+ "./background/locale/*.json": "./lib/components/background/locale/*.json"
43
58
  },
44
59
  "bin": {
45
60
  "dsh-ops": "bin/dsh-ops.mjs"