Commit Graph
3544 Commits
Author SHA1 Message Date
Yichen Jiang f50b60390c docs: regenerate the module graph for the environment package
`dsh-environment` and its consumer edges were missing from the
generated graph.
2026-08-05 12:49:05 +08:00
Yichen Jiang 33e3871c77 Merge remote-tracking branch 'origin/master' into claude/unified-environment-credentials-c8841a
# Conflicts:
#	.agents/notes/implemented/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.i18n.yaml
#	.agents/notes/implemented/architecture/2026-07-24-web-config-tree-boot-and-transport-layering.zh.md
#	.agents/notes/implemented/architecture/2026-07-29-request-level-llm-config-credentials.i18n.yaml
#	.agents/notes/implemented/architecture/2026-07-30-credential-boundaries-and-atomic-registration.i18n.yaml
#	apps/cli/package.json
#	apps/cli/src/web.ts
#	examples/mcp-memory/README.i18n.yaml
#	packages/cordis/repository-plugin/README.i18n.yaml
#	packages/credentials/credentials-local/README.i18n.yaml
#	packages/credentials/credentials-local/README.zh.md
#	packages/llm/llm-deepseek/README.i18n.yaml
#	packages/web/web-search-deepseek/README.i18n.yaml
2026-08-05 12:40:20 +08:00
Yichen Jiang 590b76a7f0 fix(config): close the review findings on configuration source ownership
Two had real security consequences:

The bootstrap rejection ran on npm dotenv's parser while process.loadEnvFile
applied the file with Node's own. Two independently maintained dialects meant
the check and the thing it guards could disagree: a name Node accepts but the
checker misses would reach process.env unchecked, and BASH_ENV there runs a
file of the project's choosing on every `bash -c` the bash tool issues. Parse
once with node:util's parseEnv — the same engine loadEnvFile uses — and assign
the entries already checked, which also drops the dotenv dependency.

llm-pi-ai still returned a literal profile.apiKey ahead of everything, and it
registers a settings namespace, so the defect removed from llm-deepseek
survived intact in its design twin. The field is gone from the profile schema,
the resolution path, and the tests.

The rest are consistency and documentation defects the review named:

- verify-config-source-ownership did not scan the Python runtime's bundled
  cordis.yml, which still inlined apiKey and baseURL. Both are covered now, and
  the line-anchored INLINE_DENY documents that it is a tripwire, not a parser.
- The deny list missed NODE_TLS_REJECT_UNAUTHORIZED, the askpass hooks, the
  GIT_CONFIG_* redirections, and PYTHONHOME — all implied by its own stated
  rule about what a variable does.
- Snapshot lookups folded case on Windows, where environment names are
  case-insensitive and an exact-match Map could miss a higher-ranked layer.
- The credentials note claimed a read-time permission check was "not taken"
  while this PR implemented it; the credentials-local README still described
  two layers, live process.env reads, dotenv-era limitations, and a renamed
  anchor; the llm-deepseek README still advertised the removed literal apiKey;
  and web.ts and base.cordis.yml kept personal-overlay wording.
- The ownership note's literal-apiKey claim now names its scope: the
  web-search providers keep a literal field but register no settings
  namespace, so nothing can shadow a stored credential through them.
2026-08-05 11:18:06 +08:00
xjt 7bd111b2af Merge origin/master into xjt/proofreading-active-docs-2-apply 2026-08-05 10:57:53 +08:00
imccyu ca27512529 fix(packages): omit source publication payloads 2026-08-05 01:15:19 +08:00
xjt 782d84116a docs(i18n): restore first-occurrence terminology 2026-08-04 20:54:42 +08:00
Huanqi Cao acda0e39bc Merge remote-tracking branch 'origin/master' into feat/pwsh-tool 2026-08-04 20:39:17 +08:00
xjt c522053cef Merge origin/master into xjt/proofreading-active-docs-2-apply 2026-08-04 19:51:18 +08:00
xjt edb3e82c35 docs(i18n): address proofreading review findings 2026-08-04 18:42:13 +08:00
Huanqi Cao 39b0695d48 Merge remote-tracking branch 'origin/master' into feat/pwsh-tool
# Conflicts:
#	apps/cli/composition.md
#	docs/testing.i18n.yaml
#	docs/testing.md
#	docs/testing.zh.md
2026-08-04 17:54:45 +08:00
Yichen Jiang a45aa28ca6 Merge branch 'master' into claude/unified-environment-credentials-c8841a
Master removed the TUI package, the `meta` and `upgrade` subcommands, and
`--config-replace`, and made raw `dsh` require a `--config` overlay. Resolved
onto that shape:

- Dropped this branch's TUI edits with the surface itself, including
  `tui.cordis.yml`, `runTui`, and the TUI keyless PTY smoke.
- Dropped the `--config-replace` plumbing rather than reintroducing a flag
  master deliberately removed. The gap this branch fixed remains: `dsh -p`
  still could not name its composition, so it keeps `--config`.
- Kept this branch's deletion of the personal `$DSH_HOME/config.yaml` layer,
  which master still carried, and provided the environment snapshot in the new
  raw `runConfig` surface alongside web and headless.
- Ported the headless shutdown PTY test off the personal overlay onto a named
  `--config` file, which is what proves that flag now exists on `-p`.
2026-08-04 17:51:44 +08:00
xjt 2db712eec7 docs(i18n): proofread active Chinese documentation 2026-08-04 17:36:14 +08:00
Yichen Jiang 8c2970e70e fix(config): trust the invoking project, and stop leaking what it must not decide
Review found five real defects in the configuration-source work, all confirmed
against the code rather than argued:

1. The note claimed --config outranks settings.yaml. It does not: the settings
   seam registers a plugin's cordis entry config as the `base` layer and the
   user section layers over it, and the seam cannot tell a shipped value from a
   --config one. The note now states shipped reality and names --config-replace
   as the lever for a deployment that must win. Separately, a literal `apiKey`
   in settings outranked both the environment and .credentials.yaml — the field
   is removed, so configuration carries a reference and nothing else.
2. DEEPSEEK_SEARCH_BASE_URL was functionally deleted: the shipped inline went
   away without the provider learning to read it. It now resolves from the
   environment snapshot, as the README always claimed.
3. The bootstrap deny list missed the interpreter start-up hooks. BASH_ENV is
   the sharpest: `bash -c` sources it on every bash tool call, so a project
   .env could run a file of its choosing before every command. The list now
   covers BASH_ENV and its per-language siblings, the Git hook commands, and
   the remaining preload and CA variables, organised by what a variable does
   rather than which runtime owns it.
4. YAML parse errors quoted the offending source line — which in a credentials
   document is the secret — into boot stderr and the watcher's logger. Only the
   error code and position are reported now, in credentials-local and
   settings-local alike, pinned by a test that asserts the secret is absent.
5. 0600 governed only files the harness wrote. A hand-created 0644 document was
   read normally. POSIX now checks the mode before reading contents, at boot
   and on every reload; Windows has no mode to inspect and is skipped rather
   than faked.

The project a session is launched in is trusted by default, with no prompt and
no stored trust record: it may supply its own endpoint, ordinary variables, and
a key ranked below the managed store. Trust stops at the harness itself — a
discovered file still cannot set DSH_PERMISSION_MODE, PATH, BASH_ENV, or the
rest, because those take effect with no user action, before any turn, outside
the permission policy and the sandbox.
2026-08-04 17:16:11 +08:00
imccyu 7f3a2dae91 fix(web): quiesce websocket teardown 2026-08-04 16:42:50 +08:00
imccyu a36c641db6 chore(docs): refresh WebSocket API catalogs 2026-08-04 16:21:09 +08:00
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
fz 193d918856 Merge remote-tracking branch 'origin/master' into codex/fix-headless-sigint
# Conflicts:
#	apps/cli/README.i18n.yaml
#	apps/cli/README.md
#	apps/cli/README.zh.md
#	apps/cli/config/base.cordis.yml
2026-08-04 15:18:48 +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 10bb9cbf4a cleanup: remove TUI package and legacy dsh entrypoints 2026-08-04 13:20:28 +08:00
fz f0e5a39d19 Merge remote-tracking branch 'origin/master' into codex/fix-headless-sigint 2026-08-04 11:07:36 +08:00
Turtle 5329bef04d perf(tui): incremental step timing and card render caches for long sessions
Resuming a long session (196k events, 2.2k steps, 1.8k tool cards) took
~12s to render and ~800ms to echo one keystroke:

- Every step's timing footer called stepTimingAt, which replayed the whole
  event log per footer - O(steps x events) on the initial render.
- pi-tui re-renders every component each frame and relies on per-component
  line caches, but ToolCardComponent/ContextCardComponent built throwaway
  Text/Markdown instances inside render(width), re-wrapping every settled
  card's output on every keystroke.

Replace the per-footer replay with one shared StepTimingTracker per chat
mount (single O(events) cursor over the append-only log), and cache card
rows by width via CardLineCache, dropped by every state mutator and
invalidate().

Measured (tmux 200x50, 196k-event session): resume prompt-ready ~12s -> ~7.6s;
per-keystroke echo ~800ms median -> ~11ms.
2026-08-04 00:37:10 +08:00
Huanqi Cao 9abc0723dd Merge remote-tracking branch 'origin/master' into feat/pwsh-tool 2026-08-03 23:44:38 +08:00
Huanqi Cao b978c62a22 Merge pull request #1119 from deepseek-harness/feat/ripgrep-packaged-binary
feat(fs-search): spawn the packaged ripgrep binary through the subprocess seam
2026-08-03 23:31:29 +08:00
Huanqi Cao f14f826648 revert(bash): drop the executor dialect guard
The ShellDialect marker on BashExecutor and the load-time rejection in
tool-bash/tool-pwsh force every test and example fake executor to declare
a dialect for a guard with no in-repo or plausible deployment to catch
(shipped compositions always pair the tool with its matching executor),
and they break the example composition suites that stub ctx.bash without
a dialect (agent-spine-demo/cli-demo/acp-demo lost the bash tool).

Keep the non-dialect doc fixes that landed with the attempt: the tool-bash
README bashEnv contract trim with the stale example import removed, the
acp-snapshot suite.ts 'usable pwsh' JSDoc, and the pwsh.cordis.yml comment
indent. The parity note records the attempt and revert under Alternatives.
2026-08-03 23:16:53 +08:00
Huanqi Cao 3d1166fcdd feat(bash): shell tools reject a mismatched executor dialect at load
The seam gains ShellDialect ('bash' | 'powershell' - concrete shells, not
families: zsh or fish would be their own values, never 'bash'); bash-local
declares bash (bash-sandbox inherits), pwsh-local declares powershell, and
both tools throw at load when the mounted executor speaks another dialect -
previously tool-pwsh over bash-local handed PowerShell text to bash -c and
the deployment error surfaced as ordinary nonzero exits. Pinned by mismatch
tests on both tools; the parity note records the contract (both languages).

Also from the review round: the tool-bash README's managed-environment
section becomes a summary linking the owning dsh-bash-env contract (the
duplicated prose carried a stale owner in its example import), the
pwshOnly JSDoc drops the stale 'on PATH' phrasing, and the task-tools
contract comment in the two pwsh compositions is indented into its block.
2026-08-03 22:47:53 +08:00
fz 003d297442 Merge remote-tracking branch 'origin/master' into codex/fix-headless-sigint 2026-08-03 22:21:46 +08:00
fz ef23b88ad4 fix(cli): bound telemetry shutdown on signals
Refs #1356
2026-08-03 22:08:47 +08:00
Turtle fea88291c1 docs: regenerate module graph for tui projection deps 2026-08-03 21:29:29 +08:00
Turtle 25d8bfac31 Merge remote-tracking branch 'origin/master' into perf/tui-resume-scan
# Conflicts:
#	packages/ui/tui/README.i18n.yaml
2026-08-03 21:16:51 +08:00
Turtle 7a26214a81 feat(tui): resolve resume titles through the projection cache
session-title already registers a title projection unit; /resume now
reads it instead of scanning logs: live rows from the registry
snapshot, persisted rows from the durable checkpoint row
(cachedSnapshot, zero I/O), and only rows without a usable checkpoint
pay a coldSnapshot — checkpoint plus readFrom tail, written back so the
next scan is metadata-only. Cold reads are bounded by the new
resumeScanConcurrency config; compositions without the cache fall back
to the bounded readTitleSnapshots batch. The TUI overlay mounts the
projection registry, storage, and projection-cache rows over the same
storages root the web surface uses, so checkpoints serve both.
2026-08-03 21:13:52 +08:00
Huanqi Cao a228fe15a2 docs(graph): regenerate the module graph for the merged tree
The merge resolved the module-graph.md conflict by regenerating doc GRAPHS
(gen-doc-graphs) while this file belongs to gen-module-graph; verify-module-graph
is a static-lane gate outside doc-sync, so the staleness only surfaced on CI.
2026-08-03 20:31:43 +08:00
Huanqi Cao ffeccc138b Merge branch 'feat/ripgrep-packaged-binary' into feat/pwsh-tool
# Conflicts:
#	docs/module-graph.md
2026-08-03 20:22:16 +08:00
Huanqi Cao 0ee198d70e Merge remote-tracking branch 'origin/master' into feat/ripgrep-packaged-binary 2026-08-03 20:19:49 +08:00
imccyu ae5ee37591 Merge remote-tracking branch 'origin/master' into mergebot/pr927-r3
# Conflicts:
#	packages/ui/tui/README.i18n.yaml
#	packages/ui/tui/src/components/dialogs.ts
2026-08-03 17:57:50 +08:00
imccyu 6b79907ffd Merge remote-tracking branch 'origin/master' into worktree/fix-multi-select-custom-answer 2026-08-03 17:19:53 +08:00
Turtle 3b58ed65b7 feat(tui): wrap and page question dialogs 2026-08-03 17:19:40 +08:00
Turtle 4edb828364 Merge remote-tracking branch 'origin/master' into codex/pr-1037-resolution
# Conflicts:
#	packages/ui/tui/README.i18n.yaml
2026-08-03 16:49:18 +08:00
Yichen Jiang 85b9914934 Merge remote-tracking branch 'origin/master' into worktree/fix-multi-select-custom-answer
# Conflicts:
#	packages/ui/tui/README.i18n.yaml
2026-08-03 16:26:55 +08:00
Yichen Jiang 109d5b5e1c Merge remote-tracking branch 'origin/master' into worktree/fix-multi-select-custom-answer
# Conflicts:
#	apps/web/tests/snapshots/question-composer/answered.expected.md
#	apps/web/tests/snapshots/question-composer/session.jsonl
#	docs/core-data-structures/user-interaction.i18n.yaml
#	packages/client/ui-question/README.i18n.yaml
#	packages/host/apiproxy/README.i18n.yaml
#	packages/host/apiproxy/README.md
#	packages/host/apiproxy/README.zh.md
#	packages/ui/tui/README.i18n.yaml
#	packages/ui/user-interaction/README.i18n.yaml
2026-08-03 16:09:17 +08:00
Turtle 4fa1d48bf9 Merge remote-tracking branch 'origin/master' into pr/tui-details-fold 2026-08-03 15:56:02 +08:00
Turtle bea60a430e Merge remote-tracking branch 'origin/master' into pr/tui-details-fold
# Conflicts:
#	docs/cordis-catalog/services.md
#	packages/ui/tui/README.i18n.yaml
#	packages/ui/tui/README.md
#	packages/ui/tui/README.zh.md
#	packages/ui/tui/src/index.ts
2026-08-03 15:55:22 +08:00
kingwl 57e2433f45 show subagent token metrics 2026-08-03 01:19:36 +08:00
kingwl c831c99981 keep subagent duration fix focused 2026-08-03 01:08:32 +08:00
Huanqi Cao b482f12d57 fix(coverage): probe the pwsh exemption with the executor's own resolution
The review caught the pwshCoverageExclusions comment claiming the inverse of
the code's behavior: a bare-PATH probe FAILING grants the exemption, so a
win32 host where only install-location pwsh (or 5.1) resolves kept the
exemption while its suites ran - over-exempting, never tightening.

resolvePwshPath/candidatePwshPaths move to the dependency-free
pwsh-local/src/resolve.ts (index.ts re-exports; public API unchanged) and
vitest.config.ts probes with that shared definition, so the exemption is
active exactly when the suites skip. The two spec headers stop saying
'on PATH' for a probe that is deliberately not PATH-only, and the parity
note records the abort backport as the one both-ways parity change (both
languages, pairing re-recorded); catalogs regenerated for the line shift.
2026-08-03 00:21:17 +08:00
kingwl 6b6acbe0c5 Merge master into subagent usage branch
# Conflicts:
#	.agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.i18n.yaml
#	.agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.md
#	.agents/notes/implemented/feature/2026-07-27-web-subagent-conversations.zh.md
#	apps/web/tests/snapshots/subagent-conversation/tree.expected.md
#	apps/web/tests/subagent-conversation.e2e.ts
#	packages/client/ui-subagent/src/client/SubagentCatalogAction.tsx
2026-08-02 23:37:17 +08:00
kingwl b30686d634 show subagent usage and active duration 2026-08-02 23:31:24 +08:00
Tianyi Cui 1ea72962eb Merge master into fix/subagent-stack-end-result 2026-08-02 23:13:00 +08:00
Huanqi Cao 37bceb7dad Merge branch 'feat/ripgrep-packaged-binary' into feat/pwsh-tool 2026-08-02 22:29:44 +08:00