Commit Graph
114 Commits
Author SHA1 Message Date
Yichen Jiang 0512b12714 feat(config)!: one ordering for configuration sources, and a bootstrap deny rule
$DSH_HOME/.env had just become an ordinary environment layer, which left the
harness resolving user-facing values from a flattened process.env that could
no longer say where a value came from. A key stored through the web page
stayed shadowed by an older key in the user's own .env. An endpoint could be
redirected by the project: the invoking directory's .env is materialized like
every other layer, and a base URL decides where a resolved API key is sent, so
a DEEPSEEK_BASE_URL written into a model-editable workspace would send the
user's credential — and the prompts carrying their code — to whatever host
that file named.

Give every user-facing value one ordering, with four kinds of source:

  explicit for this run     per-operation override, CLI argument
  > authored by deployment  --config / --config-replace
  > this launch's shell     inherited process environment
  > product-managed store   settings.yaml, .credentials.yaml
  > discovered file         $DSH_HOME/.env
  > defaults                schema default, shipped base, public default

The domains differ only in which tiers exist. The earlier split — credentials
ranking the environment over the managed file while settings ranked over the
environment — was inconsistent: the distinguishing fact is who authored the
source, not the domain.

packages/util/environment owns an immutable snapshot with per-layer
provenance. getFrom(name, sources) searches only the layers a caller names,
and omitting one is a refusal rather than a demotion: the adapters ask for
['process', 'user-env'], so no reordering can let a project file back into a
decision it was excluded from.

isBootstrapOnly rejects, before anything is materialized, any .env setting a
variable that governs how a process launches (PATH, SHELL, NODE_OPTIONS,
LD_PRELOAD), where code or model-visible instructions load from (the whole
DSH_* namespace, HOME, XDG_*), or how the network is reached (proxy and CA
variables). The namespace is denied wholesale so a switch added later cannot
become settable by being forgotten, and there is no opt-out.

verify-config-source-ownership keeps both rules: no unregistered process.env
read under packages/*/*/src (26 allowlisted with reasons), and no apiKey,
baseURL, or headers inlined from the environment in shipped Cordis config —
removing those inlines is what makes the deployment tier meaningful.
2026-08-04 16:17:32 +08:00
Yichen Jiang 8ddc53f7a0 feat(cli)!: complete --config on every surface and delete the personal overlay
$DSH_HOME/config.yaml was an implicit composition layer: if the file existed,
every launch applied an arbitrary Loader patch graph over the shipped tree,
kept live by a dedicated HMR watcher. Three costs came from the implicitness,
not the capability. A patch replaces its target row's whole config, so a file
written months ago pins that row to the field set it knew and every default
the shipped tree later adds silently stops applying. It competed with the
typed settings namespaces llm-deepseek and llm-pi-ai already register, so
which one wins was a function of layer order rather than meaning. And the
explicit escape hatch it was supposedly redundant with did not exist on every
surface: dsh -p, dsh meta, and dsh upgrade all rejected --config, so for them
the implicit file was the only composition route at all.

Complete the explicit layer first: --config and --config-replace now work on
every booting surface. A headless --config-replace tree must still mount a
webserver row, because that surface reaches its own agent over the same HTTP
gateway the browser uses; AppCLIEntry names that contract in the failure
instead of reporting a bare missing service.

Then delete the implicit one. PERSONAL_CONFIG_FILENAME, loadPersonalPatches,
watchPersonalPatches, and the config-only HMR row mounted for it are gone; a
file left at that path is inert, and --dump-config no longer reads the Harness
home. --config therefore stops *replacing* the personal overlay and simply
*is* the user overlay.

No migration: a user who wants the old behavior names the same file
(dsh --config ~/.dsh/config.yaml), which a shell alias makes permanent.
2026-08-04 15:25:04 +08:00
Yichen Jiang 03b534de16 feat(credentials): move the store to .credentials.yaml and layer $DSH_HOME/.env
$DSH_HOME/.env carried two incompatible jobs. As credentials-local's writable
secret store it could not be hoisted into process.env — hoisting makes every
stored key read as a read-only launch override and blocks rotation from the
TUI and the web page. But its name and dotenv format promise an environment
file, so a DEEPSEEK_BASE_URL sitting beside a working DEEPSEEK_API_KEY in the
same file was silently ignored: only the credential provider read the
document, and it addresses credential references alone.

Split the two jobs into two files.

.credentials.yaml is the provider-managed store: a strict YAML mapping of
CredentialRef to non-empty string, no version field, no wrapper level. Because
it holds credentials and nothing else, a non-mapping root, a non-identifier
key, a non-string value, an empty string, a duplicate key, and malformed YAML
are all rejections rather than skipped entries — loud at boot and at a write,
warn-and-keep-last-good on a live reload. The dotenv physical-line editor
gives way to a patch of the parsed document, so comments and untouched entries
keep their formatting and any string value round-trips, multi-line included.
Writer lock, read-modify-write, atomic 0600 write under a 0700 directory,
watcher, self-write suppression, and quiescent disposal are unchanged.

$DSH_HOME/.env becomes the user's ordinary environment layer. app-boot's new
loadLayeredEnv loads the invoking directory's .env then the Harness home's,
giving user < project < inherited; the home resolves from the inherited
environment first, so a project .env cannot redirect it.

Credential precedence is unchanged: the live environment still wins read-only
over the file, and shadowed writes still reject. Whether a provider-managed
store should instead win over the environment is a separate decision.

No migration: a key already in $DSH_HOME/.env keeps resolving through the new
environment layer, as a read-only env source that shadows the stored one.
2026-08-04 14:50:38 +08:00
Yichen Jiang 88c035c98e cleanup(cli): remove the profile-json config entry
`./.dsh-tmp-profile/config.json` was the web config-tree boot's user-config
plane, but never gained a writer: no production code created or edited it, no
test exercised it, and no user documentation named it. The fields it mapped
have owners elsewhere — provider/model are the api-gateway's default route and
persistenceRoot is an assembly fact, while typed user preferences live in
$DSH_HOME/settings.yaml.

Delete PROFILE_DIR, PROFILE_FILE, ProfileMapping, PROFILE_MAPPINGS, and
readProfile() with the patch source that consumed them. AppCLIEntry now
composes patches from CLI flags and the resolved frontend distIndex only; the
surrounding layers are unchanged. A file on disk is ignored completely — no
migration, replacement format, or deprecation diagnostic, per the pre-release
stance.
2026-08-04 14:11:38 +08:00
Turtle ad4aeacd19 fix(hmr,include): settle a failing boot instead of a silent exit 13
Master's transactional loader made the invalid-provider PTY case regress:
the HMR main watcher's initial scan refreshed the include mid-initial-apply,
the concurrent group updates stranded the include fiber, and once serialized
the failing apply's rollback deadlocked on HMR's refresh drain — dsh exited
13 with no diagnostic and the terminal stranded, the exact symptom this
branch fixes. Serialize every include child-tree mutation through one queue
and pass ignoreInitial to the HMR main watcher; the failing boot now settles
through boot()'s labelled rejection with the tree disposed and exit 1. The
PTY case asserts the settled diagnostic; the fail-loud release remains the
guard for rejections boot cannot see.
2026-08-03 14:14:20 +08:00
Turtle b37c22bd0a Merge origin/master: combine boot-owned settlement with the fail-loud release
Conflicts: apps/cli/src/tui.ts (keep the release install over master's comment
rewording), packages/ui/app-boot/README* (master's new installFailLoud row
wording plus this branch's release and timeout rows).
2026-08-03 14:10:23 +08:00
Tianyi Cui 2448496803 feat: configure repository plugins from DSH home 2026-08-02 01:29:04 +08:00
Tianyi Cui 52a715d303 fix(web): allow profiles without native bash env 2026-08-02 01:13:58 +08:00
Tianyi Cui 00f156fcde Merge remote-tracking branch 'origin/fix/web-agent-runtime-context' into worktree/pr830-retarget-review-20260801
# Conflicts:
#	apps/cli/src/web.ts
2026-08-02 00:56:14 +08:00
Tianyi Cui 6cd9fefe88 fix(cli): register web prompt context before boot 2026-08-02 00:49:58 +08:00
Tianyi Cui 0758839b00 Merge fix/web-agent-runtime-context into fix/web-gui-feedback-loop 2026-08-01 22:15:36 +08:00
Tianyi Cui d74048345a Merge origin/master into fix/web-agent-runtime-context 2026-08-01 22:04:10 +08:00
Ziya 7ba9059056 Merge pull request #1116 from deepseek-harness/agent/fix-frontend-plugin-loader-dependency
fix(cli): point missing frontend dist at full build
2026-08-01 07:40:01 -04:00
ZiyaZhang 90c5635355 fix(cli): point missing frontend dist at full build 2026-08-01 02:12:22 -07:00
Turtle 20e51abd4e feat: rename experimental-meta/-upgrade to meta/upgrade behind --experimental gate 2026-07-31 23:37:53 +08:00
Turtle 70f37206d2 fix(app-boot): release the terminal before a fatal load exit
A dsh launch whose config failed validation returned the user to a broken
shell: typing was invisible and the next command was mangled by a stray
Device Attributes reply (1;2;4cecho ...).

The Loader mounts entries concurrently, so ui-tui can already hold the
terminal (raw mode, bracketed paste, keyboard protocol, plus an in-flight
DA query) when a sibling entry rejects on its own config. installFailLoud
wrote its diagnostic and exited immediately, so nothing disposed the tree
and ProcessTerminal.stop() never ran.

Give installFailLoud an optional release teardown, awaited between the
diagnostic and the exit and bounded by FAIL_LOUD_RELEASE_TIMEOUT_MS. The
TUI launcher passes one that disposes the root context, reaching the same
shutdown() the /exit path already uses (drainInput() + ui.stop()). The
context is captured in boot()'s prepare hook because the rejection arrives
while boot() is still in flight.

Bins that pass no release keep the previous behavior exactly.
2026-07-31 20:37:16 +08:00
Turtle dc88440dad Merge remote-tracking branch 'origin/master' into codex/experimental-cli-command-names
# Conflicts:
#	apps/cli/README.i18n.yaml
#	packages/ui/tui/README.i18n.yaml
2026-07-31 20:18:15 +08:00
Turtle 60a0bcd358 feat(cli): mark meta and upgrade commands experimental 2026-07-31 20:16:19 +08:00
Tianyi Cui 53e7e5b247 docs(cli): track welcome storage migration 2026-07-31 19:14:24 +08:00
NI0317 c4d88bc25e Merge latest origin/master into feature/tui-first-run-welcome
# Conflicts:
#	apps/cli/README.i18n.yaml
2026-07-31 17:49:03 +08:00
NI0317 346d1e4c74 fix(tui): address onboarding review feedback 2026-07-31 17:47:07 +08:00
Turtle aeff8f6397 Merge remote-tracking branch 'origin/master' into codex/remove-cli-tui-wrappers 2026-07-31 17:20:11 +08:00
Turtle 70597eefc4 refactor(cli): call runTui directly for TUI modes 2026-07-31 17:20:03 +08:00
Turtle ce0aa90c1e feat(cli): dsh --dump-config / --dump-default-config print the composed tree
dsh --dump-config and dsh web --dump-config compose the shipped base,
the surface overlay, and the --config or personal overlay — exactly the
layers that surface boots — and print the entry list as YAML without
booting; --dump-default-config stops at the surface overlay so the two
outputs diff to precisely the user layer's effect.

The dump shares the mounting code: the vendored include exports its
patch algorithm as applyEntryPatches() and its !!js dialect as
entryListSchema (logged in vendor/README.md), dsh-app-boot's
renderConfigDump() composes and renders through both (and now imports
the dialect instead of duplicating it), and the CLI adds a thin
dump-config mode. !!js expressions print verbatim; unmatched patches
warn on stderr; boot-only flags are rejected alongside the dump flags.

(cherry picked from commit 1fdbebfa8a5dc7df840d53666320064a7e3dae59)
2026-07-31 16:20:54 +08:00
NI0317 dbb87f02bf copy(tui): disclose Session Log upload in Chinese welcome 2026-07-31 15:15:14 +08:00
NI0317 e73fa2ec9f copy(tui): refine first-run welcome message 2026-07-31 13:34:55 +08:00
NI0317 ac19ba2da3 copy(tui): use 粗糙 in first-run welcome 2026-07-31 13:28:07 +08:00
NI0317 f30579dbaa refactor(tui): own overlay terminal primitives 2026-07-31 12:36:24 +08:00
NI0317 1b4c11e7d7 fix(tui): await mounted welcome overlay service 2026-07-31 12:02:13 +08:00
NI0317 f7355e4e8e Merge latest origin/master into feature/tui-first-run-welcome
# Conflicts:
#	apps/cli/README.i18n.yaml
2026-07-31 10:58:48 +08:00
NI0317 436e557275 Merge origin/master into feature/tui-first-run-welcome
# Conflicts:
#	apps/cli/README.i18n.yaml
#	apps/cli/README.md
#	apps/cli/README.zh.md
#	apps/cli/src/tui.ts
#	apps/cli/tests/tui-keyless-smoke.e2e.ts
2026-07-31 10:58:00 +08:00
imccyu c0733ba46b Merge branch 'master' into fix/nodeboot-dep 2026-07-31 10:34:07 +08:00
imccyu 296cb1c35f fix: app-boot report plugin error detail 2026-07-31 03:02:45 +08:00
imccyu 2e034bbbd3 Merge remote-tracking branch 'origin/master' into mergebot/pr711
# Conflicts:
#	apps/cli/README.i18n.yaml
#	docs/module-graph.md
#	packages/client/connection/README.i18n.yaml
#	packages/client/runtime/README.i18n.yaml
#	packages/host/apiproxy/README.i18n.yaml
#	packages/host/apiproxy/src/api-proxy.ts
2026-07-31 02:02:47 +08:00
imccyu d802364651 feat(cli): move the telemetry row into the shared base — every surface reports
The row moves from web.cordis.yml to base.cordis.yml, so the TUI reports
too (its exit paths already drain: disposeRootAndExit on normal exit, root
dispose before the /resume execve). The TUI launcher applies the same
resolveTelemetryPatch opt-out, judged against the tree actually booting via
configHasTelemetryRow so a --config-replace tree without the row is not
failed by a switch with nothing to disable. The TUI keyless smoke disables
telemetry in its child env; README (en/zh) and the Agent Note pair updated
to the every-surface stance.
2026-07-31 01:34:43 +08:00
imccyu bd1c69149d fix(web): review round 1 — loud opt-out, headless drain, scaffold isolation, docs
- resolveTelemetryPatch: extracted pure switch resolution (unit-tested);
  fails loud when DSH_TELEMETRY_DISABLED is set but the row is absent, and
  documents that ANY non-empty value (including '0'/'false') disables.
- runHeadless: SIGINT/SIGTERM now dispose the tree before exit so the
  telemetry tail and shutdown marker drain (Node's default signal exit
  skips disposal).
- web.cordis.yml: explicit maxQueueSize beside maxExportBatchSize (the
  single-batch drain invariant no longer leans on an SDK default), comment
  covers exportTimeoutMillis's role and links the Agent Note.
- apps/web scaffold: disable telemetry-otel — fixture sessions must never
  leave the process.
- apps/cli README (en/zh + pairing): document the default endpoint, both
  env seams, and the no-redaction disclosure.
2026-07-31 01:34:42 +08:00
imccyu 91478443c2 feat(web): wire session-telemetry-otel into the dsh web composition
Mount the existing telemetry seam + OTel logs backend in the web/headless
config tree so every session-log event streams to an OTLP/HTTP collector:

- telemetry-otel row: url defaults to the standard local OTLP endpoint,
  DSH_TELEMETRY_OTLP_URL overrides; 10s batch cadence; exporter/processor
  values bound the shutdown drain to ~1s against an unreachable collector
  (timeoutMillis doubles as the retry deadline, single-batch drain).
- DSH_TELEMETRY_DISABLED opt-out: AppCLIEntry patches the row disabled
  before boot (config alone cannot disable a row, and exporter.url
  validation is load-time fail-loud).
2026-07-31 01:34:40 +08:00
imccyu ecbd8babc1 Merge remote-tracking branch 'origin/master' into mergebot/pr711
# Conflicts:
#	apps/cli/README.i18n.yaml
#	apps/cli/README.md
#	apps/cli/README.zh.md
#	apps/cli/cordis.yml
#	apps/cli/package.json
#	docs/config-catalog.md
#	packages/client/runtime/README.i18n.yaml
#	packages/client/runtime/src/client/contract/sessions.ts
#	packages/client/test-runtime/src/sessions.ts
#	packages/client/ui-workspace/README.i18n.yaml
#	packages/client/ui-workspace/README.md
#	packages/client/ui-workspace/README.zh.md
#	packages/client/ui-workspace/src/client/WorkspaceBrowser.tsx
#	packages/client/ui-workspace/src/client/tree.ts
#	packages/client/ui-workspace/tests/apply.spec.ts
#	packages/client/ui-workspace/tests/tree.spec.ts
#	packages/host/apiproxy/README.i18n.yaml
#	packages/host/apiproxy/src/api-proxy.ts
#	packages/host/apiproxy/src/api/index.ts
#	packages/host/apiproxy/tests/client-handler.spec.ts
#	packages/host/apiproxy/tests/rpc-schemas.spec.ts
#	pnpm-lock.yaml
2026-07-31 01:28:15 +08:00
NI0317 05b84f4458 fix(tui): publish welcome acknowledgement atomically 2026-07-30 23:18:51 +08:00
NI0317 31f66dbd54 fix(tui): tighten welcome layout and acceptance 2026-07-30 23:09:58 +08:00
NI0317 890343751a feat(tui): add versioned first-run welcome 2026-07-30 22:29:35 +08:00
Yichen Jiang eae4219e04 Merge branch 'worktree-config-settings-seam' into worktree-llm-dynamic-config
# Conflicts:
#	apps/cli/README.i18n.yaml
#	apps/cli/composition.md
#	apps/cli/config/base.cordis.yml
#	apps/cli/src/app-cli-entry.ts
#	apps/cli/src/tui.ts
#	apps/cli/tests/tui-keyless-smoke.e2e.ts
#	examples/package.json
#	packages/ui/app-boot/README.i18n.yaml
#	packages/ui/app-boot/README.md
#	packages/ui/app-boot/README.zh.md
#	pnpm-lock.yaml
#	python/sdk-runtime/package.json
2026-07-30 19:46:04 +08:00
Yichen Jiang 8f045bfdbd fix(cli)!: stop hoisting $DSH_HOME/.env into process.env
The shipped surfaces loaded the harness home's .env into the process
environment before cordis booted. credentials-local then saw every stored
key as an ambient launch override: describe reported source 'env' with
writable false, and set/unset rejected as shadowed — so a key the web page
or TUI stored was unrotatable and undeletable from the next run onward,
and the adapter kept using the value captured at launch.

The home's .env is now the credential provider's own store, read by that
provider alone and hot-reloaded by it. The genuine launch environment and
the invoking directory's .env (loaded by the bin) remain the read-only
ambient layer, so a plain composition without the provider still resolves
keys exactly as before.

Proven by a real restart in the loader composition: store a key through
the seam, dispose the tree, re-boot over the same harness home, and the
entry is still file-sourced and writable — rotating it lands on the very
next request.
2026-07-30 15:44:32 +08:00
Turtle a51143bded fix(cli): close shared config review gaps 2026-07-30 14:56:39 +08:00
Turtle cd100a81f1 fix(cli): harden launcher lifecycle 2026-07-30 11:14:18 +08:00
Turtle 83360c3dca fix(cli): default session root in shipped config 2026-07-30 10:24:04 +08:00
Turtle 5dbcedfbba Merge origin/master into feature/shared-cli-config-foundation 2026-07-30 10:11:38 +08:00
Hypatia May 61163b7da5 Merge remote-tracking branch 'origin/master' into codex/basic-session-search
Conflict resolutions:

- `session.list`: master's projection columns fold into the PR's cancellable,
  batched `listVisibleSessionSummaries`, which `session.search` shares as its
  visibility baseline; master's goal helpers stay beside it.
- Client sessions face: master narrowed `ctx.sessions` to `ISessions`, so the
  search verb and its protocol-constant bound are declared there and the
  test-runtime double implements them (recorded, empty page unless a scenario
  stubs hits).
- `WorkspaceBrowser`: master's per-row Rename wiring rides the PR's search
  results view; the tree keeps the PR's query-free derivations.
- `dsh web` bin: the PR's shutdown-handlers-before-readiness order with
  master's boot-time LAN address snapshot.
- `session-query-sqlite`: master's `SCHEMA_VERSION` 7 stands; the PR's bump
  carried no schema change.
- Specs: master wraps assistant/steering message payloads and requires an
  `application/json` carrier request, so the search fixtures and tests follow.
- Web aria goldens keep master's recording plus the PR's search placeholder;
  the navigation-panes inventory keeps master's terminal-card golden next to
  the PR's search-results golden.
2026-07-30 09:40:38 +08:00
Turtle c19facf9db refactor(cli): share app boot driver 2026-07-29 23:39:09 +08:00
Turtle d2270eefcd fix(cli): align personal overlay semantics 2026-07-29 23:36:58 +08:00