dsh-loop-engine-allinone 0.7.3 → 0.7.5

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,14 +8,85 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
8
8
 
9
9
  Nothing yet.
10
10
 
11
+ ## [0.7.5]
12
+
13
+ ### Added
14
+
15
+ - **Coverage is a ratchet now, and the ratchet is at 100%.** `scripts/coverage-gate.mjs`
16
+ replaces `vitest.config.ts`'s `thresholds.perFile`, which had never passed on this
17
+ branch: files the change touches must be at 100% per metric, every other file may not
18
+ fall below `.coverage-baseline.json`. The gate reads the istanbul `json` report, diffs
19
+ against the base commit, and refuses a lowered floor in the same change that needs it
20
+ (the baseline is regenerated deliberately, in its own commit, with a reason).
21
+ - The 38 files the old threshold flagged are all at per-file 100% now (1,613 → **1,817
22
+ tests**, 96 files); `.coverage-baseline.json` was rewritten to 119 × 100%, so "do not
23
+ regress" now means the same thing as "must be full".
24
+
25
+ ### Fixed
26
+
27
+ - **A profile install no longer writes its managed block into `web`.** With
28
+ `config.profile` omitted, `resolvePatchPath()` defaulted to `web`; a market install into
29
+ the Desktop app's `desktop` profile therefore disabled `agent-loop` in a profile that
30
+ had no plugin, while `desktop` kept the base loop in the agent-factory slot and every
31
+ per-session engine switch answered `router-unmounted` ("the loop engine router is not
32
+ ready"). The profile is now resolved from the launcher's `profileContext`, then from the
33
+ directory the package was installed into, and only then from `web`. An explicit
34
+ `profile:` entry still wins (needed for a `link:`/source checkout).
35
+ - **Two preset-authoring tests could flake on a loaded runner.** They polled for one second while the plugin's own roster wait is bounded at two; a docs-only push (run `39118609901`) failed both. The helper now polls for the full eight-second budget, which is what the assertion means. (Tests only.)
36
+ - **Coverage no longer depends on the host's home directory.** `model-registry.ts` sat at
37
+ 93.94% on a clean CI runner while reading 100% locally, because the local machine has a
38
+ real `~/.kimi-code/config.toml` and the tests silently relied on it. The spec now points
39
+ `KIMI_CODE_HOME` at a fixture for the discovered arm and at an empty directory for the
40
+ "not installed" arm, so both run everywhere. (Tests only; runtime behaviour unchanged.)
41
+
42
+ ### Changed
43
+
44
+ - Coverage-only source edits are comments at genuinely unreachable arms (`/* v8 ignore
45
+ start|stop */` plus a reason: POSIX-only ternaries, registry-constant branches, the
46
+ production-layout early return). Runtime behaviour is unchanged.
47
+
48
+ ## [0.7.4]
49
+
50
+ ### Fixed
51
+
52
+ - **`0.7.3` could not be installed on DeepSeek Harness `0.2.1`.** `dsh` refuses a
53
+ plugin whose declared peers the running version does not satisfy, and two things
54
+ broke at once. The union range ended at `<0.2.1-0`, so every `0.2.1-rc.N` host fell
55
+ outside it; and `@deepseek-ai/dsh-invariants` — declared as a hard peer — was never
56
+ published for that line and is not part of the `0.2.1` runtime at all. Both are
57
+ fixed: the last segment is now `>=0.2.0-rc.1 <0.2.2-0` (one range covers `0.2.0` and
58
+ `0.2.1`, which kept the same API shape), and the invariants peer is marked
59
+ `optional`, which is what it always was in practice — the `./invariant` companion
60
+ imports it for types only and the built bundle contains no runtime import of it.
61
+
62
+ ### Added
63
+
64
+ - **The `0.2.1` line is a supported generation**, verified end to end: an isolated
65
+ `.compat/021/` dependency set plus `vitest.config.compat021.ts` run the same 1,613
66
+ specs, `tsconfig.compat021.json` typechecks the sources against `0.2.1`'s published
67
+ declarations, and a real `dsh web` on `0.2.1-rc.1` installs the plugin, writes its
68
+ managed block once (byte-identical on reboot), publishes all ten engine presets,
69
+ and serves the client bundle.
70
+ - `npm run test:compat` and `npm run compat:install` cover all four generations, and
71
+ `npm run typecheck:compat` is the `0.2.1`-against-isolated-declarations check.
72
+
73
+ ### Changed
74
+
75
+ - `devDependencies` moved to the `0.2.1` line; `schemastery` to `3.18.5`,
76
+ `cordis` to `4.0.5`.
77
+ - The test suite mounts the JSONL durability backend through the new
78
+ `tests/helpers/jsonl-persistence.ts`. `0.2.1` replaced that package's default
79
+ export with named `{ name, apply }` exports, so the direct default import the specs
80
+ used resolved to `undefined` and cordis rejected the mount with
81
+ `invalid plugin, expect function or object with an "apply" method`.
82
+
11
83
  ## [0.7.3]
12
84
 
13
85
  ### Fixed
14
86
 
15
87
  - **ZCode's path probe only worked on the author's machine.** The resolver and the
16
88
  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
89
+ `D:\Programs\npm-global`), so on any other host the "well-known location" check
19
90
  silently never matched and a user with ZCode installed somewhere normal was told it
20
91
  was not installed. Both now derive candidates from the environment:
21
92
  `%ZCODE_HOME%` → `%LOCALAPPDATA%\Programs\ZCode` → `%ProgramFiles%\ZCode`, and
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  **English** · [简体中文](README.zh.md)
4
4
 
5
5
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
6
- [![Harness: 0.1.5 | 0.1.7 | 0.2.0](https://img.shields.io/badge/harness-0.1.5%20%7C%200.1.7%20%7C%200.2.0-6f42c1)](#version-compatibility)
6
+ [![Harness: 0.1.5 | 0.1.7 | 0.2.0 | 0.2.1](https://img.shields.io/badge/harness-0.1.5%20%7C%200.1.7%20%7C%200.2.0%20%7C%200.2.1-6f42c1)](#version-compatibility)
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
 
@@ -57,25 +57,32 @@ dsh plugin --profile web add ./dsh-loop-engine-allinone
57
57
 
58
58
  `web` is the profile the harness's own CLI documents (`dsh web` is short for
59
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.
60
+ `desktop`, a CI deployment may boot `headless`, and you may have made your own.
61
+
62
+ **Installing with `dsh plugin --profile desktop add dsh-loop-engine-allinone` is
63
+ enough**: the plugin resolves the profile it is actually serving — the launcher's
64
+ `profileContext` when the generation provides it, otherwise the profile directory
65
+ it was installed into (the nearest ancestor with a `dsh.profile` manifest). Only a
66
+ `link:`/source checkout has neither, and there the composition entry still wins:
67
+
68
+ ```yaml
69
+ - id: loop-engine
70
+ config:
71
+ profile: desktop # only needed for a linked checkout / global install
72
+ ```
73
+
74
+ Before 0.7.5 the field defaulted to `web` and nothing else was consulted. A market
75
+ install into `desktop` then wrote the managed block into `profiles/web/`, the
76
+ `desktop` base `agent-loop` kept the agent-factory slot, and every per-session
77
+ engine switch answered `router-unmounted` ("the loop engine router is not ready")
78
+ — with nothing logged. The same omission could disable `agent-loop` in a `web`
79
+ profile that had no plugin at all. 0.7.5 fixes both ends: it finds the right
80
+ profile, and it warns when the resolved profile has no `package.json` beside it.
81
+
82
+ If 0.7.4 already wrote the managed block into `web` while you did not install the
83
+ plugin there, delete that block (from `# -- dsh-loop-engine managed block --` through
84
+ `# -- /dsh-loop-engine managed block --`, plus the preset block) before booting
85
+ `web`; otherwise that profile has no agent factory.
79
86
 
80
87
  ### Installation notes
81
88
 
@@ -85,9 +92,35 @@ shape, naming the field to set.
85
92
  delete the managed `loop-engine` block** from that profile's `cordis.patch.yml`. The
86
93
  block outlives the plugin, and while it is present the profile has no agent factory,
87
94
  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.
95
+ - **One-time script approval.** pnpm 10+ refuses to run dependency install scripts
96
+ until the installing project approves them, so a fresh `dsh plugin add` can stop at
97
+ `ERR_PNPM_IGNORED_BUILDS` naming `@google/genai` and `protobufjs`. **This is not a
98
+ missing dependency and there is nothing to install by hand** — the whole tree,
99
+ including the bundled Pi engine, is already on disk; pnpm is only asking whether to
100
+ execute two scripts those packages ship:
101
+
102
+ | Package | Script | What it actually does |
103
+ |---|---|---|
104
+ | `@google/genai` | `preinstall` | `echo 'preinstall: no-op'` |
105
+ | `protobufjs` | `postinstall` | reads its own `package.json`, prints a version-scheme warning |
106
+
107
+ Both are no-ops, so **either answer is safe**: approving runs them, and
108
+ `allowBuilds: false` skips them. Verified for the second case — with both set to
109
+ `false`, `@google/genai` loads (145 exports), `protobufjs` loads, and Pi's
110
+ `dist/cli.js` is present. They arrive only through the bundled Pi engine;
111
+ Claude Code and Codex ship no install scripts, so this prompt never appears
112
+ because of them.
113
+
114
+ The question is asked **once per profile** — the answer is written into that
115
+ profile's `pnpm-workspace.yaml` and later installs and upgrades do not ask again.
116
+ To pre-answer it before the first install, add to the profile's
117
+ `pnpm-workspace.yaml`:
118
+
119
+ ```yaml
120
+ allowBuilds:
121
+ '@google/genai': false
122
+ protobufjs: false
123
+ ```
91
124
  - **Harness from source**: bridge the profile's harness peers with `file:` shims, or
92
125
  session resume fails with `agent-presets: refusing to compose an unscoped context`.
93
126
  Steps in [docs/source-checkout.md](docs/source-checkout.md).
@@ -193,11 +226,16 @@ Both tables enforce compile-time exhaustiveness checks — incomplete engine reg
193
226
 
194
227
  A single release seamlessly supports multiple DeepSeek Harness generations via runtime detection:
195
228
 
196
- | Plugin Release | Supported DeepSeek Harness Line |
229
+ | Plugin release | Supported DeepSeek Harness |
197
230
  |---|---|
198
- | `0.2.0-rcX` | `0.2.0-rc.1`, `0.2.0-rc.2` |
199
- | `0.1.7-rcX` | `0.1.7-rc.1`, `0.1.7-rc.2` |
200
- | `0.1.5-rcX` | `0.1.5-rc.1`, `0.1.5-rc.2` |
231
+ | `0.7.4`+ | `0.2.0-rc.1` … `0.2.1-rc.1` |
232
+ | `0.7.0` – `0.7.3` | `0.1.5-rc.1` … `0.2.0-rc.2` |
233
+
234
+ The union range is `>=0.1.5-rc.1 <0.1.6-0 || >=0.1.7-rc.1 <0.1.8-0 || >=0.2.0-rc.1 <0.2.2-0`.
235
+ `0.2.0` and `0.2.1` share the last segment because the harness kept the same API shape
236
+ across them, so one range covers both. `dsh`'s compatibility check reads these peers
237
+ and refuses to load a plugin that misses one, which is what made `0.7.3` uninstallable
238
+ on `0.2.1`.
201
239
 
202
240
  See [docs/compatibility.md](docs/compatibility.md) for harness upgrade checklists and API surface diffs.
203
241
 
package/README.zh.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [English](README.md) · **简体中文**
4
4
 
5
5
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
6
- [![Harness: 0.1.5 | 0.1.7 | 0.2.0](https://img.shields.io/badge/harness-0.1.5%20%7C%200.1.7%20%7C%200.2.0-6f42c1)](#版本兼容性)
6
+ [![Harness: 0.1.5 | 0.1.7 | 0.2.0 | 0.2.1](https://img.shields.io/badge/harness-0.1.5%20%7C%200.1.7%20%7C%200.2.0%20%7C%200.2.1-6f42c1)](#版本兼容性)
7
7
 
8
8
  **dsh-loop-engine-allinone** 是专为 [DeepSeek Harness](https://github.com/deepseek-ai)(`dsh`)打造的 Agent 循环引擎全功能切换插件。支持**按会话独立切换** 10 家主流外部引擎与 Harness 原生 `in-process` 循环。
9
9
 
@@ -79,9 +79,30 @@ dsh plugin --profile web add ./dsh-loop-engine-allinone
79
79
  - **卸载**:`dsh plugin --profile web remove dsh-loop-engine-allinone`,**然后手动删掉**
80
80
  该 profile `cordis.patch.yml` 里的 `loop-engine` 受管块。块会比插件活得久,而它还在时
81
81
  profile 里没有任何 agent factory,会话一条也建不出来。
82
- - **pnpm 10+** 可能拦下依赖的 build script(`ERR_PNPM_IGNORED_BUILDS`,列出 `esbuild`、
83
- `@google/genai`、`protobufjs`)。执行 `pnpm approve-builds`,或加一条 `allowBuilds`,然后重试
84
- ——只有安装方能授予这个权限。
82
+ - **一次性脚本授权**。pnpm 10+ 默认不执行依赖包自带的安装脚本,要由安装方批准,
83
+ 所以首次 `dsh plugin add` 可能停在 `ERR_PNPM_IGNORED_BUILDS`,点名 `@google/genai` 与
84
+ `protobufjs`。**这不是缺依赖,也没有任何东西要你手动装**——整棵树(含捆绑的 Pi 引擎)
85
+ 已经落盘,pnpm 只是在问要不要执行这两个包自带的脚本:
86
+
87
+ | 包 | 脚本 | 实际做的事 |
88
+ |---|---|---|
89
+ | `@google/genai` | `preinstall` | `echo 'preinstall: no-op'` |
90
+ | `protobufjs` | `postinstall` | 读自己的 `package.json`,打印一行版本方案警告 |
91
+
92
+ 两个都是空操作,**所以批准或拒绝都安全**:批准就执行,`allowBuilds: false` 就跳过。
93
+ 拒绝这条路径已实测——两者设为 `false` 后,`@google/genai` 正常加载(145 个导出)、
94
+ `protobufjs` 正常加载、Pi 的 `dist/cli.js` 也在。这两个包**只由捆绑的 Pi 引擎带进来**;
95
+ Claude Code 与 Codex 不带安装脚本,永远不会因为它们弹这个窗。
96
+
97
+ **每个 profile 只问一次**——答案写进该 profile 的 `pnpm-workspace.yaml`,
98
+ 之后的安装与升级都不再问。想在首次安装前就预先回答,往该 profile 的
99
+ `pnpm-workspace.yaml` 加:
100
+
101
+ ```yaml
102
+ allowBuilds:
103
+ '@google/genai': false
104
+ protobufjs: false
105
+ ```
85
106
  - **用源码启动 harness** 时,要用 `file:` shim 把 profile 的 harness peer 包桥接到 checkout 源码,
86
107
  否则恢复会话会报 `agent-presets: refusing to compose an unscoped context`。
87
108
  步骤见 [docs/source-checkout.md](docs/source-checkout.md)。
@@ -181,11 +202,14 @@ dsh plugin --profile web add ./dsh-loop-engine-allinone
181
202
 
182
203
  单一版本通过动态探测,全面兼容 DeepSeek Harness 多代版本:
183
204
 
184
- | 插件版本系列 | 兼容的 DeepSeek Harness 版本 |
205
+ | 插件版本 | 兼容的 DeepSeek Harness |
185
206
  |---|---|
186
- | `0.2.0-rcX` | `0.2.0-rc.1`, `0.2.0-rc.2` |
187
- | `0.1.7-rcX` | `0.1.7-rc.1`, `0.1.7-rc.2` |
188
- | `0.1.5-rcX` | `0.1.5-rc.1`, `0.1.5-rc.2` |
207
+ | `0.7.4` 起 | `0.2.0-rc.1` … `0.2.1-rc.1` |
208
+ | `0.7.0` – `0.7.3` | `0.1.5-rc.1` … `0.2.0-rc.2` |
209
+
210
+ 并集范围是 `>=0.1.5-rc.1 <0.1.6-0 || >=0.1.7-rc.1 <0.1.8-0 || >=0.2.0-rc.1 <0.2.2-0`。
211
+ `0.2.0` 与 `0.2.1` 共用末段,因为 harness 在这两代之间没有改 API 形状,一段范围就够了。
212
+ `dsh` 的兼容性判定读的就是这些 peer,差一条就拒绝加载——这正是 `0.7.3` 在 `0.2.1` 上装不上的原因。
189
213
 
190
214
  版本号 `<harness 线>-rcN` 中的 `rcN` 为本插件对应分支的迭代发布号。详见 [docs/compatibility.md](docs/compatibility.md)。
191
215
 
package/lib/index.js CHANGED
@@ -92,7 +92,8 @@ var __callDispose = (stack, error, hasError) => {
92
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 join22 } from "node:path";
95
+ import { basename, dirname as dirname7, join as join22 } from "node:path";
96
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
96
97
  import z12 from "@deepseek-ai/schemastery";
97
98
  import { resolveDshHome as resolveDshHome2 } from "@deepseek-ai/dsh-home-paths";
98
99
 
@@ -8548,8 +8549,10 @@ var AgyAgent = class extends CliHostedAgent {
8548
8549
  stdin: agyStdinPrompt(prompt),
8549
8550
  map: (document) => mapAgyDocument(document, accept, run)
8550
8551
  },
8552
+ /* v8 ignore start -- the mapper pushes frames straight to `accept` rather than returning them, so this collector is never called; the run signature requires one */
8551
8553
  () => {
8552
8554
  }
8555
+ /* v8 ignore stop */
8553
8556
  );
8554
8557
  if (result.parsed === 0) {
8555
8558
  accept({
@@ -8887,7 +8890,9 @@ function mapStreamJsonDocument(document, accept, run) {
8887
8890
  const subtype = typeof frame2.subtype === "string" ? frame2.subtype : void 0;
8888
8891
  const isError = frame2.is_error === true || subtype !== void 0 && subtype !== "success";
8889
8892
  if (isError) {
8890
- const message = typeof frame2.result === "string" && frame2.result !== "" ? frame2.result : typeof frame2.error === "string" && frame2.error !== "" ? frame2.error : `the engine ended the run with "${subtype ?? "an error"}"`;
8893
+ let message = `the engine ended the run with "${subtype ?? "an error"}"`;
8894
+ if (typeof frame2.result === "string" && frame2.result !== "") message = frame2.result;
8895
+ else if (typeof frame2.error === "string" && frame2.error !== "") message = frame2.error;
8891
8896
  run.reportedError = message;
8892
8897
  accept({ kind: "error", message });
8893
8898
  }
@@ -8984,8 +8989,10 @@ var QoderclicnAgent = class extends CliHostedAgent {
8984
8989
  };
8985
8990
  const result = await runNdjson(
8986
8991
  { transport, map: (document) => mapDocument(document, accept, run) },
8992
+ /* v8 ignore start -- the mapper pushes frames straight to `accept` rather than returning them, so this collector is never called; the run signature requires one */
8987
8993
  () => {
8988
8994
  }
8995
+ /* v8 ignore stop */
8989
8996
  );
8990
8997
  if (result.parsed === 0) {
8991
8998
  this.sink.accept({
@@ -9172,9 +9179,10 @@ function resolveOnPath(name2, env = process.env) {
9172
9179
  return void 0;
9173
9180
  }
9174
9181
  function npmPackageEntry(prefix, pkg, entry) {
9182
+ const slash = pkg.indexOf("/");
9175
9183
  const scoped = pkg.startsWith("@");
9176
- const scope = scoped ? pkg.slice(0, pkg.indexOf("/")) : "";
9177
- const name2 = scoped ? pkg.slice(pkg.indexOf("/") + 1) : pkg;
9184
+ const scope = scoped && slash > 0 ? pkg.slice(0, slash) : "";
9185
+ const name2 = scoped ? pkg.slice(slash + 1) : pkg;
9178
9186
  if (name2 === "" || scoped && scope === "") return void 0;
9179
9187
  const relative = scoped ? join10("node_modules", scope, name2, entry) : join10("node_modules", name2, entry);
9180
9188
  for (const candidate of [join10(prefix, relative), join10(prefix, "lib", relative)]) {
@@ -11394,12 +11402,16 @@ var Config11 = z12.object({
11394
11402
  // Derived from the registry's own capability declaration rather than restated:
11395
11403
  // a permission mode an engine declares is a mode the entry accepts, so the two
11396
11404
  // cannot drift.
11405
+ /* v8 ignore start -- the registry row always declares its permission modes, so the empty fallback is a type-level guard */
11397
11406
  permissionMode: z12.union((ENGINE_REGISTRY["claude-code"].capabilities.permissionModes ?? []).map((mode) => z12.const(mode))),
11407
+ /* v8 ignore stop */
11398
11408
  env: z12.dict(z12.string()),
11399
11409
  model: z12.string(),
11400
11410
  disposeGraceMs: z12.number(),
11401
11411
  maxTurns: z12.number(),
11412
+ /* v8 ignore start -- the registry row always declares them, so the empty fallback is a type-level guard */
11402
11413
  sandboxMode: z12.union((ENGINE_REGISTRY.codex.capabilities.permissionModes ?? []).map((mode) => z12.const(mode))),
11414
+ /* v8 ignore stop */
11403
11415
  approvalPolicy: z12.union(CODEX_APPROVAL_POLICIES.map((policy) => z12.const(policy))),
11404
11416
  piProvider: z12.string(),
11405
11417
  piThinking: z12.string(),
@@ -11437,15 +11449,37 @@ var Config11 = z12.object({
11437
11449
  showEngineBadge: LOOP_ENGINE_SHOW_ENGINE_BADGE_SCHEMA
11438
11450
  }
11439
11451
  });
11440
- function resolvePatchPath(config) {
11452
+ function resolvePatchPath(config, fallbackProfile = "web") {
11441
11453
  if (config.patchPath !== void 0 && config.patchPath !== "") return config.patchPath;
11442
11454
  return join22(
11443
11455
  resolveDshHome2(),
11444
11456
  "profiles",
11445
- config.profile ?? "web",
11457
+ config.profile ?? fallbackProfile,
11446
11458
  config.patchFilename ?? "cordis.patch.yml"
11447
11459
  );
11448
11460
  }
11461
+ function inferProfileName(entry = fileURLToPath2(import.meta.url)) {
11462
+ let dir = dirname7(entry);
11463
+ for (; ; ) {
11464
+ const manifest = join22(dir, "package.json");
11465
+ if (existsSync8(manifest)) {
11466
+ try {
11467
+ const parsed = JSON.parse(readFileSync5(manifest, "utf8"));
11468
+ if (parsed.dsh?.profile !== void 0) return basename(dir);
11469
+ } catch {
11470
+ }
11471
+ }
11472
+ const parent = dirname7(dir);
11473
+ if (parent === dir) return void 0;
11474
+ dir = parent;
11475
+ }
11476
+ }
11477
+ function resolveProfileName(ctx, config) {
11478
+ if (config.profile !== void 0 && config.profile !== "") return config.profile;
11479
+ const fromContext = ctx.get("profileContext")?.name;
11480
+ if (typeof fromContext === "string" && fromContext !== "") return fromContext;
11481
+ return inferProfileName() ?? "web";
11482
+ }
11449
11483
  function resolveSummarizerTarget(config) {
11450
11484
  const provider = config.summarizerProvider;
11451
11485
  const model = config.summarizerModel;
@@ -11497,18 +11531,18 @@ function readPatchFileSync(path) {
11497
11531
  throw error;
11498
11532
  }
11499
11533
  }
11500
- function warnIfProfileLooksWrong(ctx, config, patchPath) {
11534
+ function warnIfProfileLooksWrong(ctx, profileName, patchPath) {
11501
11535
  const profileDir = dirname7(patchPath);
11502
11536
  if (existsSync8(join22(profileDir, "package.json"))) return;
11503
- const named = config.profile ?? "web";
11504
11537
  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.`
11538
+ `loop-engine: profile "${profileName}" 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
11539
  );
11507
11540
  }
11508
11541
  function apply(ctx, config) {
11509
11542
  const summarizer = resolveSummarizerTarget(config);
11510
- const patchPath = resolvePatchPath(config);
11511
- warnIfProfileLooksWrong(ctx, config, patchPath);
11543
+ const profileName = resolveProfileName(ctx, config);
11544
+ const patchPath = resolvePatchPath(config, profileName);
11545
+ warnIfProfileLooksWrong(ctx, profileName, patchPath);
11512
11546
  const patchText = readPatchFileSync(patchPath);
11513
11547
  const legacyEngine = legacyBlockEngineOf(patchText);
11514
11548
  const needsWrite = hasLegacyManagedBlock(patchText) || !hasManagedBlock(patchText);
@@ -11783,9 +11817,11 @@ function apply(ctx, config) {
11783
11817
  export {
11784
11818
  Config11 as Config,
11785
11819
  apply,
11820
+ inferProfileName,
11786
11821
  inject,
11787
11822
  name,
11788
11823
  resolvePatchPath,
11824
+ resolveProfileName,
11789
11825
  resolveSummarizerTarget,
11790
11826
  syncManagedBlock,
11791
11827
  writePatchFile,
@@ -152,8 +152,44 @@ type ConfigInput = Omit<Config, 'engine' | 'showInComposer' | 'showEngineBadge'>
152
152
  * entry is the engine knobs alone.
153
153
  */
154
154
  export declare const Config: z<ConfigInput, Config>;
155
- /** Resolve the managed patch file from configuration, defaulting to the web profile. */
156
- export declare function resolvePatchPath(config: Config): string;
155
+ /**
156
+ * Resolve the managed patch file from configuration, defaulting to the web profile.
157
+ * @param config - composition entry; `patchPath` wins, then `profile` + `patchFilename`.
158
+ * @param fallbackProfile - profile name to use when the entry names none (the
159
+ * running profile, discovered by {@link resolveProfileName}); `web` when unknown.
160
+ * @returns the absolute patch file path.
161
+ */
162
+ export declare function resolvePatchPath(config: Config, fallbackProfile?: string): string;
163
+ /**
164
+ * Infer the profile a plugin copy was installed into from its own location.
165
+ *
166
+ * A profile is the nearest ancestor directory holding a `package.json` with a
167
+ * `dsh.profile` field. Walking up (rather than assuming
168
+ * `<home>/profiles/<name>/node_modules`) survives pnpm's real path
169
+ * (`node_modules/.pnpm/<pkg>@<ver>/node_modules/<pkg>/lib`) and nested installs,
170
+ * and returns undefined when the plugin runs from a checkout, a global install,
171
+ * or a linked source tree - none of which belongs to a profile.
172
+ * @param entry - module path to start from; the resolved module by default.
173
+ * @returns the profile name, or undefined when the copy is not inside one.
174
+ */
175
+ export declare function inferProfileName(entry?: string): string | undefined;
176
+ /**
177
+ * The profile this process is actually serving.
178
+ *
179
+ * Precedence: the composition entry's `profile` (an explicit deployment choice),
180
+ * then the harness's `profileContext.name` (0.2.x launchers), then the install
181
+ * location, and only then `web` (the historical default, correct for a global
182
+ * install and for `dsh web` before profiles had names).
183
+ *
184
+ * The install-location step is what makes a market install work without hand
185
+ * editing: installing into `desktop` used to leave `profile` unset, so the
186
+ * managed block went to `web`, the base `agent-loop` kept the factory slot in
187
+ * `desktop`, and every per-session switch answered `router-unmounted`.
188
+ * @param ctx - composing context, read for `profileContext`.
189
+ * @param config - composition entry, read for an explicit `profile`.
190
+ * @returns the profile name whose patch file this plugin owns.
191
+ */
192
+ export declare function resolveProfileName(ctx: Context, config: Config): string;
157
193
  /**
158
194
  * Resolve the summarization model the plugin writes into every hosted preset,
159
195
  * or `undefined` when the deployment names none.
package/package.json CHANGED
@@ -1,152 +1,161 @@
1
- {
2
- "name": "dsh-loop-engine-allinone",
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.3",
5
- "publishConfig": {
6
- "registry": "https://registry.npmjs.org/"
7
- },
8
- "repository": {
9
- "type": "git",
10
- "url": "git+https://github.com/yaliklinty/dsh-loop-engine-allinone.git"
11
- },
12
- "type": "module",
13
- "main": "lib/index.js",
14
- "types": "lib/types/index.d.ts",
15
- "exports": {
16
- ".": {
17
- "types": "./lib/types/index.d.ts",
18
- "default": "./lib/index.js"
19
- },
20
- "./invariant": {
21
- "types": "./lib/types/invariant.d.ts",
22
- "default": "./lib/invariant.js"
23
- },
24
- "./client": {
25
- "types": "./lib/types/client/index.d.ts",
26
- "default": "./lib/client.js"
27
- },
28
- "./src/*": "./src/*",
29
- "./package.json": "./package.json"
30
- },
31
- "files": [
32
- "lib/index.js",
33
- "lib/invariant.js",
34
- "lib/client.js",
35
- "lib/types/**/*.d.ts",
36
- "cordis.patch.yml",
37
- "README.md",
38
- "CHANGELOG.md",
39
- "CONTRIBUTING.md",
40
- "SECURITY.md",
41
- "LICENSE",
42
- "NOTICE"
43
- ],
44
- "license": "MIT",
45
- "engines": {
46
- "node": "^22.19.0 || >=24.0.0"
47
- },
48
- "packageManager": "pnpm@11.23.0",
49
- "scripts": {
50
- "build": "node build.mjs",
51
- "typecheck": "tsc -p tsconfig.build.json --noEmit",
52
- "test": "vitest run",
53
- "test:coverage": "vitest run --coverage",
54
- "compat:install": "pnpm -C .compat/015 install --frozen-lockfile && pnpm -C .compat/017 install --frozen-lockfile && pnpm -C .compat/020 install --frozen-lockfile",
55
- "test:compat": "vitest run --config vitest.config.compat015.ts && vitest run --config vitest.config.compat017.ts && vitest run --config vitest.config.compat020.ts"
56
- },
57
- "dsh": {
58
- "bundle": {
59
- "patch": "./cordis.patch.yml"
60
- },
61
- "client": {
62
- "inject": [
63
- "@deepseek-ai/dsh-client-locale",
64
- "@deepseek-ai/dsh-client-ui-settings",
65
- "@deepseek-ai/dsh-api-remotes",
66
- "@deepseek-ai/dsh-api-session-controller"
67
- ],
68
- "platform": "web",
69
- "external": [
70
- "@deepseek-ai/dsh-client-locale/client",
71
- "@deepseek-ai/dsh-client-store",
72
- "@deepseek-ai/dsh-client-ui-settings/client",
73
- "@deepseek-ai/dsh-api-remotes/client",
74
- "@deepseek-ai/dsh-settings/types"
75
- ]
76
- }
77
- },
78
- "dependencies": {
79
- "@anthropic-ai/claude-agent-sdk": "0.3.220",
80
- "@anthropic-ai/sdk": "0.93.0",
81
- "@deepseek-ai/schemastery": "3.18.4",
82
- "@earendil-works/pi-coding-agent": "0.84.3",
83
- "@openai/codex": "0.149.1"
84
- },
85
- "peerDependencies": {
86
- "@deepseek-ai/cordis": "^4.0.1",
87
- "@deepseek-ai/dsh-agent": ">=0.1.5-rc.1 <0.1.6-0 || >=0.1.7-rc.1 <0.1.8-0 || >=0.2.0-rc.1 <0.2.1-0",
88
- "@deepseek-ai/dsh-agent-loop": ">=0.1.5-rc.1 <0.1.6-0 || >=0.1.7-rc.1 <0.1.8-0 || >=0.2.0-rc.1 <0.2.1-0",
89
- "@deepseek-ai/dsh-invariants": ">=0.1.5-rc.1 <0.1.6-0 || >=0.1.7-rc.1 <0.1.8-0 || >=0.2.0-rc.1 <0.2.1-0",
90
- "@deepseek-ai/dsh-llm": ">=0.1.5-rc.1 <0.1.6-0 || >=0.1.7-rc.1 <0.1.8-0 || >=0.2.0-rc.1 <0.2.1-0",
91
- "@deepseek-ai/dsh-scope": ">=0.1.5-rc.1 <0.1.6-0 || >=0.1.7-rc.1 <0.1.8-0 || >=0.2.0-rc.1 <0.2.1-0",
92
- "@deepseek-ai/dsh-session": ">=0.1.5-rc.1 <0.1.6-0 || >=0.1.7-rc.1 <0.1.8-0 || >=0.2.0-rc.1 <0.2.1-0",
93
- "@deepseek-ai/dsh-session-persistence": ">=0.1.5-rc.1 <0.1.6-0 || >=0.1.7-rc.1 <0.1.8-0 || >=0.2.0-rc.1 <0.2.1-0",
94
- "@deepseek-ai/dsh-settings": ">=0.1.5-rc.1 <0.1.6-0 || >=0.1.7-rc.1 <0.1.8-0 || >=0.2.0-rc.1 <0.2.1-0",
95
- "@deepseek-ai/dsh-subprocess": ">=0.1.5-rc.1 <0.1.6-0 || >=0.1.7-rc.1 <0.1.8-0 || >=0.2.0-rc.1 <0.2.1-0",
96
- "@deepseek-ai/dsh-timeout": ">=0.1.5-rc.1 <0.1.6-0 || >=0.1.7-rc.1 <0.1.8-0 || >=0.2.0-rc.1 <0.2.1-0",
97
- "@deepseek-ai/dsh-typert-protocol": ">=0.1.5-rc.1 <0.1.6-0 || >=0.1.7-rc.1 <0.1.8-0 || >=0.2.0-rc.1 <0.2.1-0"
98
- },
99
- "devDependencies": {
100
- "@deepseek-ai/dsh-agent": "0.2.0-rc.2",
101
- "@deepseek-ai/dsh-agent-loop": "0.2.0-rc.2",
102
- "@deepseek-ai/dsh-invariants": "0.2.0-rc.2",
103
- "@deepseek-ai/dsh-llm": "0.2.0-rc.2",
104
- "@deepseek-ai/dsh-scope": "0.2.0-rc.2",
105
- "@deepseek-ai/dsh-session": "0.2.0-rc.2",
106
- "@deepseek-ai/dsh-session-persistence": "0.2.0-rc.2",
107
- "@deepseek-ai/dsh-settings": "0.2.0-rc.2",
108
- "@deepseek-ai/dsh-subprocess": "0.2.0-rc.2",
109
- "@deepseek-ai/dsh-timeout": "0.2.0-rc.2",
110
- "@deepseek-ai/dsh-attachment": "0.2.0-rc.2",
111
- "@deepseek-ai/dsh-client-locale": "0.2.0-rc.2",
112
- "@deepseek-ai/dsh-client-store": "0.2.0-rc.2",
113
- "@deepseek-ai/dsh-client-ui-conversation": "0.2.0-rc.2",
114
- "@deepseek-ai/dsh-client-ui-primitives": "0.2.0-rc.2",
115
- "@deepseek-ai/dsh-client-ui-renderer": "0.2.0-rc.2",
116
- "@deepseek-ai/dsh-client-ui-settings": "0.2.0-rc.2",
117
- "@deepseek-ai/dsh-client-ui-slots": "0.2.0-rc.2",
118
- "@deepseek-ai/dsh-home-paths": "0.2.0-rc.2",
119
- "@deepseek-ai/dsh-session-persistence-jsonl": "0.2.0-rc.2",
120
- "@deepseek-ai/dsh-subprocess-local": "0.2.0-rc.2",
121
- "@deepseek-ai/dsh-system-prompt": "0.2.0-rc.2",
122
- "@deepseek-ai/dsh-typert-protocol": "0.2.0-rc.2",
123
- "@types/node": "^22.20.0",
124
- "@types/react": "^19.0.0",
125
- "@vitest/coverage-v8": "^4.1.8",
126
- "esbuild": "^0.28.2",
127
- "tsx": "^4.22.4",
128
- "typescript": "^6.0.3",
129
- "vite": "^7.0.0",
130
- "vite-tsconfig-paths": "^6.1.1",
131
- "vitest": "^4.1.8"
132
- },
133
- "bugs": {
134
- "url": "https://github.com/yaliklinty/dsh-loop-engine-allinone/issues"
135
- },
136
- "homepage": "https://github.com/yaliklinty/dsh-loop-engine-allinone#readme",
137
- "author": "dsh-loop-engine-allinone contributors",
138
- "keywords": [
139
- "deepseek-harness",
140
- "dsh",
141
- "dsh-plugin",
142
- "agent-loop",
143
- "coding-agent",
144
- "claude-code",
145
- "codex",
146
- "kimi",
147
- "antigravity",
148
- "grok",
149
- "minimax",
150
- "acp"
151
- ]
152
- }
1
+ {
2
+ "name": "dsh-loop-engine-allinone",
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.5",
5
+ "publishConfig": {
6
+ "registry": "https://registry.npmjs.org/"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/yaliklinty/dsh-loop-engine-allinone.git"
11
+ },
12
+ "type": "module",
13
+ "main": "lib/index.js",
14
+ "types": "lib/types/index.d.ts",
15
+ "exports": {
16
+ ".": {
17
+ "types": "./lib/types/index.d.ts",
18
+ "default": "./lib/index.js"
19
+ },
20
+ "./invariant": {
21
+ "types": "./lib/types/invariant.d.ts",
22
+ "default": "./lib/invariant.js"
23
+ },
24
+ "./client": {
25
+ "types": "./lib/types/client/index.d.ts",
26
+ "default": "./lib/client.js"
27
+ },
28
+ "./src/*": "./src/*",
29
+ "./package.json": "./package.json"
30
+ },
31
+ "files": [
32
+ "lib/index.js",
33
+ "lib/invariant.js",
34
+ "lib/client.js",
35
+ "lib/types/**/*.d.ts",
36
+ "cordis.patch.yml",
37
+ "README.md",
38
+ "CHANGELOG.md",
39
+ "CONTRIBUTING.md",
40
+ "SECURITY.md",
41
+ "LICENSE",
42
+ "NOTICE"
43
+ ],
44
+ "license": "MIT",
45
+ "engines": {
46
+ "node": "^22.19.0 || >=24.0.0"
47
+ },
48
+ "packageManager": "pnpm@11.23.0",
49
+ "scripts": {
50
+ "build": "node build.mjs",
51
+ "typecheck": "tsc -p tsconfig.build.json --noEmit",
52
+ "test": "vitest run",
53
+ "test:coverage": "vitest run --coverage && node scripts/coverage-gate.mjs",
54
+ "coverage:baseline": "node scripts/coverage-gate.mjs --update",
55
+ "compat:install": "pnpm -C .compat/015 install --frozen-lockfile && pnpm -C .compat/017 install --frozen-lockfile && pnpm -C .compat/020 install --frozen-lockfile && pnpm -C .compat/021 install --frozen-lockfile",
56
+ "test:compat": "vitest run --config vitest.config.compat015.ts && vitest run --config vitest.config.compat017.ts && vitest run --config vitest.config.compat020.ts && vitest run --config vitest.config.compat021.ts",
57
+ "typecheck:compat": "tsc -p tsconfig.compat021.json"
58
+ },
59
+ "dsh": {
60
+ "bundle": {
61
+ "patch": "./cordis.patch.yml"
62
+ },
63
+ "client": {
64
+ "inject": [
65
+ "@deepseek-ai/dsh-client-locale",
66
+ "@deepseek-ai/dsh-client-ui-settings",
67
+ "@deepseek-ai/dsh-api-remotes",
68
+ "@deepseek-ai/dsh-api-session-controller"
69
+ ],
70
+ "platform": "web",
71
+ "external": [
72
+ "@deepseek-ai/dsh-client-locale/client",
73
+ "@deepseek-ai/dsh-client-store",
74
+ "@deepseek-ai/dsh-client-ui-settings/client",
75
+ "@deepseek-ai/dsh-api-remotes/client",
76
+ "@deepseek-ai/dsh-settings/types"
77
+ ]
78
+ }
79
+ },
80
+ "dependencies": {
81
+ "@anthropic-ai/claude-agent-sdk": "0.3.220",
82
+ "@anthropic-ai/sdk": "0.93.0",
83
+ "@deepseek-ai/schemastery": "3.18.4",
84
+ "@earendil-works/pi-coding-agent": "0.84.3",
85
+ "@openai/codex": "0.149.1"
86
+ },
87
+ "peerDependencies": {
88
+ "@deepseek-ai/cordis": "^4.0.1",
89
+ "@deepseek-ai/dsh-agent": ">=0.1.5-rc.1 <0.1.6-0 || >=0.1.7-rc.1 <0.1.8-0 || >=0.2.0-rc.1 <0.2.2-0",
90
+ "@deepseek-ai/dsh-agent-loop": ">=0.1.5-rc.1 <0.1.6-0 || >=0.1.7-rc.1 <0.1.8-0 || >=0.2.0-rc.1 <0.2.2-0",
91
+ "@deepseek-ai/dsh-invariants": ">=0.1.5-rc.1 <0.1.6-0 || >=0.1.7-rc.1 <0.1.8-0 || >=0.2.0-rc.1 <0.2.2-0",
92
+ "@deepseek-ai/dsh-llm": ">=0.1.5-rc.1 <0.1.6-0 || >=0.1.7-rc.1 <0.1.8-0 || >=0.2.0-rc.1 <0.2.2-0",
93
+ "@deepseek-ai/dsh-scope": ">=0.1.5-rc.1 <0.1.6-0 || >=0.1.7-rc.1 <0.1.8-0 || >=0.2.0-rc.1 <0.2.2-0",
94
+ "@deepseek-ai/dsh-session": ">=0.1.5-rc.1 <0.1.6-0 || >=0.1.7-rc.1 <0.1.8-0 || >=0.2.0-rc.1 <0.2.2-0",
95
+ "@deepseek-ai/dsh-session-persistence": ">=0.1.5-rc.1 <0.1.6-0 || >=0.1.7-rc.1 <0.1.8-0 || >=0.2.0-rc.1 <0.2.2-0",
96
+ "@deepseek-ai/dsh-settings": ">=0.1.5-rc.1 <0.1.6-0 || >=0.1.7-rc.1 <0.1.8-0 || >=0.2.0-rc.1 <0.2.2-0",
97
+ "@deepseek-ai/dsh-subprocess": ">=0.1.5-rc.1 <0.1.6-0 || >=0.1.7-rc.1 <0.1.8-0 || >=0.2.0-rc.1 <0.2.2-0",
98
+ "@deepseek-ai/dsh-timeout": ">=0.1.5-rc.1 <0.1.6-0 || >=0.1.7-rc.1 <0.1.8-0 || >=0.2.0-rc.1 <0.2.2-0",
99
+ "@deepseek-ai/dsh-typert-protocol": ">=0.1.5-rc.1 <0.1.6-0 || >=0.1.7-rc.1 <0.1.8-0 || >=0.2.0-rc.1 <0.2.2-0"
100
+ },
101
+ "devDependencies": {
102
+ "@deepseek-ai/dsh-agent": "0.2.1-rc.1",
103
+ "@deepseek-ai/dsh-agent-loop": "0.2.1-rc.1",
104
+ "@deepseek-ai/dsh-invariants": "0.2.0-rc.2",
105
+ "@deepseek-ai/dsh-llm": "0.2.1-rc.1",
106
+ "@deepseek-ai/dsh-scope": "0.2.1-rc.1",
107
+ "@deepseek-ai/dsh-session": "0.2.1-rc.1",
108
+ "@deepseek-ai/dsh-session-persistence": "0.2.1-rc.1",
109
+ "@deepseek-ai/dsh-settings": "0.2.1-rc.1",
110
+ "@deepseek-ai/dsh-subprocess": "0.2.1-rc.1",
111
+ "@deepseek-ai/dsh-timeout": "0.2.1-rc.1",
112
+ "@deepseek-ai/dsh-attachment": "0.2.1-rc.1",
113
+ "@deepseek-ai/dsh-client-locale": "0.2.1-rc.1",
114
+ "@deepseek-ai/dsh-client-store": "0.2.1-rc.1",
115
+ "@deepseek-ai/dsh-client-ui-conversation": "0.2.1-rc.1",
116
+ "@deepseek-ai/dsh-client-ui-primitives": "0.2.1-rc.1",
117
+ "@deepseek-ai/dsh-client-ui-renderer": "0.2.1-rc.1",
118
+ "@deepseek-ai/dsh-client-ui-settings": "0.2.1-rc.1",
119
+ "@deepseek-ai/dsh-client-ui-slots": "0.2.1-rc.1",
120
+ "@deepseek-ai/dsh-home-paths": "0.2.1-rc.1",
121
+ "@deepseek-ai/dsh-session-persistence-jsonl": "0.2.1-rc.1",
122
+ "@deepseek-ai/dsh-subprocess-local": "0.2.1-rc.1",
123
+ "@deepseek-ai/dsh-system-prompt": "0.2.1-rc.1",
124
+ "@deepseek-ai/dsh-typert-protocol": "0.2.1-rc.1",
125
+ "@types/node": "^22.20.0",
126
+ "@types/react": "^19.0.0",
127
+ "@vitest/coverage-v8": "^4.1.8",
128
+ "esbuild": "^0.28.2",
129
+ "tsx": "^4.22.4",
130
+ "typescript": "^6.0.3",
131
+ "vite": "^7.0.0",
132
+ "vite-tsconfig-paths": "^6.1.1",
133
+ "vitest": "^4.1.8",
134
+ "@deepseek-ai/cordis": "4.0.5",
135
+ "@deepseek-ai/schemastery": "3.18.5"
136
+ },
137
+ "bugs": {
138
+ "url": "https://github.com/yaliklinty/dsh-loop-engine-allinone/issues"
139
+ },
140
+ "homepage": "https://github.com/yaliklinty/dsh-loop-engine-allinone#readme",
141
+ "author": "dsh-loop-engine-allinone contributors",
142
+ "keywords": [
143
+ "deepseek-harness",
144
+ "dsh",
145
+ "dsh-plugin",
146
+ "agent-loop",
147
+ "coding-agent",
148
+ "claude-code",
149
+ "codex",
150
+ "kimi",
151
+ "antigravity",
152
+ "grok",
153
+ "minimax",
154
+ "acp"
155
+ ],
156
+ "peerDependenciesMeta": {
157
+ "@deepseek-ai/dsh-invariants": {
158
+ "optional": true
159
+ }
160
+ }
161
+ }
@@ -1,123 +0,0 @@
1
- /**
2
- * JSON-RPC client for the `kimi acp` subprocess (Agent Client Protocol over
3
- * stdio).
4
- *
5
- * The driver hands the client a process handle carrying its stdin/stdout/stderr
6
- * (projected from the dsh subprocess seam). The client frames JSON-RPC 2.0
7
- * records on a bare `\n` (via a byte decoder, tolerating a trailing `\r`),
8
- * correlates request responses by `id`, dispatches every `session/update`
9
- * notification to a buffered event stream, and answers the reverse-RPC
10
- * `session/request_permission` requests the agent publishes. The child is
11
- * long-lived (one per factory) and stepped over via `newSession` + `prompt`.
12
- *
13
- * @module dsh-loop-engine/engine-kimi/acp/client
14
- */
15
- import type { KimiProcess, KimiSpawnCapability, KimiSpawnSpec } from '../process.ts';
16
- import { type AcpFrame, type AcpImageContent, type AcpPermissionOption, type AcpPermissionResponse, type AcpUpdate } from './types.ts';
17
- /** How the client answers one `session/request_permission`. */
18
- export type AcpPermissionHandler = (request: AcpFrame) => boolean | Promise<boolean>;
19
- /**
20
- * The answerable options of one `session/request_permission` frame. The wire is
21
- * untrusted, so entries without a usable `optionId` are dropped rather than
22
- * echoed back.
23
- * @param frame - the reverse-RPC request frame.
24
- * @returns the options the client may answer with.
25
- */
26
- export declare function permissionOptionsOf(frame: AcpFrame): readonly AcpPermissionOption[];
27
- /**
28
- * Encode one approval decision as the ACP `RequestPermissionResponse` the agent
29
- * correlates against the options it advertised. The agent reads a terminal
30
- * *option*, never a boolean: an outcome it cannot resolve (or a response that
31
- * fails to parse at all) is reported to the model as a user rejection, so a
32
- * decision that no single option expresses becomes `cancelled` — the honest
33
- * "this client has no answer". Kimi re-uses this RPC for its question and
34
- * plan-review bridges, which offer one `allow_once` option per choice; picking
35
- * one of those would answer a question no human was asked, so any ambiguous
36
- * set (zero or several `allow_once`/`reject_once` candidates) cancels instead.
37
- * `allow_once` is preferred over `allow_always` because the decision is re-read
38
- * from the session knobs on every request: letting the agent cache a session
39
- * grant would outlive a mid-session switch back to `ask`.
40
- * @param approved - whether the driver approves the request.
41
- * @param options - the options the agent advertised for this request.
42
- * @returns the result payload to send back.
43
- */
44
- export declare function permissionResponse(approved: boolean, options: readonly AcpPermissionOption[]): AcpPermissionResponse;
45
- /** Callback receiving every non-response event line. */
46
- export type AcpUpdateHandler = (update: AcpUpdate) => void;
47
- /**
48
- * ACP client over one `kimi acp` child. Created once per driver scope and reused
49
- * across steps, matching the Pi RPC client's lifecycle.
50
- */
51
- export declare class AcpClient {
52
- private readonly process;
53
- private readonly pending;
54
- private readonly updateBuffer;
55
- private updateWake;
56
- private updateHandler;
57
- private permissionHandler;
58
- private sealed;
59
- private nextId;
60
- private readonly decoder;
61
- private buffer;
62
- /** Whether this client was sealed or its process exited. */
63
- get closed(): boolean;
64
- /** Mount a client over an already-spawned `kimi acp` process. */
65
- constructor(process: KimiProcess);
66
- /**
67
- * Create a client, spawning the `kimi acp` child through the supplied
68
- * capability (or the default node spawn when none is given).
69
- * @param spec - the `kimi acp` argv/cwd/env the child should run with.
70
- * @param spawn - optional process-spawn capability (the subprocess seam).
71
- * @returns the connected client.
72
- */
73
- static create(spec: KimiSpawnSpec, spawn?: KimiSpawnCapability): AcpClient;
74
- /** Register the event dispatch handler. */
75
- onUpdate(handler: AcpUpdateHandler): void;
76
- /** Register the permission-approval handler (reverse-RPC answers). */
77
- onPermission(handler: AcpPermissionHandler): void;
78
- /** Send one request and await the correlated response. */
79
- request(method: string, params: unknown): Promise<unknown>;
80
- /** Send a notification (no correlated response awaited). */
81
- notify(method: string, params: unknown): void;
82
- /** Open the protocol handshake. */
83
- initialize(): Promise<unknown>;
84
- /** Start a fresh ACP session and resolve to its session id. */
85
- newSession(cwd: string): Promise<string>;
86
- /**
87
- * Select the model for one ACP session. The agent answers with an error frame
88
- * when it cannot serve the id, and {@link request} rejects on that frame — so
89
- * an engine that refuses the model fails the step loud rather than silently
90
- * running its own default.
91
- * @param sessionId - the ACP session the model applies to.
92
- * @param modelId - the model id to select.
93
- * @returns the agent's response to `session/set_model`.
94
- */
95
- setModel(sessionId: string, modelId: string): Promise<unknown>;
96
- /**
97
- * Prompt the agent in a session and resolve when the turn completes.
98
- * @param sessionId - the ACP session to prompt.
99
- * @param text - the serialized transcript, or a bare slash command.
100
- * @param images - image blocks to carry after the text block; omitted or
101
- * empty leaves the request exactly the text-only shape.
102
- * @returns the agent's response to `session/prompt`.
103
- */
104
- prompt(sessionId: string, text: string, images?: readonly AcpImageContent[]): Promise<unknown>;
105
- /** Cancel the active turn in a session (fire-and-forget). */
106
- cancel(sessionId: string): void;
107
- /**
108
- * Answer a pending `session/request_permission`.
109
- * @param id - the reverse-RPC request id.
110
- * @param response - the ACP outcome to answer with.
111
- */
112
- respondPermission(id: number, response: AcpPermissionResponse): void;
113
- /** Consume every buffered update as an async generator. */
114
- updates(): AsyncGenerator<AcpUpdate, void, void>;
115
- /** Seal the client and request child termination. */
116
- dispose(): void;
117
- private feed;
118
- /** Dispatch one parsed line: a response, an update notification, or a reverse-RPC request. */
119
- private dispatch;
120
- private settle;
121
- private handlePermission;
122
- }
123
- //# sourceMappingURL=client.d.ts.map
@@ -1,73 +0,0 @@
1
- /**
2
- * Maps `kimi acp` `session/update` events to dsh session-log projections.
3
- *
4
- * Kimi streams incremental assistant text (`agent_message_chunk`), incremental
5
- * thinking (`agent_thought_chunk`), a tool-call announcement (`tool_call`) and its
6
- * progress/result updates (`tool_call_update`). Assistant text and thinking are
7
- * Deltas; a tool update's content is a whole Snapshot that replaces the previous
8
- * one. A tool call's identity arrives on the announcement but its input
9
- * (`rawInput`) does not — only a later update carries it. This module is pure: it
10
- * classifies an update, extracts chunk deltas, and projects the tool-call
11
- * identity/input/content/result so the agent can fold them into the durable log.
12
- * Content blocks use the observed kimi
13
- * `{ type: 'content', content: { type: 'text', text } }` nesting; unknown block
14
- * types are ignored.
15
- *
16
- * @module dsh-loop-engine/engine-kimi/acp/mapping
17
- */
18
- import type { ToolResultMessage } from '@deepseek-ai/dsh-llm';
19
- import type { AcpContentBlock, AcpToolCallExt, AcpToolCallStreamExt, AcpUpdate } from './types.ts';
20
- /** Whether the update is an incremental assistant text chunk. */
21
- export declare function isTextChunk(update: AcpUpdate): update is AcpUpdate & {
22
- readonly sessionUpdate: 'agent_message_chunk';
23
- readonly content: AcpContentBlock;
24
- };
25
- /** Whether the update is an incremental thinking chunk. */
26
- export declare function isThoughtChunk(update: AcpUpdate): update is AcpUpdate & {
27
- readonly sessionUpdate: 'agent_thought_chunk';
28
- readonly content: AcpContentBlock;
29
- };
30
- /** Whether the update announces a tool call. */
31
- export declare function isToolCall(update: AcpUpdate): update is AcpToolCallExt;
32
- /** Whether the update streams a tool call's progress/result. */
33
- export declare function isToolCallUpdate(update: AcpUpdate): update is AcpToolCallStreamExt;
34
- /** The delta text of a text/thinking chunk. */
35
- export declare function chunkDelta(update: AcpUpdate): string;
36
- /** The raw tool-call id (+ content index) as the wire carries it. */
37
- export declare function toolCallIdOf(update: AcpUpdate): string;
38
- /** The tool display name (`title`). */
39
- export declare function toolCallName(update: AcpUpdate): string;
40
- /**
41
- * The tool call's real input as the JSON `arguments` string the durable
42
- * `tool/call` carries, when the frame supplies it.
43
- *
44
- * The `tool_call` announcement never carries `rawInput`; a later
45
- * `tool_call_update` does (measured against kimi 0.28.x: the execution-start
46
- * frame, `status: 'in_progress'`). A call's arguments are therefore unknowable
47
- * at announce time, and the driver logs the call only once an update supplies
48
- * them — or when the call settles, whichever comes first.
49
- * @param update - one tool-call announcement or update.
50
- * @returns the arguments string, or `undefined` when the frame omits the field.
51
- */
52
- export declare function toolRawInput(update: AcpToolCallExt | AcpToolCallStreamExt): string | undefined;
53
- /** Whether a tool stream status is settled (no longer streaming). */
54
- export declare function isToolSettledStatus(status: string): boolean;
55
- /** Whether a tool stream status denotes a failure. */
56
- export declare function isToolErrorStatus(status: string): boolean;
57
- /**
58
- * The tool call's content as ONE update carries it: the joined text of its
59
- * observed `{ type: 'content', content: { type: 'text', text } }` blocks.
60
- *
61
- * Kimi re-sends the call's whole content on every `tool_call_update` rather than
62
- * streaming deltas (measured against 0.28.1: the string grows from the tool
63
- * input's rendering to the final output), so this is a *snapshot* — callers
64
- * replace with it, never append. `undefined` means the update carried no
65
- * content field at all (nothing to replace), which is distinct from a present
66
- * but empty one.
67
- * @param update - one tool-call announcement or update.
68
- * @returns the content snapshot, or `undefined` when the field is absent.
69
- */
70
- export declare function toolContentText(update: AcpToolCallExt | AcpToolCallStreamExt): string | undefined;
71
- /** Project a completed tool call to a durable tool/result message. */
72
- export declare function toolResult(callId: string, text: string, isError: boolean): ToolResultMessage;
73
- //# sourceMappingURL=mapping.d.ts.map
@@ -1,143 +0,0 @@
1
- /**
2
- * Agent Client Protocol (ACP) wire types for the `kimi acp` driver.
3
- *
4
- * Kimi exposes the ACP adapter over stdio (JSON-RPC 2.0). The driver speaks a
5
- * minimal, stable subset that a normal agent flow needs: initialize, session/new,
6
- * session/prompt, session/cancel, plus the `session/update` notifications carrying
7
- * incremental assistant text (`agent_message_chunk`), thinking
8
- * (`agent_thought_chunk`), and tool calls (`tool_call` / `tool_call_update`).
9
- * Tool-approval arrives as the reverse-RPC `session/request_permission`, which the
10
- * client must answer. Shapes below are the live-record deltas observed against the
11
- * 0.28.1 CLI; content blocks re-use a `{ type, text }` text shape (and tolerate
12
- * other block types by ignoring them). The image block is modelled for the
13
- * OUTGOING side — the bytes a dsh step delivers as an ACP image block.
14
- *
15
- * @module dsh-loop-engine/engine-kimi/acp/types
16
- */
17
- /** A text content block in an ACP update (the observed kimi shape). */
18
- export interface AcpTextContent {
19
- readonly type: 'text';
20
- readonly text: string;
21
- }
22
- /** One tool-call content block: the observed kimi `{ type: 'content', content }` nesting. */
23
- export interface AcpToolContentBlock {
24
- readonly type: 'content';
25
- readonly content: AcpTextContent;
26
- }
27
- /** An image content block, as ACP/MCP spells it (base64 bytes + media type). */
28
- export interface AcpImageContent {
29
- readonly type: 'image';
30
- /** Base64-encoded image bytes. */
31
- readonly data: string;
32
- /** The image's own media type. */
33
- readonly mimeType: string;
34
- }
35
- /** A content block in a chunk/thought update; text is handled, others are ignored. */
36
- export type AcpContentBlock = AcpTextContent | AcpImageContent | {
37
- readonly type: 'resource_link' | 'audio' | string;
38
- readonly [key: string]: unknown;
39
- };
40
- /** One `session/update` notification (partial; the discriminator is `sessionUpdate`). */
41
- export interface AcpUpdate {
42
- readonly sessionUpdate: string;
43
- readonly [key: string]: unknown;
44
- }
45
- /**
46
- * A tool-call announcement (`sessionUpdate: 'tool_call'`).
47
- *
48
- * Measured against kimi 0.28.x: an announcement carries the call's identity
49
- * (`toolCallId`/`title`/`kind`) and an empty content card, but **never** its
50
- * input — `rawInput` only ever arrives on a later `tool_call_update` (the
51
- * execution-start frame).
52
- */
53
- export interface AcpToolCallExt extends AcpUpdate {
54
- readonly sessionUpdate: 'tool_call';
55
- readonly toolCallId: string;
56
- readonly title: string;
57
- readonly kind: string;
58
- readonly status: string;
59
- readonly content?: readonly AcpToolContentBlock[];
60
- /** The call's real input; absent on the announcement (see above). */
61
- readonly rawInput?: unknown;
62
- }
63
- /** A tool-call progress/result stream (`sessionUpdate: 'tool_call_update'`). */
64
- export interface AcpToolCallStreamExt extends AcpUpdate {
65
- readonly sessionUpdate: 'tool_call_update';
66
- readonly toolCallId: string;
67
- readonly status: string;
68
- readonly content?: readonly AcpToolContentBlock[];
69
- /** The call's real input, first present on the execution-start frame. */
70
- readonly rawInput?: unknown;
71
- /** The tool's raw structured output, present once the call settles. */
72
- readonly rawOutput?: unknown;
73
- }
74
- /** One option the agent offers for a `session/request_permission`. */
75
- export interface AcpPermissionOption {
76
- /** Opaque id the client sends back in `outcome.optionId` (the only field the agent reads). */
77
- readonly optionId: string;
78
- /** Human-readable label for the option. */
79
- readonly name?: string;
80
- /** Option class: `allow_once` / `allow_always` / `reject_once` / `reject_always`. */
81
- readonly kind?: string;
82
- }
83
- /** The `session/request_permission` reverse-RPC params. */
84
- export interface AcpPermissionRequest {
85
- readonly sessionId?: string;
86
- /** Every option the agent will accept an answer for; empty means no answerable choice. */
87
- readonly options?: readonly AcpPermissionOption[];
88
- /** The tool call the prompt belongs to (presentation/correlation only). */
89
- readonly toolCall?: unknown;
90
- }
91
- /**
92
- * Terminal outcome of a permission request: the option the client picked, or
93
- * `cancelled` when it had no answer to give.
94
- */
95
- export type AcpPermissionOutcome = {
96
- readonly outcome: 'selected';
97
- readonly optionId: string;
98
- } | {
99
- readonly outcome: 'cancelled';
100
- };
101
- /** The `session/request_permission` result the agent expects (ACP `RequestPermissionResponse`). */
102
- export interface AcpPermissionResponse {
103
- readonly outcome: AcpPermissionOutcome;
104
- }
105
- /** Result of a completed `session/prompt` request (opaque; the turn ended). */
106
- export interface AcpPromptResult {
107
- readonly [key: string]: unknown;
108
- }
109
- /** The id-scoped response to a request (result or error). */
110
- export interface AcpResponse {
111
- readonly id: number;
112
- readonly result?: unknown;
113
- readonly error?: {
114
- readonly code: number;
115
- readonly message: string;
116
- };
117
- }
118
- /** A JSON-RPC request/notification frame on the wire. */
119
- export interface AcpFrame {
120
- readonly jsonrpc: '2.0';
121
- readonly id?: number;
122
- readonly method?: string;
123
- readonly params?: unknown;
124
- readonly result?: unknown;
125
- readonly error?: {
126
- readonly code: number;
127
- readonly message: string;
128
- };
129
- }
130
- /** Guards toolkit: is the frame a `session/update` notification carrying an update. */
131
- export declare function isUpdateFrame(frame: AcpFrame): frame is AcpFrame & {
132
- readonly method: 'session/update';
133
- readonly params: {
134
- readonly sessionId: string;
135
- readonly update: AcpUpdate;
136
- };
137
- };
138
- /** Guards toolkit: is the frame a reverse-RPC request needing an approval answer. */
139
- export declare function isPermissionRequestFrame(frame: AcpFrame): frame is AcpFrame & {
140
- readonly id: number;
141
- readonly method: 'session/request_permission';
142
- };
143
- //# sourceMappingURL=types.d.ts.map