From eb0cc4eb187c426e6497d18cae7cff53c73107d8 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Thu, 23 Jul 2026 20:35:09 +0800 Subject: [PATCH] refactor: drop obsolete expose-internals flag --- .../2026-06-20-extract-example-app-packages.md | 2 +- .../2026-07-20-dsh-cli-personal-config.i18n.yaml | 4 ++-- .../feature/2026-07-20-dsh-cli-personal-config.md | 2 +- .../2026-07-20-dsh-cli-personal-config.zh.md | 2 +- .../2026-07-21-tui-reload-command.i18n.yaml | 4 ++-- .../feature/2026-07-21-tui-reload-command.md | 2 +- .../feature/2026-07-21-tui-reload-command.zh.md | 2 +- apps/cli/README.md | 2 +- bin/dsh | 3 +-- docs/cordis-tutorial/06-composition-and-hmr.md | 4 ++-- examples/cordis-agent/cordis.yml | 2 +- examples/jsonrpc-agent/tests/keyless-smoke.e2e.ts | 2 -- examples/tui-agent/README.md | 2 +- examples/tui-agent/cordis.yml | 7 +++---- examples/tui-agent/tests/pty-harness.ts | 1 - package.json | 6 +++--- packages/examples/acp-demo/README.md | 2 +- packages/examples/acp-demo/tests/built-bin.e2e.ts | 7 +++---- packages/examples/cli-demo/README.md | 2 +- packages/examples/cli-demo/tests/built-bin.e2e.ts | 2 +- packages/examples/tui-demo/README.md | 2 +- packages/examples/tui-demo/tests/built-bin.e2e.ts | 6 +++--- packages/support/loader-smoke/src/index.ts | 14 +++++--------- .../loader-smoke/tests/example-launch.spec.ts | 6 ------ packages/ui/app-boot/README.md | 4 ++-- packages/ui/app-boot/src/index.ts | 5 ++--- scripts/demo-code-mode.mjs | 2 +- 27 files changed, 41 insertions(+), 58 deletions(-) diff --git a/.agents/notes/implemented/architecture/2026-06-20-extract-example-app-packages.md b/.agents/notes/implemented/architecture/2026-06-20-extract-example-app-packages.md index 05d72e4e49..953a977c19 100644 --- a/.agents/notes/implemented/architecture/2026-06-20-extract-example-app-packages.md +++ b/.agents/notes/implemented/architecture/2026-06-20-extract-example-app-packages.md @@ -24,7 +24,7 @@ Each example is now **mostly an invocation of an app package**, splitting the wi The proposal listed `hmr` among the interactive app's baked-in front-door cluster. Validating against the code, baking `hmr` into the app package fights Cordis in two ways, so it ships as a **leaf `cordis.yml` entry** instead: -1. `@cordisjs/plugin-hmr` is a Loader-only, subprocess-only dev plugin — its constructor throws without `node --expose-internals` + a live `loader` service, so it can only run in the real `demo:*`/bin subprocess, never in the in-process unit/coverage tier. +1. `@cordisjs/plugin-hmr` is a Loader-only, subprocess-only dev plugin — it requires the live `loader` service and its internal module access, so it can only run in the real `demo:*`/bin subprocess, never in the in-process unit/coverage tier. 2. The in-process test tier (vitest) cannot even *import* the vendored `hmr` module (its class-decorator `@Inject` form fails under Vite's transform), so a package whose `apply` statically imported it could never satisfy the per-file 100% coverage gate on its headline function. Crucially, `hmr` is not a stdout-purity footgun: a stray entry in the ACP config does not corrupt JSON-RPC frames. Every shipped app omits a stdout console logger; the app or protocol driver alone owns stdout. diff --git a/.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.i18n.yaml b/.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.i18n.yaml index 7addc991d2..76dd488060 100644 --- a/.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write -2026-07-20-dsh-cli-personal-config.md: 514bb5b12a3e04c7deaad1e8616472eed1c920e1 -2026-07-20-dsh-cli-personal-config.zh.md: 16fada82c59c8a356e6df112234e6b7565aae1bf +2026-07-20-dsh-cli-personal-config.md: 9525aa811d792a918f03a52c21bc273e92fb8be7 +2026-07-20-dsh-cli-personal-config.zh.md: f21d4b1f22b3a3807b6b4155969282343f6048f5 diff --git a/.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.md b/.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.md index 514bb5b12a..9525aa811d 100644 --- a/.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.md +++ b/.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.md @@ -12,7 +12,7 @@ A developer's own preferences — which provider and model the TUI uses, persona Two coupled pieces, aligned with the `apps/` assembly tier proposed by the `dsh web` PR (#443): -**The `dsh` CLI (`apps/cli`, npm name `@deepseek-ai/dsh`).** `apps/*` joins the workspaces as the product-assembly tier over `packages/*` libraries. The bin's dispatch reserves `web` and `-p`/`--prompt` for PR #443 (they exit with a pointer) so the two branches merge as a near-union; everything else runs the default surface: the interactive TUI, booting the shipped `examples/tui-agent/cordis.yml` (or an explicit config argument) with the invoking directory as the workspace. The committed `bin/dsh` launcher resolves the checkout through its own real path and runs the bin **from source** via the repo's tsx (with `--expose-internals` for the config's HMR entry), so `ln -sf "$(pwd)/bin/dsh" ~/.local/bin/dsh` installs a command that always executes the current working tree. `pnpm run demo:tui` runs the same entry. +**The `dsh` CLI (`apps/cli`, npm name `@deepseek-ai/dsh`).** `apps/*` joins the workspaces as the product-assembly tier over `packages/*` libraries. The bin's dispatch reserves `web` and `-p`/`--prompt` for PR #443 (they exit with a pointer) so the two branches merge as a near-union; everything else runs the default surface: the interactive TUI, booting the shipped `examples/tui-agent/cordis.yml` (or an explicit config argument) with the invoking directory as the workspace. The committed `bin/dsh` launcher resolves the checkout through its own real path and runs the bin **from source** via the repo's tsx, so `ln -sf "$(pwd)/bin/dsh" ~/.local/bin/dsh` installs a command that always executes the current working tree. `pnpm run demo:tui` runs the same entry. **Personal config (`dsh-app-boot`).** The personal overlay lives in the Harness home — `$DSH_HOME`, else `~/.dsh` — resolved by the shared [`resolveDshHome`](../architecture/2026-07-24-single-harness-home-resolver.md) (`@deepseek-ai/dsh-paths`), the same single root skills and AGENTS.md resolve against. The dsh TUI surface consumes its two optional files; the demo bins boot their committed trees verbatim: diff --git a/.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.zh.md b/.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.zh.md index 16fada82c5..f21d4b1f22 100644 --- a/.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.zh.md +++ b/.agents/notes/implemented/feature/2026-07-20-dsh-cli-personal-config.zh.md @@ -12,7 +12,7 @@ Status: implemented 两个耦合的部分,与 `dsh web` PR(#443)提出的 `apps/` 装配层对齐: -**`dsh` CLI(`apps/cli`,npm 名 `@deepseek-ai/dsh`)。** `apps/*` 作为 `packages/*` 库之上的产品装配层加入 workspaces。bin 的分发把 `web` 和 `-p`/`--prompt` 保留给 PR #443(它们以指引退出),使两个分支能以接近并集的方式合并;其余一切都运行默认表面:交互式 TUI,加载随仓库提供的 `examples/tui-agent/cordis.yml`(或显式的配置参数),并以调用目录为工作区。已提交的 `bin/dsh` 启动器通过自身真实路径解析 checkout,用仓库的 tsx **从源码**运行该 bin(带 `--expose-internals`,供配置里的 HMR 配置项使用),因此 `ln -sf "$(pwd)/bin/dsh" ~/.local/bin/dsh` 安装的命令永远执行当前工作树。`pnpm run demo:tui` 运行同一入口。 +**`dsh` CLI(`apps/cli`,npm 名 `@deepseek-ai/dsh`)。** `apps/*` 作为 `packages/*` 库之上的产品装配层加入 workspaces。bin 的分发把 `web` 和 `-p`/`--prompt` 保留给 PR #443(它们以指引退出),使两个分支能以接近并集的方式合并;其余一切都运行默认表面:交互式 TUI,加载随仓库提供的 `examples/tui-agent/cordis.yml`(或显式的配置参数),并以调用目录为工作区。已提交的 `bin/dsh` 启动器通过自身真实路径解析 checkout,用仓库的 tsx **从源码**运行该 bin,因此 `ln -sf "$(pwd)/bin/dsh" ~/.local/bin/dsh` 安装的命令永远执行当前工作树。`pnpm run demo:tui` 运行同一入口。 **个人配置(`dsh-app-boot`)。** 个人 overlay 存放在 Harness home——`$DSH_HOME`,否则 `~/.dsh`——由共享的 [`resolveDshHome`](../architecture/2026-07-24-single-harness-home-resolver.md)(`@deepseek-ai/dsh-paths`)解析,与 skills、AGENTS.md 解析所依据的单一根目录相同。dsh 的 TUI 表面消费其中两个可选文件;各示例 bin 仍然逐字节按已提交的配置树启动: diff --git a/.agents/notes/implemented/feature/2026-07-21-tui-reload-command.i18n.yaml b/.agents/notes/implemented/feature/2026-07-21-tui-reload-command.i18n.yaml index f3fba8a006..05f9ae37b6 100644 --- a/.agents/notes/implemented/feature/2026-07-21-tui-reload-command.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-07-21-tui-reload-command.i18n.yaml @@ -2,5 +2,5 @@ # side as of the last confirmed-consistent state. Both languages carry equal authority; # after editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write -2026-07-21-tui-reload-command.md: e5600f0ab5cd82dc556df76006fcf532d8c7d302 -2026-07-21-tui-reload-command.zh.md: 3798b0518df1c379cca808bd4af38490016567cb +2026-07-21-tui-reload-command.md: 89bf2f7bb482d7f3889136c1a6ac9918ba0c4919 +2026-07-21-tui-reload-command.zh.md: cfea10690af49f2cf484938a3f9f12d954766a71 diff --git a/.agents/notes/implemented/feature/2026-07-21-tui-reload-command.md b/.agents/notes/implemented/feature/2026-07-21-tui-reload-command.md index e5600f0ab5..89bf2f7bb4 100644 --- a/.agents/notes/implemented/feature/2026-07-21-tui-reload-command.md +++ b/.agents/notes/implemented/feature/2026-07-21-tui-reload-command.md @@ -6,7 +6,7 @@ English | [中文](2026-07-21-tui-reload-command.zh.md) ## Problem -HMR's file watcher only reacts to in-place `change` events under its configured roots (the config leaf's directory in the shipped demos). Editors that replace files by rename (BSD `sed -i`, `git checkout`) produce no event, and runtimes without the HMR entry (or without `--expose-internals`) have no config reload path at all. During development that means restarting the TUI to apply a config edit the watcher missed. Widening the watch roots to the whole repo was considered and rejected in discussion: dense package sharing makes module-level HMR a remount-most-of-the-tree operation with unpredictable externals boundaries. +HMR's file watcher only reacts to in-place `change` events under its configured roots (the config leaf's directory in the shipped demos). Editors that replace files by rename (BSD `sed -i`, `git checkout`) produce no event, and runtimes without the HMR entry have no config reload path at all. During development that means restarting the TUI to apply a config edit the watcher missed. Widening the watch roots to the whole repo was considered and rejected in discussion: dense package sharing makes module-level HMR a remount-most-of-the-tree operation with unpredictable externals boundaries. ## Decision diff --git a/.agents/notes/implemented/feature/2026-07-21-tui-reload-command.zh.md b/.agents/notes/implemented/feature/2026-07-21-tui-reload-command.zh.md index 3798b0518d..cfea10690a 100644 --- a/.agents/notes/implemented/feature/2026-07-21-tui-reload-command.zh.md +++ b/.agents/notes/implemented/feature/2026-07-21-tui-reload-command.zh.md @@ -6,7 +6,7 @@ Status: implemented ## Problem -HMR 的文件监听器只对其配置根目录(示例中即配置叶子所在目录)下的就地 `change` 事件起反应。以重命名方式替换文件的编辑器(BSD `sed -i`、`git checkout`)不产生事件,而没有挂载 HMR 配置项(或没有 `--expose-internals`)的运行时则完全没有配置重载路径。开发时这意味着监听器漏掉一次配置编辑就得重启 TUI。曾考虑把监听根目录扩大到整个仓库,讨论后否决:包之间的密集共享使模块级 HMR 变成「重挂大半棵树」的操作,externals 边界也不可预测。 +HMR 的文件监听器只对其配置根目录(示例中即配置叶子所在目录)下的就地 `change` 事件起反应。以重命名方式替换文件的编辑器(BSD `sed -i`、`git checkout`)不产生事件,而没有挂载 HMR 配置项的运行时则完全没有配置重载路径。开发时这意味着监听器漏掉一次配置编辑就得重启 TUI。曾考虑把监听根目录扩大到整个仓库,讨论后否决:包之间的密集共享使模块级 HMR 变成「重挂大半棵树」的操作,externals 边界也不可预测。 ## Decision diff --git a/apps/cli/README.md b/apps/cli/README.md index 87f5e670e3..9d0015b24e 100644 --- a/apps/cli/README.md +++ b/apps/cli/README.md @@ -20,4 +20,4 @@ Symlink the source-running launcher onto your PATH; it resolves the checkout thr ln -sf "$(pwd)/bin/dsh" ~/.local/bin/dsh ``` -`pnpm run demo:tui` runs the same entry from the repo root. The built form (`lib/bin.js`, via `pnpm run build`) needs `node --expose-internals` for the shipped config's HMR entry, exactly like the demo bins. +`pnpm run demo:tui` runs the same entry from the repo root. The built form (`lib/bin.js`, via `pnpm run build`) boots the same config under plain Node. diff --git a/bin/dsh b/bin/dsh index 88eaa0ab71..040cfface2 100755 --- a/bin/dsh +++ b/bin/dsh @@ -2,7 +2,6 @@ # dsh launcher: runs the apps/cli `dsh` bin FROM SOURCE with this checkout's # tsx, so a symlink from anywhere (e.g. ~/.local/bin/dsh) always executes the # current working tree — code changes apply on the next launch, no build step. -# --expose-internals: the shipped config mounts HMR, which needs Loader internals. set -eu # Resolve symlink chains without readlink -f (not on every macOS). @@ -19,4 +18,4 @@ root=$(CDPATH='' cd -- "$(dirname -- "$script")/.." && pwd) # tsx is imported by absolute path because bare `--import tsx` resolves from # the invoking cwd, which is usually outside this repository. export TSX_TSCONFIG_PATH="$root/tsconfig.json" -exec node --expose-internals --import "$root/node_modules/tsx/dist/loader.mjs" "$root/apps/cli/src/bin.ts" "$@" +exec node --import "$root/node_modules/tsx/dist/loader.mjs" "$root/apps/cli/src/bin.ts" "$@" diff --git a/docs/cordis-tutorial/06-composition-and-hmr.md b/docs/cordis-tutorial/06-composition-and-hmr.md index b11fdbe45c..bb236cc169 100644 --- a/docs/cordis-tutorial/06-composition-and-hmr.md +++ b/docs/cordis-tutorial/06-composition-and-hmr.md @@ -39,10 +39,10 @@ In `tmp/cordis-tutorial`, write `cordis.yml`: Two support plugins joined the list: HMR logs through the Cordis logger service, so without a console exporter you would not see its messages, and it `inject`s the `timer` service for debouncing — without `@cordisjs/plugin-timer` it sits in PENDING forever, silently. That silence is the subject of the next section. -HMR also needs Node's loader internals: +HMR reads Node's loader internals through the Loader's native helper. Run Cordis under tsx: ```sh -node --expose-internals --import tsx ../../vendor/cordis/bin.js +node --import tsx ../../vendor/cordis/bin.js ``` Now edit `hello.ts` — change the log message — and save: diff --git a/examples/cordis-agent/cordis.yml b/examples/cordis-agent/cordis.yml index 5947e42456..df79fd4a4e 100644 --- a/examples/cordis-agent/cordis.yml +++ b/examples/cordis-agent/cordis.yml @@ -7,7 +7,7 @@ # such as `ctx.bash`. Grant this toolset like bash access. See # ../../.agents/notes/implemented/feature/2026-07-08-self-referential-cordis-toolset.md. -# Hot-module reload for the dev/demo loop (needs `node --expose-internals`). +# Development-only hot reload; production assemblies omit it. - id: hmr name: '@cordisjs/plugin-hmr' config: diff --git a/examples/jsonrpc-agent/tests/keyless-smoke.e2e.ts b/examples/jsonrpc-agent/tests/keyless-smoke.e2e.ts index fb8ee81f64..cb2ab9687e 100644 --- a/examples/jsonrpc-agent/tests/keyless-smoke.e2e.ts +++ b/examples/jsonrpc-agent/tests/keyless-smoke.e2e.ts @@ -70,7 +70,6 @@ describe('jsonrpc-agent keyless smoke', () => { const address = modelServer.address() if (address === null || typeof address === 'string') throw new Error('model server did not bind a TCP port') const child = spawn(process.execPath, [ - '--expose-internals', '--import', 'tsx', binScript, @@ -171,7 +170,6 @@ describe('jsonrpc-agent keyless smoke', () => { it('rejects an invalid max-token success env value', async () => { const child = spawn(process.execPath, [ - '--expose-internals', '--import', 'tsx', binScript, diff --git a/examples/tui-agent/README.md b/examples/tui-agent/README.md index 0a26bb5a6b..2aa9efeeab 100644 --- a/examples/tui-agent/README.md +++ b/examples/tui-agent/README.md @@ -50,7 +50,7 @@ This example is a thin leaf `cordis.yml`: it picks the swappable backends, loads | Entry | Demonstrates | |---|---| -| `hmr` (`@cordisjs/plugin-hmr`) | the dev/demo edit-reload loop — a **leaf** entry (not baked into the app) because it is Loader-only and needs `node --expose-internals`, which `demo:tui` passes | +| `hmr` (`@cordisjs/plugin-hmr`) | the dev/demo edit-reload loop — a **leaf** entry (not baked into the app) because it depends on the Loader's internal module access | | `llm-deepseek` | real `LlmAdapter` via config (`!!js process.env.…` secrets); swap one line to `@deepseek-ai/dsh-llm-pi-ai` for the library-backed twin | | `bash` (`dsh-bash-local`) | the executor implementation — the swappable half of the bash seam. The model-facing `bash` schema (`tool-bash`) and generic `task_*` controls (`tool-tasks`) come from `dsh-agent-spine-demo`, so only the executor is a leaf choice | | `tui-agent` (`@deepseek-ai/dsh-tui-demo`) | the app bundle: the agent-spine demo + JSONL persistence + the pi-tui channel + a pre-created `main` agent | diff --git a/examples/tui-agent/cordis.yml b/examples/tui-agent/cordis.yml index eeb44dc7fb..d43c073d99 100644 --- a/examples/tui-agent/cordis.yml +++ b/examples/tui-agent/cordis.yml @@ -1,11 +1,10 @@ # Full-screen TUI coding agent with swappable DeepSeek and local-bash backends. # `dsh-tui-demo` supplies the agent spine, workspace instructions, generic # task controls, JSONL persistence, the pi-tui front door, and `main`. -# HMR remains a leaf because it requires Loader internals; `demo:tui` passes -# `--expose-internals`. The app bin loads the gitignored root `.env`; this file -# reads `DEEPSEEK_API_KEY` and optional `DEEPSEEK_BASE_URL` through `!!js`. +# HMR remains a leaf because it depends on Loader internals. The app bin loads +# the gitignored root `.env`; this file reads `DEEPSEEK_API_KEY` and optional +# `DEEPSEEK_BASE_URL` through `!!js`. -# Hot-module reload for the dev/demo loop (needs `node --expose-internals`). - id: hmr name: '@cordisjs/plugin-hmr' config: diff --git a/examples/tui-agent/tests/pty-harness.ts b/examples/tui-agent/tests/pty-harness.ts index 257198b7d2..e55e77f4de 100644 --- a/examples/tui-agent/tests/pty-harness.ts +++ b/examples/tui-agent/tests/pty-harness.ts @@ -197,7 +197,6 @@ export async function runTuiPtySmoke(options: TuiPtySmokeOptions): Promise { describe.skipIf(!existsSync(acpBin))('dsh-acp-demo BUILT bin (node lib/bin.js, no tsx)', () => { it('boots the published bin, completes a turn, and writes default Zstandard persistence', async () => { consumer = await makeConsumer() - child = spawn(process.execPath, ['--expose-internals', acpBin, '--config', './cordis.yml'], { + child = spawn(process.execPath, [acpBin, '--config', './cordis.yml'], { cwd: consumer, env: { ...process.env, @@ -226,7 +225,7 @@ describe.skipIf(!existsSync(acpBin))('dsh-acp-demo BUILT bin (node lib/bin.js, n /** Spawn the built acp bin against `configArg` and resolve with its exit code + stderr. */ function runBinExpectingExit(configArg: string, cwd: string = tmpdir()): Promise<{ code: number; stderr: string }> { return new Promise((resolve, reject) => { - const proc = spawn(process.execPath, ['--expose-internals', acpBin, '--config', configArg], { + const proc = spawn(process.execPath, [acpBin, '--config', configArg], { cwd, env: { ...process.env, diff --git a/packages/examples/cli-demo/README.md b/packages/examples/cli-demo/README.md index ee806614eb..8a931cc147 100644 --- a/packages/examples/cli-demo/README.md +++ b/packages/examples/cli-demo/README.md @@ -38,7 +38,7 @@ The root headless-agent example supplies its leaf: pnpm run demo:headless "inspect the failing test and fix it" ``` -Loader configs with bare package specifiers require `node --expose-internals` or the Loader's optional native fallback. The root command supplies the Node flag. +Loader configs resolve bare package specifiers through the optional native helper installed by the repository, so the root command needs no special Node flags. ### Output formats diff --git a/packages/examples/cli-demo/tests/built-bin.e2e.ts b/packages/examples/cli-demo/tests/built-bin.e2e.ts index e002be43e0..5c3a6ad62e 100644 --- a/packages/examples/cli-demo/tests/built-bin.e2e.ts +++ b/packages/examples/cli-demo/tests/built-bin.e2e.ts @@ -116,7 +116,7 @@ interface BinResult { function runBuiltBin(cwd: string, args: readonly string[], interrupt?: NodeJS.Signals): Promise { return new Promise((resolveResult, reject) => { - const child = spawn(process.execPath, ['--expose-internals', cliBin, ...args], { + const child = spawn(process.execPath, [cliBin, ...args], { cwd, env: { ...process.env, DSH_HOME: join(cwd, '.dsh'), DSH_AGENTS_HOME: join(cwd, '.agents') }, stdio: ['ignore', 'pipe', 'pipe'], diff --git a/packages/examples/tui-demo/README.md b/packages/examples/tui-demo/README.md index 146e6503bf..87b5d9dd52 100644 --- a/packages/examples/tui-demo/README.md +++ b/packages/examples/tui-demo/README.md @@ -48,7 +48,7 @@ Fresh runs mint a `main-session-` session id and pass it to both the TUI a ## The bin -`dsh-tui-demo [path-to-cordis.yml]` defaults to `./cordis.yml`, loads the optional cwd `.env`, boots the Cordis Loader, and waits for the full plugin tree. Bare package specifiers require `node --expose-internals` or the Loader's optional native fallback; the repository scripts use `--expose-internals`. +`dsh-tui-demo [path-to-cordis.yml]` defaults to `./cordis.yml`, loads the optional cwd `.env`, boots the Cordis Loader, and waits for the full plugin tree. The repository installs Loader's optional native helper, so bare package specifiers resolve under plain Node. ## Example leaf diff --git a/packages/examples/tui-demo/tests/built-bin.e2e.ts b/packages/examples/tui-demo/tests/built-bin.e2e.ts index 904c75784a..6a793bf104 100644 --- a/packages/examples/tui-demo/tests/built-bin.e2e.ts +++ b/packages/examples/tui-demo/tests/built-bin.e2e.ts @@ -54,9 +54,9 @@ async function makeConsumer(): Promise { /** Run the built bin in `cwd` with PIPED stdio; resolve with output + exit code. */ function runBuiltBin(cwd: string): Promise<{ stdout: string; code: number; stderr: string }> { return new Promise((resolve, reject) => { - // NO tsx — this is the published `node lib/bin.js` path (`--expose-internals` - // matches the demo command; the guard fires before the Loader needs it). - const child = spawn(process.execPath, ['--expose-internals', tuiBin, './cordis.yml'], { + // NO tsx — this is the published `node lib/bin.js` path; the guard fires + // before the Loader resolves the config tree. + const child = spawn(process.execPath, [tuiBin, './cordis.yml'], { cwd, env: { ...process.env, DSH_HOME: join(cwd, '.dsh'), DSH_AGENTS_HOME: join(cwd, '.agents') }, stdio: ['pipe', 'pipe', 'pipe'], diff --git a/packages/support/loader-smoke/src/index.ts b/packages/support/loader-smoke/src/index.ts index 717e0c6a12..61ad3b9d16 100644 --- a/packages/support/loader-smoke/src/index.ts +++ b/packages/support/loader-smoke/src/index.ts @@ -59,8 +59,6 @@ export interface ExampleLaunchOptions { readonly mode?: ExampleMode /** Absolute repo tsconfig whose `paths` map resolves unbuilt workspace imports. Required in `src` mode, ignored in `lib`. */ readonly tsconfigPath?: string - /** Prepend `--expose-internals` (the Cordis Loader's bare-plugin resolver needs it for some bins); defaults to `false`. */ - readonly exposeInternals?: boolean /** Extra environment entries the mode-specific ones layer over; the caller then merges the result over `process.env`. */ readonly env?: NodeJS.ProcessEnv } @@ -90,9 +88,9 @@ function toLibBin(srcBin: string): string { /** * Resolve how to spawn an example bin in the selected mode. * - * `src` yields `node [--expose-internals] --import ` with `TSX_TSCONFIG_PATH` - * set so the tsconfig `paths` map resolves workspace imports to source. `lib` yields - * `node [--expose-internals] ` under plain Node with no tsx and no paths map, so + * `src` yields `node --import ` with `TSX_TSCONFIG_PATH` set so the + * tsconfig `paths` map resolves workspace imports to source. `lib` yields + * `node ` under plain Node with no tsx and no paths map, so * bare package plugins resolve through real package `exports` into built `lib/`; relative example-local * TypeScript plugins remain source files loaded through Node's built-in type stripping. Bare resolution * requires the config to live below a workspace that declares its `cordis.yml` package dependencies. @@ -103,7 +101,6 @@ function toLibBin(srcBin: string): string { export function resolveExampleLaunch(options: ExampleLaunchOptions): ExampleLaunch { const mode = options.mode ?? resolveExampleMode() const configArgs = options.configArgs ?? [] - const flags = options.exposeInternals === true ? ['--expose-internals'] : [] const env: NodeJS.ProcessEnv = { ...options.env } if (mode === 'src') { @@ -112,10 +109,10 @@ export function resolveExampleLaunch(options: ExampleLaunchOptions): ExampleLaun } const tsxLoader = import.meta.resolve('tsx') env.TSX_TSCONFIG_PATH = options.tsconfigPath - return { command: process.execPath, args: [...flags, '--import', tsxLoader, options.srcBin, ...configArgs], env } + return { command: process.execPath, args: ['--import', tsxLoader, options.srcBin, ...configArgs], env } } - return { command: process.execPath, args: [...flags, options.libBin ?? toLibBin(options.srcBin), ...configArgs], env } + return { command: process.execPath, args: [options.libBin ?? toLibBin(options.srcBin), ...configArgs], env } } /** Inputs that vary between real-Loader example smokes. */ @@ -172,7 +169,6 @@ export async function runLoaderSmoke(options: LoaderSmokeOptions): Promise((resolve, reject) => { diff --git a/packages/support/loader-smoke/tests/example-launch.spec.ts b/packages/support/loader-smoke/tests/example-launch.spec.ts index 77a0791516..8033645d53 100644 --- a/packages/support/loader-smoke/tests/example-launch.spec.ts +++ b/packages/support/loader-smoke/tests/example-launch.spec.ts @@ -50,7 +50,6 @@ describe('resolveExampleLaunch', () => { expect(args).toContain('--import') expect(args).toContain(SRC_BIN) expect(args[args.length - 1]).toBe('./cordis.yml') - expect(args).not.toContain('--expose-internals') expect(env.TSX_TSCONFIG_PATH).toBe(TSCONFIG) }) @@ -78,11 +77,6 @@ describe('resolveExampleLaunch', () => { expect(args).toContain(fixture) }) - it('prepends --expose-internals when requested', () => { - const { args } = resolveExampleLaunch({ srcBin: SRC_BIN, mode: 'lib', exposeInternals: true }) - expect(args[0]).toBe('--expose-internals') - }) - it('lib mode: rewrites only the last /src/ segment', () => { const { args } = resolveExampleLaunch({ srcBin: '/repo/src/packages/examples/acp-demo/src/bin.ts', diff --git a/packages/ui/app-boot/README.md b/packages/ui/app-boot/README.md index 24840b7fda..abd8feec20 100644 --- a/packages/ui/app-boot/README.md +++ b/packages/ui/app-boot/README.md @@ -16,7 +16,7 @@ Shared boot glue for the app bins ([`dsh-tui-demo`](../../examples/tui-demo/READ Two failure classes the guards handle: `loader.await()` swallows init rejections (`Promise.allSettled`) — Node still exits non-zero on the resulting unhandled rejection, and `installFailLoud` replaces the noisy dump with one labelled line and a guaranteed `exit(1)`; a failed plugin IMPORT is only logged by the Loader (the process would otherwise exit 0 on a usable config typo), leaving a fiber-less entry that `assertEntriesLoaded` turns into a `boot()` rejection. -Bare plugin specifiers in a config (`@deepseek-ai/dsh-*`, npm packages) resolve through the cordis Loader's internal module loader when Node runs with `--expose-internals` or the optional `node-addon-require-builtin` fallback is installed; without either, consumers must install plugins where plain Node import resolution can find them. Relative specifiers resolve against the config directory with no flag. The bins' subprocess smokes exercise the internal-loader path, while this package's unit suite drives `boot()` in-process against configs with relative specifiers. +Bare plugin specifiers in a config (`@deepseek-ai/dsh-*`, npm packages) resolve through the Cordis Loader's internal module loader. Repository bins install Loader's optional `node-addon-require-builtin` peer; external callers must supply it or install plugins where plain Node import resolution can find them. Relative specifiers resolve against the config directory without the native helper. The bins' subprocess smokes exercise the internal-loader path, while this package's unit suite drives `boot()` in-process against configs with relative specifiers. This package carries no loader hooks and no dev-mode surface: the `dsh-scripts` launcher ([`sdk/scripts`](../../sdk/scripts/README.md), with the shared project model in [`sdk/helper`](../../sdk/helper/README.md)) owns process startup, tsx registration, and local-plugin source resolution, and consumes these helpers for the boot sequence itself. @@ -39,7 +39,7 @@ No direct invalidation from `boot()`; a consumer that calls `addHarnessSourceSec ## Known Limitations and Deferred Work -- **Bare package specifiers depend on Loader internals** — production bins need `node --expose-internals` or the Loader's optional native fallback; an in-process caller without either must use resolvable relative/file specifiers or tsx path mapping. +- **Bare package specifiers depend on Loader internals** — production bins need Loader's optional native helper; an in-process caller without it must use resolvable relative/file specifiers or tsx path mapping. - **Snapshot replay swapping is basename-specific** — only a config ending in `cordis.yml` or `cordis.yaml` maps to the sibling `cordis.snapshot.yml`; custom config names require caller-managed selection. - **Environment loading is cwd-scoped and optional** — the helper loads one `.env` file and warns on failure; it does not search parents, merge profiles, or validate required variables. - **Personal config is patch-shaped** — an id-targeted patch replaces the entry's whole `config` rather than deep-merging, so a personal override restates the base fields it keeps. diff --git a/packages/ui/app-boot/src/index.ts b/packages/ui/app-boot/src/index.ts index 2fd4ba4c05..c303ac1e71 100644 --- a/packages/ui/app-boot/src/index.ts +++ b/packages/ui/app-boot/src/index.ts @@ -209,9 +209,8 @@ export function assertEntriesLoaded(ctx: Context, binName: string): void { * `cordis:include` builtin, loading through the ambient module pipeline * (vite/tsx/plain ESM) while the included tree's own specifiers stay * config-relative. A missing fiber rejects here; a later init rejection is - * handled by {@link installFailLoud}. Built bins need `--expose-internals` or - * the Loader's native fallback for bare plugin specifiers; relative specifiers - * do not. + * handled by {@link installFailLoud}. Built bins need the Loader's native + * helper for bare plugin specifiers; relative specifiers do not. * @param binName - the diagnostic prefix for load-failure errors. * @param absoluteConfigPath - the config to include; must already be absolute * (see {@link resolveConfigPath}). diff --git a/scripts/demo-code-mode.mjs b/scripts/demo-code-mode.mjs index 273e6e1b38..c3e7849a6b 100644 --- a/scripts/demo-code-mode.mjs +++ b/scripts/demo-code-mode.mjs @@ -7,7 +7,7 @@ import { spawn } from 'node:child_process' // Each UI's node invocation matches its base demo script plus the overlay config. const UIS = new Map([ - ['tui', ['--expose-internals', '--import', 'tsx', 'packages/examples/tui-demo/src/bin.ts', 'examples/tui-agent/code-mode.cordis.yml']], + ['tui', ['--import', 'tsx', 'packages/examples/tui-demo/src/bin.ts', 'examples/tui-agent/code-mode.cordis.yml']], ['acp', ['--import', 'tsx', 'packages/examples/acp-demo/src/bin.ts', '--config', 'examples/acp-agent/code-mode.cordis.yml']], ])