dsh-loop-engine-allinone 0.7.0 → 0.7.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/CHANGELOG.md CHANGED
@@ -8,6 +8,68 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
8
8
 
9
9
  Nothing yet.
10
10
 
11
+ ## [0.7.3]
12
+
13
+ ### Fixed
14
+
15
+ - **ZCode's path probe only worked on the author's machine.** The resolver and the
16
+ probe script both hardcoded `D:\Programs\ZCode\...` (and the probe also
17
+ `D:\Programs
18
+ pm-global`), so on any other host the "well-known location" check
19
+ silently never matched and a user with ZCode installed somewhere normal was told it
20
+ was not installed. Both now derive candidates from the environment:
21
+ `%ZCODE_HOME%` → `%LOCALAPPDATA%\Programs\ZCode` → `%ProgramFiles%\ZCode`, and
22
+ the probe's npm prefixes from the running npm configuration and the standard
23
+ per-machine layouts.
24
+ - A test run's probe output directory (`probe-out/`) was picked up by `git add -A`.
25
+ Removed, and ignored from now on.
26
+
27
+ ### Added
28
+
29
+ - **`ZCODE_HOME` is documented**, with a `zcodeBin` key in the configuration example.
30
+ ZCode ships as a desktop bundle rather than a plain executable, so a manual install
31
+ outside an installer-written root needs one of the two to be found — and the README
32
+ previously mentioned neither, which is why the gap went unnoticed.
33
+
34
+ ## [0.7.2]
35
+
36
+ ### Changed
37
+
38
+ - **Installation leads with `--profile web` again.** 0.7.1 made `<your-profile>` the
39
+ primary spelling, on the reasoning that `web` is one of several shipped templates.
40
+ That is true but the wrong shape for an install section: `dsh web` is the harness
41
+ CLI's own documented example (`dsh web` = `dsh --profile web`), and every published
42
+ plugin says `dsh plugin --profile web add ...` — dsh-market included. Forcing a
43
+ placeholder substitution before the first command is friction with no payoff for the
44
+ majority.
45
+
46
+ The non-`web` case is now a caveat section instead: `desktop`, `headless`, and
47
+ self-named profiles get both steps, plus the silent failure that follows from doing
48
+ only the first one. npm / GitHub / local-clone remain the three sources.
49
+
50
+ ## [0.7.1]
51
+
52
+ ### Fixed
53
+
54
+ - **Installation no longer assumes a `web` profile.** `web` is one of several shipped
55
+ templates; the Desktop app boots `desktop`, a CI deployment boots `headless`, and
56
+ many people run a profile they named themselves. The instructions now say
57
+ `<your-profile>` and give the three real sources: npm, GitHub, and a local clone for
58
+ plugin development.
59
+ - **A wrong `profile:` is now diagnosable.** The field defaults to `web`, so forgetting
60
+ it writes the managed block into another profile's patch file while the profile that
61
+ actually boots never reads it — the plugin mounts, the base `agent-loop` row is never
62
+ disabled, and no hosted engine is reachable, with **nothing logged**. The harness
63
+ gives a plugin no way to ask which profile is running, so `apply` now checks whether
64
+ the target directory holds a profile's `package.json` and warns with the field to set.
65
+ It is a warning, not a failure: a first boot may legitimately precede the profile's
66
+ own files.
67
+
68
+ ### Documented
69
+
70
+ - The engine picker appears in whichever profile renders a web UI, not specifically in
71
+ `web`.
72
+
11
73
  ## [0.7.0]
12
74
 
13
75
  ### Added
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  **dsh-loop-engine-allinone** is an all-in-one agent loop engine switcher for **[DeepSeek Harness](https://github.com/deepseek-ai)** (`dsh`). It enables per-session engine switching across **10 hosted engines** (Claude Code, Codex, Pi, Kimi Code, Antigravity, ZCode, Qoder CN, Hermes Agent, Grok Build, MiniMax Code) alongside the built-in `in-process` loop.
9
9
 
10
- > `dsh` is the harness CLI. `dsh web` boots its **web profile**, which is the UI this plugin's engine picker and settings page live in.
10
+ > `dsh` is the harness CLI. `dsh --profile <name>` boots one of its **profiles**, and the engine picker plus this plugin's settings page appear in whichever profile renders a web UI (the shipped `web` template is one; the Desktop app uses `desktop`).
11
11
 
12
12
  Sessions are fully independent: run Codex in one chat while another runs Kimi Code or Antigravity simultaneously. Handover between two hosted engines happens seamlessly in place; switching to or from the native `in-process` engine performs an in-browser reload without restarting the backend process. All without modifying the DeepSeek Harness core codebase.
13
13
 
@@ -33,40 +33,64 @@ Sessions are fully independent: run Codex in one chat while another runs Kimi Co
33
33
  ## Installation
34
34
 
35
35
  ```sh
36
- # From npm (recommended)
37
36
  dsh plugin --profile web add dsh-loop-engine-allinone
38
37
  ```
39
38
 
40
- Then boot the web profile once and open **Settings → Loop engine**:
39
+ Restart `dsh web`, then open **Settings → Loop engine**.
40
+
41
+ ### From GitHub instead of npm
41
42
 
42
43
  ```sh
43
- dsh web
44
+ dsh plugin --profile web add yaliklinty/dsh-loop-engine-allinone
44
45
  ```
45
46
 
46
- Installing into a profile other than `web` (the Desktop app's `desktop`, a
47
- `headless` deployment, a renamed profile) means naming that profile instead:
48
- `dsh plugin --profile desktop add dsh-loop-engine-allinone`, **and** setting
49
- `profile: desktop` in the composition entry — see the notes below, because
50
- forgetting the second half is the one mistake that leaves the plugin mounted but
51
- unable to run any engine.
52
-
53
- ### Installation Notes
54
-
55
- - **Profile Configuration**: The installer automatically injects a managed configuration block into your profile's `cordis.patch.yml`. All other custom configuration in that file is preserved.
56
- - **Non-web Profiles**: When installing into non-web profiles (such as `desktop` or headless), explicitly specify `profile: <name>` in the composition entry:
57
- ```yaml
58
- - id: loop-engine
59
- config:
60
- profile: desktop
61
- ```
62
- *(Without this, configuration defaults to `web` and the plugin will not attach to custom profiles.)*
63
- - **Uninstallation**: Run `dsh plugin --profile web remove dsh-loop-engine-allinone`, then remove the managed `loop-engine` block from `cordis.patch.yml`.
64
- - **pnpm 10+**: If dependency build scripts are blocked (`ERR_PNPM_IGNORED_BUILDS` mentioning `esbuild`, `@google/genai`, etc.), run `pnpm approve-builds`.
65
- - **Harness from Source**: If launching the harness directly from a source checkout, bridge peer dependencies using `file:` shims as described in [docs/source-checkout.md](docs/source-checkout.md).
47
+ ### From a local clone
66
48
 
67
- ---
49
+ Only when you are working on the plugin itself:
68
50
 
69
- ---
51
+ ```sh
52
+ git clone https://github.com/yaliklinty/dsh-loop-engine-allinone.git
53
+ dsh plugin --profile web add ./dsh-loop-engine-allinone
54
+ ```
55
+
56
+ ### Not on the `web` profile?
57
+
58
+ `web` is the profile the harness's own CLI documents (`dsh web` is short for
59
+ `dsh --profile web`), so it is the right default. But the Desktop app boots
60
+ `desktop`, a CI deployment may boot `headless`, and you may have made your own. In
61
+ that case:
62
+
63
+ 1. install with that profile's name — `dsh plugin --profile desktop add
64
+ dsh-loop-engine-allinone`;
65
+ 2. **and** set the same name in the composition entry:
66
+
67
+ ```yaml
68
+ - id: loop-engine
69
+ config:
70
+ profile: desktop
71
+ ```
72
+
73
+ Step 2 is the one that bites. The field defaults to `web`, so omitting it makes the
74
+ plugin write its managed block into `profiles/web/cordis.patch.yml` while the profile
75
+ you actually boot never reads it: the plugin mounts, but the base `agent-loop` row is
76
+ never disabled, the router cannot take the agent-factory slot, and **no hosted engine
77
+ is reachable** — with nothing logged. This release also warns when it detects that
78
+ shape, naming the field to set.
79
+
80
+ ### Installation notes
81
+
82
+ - The installer rewrites one managed block in the profile's `cordis.patch.yml`; every
83
+ other byte you wrote there is preserved, so upgrading needs no hand edit.
84
+ - **Uninstall**: `dsh plugin --profile web remove dsh-loop-engine-allinone`, **then
85
+ delete the managed `loop-engine` block** from that profile's `cordis.patch.yml`. The
86
+ block outlives the plugin, and while it is present the profile has no agent factory,
87
+ so no session can be created.
88
+ - **pnpm 10+** may block dependency build scripts (`ERR_PNPM_IGNORED_BUILDS` naming
89
+ `esbuild`, `@google/genai`, `protobufjs`). Run `pnpm approve-builds`, or add an
90
+ `allowBuilds` entry, and retry — only the installing project can grant this.
91
+ - **Harness from source**: bridge the profile's harness peers with `file:` shims, or
92
+ session resume fails with `agent-presets: refusing to compose an unscoped context`.
93
+ Steps in [docs/source-checkout.md](docs/source-checkout.md).
70
94
 
71
95
  ## Supported Engines
72
96
 
@@ -106,6 +130,7 @@ Custom settings can be configured under your profile's composition entry:
106
130
  qoderclicnBin: 'D:/tools/qoderclicn.exe'
107
131
  grokBin: 'C:/Users/user/.grok/bin/grok.exe'
108
132
  minimaxcodeBin: 'D:/npm-global/node_modules/@minimax-ai/code/cli.js'
133
+ zcodeBin: 'D:/Programs/ZCode/resources/glm/zcode.cjs'
109
134
 
110
135
  # 2. Region override (optional - MiniMax Code supports 'cn' or 'en')
111
136
  regions:
@@ -125,6 +150,12 @@ Custom settings can be configured under your profile's composition entry:
125
150
  summarizerModel: 'deepseek-flash'
126
151
  ```
127
152
 
153
+ **ZCode's install location.** ZCode ships as a desktop bundle rather than a plain
154
+ executable, so it is found by probing an install root: `%ZCODE_HOME%` first, then
155
+ `%LOCALAPPDATA%\Programs\ZCode` and `%ProgramFiles%\ZCode`. A manual install
156
+ anywhere else (`D:\Programs\ZCode`, say) is named either with the `ZCODE_HOME`
157
+ environment variable or with `bins.zcodeBin` — nothing hardcodes a drive letter.
158
+
128
159
  ---
129
160
 
130
161
  ## Usage Guide
package/README.zh.md CHANGED
@@ -7,6 +7,8 @@
7
7
 
8
8
  **dsh-loop-engine-allinone** 是专为 [DeepSeek Harness](https://github.com/deepseek-ai)(`dsh`)打造的 Agent 循环引擎全功能切换插件。支持**按会话独立切换** 10 家主流外部引擎与 Harness 原生 `in-process` 循环。
9
9
 
10
+ > `dsh` 是 harness 的命令行程序;`dsh --profile <名字>` 启动它的某个 **profile**。引擎选择器与本插件的设置页出现在**任何渲染 web UI 的 profile** 里(随附的 `web` 模板是其中之一,桌面版用的是 `desktop`)。
11
+
10
12
  各会话完全隔离互不干扰:一个窗口运行 Codex,另一个窗口可同时运行 Kimi Code 或 Antigravity。托管引擎之间支持**无缝原地热换**;切换至原生 `in-process` 时会自动刷新页面重新挂载(无需重启 `dsh web` 后端进程)。**全程无需修改 DeepSeek Harness 主仓库源码**。
11
13
 
12
14
  > **Fork 说明**:本项目基于 [`kuun993/dsh-loop-engine`](https://github.com/kuun993/dsh-loop-engine) 进行深度重构与扩展。
@@ -30,42 +32,59 @@
30
32
 
31
33
  ## 安装说明
32
34
 
33
- 通过 `dsh plugin` 命令行直接安装:
34
-
35
35
  ```sh
36
- # 从 npm 安装(推荐)
37
36
  dsh plugin --profile web add dsh-loop-engine-allinone
38
37
  ```
39
38
 
40
- 然后启动一次 web profile,打开 **Settings → Loop engine**:
39
+ 重启 `dsh web`,打开 **Settings → Loop engine**。
40
+
41
+ ### 想从 GitHub 装而不是 npm
41
42
 
42
43
  ```sh
43
- dsh web
44
+ dsh plugin --profile web add yaliklinty/dsh-loop-engine-allinone
44
45
  ```
45
46
 
46
- > `dsh` 是 harness 的命令行程序;`dsh web` 启动它的 **web profile**——本插件的引擎选择器与设置页就在那个界面里。
47
+ ### 从本地克隆安装
47
48
 
48
- 装到 `web` 以外的 profile(桌面版的 `desktop`、`headless` 部署、改过名的 profile)时,
49
- 要把 `--profile web` 换成对应名字,**并且**在组合条目里写 `profile: <名字>`。
50
- 只做前一半是本插件最容易犯的错:插件挂上了,却一个引擎都跑不起来。详见下方安装须知。
49
+ 只在开发插件本身时才需要:
51
50
 
52
- ### 安装注意事项
51
+ ```sh
52
+ git clone https://github.com/yaliklinty/dsh-loop-engine-allinone.git
53
+ dsh plugin --profile web add ./dsh-loop-engine-allinone
54
+ ```
53
55
 
54
- - **Profile 自动配置**:安装时会自动在对应 Profile 的 `cordis.patch.yml` 中写入一段受管配置块,原有配置内容均逐字节安全保留。
55
- - **非 Web Profile 部署**:若安装至桌面版 `desktop` 或 headless 等自定义 profile,需在配置中显式指定 `profile: <name>`:
56
- ```yaml
57
- - id: loop-engine
58
- config:
59
- profile: desktop
60
- ```
61
- *(若未指定,插件默认挂载到 `web` profile,导致自定义 profile 无法生效。)*
62
- - **卸载插件**:执行 `dsh plugin --profile web remove dsh-loop-engine-allinone`,并手动清理 `cordis.patch.yml` 中残留的 `loop-engine` 受管块。
63
- - **pnpm 10+ 构建拦截**:若遇到 `ERR_PNPM_IGNORED_BUILDS`(提示 `esbuild`、`@google/genai` 等构建被忽略),执行 `pnpm approve-builds` 允许即可。
64
- - **源码模式运行 Harness**:若通过源码启动 Harness,需按照 [docs/source-checkout.md](docs/source-checkout.md) 说明配置 `file:` shim 桥接依赖包。
56
+ ### 不是 `web` profile?
65
57
 
66
- ---
58
+ `web` 是 harness 自己的 CLI 文档里用的那个 profile(`dsh web` 就是 `dsh --profile web`
59
+ 的简写),所以它是正确的默认值。但桌面版启动的是 `desktop`,CI 部署可能用 `headless`,
60
+ 你也可能自己建过一个。这种情况下:
67
61
 
68
- ---
62
+ 1. 用那个 profile 的名字安装 —— `dsh plugin --profile desktop add dsh-loop-engine-allinone`;
63
+ 2. **并且**在组合条目里写上同一个名字:
64
+
65
+ ```yaml
66
+ - id: loop-engine
67
+ config:
68
+ profile: desktop
69
+ ```
70
+
71
+ **第 2 步才是会咬人的地方。** 该字段默认是 `web`,漏写会让插件把受管块写进
72
+ `profiles/web/cordis.patch.yml`,而你实际启动的 profile 永远读不到它:插件挂上了,
73
+ 但基础 `agent-loop` 行没被禁,路由器拿不到 agent factory 槽位,**任何托管引擎都不可达**
74
+ ——而且不报错。本版本会在检测到这种情形时告警,并指出该设哪个字段。
75
+
76
+ ### 安装注意事项
77
+
78
+ - 安装会重写该 profile `cordis.patch.yml` 里的一段受管块;你写的其它内容逐字节保留,升级不需要手工改。
79
+ - **卸载**:`dsh plugin --profile web remove dsh-loop-engine-allinone`,**然后手动删掉**
80
+ 该 profile `cordis.patch.yml` 里的 `loop-engine` 受管块。块会比插件活得久,而它还在时
81
+ profile 里没有任何 agent factory,会话一条也建不出来。
82
+ - **pnpm 10+** 可能拦下依赖的 build script(`ERR_PNPM_IGNORED_BUILDS`,列出 `esbuild`、
83
+ `@google/genai`、`protobufjs`)。执行 `pnpm approve-builds`,或加一条 `allowBuilds`,然后重试
84
+ ——只有安装方能授予这个权限。
85
+ - **用源码启动 harness** 时,要用 `file:` shim 把 profile 的 harness peer 包桥接到 checkout 源码,
86
+ 否则恢复会话会报 `agent-presets: refusing to compose an unscoped context`。
87
+ 步骤见 [docs/source-checkout.md](docs/source-checkout.md)。
69
88
 
70
89
  ## 支持引擎一览
71
90
 
@@ -105,6 +124,7 @@ dsh web
105
124
  qoderclicnBin: 'D:/tools/qoderclicn.exe'
106
125
  grokBin: 'C:/Users/username/.grok/bin/grok.exe'
107
126
  minimaxcodeBin: 'D:/npm-global/node_modules/@minimax-ai/code/cli.js'
127
+ zcodeBin: 'D:/Programs/ZCode/resources/glm/zcode.cjs'
108
128
 
109
129
  # 2. 账号区域设置(可选,MiniMax Code 支持 'cn' 或 'en')
110
130
  regions:
package/lib/index.js CHANGED
@@ -89,10 +89,10 @@ var __callDispose = (stack, error, hasError) => {
89
89
  };
90
90
 
91
91
  // src/index.ts
92
- import { readFileSync as readFileSync5 } from "node:fs";
92
+ import { existsSync as existsSync8, readFileSync as readFileSync5 } from "node:fs";
93
93
  import { mkdir as mkdir2, readFile as readFile6, rename as rename2, writeFile as writeFile2 } from "node:fs/promises";
94
94
  import { randomUUID as randomUUID3 } from "node:crypto";
95
- import { dirname as dirname7, join as join21 } from "node:path";
95
+ import { dirname as dirname7, join as join22 } from "node:path";
96
96
  import z12 from "@deepseek-ai/schemastery";
97
97
  import { resolveDshHome as resolveDshHome2 } from "@deepseek-ai/dsh-home-paths";
98
98
 
@@ -8669,11 +8669,27 @@ function textRunOutcome(result) {
8669
8669
 
8670
8670
  // src/engine-zcode/process.ts
8671
8671
  import { existsSync as existsSync3 } from "node:fs";
8672
+ import { join as join7 } from "node:path";
8672
8673
  var ZCODE_DISPOSE_GRACE_MS = 3e3;
8673
- function wellKnownZcode() {
8674
- if (process.platform !== "win32") return void 0;
8675
- const candidate = "D:ProgramsZCode\resourcesglmzcode.cjs";
8676
- return existsSync3(candidate) ? candidate : void 0;
8674
+ function zcodeInstallRoots(env = process.env) {
8675
+ const roots = [];
8676
+ if (env.ZCODE_HOME !== void 0 && env.ZCODE_HOME !== "") roots.push(env.ZCODE_HOME);
8677
+ if (env.LOCALAPPDATA !== void 0 && env.LOCALAPPDATA !== "") {
8678
+ roots.push(join7(env.LOCALAPPDATA, "Programs", "ZCode"));
8679
+ }
8680
+ if (process.platform === "win32" && env.ProgramFiles !== void 0 && env.ProgramFiles !== "") {
8681
+ roots.push(join7(env.ProgramFiles, "ZCode"));
8682
+ }
8683
+ return roots;
8684
+ }
8685
+ function zcodeCliCandidates(env = process.env) {
8686
+ return zcodeInstallRoots(env).map((root) => join7(root, "resources", "glm", "zcode.cjs"));
8687
+ }
8688
+ function wellKnownZcode(env = process.env) {
8689
+ for (const candidate of zcodeCliCandidates(env)) {
8690
+ if (existsSync3(candidate)) return candidate;
8691
+ }
8692
+ return void 0;
8677
8693
  }
8678
8694
  function zcodeArgvPrefix(configBin) {
8679
8695
  const pinned = configBin === void 0 || configBin === "" ? wellKnownZcode() : configBin;
@@ -8681,9 +8697,14 @@ function zcodeArgvPrefix(configBin) {
8681
8697
  return /\.(?:c|m)?js$/i.test(pinned) ? [process.execPath, pinned] : [pinned];
8682
8698
  }
8683
8699
  var ZCODE_PROVIDER_CONFIG_ENV = "ZCODE_BUILTIN_PROVIDER_CONFIG_FILE";
8684
- function zcodeBuiltinProviderConfig() {
8685
- const candidate = "D:ProgramsZCode\resourcesconfigproviderzcode-builtin.json";
8686
- return existsSync3(candidate) ? candidate : void 0;
8700
+ function zcodeProviderConfigCandidates(env = process.env) {
8701
+ return zcodeInstallRoots(env).map((root) => join7(root, "resources", "config", "provider", "zcode-builtin.json"));
8702
+ }
8703
+ function zcodeBuiltinProviderConfig(env = process.env) {
8704
+ for (const candidate of zcodeProviderConfigCandidates(env)) {
8705
+ if (existsSync3(candidate)) return candidate;
8706
+ }
8707
+ return void 0;
8687
8708
  }
8688
8709
  function zcodeMode(dangerFullAccess) {
8689
8710
  return dangerFullAccess ? "yolo" : "build";
@@ -8883,16 +8904,16 @@ function mapStreamJsonDocument(document, accept, run) {
8883
8904
  // src/engine-qoderclicn/process.ts
8884
8905
  import { existsSync as existsSync4 } from "node:fs";
8885
8906
  import { homedir as homedir2 } from "node:os";
8886
- import { join as join7 } from "node:path";
8907
+ import { join as join8 } from "node:path";
8887
8908
  var QODERCLICN_DISPOSE_GRACE_MS = 3e3;
8888
8909
  function qoderclicnHomeDir() {
8889
8910
  const envHome = process.env.QODER_HOME;
8890
- return envHome !== void 0 && envHome !== "" ? envHome : join7(homedir2(), ".qoder-cn");
8911
+ return envHome !== void 0 && envHome !== "" ? envHome : join8(homedir2(), ".qoder-cn");
8891
8912
  }
8892
8913
  function qoderclicnBinResolver(configBin) {
8893
8914
  if (configBin !== void 0 && configBin !== "") return configBin;
8894
8915
  const executable = process.platform === "win32" ? "qoderclicn.exe" : "qoderclicn";
8895
- const candidate = join7(qoderclicnHomeDir(), "bin", "qoderclicn", executable);
8916
+ const candidate = join8(qoderclicnHomeDir(), "bin", "qoderclicn", executable);
8896
8917
  return existsSync4(candidate) ? candidate : "qoderclicn";
8897
8918
  }
8898
8919
  function qoderclicnPermissionMode(sandbox, approval) {
@@ -9021,14 +9042,14 @@ import z8 from "@deepseek-ai/schemastery";
9021
9042
 
9022
9043
  // src/engine-hermes/process.ts
9023
9044
  import { existsSync as existsSync5 } from "node:fs";
9024
- import { delimiter, join as join8 } from "node:path";
9045
+ import { delimiter, join as join9 } from "node:path";
9025
9046
  var HERMES_DISPOSE_GRACE_MS = 3e3;
9026
9047
  var HERMES_PROMPT_FLAG = "-z";
9027
9048
  function wellKnownHermes() {
9028
9049
  if (process.platform !== "win32") return void 0;
9029
9050
  const local = process.env.LOCALAPPDATA;
9030
9051
  if (local === void 0 || local === "") return void 0;
9031
- const candidate = join8(local, "hermes", "hermes-agent", "venv", "Scripts", "hermes.exe");
9052
+ const candidate = join9(local, "hermes", "hermes-agent", "venv", "Scripts", "hermes.exe");
9032
9053
  return existsSync5(candidate) ? candidate : void 0;
9033
9054
  }
9034
9055
  function hermesBinResolver(configBin) {
@@ -9126,11 +9147,11 @@ import z9 from "@deepseek-ai/schemastery";
9126
9147
  // src/engine-grok/process.ts
9127
9148
  import { existsSync as existsSync7 } from "node:fs";
9128
9149
  import { homedir as homedir3 } from "node:os";
9129
- import { join as join10 } from "node:path";
9150
+ import { join as join11 } from "node:path";
9130
9151
 
9131
9152
  // src/driver-acp/executable.ts
9132
9153
  import { existsSync as existsSync6 } from "node:fs";
9133
- import { join as join9 } from "node:path";
9154
+ import { join as join10 } from "node:path";
9134
9155
  function execArgvPrefix(bin) {
9135
9156
  if (/\.(?:c|m)?js$/i.test(bin)) return [process.execPath, bin];
9136
9157
  if (/\.(?:cmd|bat)$/i.test(bin)) return [process.env.ComSpec ?? "cmd.exe", "/c", bin];
@@ -9144,7 +9165,7 @@ function resolveOnPath(name2, env = process.env) {
9144
9165
  for (const directory of path.split(separator)) {
9145
9166
  if (directory === "") continue;
9146
9167
  for (const extension of extensions) {
9147
- const candidate = join9(directory, `${name2}${extension}`);
9168
+ const candidate = join10(directory, `${name2}${extension}`);
9148
9169
  if (existsSync6(candidate)) return candidate;
9149
9170
  }
9150
9171
  }
@@ -9155,8 +9176,8 @@ function npmPackageEntry(prefix, pkg, entry) {
9155
9176
  const scope = scoped ? pkg.slice(0, pkg.indexOf("/")) : "";
9156
9177
  const name2 = scoped ? pkg.slice(pkg.indexOf("/") + 1) : pkg;
9157
9178
  if (name2 === "" || scoped && scope === "") return void 0;
9158
- const relative = scoped ? join9("node_modules", scope, name2, entry) : join9("node_modules", name2, entry);
9159
- for (const candidate of [join9(prefix, relative), join9(prefix, "lib", relative)]) {
9179
+ const relative = scoped ? join10("node_modules", scope, name2, entry) : join10("node_modules", name2, entry);
9180
+ for (const candidate of [join10(prefix, relative), join10(prefix, "lib", relative)]) {
9160
9181
  if (existsSync6(candidate)) return candidate;
9161
9182
  }
9162
9183
  return void 0;
@@ -9166,12 +9187,12 @@ function npmPackageEntry(prefix, pkg, entry) {
9166
9187
  var GROK_DISPOSE_GRACE_MS = 3e3;
9167
9188
  function grokHomeDir() {
9168
9189
  const envHome = process.env.GROK_HOME;
9169
- return envHome !== void 0 && envHome !== "" ? envHome : join10(homedir3(), ".grok");
9190
+ return envHome !== void 0 && envHome !== "" ? envHome : join11(homedir3(), ".grok");
9170
9191
  }
9171
9192
  function grokBinResolver(configBin) {
9172
9193
  if (configBin !== void 0 && configBin !== "") return configBin;
9173
9194
  const executable = process.platform === "win32" ? "grok.exe" : "grok";
9174
- const candidate = join10(grokHomeDir(), "bin", executable);
9195
+ const candidate = join11(grokHomeDir(), "bin", executable);
9175
9196
  if (existsSync7(candidate)) return candidate;
9176
9197
  return resolveOnPath("grok") ?? "grok";
9177
9198
  }
@@ -9276,7 +9297,7 @@ import z10 from "@deepseek-ai/schemastery";
9276
9297
 
9277
9298
  // src/engine-minimaxcode/process.ts
9278
9299
  import { homedir as homedir4 } from "node:os";
9279
- import { dirname as dirname3, join as join11 } from "node:path";
9300
+ import { dirname as dirname3, join as join12 } from "node:path";
9280
9301
  var MINIMAXCODE_DISPOSE_GRACE_MS = 3e3;
9281
9302
  var MINIMAXCODE_NPM_PACKAGE = "@minimax-ai/code";
9282
9303
  var MINIMAXCODE_ENTRYPOINT = "cli.js";
@@ -9284,7 +9305,7 @@ function mcodeBinResolver(configBin, env = process.env) {
9284
9305
  if (configBin !== void 0 && configBin !== "") return configBin;
9285
9306
  const prefixes = [
9286
9307
  env.MCODE_INSTALL_ROOT,
9287
- env.APPDATA === void 0 ? void 0 : join11(env.APPDATA, "npm"),
9308
+ env.APPDATA === void 0 ? void 0 : join12(env.APPDATA, "npm"),
9288
9309
  // A standard Windows node install puts the global prefix beside node.exe.
9289
9310
  dirname3(process.execPath),
9290
9311
  dirname3(dirname3(process.execPath)),
@@ -9293,7 +9314,7 @@ function mcodeBinResolver(configBin, env = process.env) {
9293
9314
  // `HOME`, and the whole point of this resolver is to find an install the
9294
9315
  // inherited environment may not describe.
9295
9316
  "/usr/local",
9296
- join11(homedir4(), ".npm-global")
9317
+ join12(homedir4(), ".npm-global")
9297
9318
  ];
9298
9319
  for (const prefix of prefixes) {
9299
9320
  if (prefix === void 0 || prefix === "") continue;
@@ -9593,7 +9614,7 @@ ${block}`);
9593
9614
  // src/preset.ts
9594
9615
  import { mkdir, readFile as readFile2, rename, writeFile } from "node:fs/promises";
9595
9616
  import { randomUUID } from "node:crypto";
9596
- import { dirname as dirname4, join as join12 } from "node:path";
9617
+ import { dirname as dirname4, join as join13 } from "node:path";
9597
9618
  var USER_PRESET_DIR = ".agent-presets";
9598
9619
  var COMPOSITION_FILE = "agent.cordis.yml";
9599
9620
  var METADATA_FILE = "preset.yml";
@@ -9793,9 +9814,9 @@ async function ensureEnginePresets(dshHome, source, target) {
9793
9814
  ${applySummarizerTarget(stripPresetRows(composition), target)}`;
9794
9815
  let changed = false;
9795
9816
  for (const engine of HOSTED_ENGINE_IDS) {
9796
- const dir = join12(dshHome, USER_PRESET_DIR, enginePresetId(engine));
9797
- const compositionChanged = await writeIfDifferent(join12(dir, COMPOSITION_FILE), stripped);
9798
- const metadataChanged = await writeIfDifferent(join12(dir, METADATA_FILE), managedMetadata(engine));
9817
+ const dir = join13(dshHome, USER_PRESET_DIR, enginePresetId(engine));
9818
+ const compositionChanged = await writeIfDifferent(join13(dir, COMPOSITION_FILE), stripped);
9819
+ const metadataChanged = await writeIfDifferent(join13(dir, METADATA_FILE), managedMetadata(engine));
9799
9820
  changed = changed || compositionChanged || metadataChanged;
9800
9821
  }
9801
9822
  return changed;
@@ -10051,7 +10072,7 @@ function usableSelection(answered) {
10051
10072
  // src/commands.ts
10052
10073
  import { readdirSync, readFileSync as readFileSync3 } from "node:fs";
10053
10074
  import { homedir as homedir5 } from "node:os";
10054
- import { join as join13 } from "node:path";
10075
+ import { join as join14 } from "node:path";
10055
10076
  import { createUserMessage as createUserMessage6 } from "@deepseek-ai/dsh-llm";
10056
10077
  var COMMAND_NAME = /^[a-z][a-z0-9_-]*$/;
10057
10078
  function forwardClaudeCodeCommand(name2) {
@@ -10085,7 +10106,7 @@ function discoverUserSlashCommands() {
10085
10106
  if (!entry.endsWith(".md")) continue;
10086
10107
  const name2 = entry.slice(0, -".md".length);
10087
10108
  if (!COMMAND_NAME.test(name2) || seen.has(name2)) continue;
10088
- const path = join13(userCommandsDir(), entry);
10109
+ const path = join14(userCommandsDir(), entry);
10089
10110
  let raw;
10090
10111
  try {
10091
10112
  raw = readFileSync3(path, "utf8");
@@ -10100,7 +10121,7 @@ function discoverUserSlashCommands() {
10100
10121
  return definitions;
10101
10122
  }
10102
10123
  function userCommandsDir() {
10103
- return join13(homedir5(), ".claude", "commands");
10124
+ return join14(homedir5(), ".claude", "commands");
10104
10125
  }
10105
10126
  function commandDescription(raw) {
10106
10127
  const trimmed = raw.trim();
@@ -10151,7 +10172,7 @@ var KIMI_COMMANDS = [
10151
10172
  // src/skills.ts
10152
10173
  import { readFile as readFile3, readdir, stat } from "node:fs/promises";
10153
10174
  import { homedir as homedir6 } from "node:os";
10154
- import { join as join14, resolve } from "node:path";
10175
+ import { join as join15, resolve } from "node:path";
10155
10176
  var SKILL_NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
10156
10177
  var PROVIDER_NAME = "claude-code";
10157
10178
  var CLAUDE_CODE_RANK = 150;
@@ -10261,10 +10282,10 @@ var ClaudeCodeSkillProvider = class {
10261
10282
  const cwd = options.cwd;
10262
10283
  if (cwd !== void 0) {
10263
10284
  const projectRoot = await findProjectRoot(resolve(cwd));
10264
- await collectSkillsDir(join14(projectRoot, ".claude", "skills"), CLAUDE_CODE_RANK, candidates);
10285
+ await collectSkillsDir(join15(projectRoot, ".claude", "skills"), CLAUDE_CODE_RANK, candidates);
10265
10286
  await collectClaudeMd(projectRoot, candidates);
10266
10287
  }
10267
- await collectSkillsDir(join14(homedir6(), ".claude", "skills"), CLAUDE_CODE_USER_RANK, candidates);
10288
+ await collectSkillsDir(join15(homedir6(), ".claude", "skills"), CLAUDE_CODE_USER_RANK, candidates);
10268
10289
  if (this.control.signal.aborted) return [];
10269
10290
  return candidates;
10270
10291
  }
@@ -10298,11 +10319,11 @@ async function collectSkillsDir(skillsDir, rank, candidates) {
10298
10319
  return;
10299
10320
  }
10300
10321
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
10301
- const entryPath = join14(skillsDir, entry.name);
10322
+ const entryPath = join15(skillsDir, entry.name);
10302
10323
  const info = await stat(entryPath).catch(() => void 0);
10303
10324
  if (info === void 0) continue;
10304
10325
  if (info.isDirectory()) {
10305
- const path = join14(entryPath, "SKILL.md");
10326
+ const path = join15(entryPath, "SKILL.md");
10306
10327
  const skill2 = await tryParseSkill(path);
10307
10328
  if (skill2 === void 0) continue;
10308
10329
  candidates.push(toCandidate(skill2, path, rank, entryPath));
@@ -10315,7 +10336,7 @@ async function collectSkillsDir(skillsDir, rank, candidates) {
10315
10336
  }
10316
10337
  }
10317
10338
  async function collectClaudeMd(projectRoot, candidates) {
10318
- const claudeMd = join14(projectRoot, "CLAUDE.md");
10339
+ const claudeMd = join15(projectRoot, "CLAUDE.md");
10319
10340
  try {
10320
10341
  const info = await stat(claudeMd);
10321
10342
  if (!info.isFile()) return;
@@ -10352,7 +10373,7 @@ async function findProjectRoot(cwd) {
10352
10373
  let current = cwd;
10353
10374
  while (true) {
10354
10375
  try {
10355
- await stat(join14(current, ".git"));
10376
+ await stat(join15(current, ".git"));
10356
10377
  return current;
10357
10378
  } catch {
10358
10379
  }
@@ -10364,15 +10385,15 @@ async function findProjectRoot(cwd) {
10364
10385
 
10365
10386
  // src/engine-codex/skills.ts
10366
10387
  import { homedir as homedir7 } from "node:os";
10367
- import { join as join17 } from "node:path";
10388
+ import { join as join18 } from "node:path";
10368
10389
 
10369
10390
  // src/driver-core/agents-md-skill-provider.ts
10370
10391
  import { readdir as readdir2, readFile as readFile5, stat as stat3 } from "node:fs/promises";
10371
- import { dirname as dirname5, join as join16 } from "node:path";
10392
+ import { dirname as dirname5, join as join17 } from "node:path";
10372
10393
 
10373
10394
  // src/driver-core/context-files.ts
10374
10395
  import { readFile as readFile4, stat as stat2 } from "node:fs/promises";
10375
- import { join as join15, resolve as resolve2 } from "node:path";
10396
+ import { join as join16, resolve as resolve2 } from "node:path";
10376
10397
  async function projectAncestors(cwd) {
10377
10398
  const root = await findProjectRoot(resolve2(cwd));
10378
10399
  const dirs = [];
@@ -10393,11 +10414,11 @@ async function collectProjectContextFiles(cwd, policy) {
10393
10414
  }
10394
10415
  async function dirContextFile(dir, policy) {
10395
10416
  if (policy.override !== void 0) {
10396
- const override = join15(dir, policy.override);
10417
+ const override = join16(dir, policy.override);
10397
10418
  if (await pathExists(override)) return override;
10398
10419
  }
10399
10420
  for (const name2 of policy.primary) {
10400
- const candidate = join15(dir, name2);
10421
+ const candidate = join16(dir, name2);
10401
10422
  if (await pathExists(candidate)) return candidate;
10402
10423
  }
10403
10424
  return void 0;
@@ -10457,18 +10478,18 @@ var AgentsMdSkillProvider = class {
10457
10478
  if (this.spec.skills !== void 0) {
10458
10479
  const catalog = this.spec.skills;
10459
10480
  for (const dir of await projectAncestors(cwd)) {
10460
- await this.collectSkillsDir(join16(dir, ...catalog.project), catalog.projectRank, candidates);
10481
+ await this.collectSkillsDir(join17(dir, ...catalog.project), catalog.projectRank, candidates);
10461
10482
  }
10462
10483
  }
10463
10484
  }
10464
10485
  const userDir = this.spec.userDir();
10465
10486
  const userContext = this.spec.userContext;
10466
10487
  if (userContext !== void 0) {
10467
- const userFile = join16(userDir, userContext.file);
10488
+ const userFile = join17(userDir, userContext.file);
10468
10489
  if (await fileNonEmpty(userFile)) candidates.push(this.agentsCandidate([userFile], userContext.rank));
10469
10490
  }
10470
10491
  if (this.spec.skills !== void 0) {
10471
- await this.collectSkillsDir(join16(userDir, this.spec.skills.userDir), this.spec.skills.userRank, candidates);
10492
+ await this.collectSkillsDir(join17(userDir, this.spec.skills.userDir), this.spec.skills.userRank, candidates);
10472
10493
  }
10473
10494
  if (this.control.signal.aborted) return [];
10474
10495
  return candidates;
@@ -10528,11 +10549,11 @@ var AgentsMdSkillProvider = class {
10528
10549
  return;
10529
10550
  }
10530
10551
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
10531
- const entryPath = join16(skillsDir, entry.name);
10552
+ const entryPath = join17(skillsDir, entry.name);
10532
10553
  const info = await stat3(entryPath).catch(() => void 0);
10533
10554
  if (info === void 0) continue;
10534
10555
  if (info.isDirectory()) {
10535
- const path = join16(entryPath, "SKILL.md");
10556
+ const path = join17(entryPath, "SKILL.md");
10536
10557
  const parsed2 = await this.tryParse(path);
10537
10558
  if (parsed2 === void 0) continue;
10538
10559
  candidates.push(this.skillCandidate(parsed2, path, rank, entryPath));
@@ -10578,7 +10599,7 @@ var CODEX_SPEC = {
10578
10599
  name: "codex",
10579
10600
  agentsMdDescription: "Codex project/user instructions (AGENTS.md)",
10580
10601
  contextPolicy: CODEX_CONTEXT_POLICY,
10581
- userDir: () => join17(homedir7(), ".codex"),
10602
+ userDir: () => join18(homedir7(), ".codex"),
10582
10603
  projectRank: CODEX_PROJECT_RANK,
10583
10604
  userContext: { file: "AGENTS.md", rank: CODEX_USER_RANK }
10584
10605
  };
@@ -10590,7 +10611,7 @@ var CodexSkillProvider = class extends AgentsMdSkillProvider {
10590
10611
 
10591
10612
  // src/engine-pi/skills.ts
10592
10613
  import { homedir as homedir8 } from "node:os";
10593
- import { join as join18, resolve as resolve3 } from "node:path";
10614
+ import { join as join19, resolve as resolve3 } from "node:path";
10594
10615
  var PI_AGENTS_PROJECT_RANK = 140;
10595
10616
  var PI_SKILL_PROJECT_RANK = 150;
10596
10617
  var PI_AGENTS_USER_RANK = 160;
@@ -10602,7 +10623,7 @@ var PI_CONTEXT_POLICY = {
10602
10623
  function piAgentDir2() {
10603
10624
  const override = process.env.PI_CODING_AGENT_DIR;
10604
10625
  if (override !== void 0 && override.length > 0) return resolve3(override);
10605
- return join18(homedir8(), ".pi", "agent");
10626
+ return join19(homedir8(), ".pi", "agent");
10606
10627
  }
10607
10628
  var PI_SPEC = {
10608
10629
  name: "pi",
@@ -10626,7 +10647,7 @@ var PiSkillProvider = class extends AgentsMdSkillProvider {
10626
10647
 
10627
10648
  // src/engine-kimi/skills.ts
10628
10649
  import { homedir as homedir9 } from "node:os";
10629
- import { join as join19, resolve as resolve4 } from "node:path";
10650
+ import { join as join20, resolve as resolve4 } from "node:path";
10630
10651
  var KIMI_AGENTS_PROJECT_RANK = 140;
10631
10652
  var KIMI_SKILL_PROJECT_RANK = 150;
10632
10653
  var KIMI_SKILL_USER_RANK = 160;
@@ -10636,7 +10657,7 @@ var KIMI_CONTEXT_POLICY = {
10636
10657
  function kimiAgentDir() {
10637
10658
  const override = process.env.KIMI_CODE_HOME;
10638
10659
  if (override !== void 0 && override.length > 0) return resolve4(override);
10639
- return join19(homedir9(), ".kimi-code");
10660
+ return join20(homedir9(), ".kimi-code");
10640
10661
  }
10641
10662
  var KIMI_SPEC = {
10642
10663
  name: "kimi",
@@ -11257,13 +11278,13 @@ function handoverOf(handle) {
11257
11278
  // src/session-engine-store.ts
11258
11279
  import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, renameSync, writeFileSync as writeFileSync2 } from "node:fs";
11259
11280
  import { randomUUID as randomUUID2 } from "node:crypto";
11260
- import { dirname as dirname6, join as join20 } from "node:path";
11281
+ import { dirname as dirname6, join as join21 } from "node:path";
11261
11282
  import { resolveDshHome } from "@deepseek-ai/dsh-home-paths";
11262
11283
  var ENGINE_RECORD_DIR = ".loop-engine";
11263
11284
  var ENGINE_RECORD_FILE = "engines.json";
11264
11285
  var ENGINE_RECORD_VERSION = 1;
11265
11286
  function resolveEngineRecordPath() {
11266
- return join20(resolveDshHome(), ENGINE_RECORD_DIR, ENGINE_RECORD_FILE);
11287
+ return join21(resolveDshHome(), ENGINE_RECORD_DIR, ENGINE_RECORD_FILE);
11267
11288
  }
11268
11289
  function isMissing(error) {
11269
11290
  return error?.code === "ENOENT";
@@ -11418,7 +11439,7 @@ var Config11 = z12.object({
11418
11439
  });
11419
11440
  function resolvePatchPath(config) {
11420
11441
  if (config.patchPath !== void 0 && config.patchPath !== "") return config.patchPath;
11421
- return join21(
11442
+ return join22(
11422
11443
  resolveDshHome2(),
11423
11444
  "profiles",
11424
11445
  config.profile ?? "web",
@@ -11476,9 +11497,18 @@ function readPatchFileSync(path) {
11476
11497
  throw error;
11477
11498
  }
11478
11499
  }
11500
+ function warnIfProfileLooksWrong(ctx, config, patchPath) {
11501
+ const profileDir = dirname7(patchPath);
11502
+ if (existsSync8(join22(profileDir, "package.json"))) return;
11503
+ const named = config.profile ?? "web";
11504
+ ctx.logger.warn(
11505
+ `loop-engine: profile "${named}" has no package.json beside "${patchPath}". If you boot a different profile, set \`profile: <that-name>\` in this plugin's composition entry - otherwise its managed block is written here, the profile you actually boot never reads it, and no hosted engine will be reachable.`
11506
+ );
11507
+ }
11479
11508
  function apply(ctx, config) {
11480
11509
  const summarizer = resolveSummarizerTarget(config);
11481
11510
  const patchPath = resolvePatchPath(config);
11511
+ warnIfProfileLooksWrong(ctx, config, patchPath);
11482
11512
  const patchText = readPatchFileSync(patchPath);
11483
11513
  const legacyEngine = legacyBlockEngineOf(patchText);
11484
11514
  const needsWrite = hasLegacyManagedBlock(patchText) || !hasManagedBlock(patchText);
@@ -42,6 +42,29 @@ export declare const ZCODE_DISPOSE_GRACE_MS = 3000;
42
42
  export declare const ZCODE_MODES: readonly ["build", "edit", "plan", "yolo"];
43
43
  /** One ZCode permission mode. */
44
44
  export type ZcodeMode = (typeof ZCODE_MODES)[number];
45
+ /**
46
+ * The install roots a ZCode desktop build may sit under, most specific first.
47
+ *
48
+ * One list, used by both the CLI lookup and the provider-catalog lookup, so the two
49
+ * can never disagree about where an install is. No drive letter is assumed: the fork
50
+ * was written against one machine's `D:\Programs\ZCode`, and `%ZCODE_HOME%` is how an
51
+ * operator names a manual install like that one.
52
+ * @param env - the environment to read the roots from.
53
+ * @returns candidate roots.
54
+ */
55
+ export declare function zcodeInstallRoots(env?: NodeJS.ProcessEnv): readonly string[];
56
+ /**
57
+ * Every place a ZCode desktop install may keep its bundled CLI, in probe order.
58
+ *
59
+ * Derived from the environment rather than hardcoded: the fork was written against
60
+ * one machine's `D:\Programs\ZCode`, and shipping that literal meant the probe
61
+ * never matched anywhere else. `%LOCALAPPDATA%\Programs` is where the installer puts
62
+ * a per-user app, `%ProgramFiles%` where an all-users install goes, and
63
+ * `%ZCODE_HOME%` is the app's own override.
64
+ * @param env - the environment to read install roots from.
65
+ * @returns candidate paths for the bundled CLI, most specific first.
66
+ */
67
+ export declare function zcodeCliCandidates(env?: NodeJS.ProcessEnv): readonly string[];
45
68
  /**
46
69
  * Resolve the argv prefix that launches ZCode.
47
70
  *
@@ -63,8 +86,18 @@ export declare function zcodeArgvPrefix(configBin?: string): string[];
63
86
  * only when the deployment has not — an explicit deployment value always wins.
64
87
  */
65
88
  export declare const ZCODE_PROVIDER_CONFIG_ENV = "ZCODE_BUILTIN_PROVIDER_CONFIG_FILE";
66
- /** The bundled provider catalog inside the desktop app, when it exists. */
67
- export declare function zcodeBuiltinProviderConfig(): string | undefined;
89
+ /**
90
+ * Every place a ZCode install may keep the provider catalog its bundle needs.
91
+ * @param env - the environment to read install roots from.
92
+ * @returns candidate paths, most specific first.
93
+ */
94
+ export declare function zcodeProviderConfigCandidates(env?: NodeJS.ProcessEnv): readonly string[];
95
+ /**
96
+ * The bundled provider catalog inside the desktop app, when it exists.
97
+ * @param env - the environment to probe.
98
+ * @returns the catalog path, or undefined when no install was found.
99
+ */
100
+ export declare function zcodeBuiltinProviderConfig(env?: NodeJS.ProcessEnv): string | undefined;
68
101
  /**
69
102
  * Map the session's dsh knobs onto ZCode's `--mode`.
70
103
  *
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-loop-engine-allinone",
3
3
  "description": "Web-switchable agent loop engine selection for the DeepSeek Harness - out-of-tree plugin with a data-driven engine registry and a pluggable driver layer. Ten engines: Claude Code / Codex / Pi / Kimi Code / Antigravity / ZCode / Qoder CN / Hermes / Grok Build / MiniMax Code. All-in-one fork of kuun993/dsh-loop-engine.",
4
- "version": "0.7.0",
4
+ "version": "0.7.3",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/"
7
7
  },