dsh-loop-engine 0.1.5-rc1 → 0.1.5-rc2

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.
Files changed (35) hide show
  1. package/README.md +88 -5
  2. package/README.zh.md +74 -0
  3. package/lib/client.js +158 -0
  4. package/lib/index.js +1020 -1486
  5. package/lib/invariant.js +6 -3
  6. package/lib/types/client/turn-status.d.ts +43 -0
  7. package/lib/types/driver-core/agents-md-skill-provider.d.ts +72 -0
  8. package/lib/types/driver-core/hosted-loop-factory.d.ts +119 -0
  9. package/lib/types/driver-core/ownership.d.ts +6 -6
  10. package/lib/types/driver-core/permission-knobs.d.ts +1 -1
  11. package/lib/types/driver-core/prompt.d.ts +1 -1
  12. package/lib/types/driver-core/skill-inject.d.ts +2 -2
  13. package/lib/types/engine-claude/agent.d.ts +22 -0
  14. package/lib/types/engine-claude/loop.d.ts +7 -56
  15. package/lib/types/engine-claude/mapping.d.ts +28 -3
  16. package/lib/types/engine-codex/agent.d.ts +46 -3
  17. package/lib/types/engine-codex/appserver/client.d.ts +16 -0
  18. package/lib/types/engine-codex/loop.d.ts +7 -56
  19. package/lib/types/engine-codex/permission.d.ts +100 -6
  20. package/lib/types/engine-codex/skills.d.ts +7 -8
  21. package/lib/types/engine-kimi/acp/client.d.ts +33 -3
  22. package/lib/types/engine-kimi/acp/mapping.d.ts +36 -7
  23. package/lib/types/engine-kimi/acp/types.d.ts +41 -2
  24. package/lib/types/engine-kimi/agent.d.ts +65 -8
  25. package/lib/types/engine-kimi/loop.d.ts +7 -56
  26. package/lib/types/engine-kimi/skills.d.ts +9 -14
  27. package/lib/types/engine-pi/agent.d.ts +23 -4
  28. package/lib/types/engine-pi/loop.d.ts +7 -56
  29. package/lib/types/engine-pi/permission.d.ts +16 -12
  30. package/lib/types/engine-pi/skills.d.ts +6 -14
  31. package/lib/types/patch-manager.d.ts +7 -0
  32. package/lib/types/provider-route.d.ts +16 -7
  33. package/lib/types/settings.d.ts +4 -1
  34. package/lib/types/skills.d.ts +6 -14
  35. package/package.json +1 -1
package/README.md CHANGED
@@ -20,11 +20,94 @@ Restart `dsh web`, then open **Settings → Loop engine**.
20
20
  > span changes.
21
21
 
22
22
  > **pnpm users:** pnpm 10+ blocks dependency build scripts by default, so the
23
- > install may report `@google/genai`, `node-pty`, and `protobufjs` as blocked.
24
- > This is expected — click **"Allow build scripts and retry"** (or run
25
- > `pnpm approve-builds`, or list them under `pnpm.onlyBuiltDependencies` in
26
- > your project root) and retry. Only the installing project can grant this;
27
- > the plugin cannot pre-approve its own dependencies.
23
+ > install may fail with `ERR_PNPM_IGNORED_BUILDS` naming `esbuild`,
24
+ > `@google/genai`, and `protobufjs` (all reached through the engine SDKs). This
25
+ > is expected — allow them and retry, either interactively with
26
+ > `pnpm approve-builds`, or by declaring them in the installing project's
27
+ > `pnpm-workspace.yaml`:
28
+ >
29
+ > ```yaml
30
+ > allowBuilds:
31
+ > esbuild: true
32
+ > '@google/genai': true
33
+ > protobufjs: true
34
+ > ```
35
+ >
36
+ > Only the installing project can grant this; the plugin cannot pre-approve its
37
+ > own dependencies. Note that `allowBuilds` is the pnpm 11 spelling — pnpm 11
38
+ > **deletes** the legacy `onlyBuiltDependencies` (and `neverBuiltDependencies`,
39
+ > `ignoredBuiltDependencies`) keys from `package.json` and no longer honors
40
+ > them, so putting them there silently does nothing.
41
+
42
+ ### Running against a harness source checkout
43
+
44
+ The install above assumes a **published** dsh (`npx @deepseek-ai/dsh`) and needs
45
+ no extra setup. Booting the harness from its **source checkout**
46
+ (`cd deepseek-harness && pnpm dsh web`) takes one more step, because the two
47
+ halves then resolve harness packages to different files:
48
+
49
+ | Side | `@deepseek-ai/dsh-scope` resolves to |
50
+ |---|---|
51
+ | Source-launched harness | `packages/core/scope/src/index.ts` (via tsconfig `paths`) |
52
+ | Installed plugin (its tarball ships only `lib/`) | `packages/core/scope/lib/index.js` |
53
+
54
+ That is one package loaded as two module instances. `dsh-scope` tags a context
55
+ with a module-local `Symbol('dsh.scope')`, so a scope minted through one instance
56
+ is invisible to the other, and resuming a session fails with:
57
+
58
+ ```
59
+ agent-presets: refusing to compose an unscoped context;
60
+ the scope key is what joins an agent to its preset
61
+ ```
62
+
63
+ Bridge the profile's peers to the harness source so both halves share one
64
+ instance. Set `HARNESS` to the harness checkout **as a `file://` URL**, then run
65
+ this from the profile directory:
66
+
67
+ ```sh
68
+ HARNESS=file:///path/to/deepseek-harness # e.g. file:///D:/repos/deepseek-harness
69
+ cd "$DSH_HOME/profiles/web" && mkdir -p shims
70
+ while IFS='|' read -r name rel; do
71
+ mkdir -p "shims/$name"
72
+ printf '{"name":"@deepseek-ai/%s","version":"0.0.0","private":true,"type":"module","main":"index.mjs"}\n' \
73
+ "$name" > "shims/$name/package.json"
74
+ printf "export * from '%s/%s'\nimport * as mod from '%s/%s'\nexport default mod.default\n" \
75
+ "$HARNESS" "$rel" "$HARNESS" "$rel" > "shims/$name/index.mjs"
76
+ done <<EOF
77
+ cordis|vendor/cordis/src/index.ts
78
+ schemastery|vendor/schemastery/src/index.ts
79
+ dsh-agent|packages/core/agent/src/index.ts
80
+ dsh-scope|packages/core/scope/src/index.ts
81
+ dsh-session|packages/core/session/src/index.ts
82
+ dsh-session-persistence|packages/session/session-persistence/src/index.ts
83
+ dsh-settings|packages/settings/settings/src/index.ts
84
+ dsh-subprocess|packages/subprocess/subprocess/src/index.ts
85
+ dsh-timeout|packages/util/timeout/src/index.ts
86
+ dsh-llm|packages/llm/llm/src/index.ts
87
+ dsh-invariants|packages/runtime-diagnostics/invariants/src/index.ts
88
+ dsh-home-paths|packages/util/home-paths/src/index.ts
89
+ EOF
90
+ ```
91
+
92
+ Then point the profile's `package.json` at them and reinstall:
93
+
94
+ ```sh
95
+ node -e 'const f="package.json",j=require("./"+f),d=j.dependencies??={}
96
+ for(const n of ["cordis","schemastery","dsh-agent","dsh-scope","dsh-session","dsh-session-persistence","dsh-settings","dsh-subprocess","dsh-timeout","dsh-llm","dsh-invariants","dsh-home-paths"])
97
+ d["@deepseek-ai/"+n]="file:./shims/"+n
98
+ require("fs").writeFileSync(f,JSON.stringify(j,null,2)+"\n")'
99
+ pnpm install
100
+ ```
101
+
102
+ Restart `dsh web`. If something loads the `@deepseek-ai/dsh-scope/invariant`
103
+ subpath, also give that shim an `invariant.mjs` (`export * from
104
+ '$HARNESS/packages/core/scope/src/invariant.ts'`) and add
105
+ `"./invariant": "./invariant.mjs"` to its `exports`.
106
+
107
+ > Installing the plugin as a local **`link:`** checkout sidesteps this entirely:
108
+ > when the checkout sits beside the harness repo it inherits the harness's own
109
+ > `tsconfig.json` and with it the same `paths` mapping. The split only appears
110
+ > when a *packed* plugin (npm or tarball) meets a *source* harness.
28
111
 
29
112
  ## Version compatibility
30
113
 
package/README.zh.md CHANGED
@@ -14,6 +14,80 @@ dsh plugin --profile web add dsh-loop-engine
14
14
 
15
15
  > 切换引擎会重写 `cordis.patch.yml` 中一小段受管理的内容,文件里你写的其它部分都会保留,只改动插件自己的区间。
16
16
 
17
+ > **pnpm 用户:** pnpm 10+ 默认拦截依赖的 build script,安装可能以
18
+ > `ERR_PNPM_IGNORED_BUILDS` 失败,并列出 `esbuild`、`@google/genai`、
19
+ > `protobufjs`(都经引擎 SDK 传递而来)。这是预期行为——放行后重试即可:可用
20
+ > `pnpm approve-builds` 交互放行,或在安装项目的 `pnpm-workspace.yaml` 里声明:
21
+ >
22
+ > ```yaml
23
+ > allowBuilds:
24
+ > esbuild: true
25
+ > '@google/genai': true
26
+ > protobufjs: true
27
+ > ```
28
+ >
29
+ > 只有安装方能授予该权限,插件无法预先放行自己的依赖。注意 `allowBuilds` 是
30
+ > pnpm 11 的写法——pnpm 11 会**删除** `package.json` 里遗留的
31
+ > `onlyBuiltDependencies`(以及 `neverBuiltDependencies`、`ignoredBuiltDependencies`)
32
+ > 且不再识别它们,写在那里会静默失效。
33
+
34
+ ### 源码启动 harness 时的额外步骤
35
+
36
+ 上面的安装针对 **发布版** dsh(`npx @deepseek-ai/dsh`),不需要额外操作。若改用**源码**启动 harness(`cd deepseek-harness && pnpm dsh web`),则要多做一步——因为两边会把 harness 的包解析到不同文件:
37
+
38
+ | 一侧 | `@deepseek-ai/dsh-scope` 解析到 |
39
+ |---|---|
40
+ | 源码启动的 harness | `packages/core/scope/src/index.ts`(经 tsconfig `paths`) |
41
+ | 安装的插件(包内只有 `lib/`) | `packages/core/scope/lib/index.js` |
42
+
43
+ 也就是同一个包被加载成了两个模块实例。`dsh-scope` 用模块私有的 `Symbol('dsh.scope')` 给 context 打标记,一个实例打的标记另一个实例读不到,于是恢复会话时报错:
44
+
45
+ ```
46
+ agent-presets: refusing to compose an unscoped context;
47
+ the scope key is what joins an agent to its preset
48
+ ```
49
+
50
+ 把 profile 的 peer 桥接到 harness 源码,让两边共用同一个实例。把 `HARNESS` 设为 harness checkout 的 **`file://` URL**,在 profile 目录下执行:
51
+
52
+ ```sh
53
+ HARNESS=file:///path/to/deepseek-harness # 例如 file:///D:/repos/deepseek-harness
54
+ cd "$DSH_HOME/profiles/web" && mkdir -p shims
55
+ while IFS='|' read -r name rel; do
56
+ mkdir -p "shims/$name"
57
+ printf '{"name":"@deepseek-ai/%s","version":"0.0.0","private":true,"type":"module","main":"index.mjs"}\n' \
58
+ "$name" > "shims/$name/package.json"
59
+ printf "export * from '%s/%s'\nimport * as mod from '%s/%s'\nexport default mod.default\n" \
60
+ "$HARNESS" "$rel" "$HARNESS" "$rel" > "shims/$name/index.mjs"
61
+ done <<EOF
62
+ cordis|vendor/cordis/src/index.ts
63
+ schemastery|vendor/schemastery/src/index.ts
64
+ dsh-agent|packages/core/agent/src/index.ts
65
+ dsh-scope|packages/core/scope/src/index.ts
66
+ dsh-session|packages/core/session/src/index.ts
67
+ dsh-session-persistence|packages/session/session-persistence/src/index.ts
68
+ dsh-settings|packages/settings/settings/src/index.ts
69
+ dsh-subprocess|packages/subprocess/subprocess/src/index.ts
70
+ dsh-timeout|packages/util/timeout/src/index.ts
71
+ dsh-llm|packages/llm/llm/src/index.ts
72
+ dsh-invariants|packages/runtime-diagnostics/invariants/src/index.ts
73
+ dsh-home-paths|packages/util/home-paths/src/index.ts
74
+ EOF
75
+ ```
76
+
77
+ 再把这些写进 profile 的 `package.json` 并重新安装:
78
+
79
+ ```sh
80
+ node -e 'const f="package.json",j=require("./"+f),d=j.dependencies??={}
81
+ for(const n of ["cordis","schemastery","dsh-agent","dsh-scope","dsh-session","dsh-session-persistence","dsh-settings","dsh-subprocess","dsh-timeout","dsh-llm","dsh-invariants","dsh-home-paths"])
82
+ d["@deepseek-ai/"+n]="file:./shims/"+n
83
+ require("fs").writeFileSync(f,JSON.stringify(j,null,2)+"\n")'
84
+ pnpm install
85
+ ```
86
+
87
+ 重启 `dsh web`。若有代码加载 `@deepseek-ai/dsh-scope/invariant` 子路径,再给该 shim 补一个 `invariant.mjs`(`export * from '$HARNESS/packages/core/scope/src/invariant.ts'`),并在它的 `exports` 里加上 `"./invariant": "./invariant.mjs"`。
88
+
89
+ > 用本地 **`link:`** 方式安装插件可以完全绕开这一步:checkout 与 harness 仓库相邻时,它会继承 harness 自己的 `tsconfig.json`,从而共用同一份 `paths` 映射。这个分裂只在**打包版**插件(npm 或 tarball)遇到**源码版** harness 时出现。
90
+
17
91
  ### 环境要求
18
92
 
19
93
  - 使用 Claude Code 引擎时需要本机已安装并登录 Claude Code CLI。
package/lib/client.js CHANGED
@@ -565,6 +565,163 @@ var LoopEngineStore = class {
565
565
  }
566
566
  };
567
567
 
568
+ // src/client/turn-status.ts
569
+ var ENGINE_ATTR = "data-loop-engine";
570
+ var PLUGIN_ID = "dsh-loop-engine";
571
+ var STYLESHEET = `
572
+ [class$="_turnStatus"]::before {
573
+ margin-right: 6px;
574
+ background: none;
575
+ -webkit-background-clip: border-box;
576
+ background-clip: border-box;
577
+ }
578
+
579
+ /*
580
+ * The row's own sweep, re-asserted for hosted engines.
581
+ *
582
+ * ui-chat disables that animation under \`prefers-reduced-motion: reduce\`
583
+ * (ChatView.module.css), and a media query carries no specificity \u2014 so this
584
+ * attribute-gated rule outranks it. That is deliberate here: deployment images
585
+ * ship with Windows' client-area animation off (SPI_GETCLIENTAREAANIMATION
586
+ * false), and with the guard in force EVERY indicator on this row is frozen \u2014
587
+ * the sweep and the glyph alike. Scoped to hosted engines, so in-process
588
+ * sessions keep the stock reduced-motion behaviour; delete this rule and
589
+ * restore the guard at the foot of the sheet to hand the decision back to the OS.
590
+ */
591
+ html[${ENGINE_ATTR}] [class$="_turnStatus"] {
592
+ background-position: 100% 0;
593
+ background-size: 250% 100%;
594
+ animation: le-shimmer 1.8s linear infinite;
595
+ }
596
+
597
+ @keyframes le-shimmer {
598
+ to { background-position: 0 0; }
599
+ }
600
+
601
+ html[${ENGINE_ATTR}="claude-code"] [class$="_turnStatus"] {
602
+ --dsw-static-deepseek-500: #d97757;
603
+ --dsw-static-deepseek-200: #f5bda6;
604
+ }
605
+ html[${ENGINE_ATTR}="claude-code"] [class$="_turnStatus"]::before {
606
+ content: "\u273B";
607
+ color: #d97757;
608
+ -webkit-text-fill-color: #d97757;
609
+ animation: le-bloom 1.6s ease-in-out infinite;
610
+ }
611
+
612
+ html[${ENGINE_ATTR}="codex"] [class$="_turnStatus"] {
613
+ --dsw-static-deepseek-500: #a9b1c0;
614
+ --dsw-static-deepseek-200: #e6eaf2;
615
+ }
616
+ html[${ENGINE_ATTR}="codex"] [class$="_turnStatus"]::before {
617
+ content: "\u2022";
618
+ color: #a9b1c0;
619
+ -webkit-text-fill-color: #a9b1c0;
620
+ text-shadow: 0 0 6px currentColor;
621
+ animation: le-pulse 1.4s ease-in-out infinite;
622
+ }
623
+
624
+ html[${ENGINE_ATTR}="pi"] [class$="_turnStatus"] {
625
+ --dsw-static-deepseek-500: #8e4ec6;
626
+ --dsw-static-deepseek-200: #d6bff0;
627
+ }
628
+ html[${ENGINE_ATTR}="pi"] [class$="_turnStatus"]::before {
629
+ content: "\u280B";
630
+ color: #8e4ec6;
631
+ -webkit-text-fill-color: #8e4ec6;
632
+ font-size: 1.1em;
633
+ animation: le-braille 1s linear infinite;
634
+ }
635
+
636
+ html[${ENGINE_ATTR}="kimi"] [class$="_turnStatus"] {
637
+ --dsw-static-deepseek-500: #e5484d;
638
+ --dsw-static-deepseek-200: #f5b2b4;
639
+ }
640
+ html[${ENGINE_ATTR}="kimi"] [class$="_turnStatus"]::before {
641
+ content: "\u{1F317}";
642
+ color: #e5484d;
643
+ -webkit-text-fill-color: #e5484d;
644
+ animation: le-moon 2.5s linear infinite;
645
+ }
646
+
647
+ /* Grows from small to large each cycle \u2014 the opposite of a twinkle. The range
648
+ is deliberately wide (3x) because a bare size change on a thin glyph reads
649
+ weakly otherwise, and the large state is held briefly (50%-62%) so it blooms
650
+ rather than throbs. 1.35x extends ~2.5px per side at the 14px glyph, inside
651
+ the 6px ::before margin. */
652
+ @keyframes le-bloom {
653
+ 0%, 100% { transform: scale(0.45); opacity: 0.45; }
654
+ 50%, 62% { transform: scale(1.35); opacity: 1; }
655
+ }
656
+ /* A terminal braille spinner. The glyph is an ordinary text character, not an
657
+ emoji, so the engine color actually paints \u2014 a colored emoji silently ignores
658
+ color/-webkit-text-fill-color. Stepping content walks the ten dot frames. */
659
+ @keyframes le-braille {
660
+ 0% { content: "\u280B"; }
661
+ 10% { content: "\u2819"; }
662
+ 20% { content: "\u2839"; }
663
+ 30% { content: "\u2838"; }
664
+ 40% { content: "\u283C"; }
665
+ 50% { content: "\u2834"; }
666
+ 60% { content: "\u2826"; }
667
+ 70% { content: "\u2827"; }
668
+ 80% { content: "\u2807"; }
669
+ 90% { content: "\u280F"; }
670
+ 100% { content: "\u280B"; }
671
+ }
672
+ /* A soft pulse for the codex dot: the glyph breathes between a small, dim
673
+ point and a larger, full-brightness one \u2014 a light dot, not a spinner. */
674
+ @keyframes le-pulse {
675
+ 0%, 100% { transform: scale(0.5); opacity: 0.35; }
676
+ 50% { transform: scale(1.3); opacity: 1; }
677
+ }
678
+ /* Moon phases rather than a rigid rotation: a spinning moon bitmap can only
679
+ squash and mirror itself, never show a full or a new moon. Stepping the
680
+ glyph through the phase set sweeps the lit edge across the disc AND actually
681
+ reaches \u{1F315} and \u{1F311}. The order runs waning (full \u2192 new), so the lit edge
682
+ retreats right-to-left \u2014 the direction the user picked. */
683
+ @keyframes le-moon {
684
+ 0% { content: "\u{1F315}"; }
685
+ 12.5% { content: "\u{1F314}"; }
686
+ 25% { content: "\u{1F313}"; }
687
+ 37.5% { content: "\u{1F312}"; }
688
+ 50% { content: "\u{1F311}"; }
689
+ 62.5% { content: "\u{1F318}"; }
690
+ 75% { content: "\u{1F317}"; }
691
+ 87.5% { content: "\u{1F316}"; }
692
+ 100% { content: "\u{1F315}"; }
693
+ }
694
+
695
+ `;
696
+ function reflect(engine, settled) {
697
+ const root = document.documentElement;
698
+ if (!settled || engine === "in-process") {
699
+ delete root.dataset.loopEngine;
700
+ return;
701
+ }
702
+ root.dataset.loopEngine = engine;
703
+ }
704
+ function installTurnStatusStyles(ctx, store) {
705
+ if (typeof document === "undefined") return;
706
+ ctx.effect(() => {
707
+ const tag = document.createElement("style");
708
+ tag.dataset.plugin = PLUGIN_ID;
709
+ tag.dataset.pluginCss = `${PLUGIN_ID}/turn-status.css`;
710
+ tag.textContent = STYLESHEET;
711
+ document.head.appendChild(tag);
712
+ return () => {
713
+ tag.remove();
714
+ delete document.documentElement.dataset.loopEngine;
715
+ };
716
+ }, "loop-engine: per-engine turn status styles");
717
+ const sync = () => {
718
+ const { status, engine } = store.getSnapshot();
719
+ reflect(engine, status === "ready");
720
+ };
721
+ ctx.effect(() => store.subscribe(sync), "loop-engine: turn status engine reflection");
722
+ sync();
723
+ }
724
+
568
725
  // src/client/locales.ts
569
726
  var zh = {
570
727
  nav: "\u5FAA\u73AF\u5F15\u64CE",
@@ -624,6 +781,7 @@ function apply(ctx) {
624
781
  controller.dispose();
625
782
  };
626
783
  }, "loop-engine: store lifecycle");
784
+ installTurnStatusStyles(ctx, controller.store);
627
785
  const t = ctx.locale.bind(NS);
628
786
  const injected = () => ({
629
787
  controller,