Merge pull request #576 from deepseek-harness/worktree/remove-expose-internals
refactor: drop obsolete --expose-internals launches
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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 仍然逐字节按已提交的配置树启动:
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+1
-1
@@ -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.
|
||||
@@ -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" "$@"
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -197,7 +197,6 @@ export async function runTuiPtySmoke(options: TuiPtySmokeOptions): Promise<strin
|
||||
/* v8 ignore next -- every caller passes configPath or configArgs; the fallback keeps the type total */
|
||||
: [options.configPath ?? './cordis.yml'],
|
||||
tsconfigPath: options.tsconfigPath,
|
||||
exposeInternals: true,
|
||||
env: {
|
||||
DSH_HOME: join(cwd, '.dsh'),
|
||||
DSH_AGENTS_HOME: join(cwd, '.agents'),
|
||||
|
||||
+3
-3
@@ -89,10 +89,10 @@
|
||||
"constraints": "tsx scripts/check-workspace-constraints.ts",
|
||||
"doc-sync": "tsx scripts/run-gates.ts doc-sync",
|
||||
"hygiene": "pnpm run knip && pnpm run publint && pnpm run constraints && pnpm run verify-package-invariants && pnpm run verify-built-package-invariants && pnpm run verify-cordis-config && pnpm run verify-node-next-types && pnpm run verify-runtime-closure",
|
||||
"demo:headless": "node --expose-internals --import tsx packages/examples/cli-demo/src/bin.ts --config examples/headless-agent/cordis.yml",
|
||||
"demo:tui": "node --expose-internals --import tsx apps/cli/src/bin.ts",
|
||||
"demo:headless": "node --import tsx packages/examples/cli-demo/src/bin.ts --config examples/headless-agent/cordis.yml",
|
||||
"demo:tui": "node --import tsx apps/cli/src/bin.ts",
|
||||
"demo:code-mode": "node scripts/demo-code-mode.mjs",
|
||||
"demo:cordis": "node --expose-internals --import tsx packages/examples/tui-demo/src/bin.ts examples/cordis-agent/cordis.yml",
|
||||
"demo:cordis": "node --import tsx packages/examples/tui-demo/src/bin.ts examples/cordis-agent/cordis.yml",
|
||||
"demo:acp": "node --import tsx packages/examples/acp-demo/src/bin.ts --config examples/acp-agent/cordis.yml",
|
||||
"demo:web": "npm run build && npm run build:web && node --import tsx apps/cli/src/bin.ts web",
|
||||
"postinstall": "node scripts/install-lefthook.mjs"
|
||||
|
||||
@@ -58,7 +58,7 @@ The leaf supplies the swappable backends: an LLM adapter (`llm-deepseek` for the
|
||||
- honors `DSH_SNAPSHOT=replay` by booting the sibling `cordis.snapshot.yml` (the keyless replay tree, `llm-replay` in place of `llm-deepseek`);
|
||||
- in a snapshot run, disposes the context on stdin EOF so the session log is fully flushed before exit.
|
||||
|
||||
Run it under `node --expose-internals`, or Loader's optional `node-addon-require-builtin` fallback is required, so the cordis Loader can resolve the config's bare plugin specifiers through its internal module loader. (`demo:acp` runs under tsx, whose tsconfig `paths` map resolves them instead.)
|
||||
The repository installs Loader's optional `node-addon-require-builtin` peer, so the built bin resolves bare plugin specifiers through the internal module loader under plain Node. (`demo:acp` runs under tsx, whose tsconfig `paths` map resolves them instead.)
|
||||
|
||||
All diagnostics go to **stderr** — stdout is the protocol.
|
||||
|
||||
|
||||
@@ -23,8 +23,7 @@ import { ACP_SESSION_REFERENCE_META_KEY } from '@deepseek-ai/dsh-acp'
|
||||
/**
|
||||
* Published-entry smoke: run `lib/bin.js` under plain Node in a symlinked external consumer and
|
||||
* complete a mock-backed turn. This catches built-only settle races, stdout protocol leaks, and
|
||||
* published persistence behavior that the tsx source-path smoke cannot. It skips before build;
|
||||
* `--expose-internals` enables Cordis bare-plugin loading.
|
||||
* published persistence behavior that the tsx source-path smoke cannot. It skips before build.
|
||||
*/
|
||||
|
||||
const repoRoot = fileURLToPath(new URL('../../../../', import.meta.url))
|
||||
@@ -134,7 +133,7 @@ afterEach(async () => {
|
||||
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,
|
||||
@@ -227,7 +226,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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -116,7 +116,7 @@ interface BinResult {
|
||||
|
||||
function runBuiltBin(cwd: string, args: readonly string[], interrupt?: NodeJS.Signals): Promise<BinResult> {
|
||||
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'],
|
||||
|
||||
@@ -48,7 +48,7 @@ Fresh runs mint a `main-session-<uuid>` 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
|
||||
|
||||
|
||||
@@ -54,9 +54,9 @@ async function makeConsumer(): Promise<string> {
|
||||
/** 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'],
|
||||
|
||||
@@ -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 <tsx> <srcBin> <configArgs>` with `TSX_TSCONFIG_PATH`
|
||||
* set so the tsconfig `paths` map resolves workspace imports to source. `lib` yields
|
||||
* `node [--expose-internals] <libBin> <configArgs>` under plain Node with no tsx and no paths map, so
|
||||
* `src` yields `node --import <tsx> <srcBin> <configArgs>` with `TSX_TSCONFIG_PATH` set so the
|
||||
* tsconfig `paths` map resolves workspace imports to source. `lib` yields
|
||||
* `node <libBin> <configArgs>` 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<Loade
|
||||
configArgs: options.binArgs ?? [options.configPath],
|
||||
...options.mode !== undefined ? { mode: options.mode } : {},
|
||||
tsconfigPath: options.tsconfigPath,
|
||||
exposeInternals: true,
|
||||
env: { DSH_HOME: join(cwd, '.dsh'), DSH_AGENTS_HOME: join(cwd, '.agents'), ...options.env },
|
||||
})
|
||||
const result = await new Promise<LoaderSmokeResult>((resolve, reject) => {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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}).
|
||||
|
||||
@@ -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']],
|
||||
])
|
||||
|
||||
|
||||
Reference in New Issue
Block a user