wechatbridge-cli 1.5.0__tar.gz → 1.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. {wechatbridge_cli-1.5.0/wechatbridge_cli.egg-info → wechatbridge_cli-1.6.0}/PKG-INFO +22 -11
  2. {wechatbridge_cli-1.5.0 → wechatbridge_cli-1.6.0}/README.md +20 -10
  3. {wechatbridge_cli-1.5.0 → wechatbridge_cli-1.6.0}/pyproject.toml +1 -0
  4. wechatbridge_cli-1.6.0/tests/test_dsh.py +1749 -0
  5. {wechatbridge_cli-1.5.0 → wechatbridge_cli-1.6.0}/tests/test_hardening.py +195 -0
  6. {wechatbridge_cli-1.5.0 → wechatbridge_cli-1.6.0}/wechatbridge/__init__.py +1 -1
  7. {wechatbridge_cli-1.5.0 → wechatbridge_cli-1.6.0}/wechatbridge/config.py +27 -4
  8. wechatbridge_cli-1.6.0/wechatbridge/dsh.py +1084 -0
  9. {wechatbridge_cli-1.5.0 → wechatbridge_cli-1.6.0}/wechatbridge/main.py +45 -25
  10. {wechatbridge_cli-1.5.0 → wechatbridge_cli-1.6.0}/wechatbridge/runner_common.py +71 -14
  11. {wechatbridge_cli-1.5.0 → wechatbridge_cli-1.6.0/wechatbridge_cli.egg-info}/PKG-INFO +22 -11
  12. {wechatbridge_cli-1.5.0 → wechatbridge_cli-1.6.0}/wechatbridge_cli.egg-info/requires.txt +1 -0
  13. wechatbridge_cli-1.5.0/tests/test_dsh.py +0 -657
  14. wechatbridge_cli-1.5.0/wechatbridge/dsh.py +0 -478
  15. {wechatbridge_cli-1.5.0 → wechatbridge_cli-1.6.0}/LICENSE +0 -0
  16. {wechatbridge_cli-1.5.0 → wechatbridge_cli-1.6.0}/setup.cfg +0 -0
  17. {wechatbridge_cli-1.5.0 → wechatbridge_cli-1.6.0}/tests/test_codex.py +0 -0
  18. {wechatbridge_cli-1.5.0 → wechatbridge_cli-1.6.0}/tests/test_codex_auth.py +0 -0
  19. {wechatbridge_cli-1.5.0 → wechatbridge_cli-1.6.0}/tests/test_grok_auth.py +0 -0
  20. {wechatbridge_cli-1.5.0 → wechatbridge_cli-1.6.0}/wechatbridge/__main__.py +0 -0
  21. {wechatbridge_cli-1.5.0 → wechatbridge_cli-1.6.0}/wechatbridge/agy.py +0 -0
  22. {wechatbridge_cli-1.5.0 → wechatbridge_cli-1.6.0}/wechatbridge/codex.py +0 -0
  23. {wechatbridge_cli-1.5.0 → wechatbridge_cli-1.6.0}/wechatbridge/grok.py +0 -0
  24. {wechatbridge_cli-1.5.0 → wechatbridge_cli-1.6.0}/wechatbridge/ilink.py +0 -0
  25. {wechatbridge_cli-1.5.0 → wechatbridge_cli-1.6.0}/wechatbridge/update_check.py +0 -0
  26. {wechatbridge_cli-1.5.0 → wechatbridge_cli-1.6.0}/wechatbridge_cli.egg-info/SOURCES.txt +0 -0
  27. {wechatbridge_cli-1.5.0 → wechatbridge_cli-1.6.0}/wechatbridge_cli.egg-info/dependency_links.txt +0 -0
  28. {wechatbridge_cli-1.5.0 → wechatbridge_cli-1.6.0}/wechatbridge_cli.egg-info/entry_points.txt +0 -0
  29. {wechatbridge_cli-1.5.0 → wechatbridge_cli-1.6.0}/wechatbridge_cli.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wechatbridge-cli
3
- Version: 1.5.0
3
+ Version: 1.6.0
4
4
  Summary: Bridge WeChat messages to agy, Grok Build, or Codex CLIs — text/image/file/voice in, CLI replies and generated files back.
5
5
  Author: WeChatBridge contributors
6
6
  License: MIT
@@ -27,6 +27,7 @@ Requires-Dist: httpx
27
27
  Requires-Dist: qrcode
28
28
  Requires-Dist: Pillow
29
29
  Requires-Dist: cryptography
30
+ Requires-Dist: PyYAML
30
31
  Dynamic: license-file
31
32
 
32
33
  # WeChatBridge
@@ -78,11 +79,16 @@ Per-user switch: `/backend agy`, `/backend grok`, `/backend codex`, or `/backend
78
79
 
79
80
  ### dsh backend notes
80
81
 
81
- - **Single-turn:** the `headless` profile always creates a fresh session per invocation (`session-<uuid>`), so every WeChat message starts a new dsh session. `/clear` / `/new` are accepted but are no-ops, and `/model`, `/fast`, `/planning`, `/persona`, `/add-dir` are not wired yet (they return a short "not supported" notice).
82
- - Runs `dsh --profile headless -- <prompt>` with `cwd` = the per-user session directory (per-user workspace; model-created files land there and can be sent back via CDN).
83
- - Image and file attachments are merged into the prompt text as `@/absolute/path` mentions. Verified in dsh v0.1.1-rc.2: `headless` passes the prompt text directly to the model without pre-reading or inlining mentions; the model receives system guidance on @-paths and may invoke file tools (e.g. `read`) if needed. The bridge only filters out-of-bounds mentions starting with absolute paths, `~`, or `file://` (`@/abs`, `@~/x`, `@file://`, replaced with `[blocked-path]`), which is best-effort prompt text filtering rather than a sandbox boundary.
84
- - Auth / profiles are **machine-wide** (`~/.dsh`, same model as grok's machine-wide login): the child's `HOME` points at the per-user session dir, so the bridge always passes `DSH_HOME` explicitly. Set `WECHATBRIDGE_DSH_HOME` to configure a dedicated service home with automatic session retention cleanup; when unset, it reuses the host `~/.dsh` without automatic session cleanup (managed by the operator). `DSH_BIN_PATH`, `DSH_PROFILE`, and `DSH_TIMEOUT` are configurable.
85
- - Status: implemented from the published `dsh` CLI contract (headless bundle source) plus a fake CLI in the test suite; final acceptance depends on a real `dsh login` + headless run.
82
+ - **What is dsh:** DeepSeek Harness CLI (`dsh`), running one-shot tasks via the `headless` profile.
83
+ - **Enable / Switch:** Set `WECHATBRIDGE_BACKEND=dsh` globally in `.env`, or switch per-user in WeChat via `/backend dsh`.
84
+ - **Dependency (PyYAML):** The dsh backend requires `PyYAML` to parse profile plugin configurations. If missing, `/backend dsh` returns a user-visible notice and preserves existing backend preferences. Install with `pip install PyYAML` or `pipx inject wechatbridge-cli PyYAML`.
85
+ - **Window memory (default):** The `headless` profile creates a fresh session per invocation (`session-<uuid>`), so the bridge manages long-term conversation memory: recent turns (default 10 pairs, up to `WECHATBRIDGE_DSH_MEMORY_CHARS` chars) are saved to `dsh_memory.jsonl` and injected into every prompt. `/clear` or `/new` wipes this memory to start fresh.
86
+ - **Persistent resume mode:** Set `WECHATBRIDGE_DSH_RESUME=true` for true persistent sessions (codex-style resume). Requires the headless profile bundle to mount the `dsh-bridge-runner` plugin (`dsh_bridge_runner.py`, which reads `DSH_BRIDGE_SESSION_ID` and `DSH_BRIDGE_TASK` from env). Context accumulates natively across turns without windowing. `/clear` or `/new` deletes the stored session ID to start a fresh session. Window memory injection is skipped in resume mode.
87
+ - **Workspace & State Isolation:** Runs `dsh --profile headless -- <prompt>` with `cwd` = per-user session directory (`WECHATBRIDGE_SESSION_DIR/<user_id>`). Bridge-private state files (`dsh_memory.jsonl` and persistent session ID) are stored under `WECHATBRIDGE_DSH_STATE_DIR` (`~/.local/share/wechatbridge/<instance>/dsh_state/`), outside the child's `session_dir` tree to eliminate direct 1-level parent/sibling relative traversal (`../`).
88
+ - **Threat Model:** Child processes run under the same host UID without container sandbox isolation. While 1-level relative traversal (`../`) cannot reach private state, 2-level traversal (`../../dsh_state/<user_id>`) remains reachable on the filesystem and is accepted. Treat as trusted-user tooling.
89
+ - Image and file attachments are merged into the prompt text as `@/absolute/path` mentions. Verified against dsh v0.1.1-rc.2 source: `headless` profile does not pre-read or inline @mention files at the CLI/runtime level, but passes prompt text directly to the model; the model receives system guidance on @-paths and invokes file tools (`read`) as needed. The bridge filters out-of-bounds mentions (`@/abs`, `@~/x`, `@file://`, replaced with `[blocked-path]`), which is a prompt text layer best-effort filter, not a sandbox boundary.
90
+ - Auth / profiles are **machine-wide** (resolved via precedence `WECHATBRIDGE_DSH_HOME` > `WECHATBRIDGE_HOST_HOME/.dsh` > `~/.dsh`, same host-fallback model as grok/codex): the child's `HOME` points at the per-user session dir, so the bridge always passes `DSH_HOME` explicitly. Set `WECHATBRIDGE_DSH_HOME` to configure a dedicated service home with automatic session retention cleanup; when unset, it falls back to `$WECHATBRIDGE_HOST_HOME/.dsh` (if set) or `~/.dsh` without automatic session cleanup (managed by the operator). If the bridge runs as a dedicated system user (e.g. `wechatbridge`), set `WECHATBRIDGE_HOST_HOME` to point to the operator's home directory so CLI credentials and profiles are discovered. `DSH_BIN_PATH`, `DSH_PROFILE`, and `DSH_TIMEOUT` are configurable.
91
+ - **Status:** Implemented against the published dsh CLI contract and covered by the test suite (fake CLI). The persistent resume mode requires users to configure and mount the `dsh-bridge-runner` plugin in their dsh profile bundle; plugin files are not bundled with this repository.
86
92
 
87
93
  ### Grok backend notes
88
94
 
@@ -177,7 +183,12 @@ Key variables (all have defaults):
177
183
  | `DSH_BIN_PATH` | `dsh` | path to the dsh binary |
178
184
  | `DSH_PROFILE` | `headless` | dsh profile booted for one-shot tasks |
179
185
  | `DSH_TIMEOUT` | `600` | dsh CLI run timeout in seconds |
180
- | `WECHATBRIDGE_DSH_HOME` | _empty_ | explicit `DSH_HOME` passed to the dsh child. Explicitly set = dedicated home + auto session cleanup; unset = reuse host `~/.dsh` without auto cleanup |
186
+ | `WECHATBRIDGE_DSH_MEMORY_TURNS` | `10` | dsh backend: recent user+assistant turns injected as context |
187
+ | `WECHATBRIDGE_DSH_MEMORY_CHARS` | `6000` | dsh backend: max chars of injected memory context |
188
+ | `WECHATBRIDGE_DSH_RESUME` | `false` | dsh backend: true persistent-session mode — one dsh session per user resumed on every message (codex-style); requires the dsh-bridge-runner plugin in the headless profile; skips windowed memory when enabled |
189
+ | `WECHATBRIDGE_DSH_HOME` | _empty_ | explicit `DSH_HOME` passed to the dsh child. Explicitly set = dedicated home + auto session cleanup; unset = fallback to `WECHATBRIDGE_HOST_HOME/.dsh` or `~/.dsh` without auto cleanup (precedence: `WECHATBRIDGE_DSH_HOME` > `WECHATBRIDGE_HOST_HOME/.dsh` > `~/.dsh`) |
190
+ | `WECHATBRIDGE_HOST_HOME` | _empty_ | host home directory override for service user deployments; used as fallback base for grok (`~/.grok`), codex (`~/.codex`), and dsh (`~/.dsh`) |
191
+ | `WECHATBRIDGE_DSH_STATE_DIR` | _derived_ | bridge-private dsh state directory (memory JSONL and persistent session IDs; defaults to `~/.local/share/wechatbridge/<instance>/dsh_state`) |
181
192
  | `WECHATBRIDGE_BACKEND` | `agy` | global default backend (`agy` / `grok` / `codex` / `dsh`; overridable per user via `/backend`) |
182
193
  | `WECHATBRIDGE_INSTANCE` | `default` | instance name; state / session / QR paths derive from it |
183
194
  | `WECHATBRIDGE_ALLOWED_SENDERS` | _empty_ | comma-separated WeChat IDs (empty = allow all) |
@@ -267,8 +278,8 @@ See [`deploy/wechatbridge-windows.md`](deploy/wechatbridge-windows.md).
267
278
  | Command | Action |
268
279
  |---|---|
269
280
  | `/help` | list supported commands for the active backend |
270
- | `/backend <agy\|grok\|codex\|dsh>` | switch CLI backend for this WeChat user (on real change: clears that backend's continuation state — agy/grok flag and codex `thread_id`/resume — so the next turn starts a fresh session; history files may remain until retention cleanup) |
271
- | `/clear` or `/new` | drop continue flag so the next CLI turn is a new conversation (does not instantly delete history files) |
281
+ | `/backend <agy\|grok\|codex\|dsh>` | switch CLI backend for this WeChat user (on real change: clears that backend's continuation state — agy/grok flag, codex `thread_id`/resume, or dsh memory/session ID — so the next turn starts a fresh session; history files may remain until retention cleanup) |
282
+ | `/clear` or `/new` | start a new conversation (agy/grok: drops continue flag; codex: clears thread ID; dsh: clears window memory or persistent session ID) |
272
283
  | `/model <name>` | set model (all backends validate against a live list: agy/grok via CLI `models`; codex via `codex debug models` [then `--bundled`]; unknown name or list-fetch failure refuse and do not write prefs; see `/models`) |
273
284
  | `/models` | list models — agy/grok/codex all query the live CLI (codex: `debug models`; falls back to a built-in reference note only if the live list cannot be fetched) |
274
285
  | `/fast` | set low reasoning effort (**on only** — not a toggle; no “off” command) |
@@ -290,7 +301,7 @@ Other `/…` commands are either rejected (e.g. `/exit`), reported as unsupporte
290
301
  - **Auto-approve CLIs.** agy runs with `--dangerously-skip-permissions`; grok with `--always-approve` (unless planning mode); dsh tools run without host path restrictions. Treat this as trusted-user tooling, not a multi-tenant sandbox.
291
302
  - **Danger gate is keyword-based**, not full intent understanding. Defaults target concrete patterns (`rm -rf /`, pipe-to-shell, `mkfs`, `format c:`, a few heavy Chinese phrases, …). Everyday wording like bare “delete” is **not** gated. Override list via `WECHATBRIDGE_CONFIRM_KEYWORDS`; approve with `WECHATBRIDGE_CONFIRM_TOKEN` (default `y`), TTL `WECHATBRIDGE_PENDING_TTL`.
292
303
  - **Inbound media** is size-capped (default 20 MB), streamed, and CDN hosts are allowlisted. Missing `aes_key` returns a clear error.
293
- - **Outbound artifacts** only leave the allowed per-user tree (agy: session scratch; grok: under session dir), after `realpath` checks, and only if under `WECHATBRIDGE_MAX_OUTBOUND_BYTES`.
304
+ - **Outbound artifacts** only leave the allowed per-user tree (agy: session scratch; grok: under session dir; dsh: under session dir), after `realpath` checks, and only if under `WECHATBRIDGE_MAX_OUTBOUND_BYTES`.
294
305
  - **Concurrency:** global process-slot cap (`WECHATBRIDGE_MAX_CONCURRENT`, default 4). Same user is serialized and does **not** hold a global slot while waiting on their previous message; different users can run in parallel up to the cap.
295
306
  - **Long replies** are split into chunks (`WECHATBRIDGE_MESSAGE_CHUNK`, default 2000 characters).
296
307
  - **Data layout:** instance data under `~/.local/share/wechatbridge/<instance>/` (override with env). Runtime dirs prefer `0700`; token/QR files prefer `0600` (Unix; Windows relies on NTFS ACLs).
@@ -301,7 +312,7 @@ Other `/…` commands are either rejected (e.g. `/exit`), reported as unsupporte
301
312
 
302
313
  - Not a standalone agent — requires agy and/or grok and/or codex and/or dsh.
303
314
  - The **codex** backend is not yet verified against a real Codex subscription/CLI; it is validated by source research, a JSONL fixture, and a fake CLI in tests. Treat it as community-tested until a real user confirms.
304
- - The **dsh** backend is single-turn only (the `headless` profile always starts a fresh session) and has not yet been verified against a real `dsh login` + headless run; it is validated against the published headless bundle contract and a fake CLI in tests.
315
+ - The **dsh** backend supports both windowed memory (default, last `WECHATBRIDGE_DSH_MEMORY_TURNS` turns) and true persistent sessions (`WECHATBRIDGE_DSH_RESUME=true`). Covered by the test suite; persistent resume mode requires users to configure and mount their own `dsh-bridge-runner` plugin (this repository does not bundle plugin files).
305
316
  - dsh model/effort/mode/persona slash commands are not wired yet; `/model`, `/fast`, `/planning`, `/persona`, `/add-dir` return a "not supported" notice on the dsh backend.
306
317
  - Voice is WeChat speech-to-text only; no local ASR; empty transcript → “type instead”.
307
318
  - No video send/receive; no native WeChat voice-bubble replies (no silk encode).
@@ -47,11 +47,16 @@ Per-user switch: `/backend agy`, `/backend grok`, `/backend codex`, or `/backend
47
47
 
48
48
  ### dsh backend notes
49
49
 
50
- - **Single-turn:** the `headless` profile always creates a fresh session per invocation (`session-<uuid>`), so every WeChat message starts a new dsh session. `/clear` / `/new` are accepted but are no-ops, and `/model`, `/fast`, `/planning`, `/persona`, `/add-dir` are not wired yet (they return a short "not supported" notice).
51
- - Runs `dsh --profile headless -- <prompt>` with `cwd` = the per-user session directory (per-user workspace; model-created files land there and can be sent back via CDN).
52
- - Image and file attachments are merged into the prompt text as `@/absolute/path` mentions. Verified in dsh v0.1.1-rc.2: `headless` passes the prompt text directly to the model without pre-reading or inlining mentions; the model receives system guidance on @-paths and may invoke file tools (e.g. `read`) if needed. The bridge only filters out-of-bounds mentions starting with absolute paths, `~`, or `file://` (`@/abs`, `@~/x`, `@file://`, replaced with `[blocked-path]`), which is best-effort prompt text filtering rather than a sandbox boundary.
53
- - Auth / profiles are **machine-wide** (`~/.dsh`, same model as grok's machine-wide login): the child's `HOME` points at the per-user session dir, so the bridge always passes `DSH_HOME` explicitly. Set `WECHATBRIDGE_DSH_HOME` to configure a dedicated service home with automatic session retention cleanup; when unset, it reuses the host `~/.dsh` without automatic session cleanup (managed by the operator). `DSH_BIN_PATH`, `DSH_PROFILE`, and `DSH_TIMEOUT` are configurable.
54
- - Status: implemented from the published `dsh` CLI contract (headless bundle source) plus a fake CLI in the test suite; final acceptance depends on a real `dsh login` + headless run.
50
+ - **What is dsh:** DeepSeek Harness CLI (`dsh`), running one-shot tasks via the `headless` profile.
51
+ - **Enable / Switch:** Set `WECHATBRIDGE_BACKEND=dsh` globally in `.env`, or switch per-user in WeChat via `/backend dsh`.
52
+ - **Dependency (PyYAML):** The dsh backend requires `PyYAML` to parse profile plugin configurations. If missing, `/backend dsh` returns a user-visible notice and preserves existing backend preferences. Install with `pip install PyYAML` or `pipx inject wechatbridge-cli PyYAML`.
53
+ - **Window memory (default):** The `headless` profile creates a fresh session per invocation (`session-<uuid>`), so the bridge manages long-term conversation memory: recent turns (default 10 pairs, up to `WECHATBRIDGE_DSH_MEMORY_CHARS` chars) are saved to `dsh_memory.jsonl` and injected into every prompt. `/clear` or `/new` wipes this memory to start fresh.
54
+ - **Persistent resume mode:** Set `WECHATBRIDGE_DSH_RESUME=true` for true persistent sessions (codex-style resume). Requires the headless profile bundle to mount the `dsh-bridge-runner` plugin (`dsh_bridge_runner.py`, which reads `DSH_BRIDGE_SESSION_ID` and `DSH_BRIDGE_TASK` from env). Context accumulates natively across turns without windowing. `/clear` or `/new` deletes the stored session ID to start a fresh session. Window memory injection is skipped in resume mode.
55
+ - **Workspace & State Isolation:** Runs `dsh --profile headless -- <prompt>` with `cwd` = per-user session directory (`WECHATBRIDGE_SESSION_DIR/<user_id>`). Bridge-private state files (`dsh_memory.jsonl` and persistent session ID) are stored under `WECHATBRIDGE_DSH_STATE_DIR` (`~/.local/share/wechatbridge/<instance>/dsh_state/`), outside the child's `session_dir` tree to eliminate direct 1-level parent/sibling relative traversal (`../`).
56
+ - **Threat Model:** Child processes run under the same host UID without container sandbox isolation. While 1-level relative traversal (`../`) cannot reach private state, 2-level traversal (`../../dsh_state/<user_id>`) remains reachable on the filesystem and is accepted. Treat as trusted-user tooling.
57
+ - Image and file attachments are merged into the prompt text as `@/absolute/path` mentions. Verified against dsh v0.1.1-rc.2 source: `headless` profile does not pre-read or inline @mention files at the CLI/runtime level, but passes prompt text directly to the model; the model receives system guidance on @-paths and invokes file tools (`read`) as needed. The bridge filters out-of-bounds mentions (`@/abs`, `@~/x`, `@file://`, replaced with `[blocked-path]`), which is a prompt text layer best-effort filter, not a sandbox boundary.
58
+ - Auth / profiles are **machine-wide** (resolved via precedence `WECHATBRIDGE_DSH_HOME` > `WECHATBRIDGE_HOST_HOME/.dsh` > `~/.dsh`, same host-fallback model as grok/codex): the child's `HOME` points at the per-user session dir, so the bridge always passes `DSH_HOME` explicitly. Set `WECHATBRIDGE_DSH_HOME` to configure a dedicated service home with automatic session retention cleanup; when unset, it falls back to `$WECHATBRIDGE_HOST_HOME/.dsh` (if set) or `~/.dsh` without automatic session cleanup (managed by the operator). If the bridge runs as a dedicated system user (e.g. `wechatbridge`), set `WECHATBRIDGE_HOST_HOME` to point to the operator's home directory so CLI credentials and profiles are discovered. `DSH_BIN_PATH`, `DSH_PROFILE`, and `DSH_TIMEOUT` are configurable.
59
+ - **Status:** Implemented against the published dsh CLI contract and covered by the test suite (fake CLI). The persistent resume mode requires users to configure and mount the `dsh-bridge-runner` plugin in their dsh profile bundle; plugin files are not bundled with this repository.
55
60
 
56
61
  ### Grok backend notes
57
62
 
@@ -146,7 +151,12 @@ Key variables (all have defaults):
146
151
  | `DSH_BIN_PATH` | `dsh` | path to the dsh binary |
147
152
  | `DSH_PROFILE` | `headless` | dsh profile booted for one-shot tasks |
148
153
  | `DSH_TIMEOUT` | `600` | dsh CLI run timeout in seconds |
149
- | `WECHATBRIDGE_DSH_HOME` | _empty_ | explicit `DSH_HOME` passed to the dsh child. Explicitly set = dedicated home + auto session cleanup; unset = reuse host `~/.dsh` without auto cleanup |
154
+ | `WECHATBRIDGE_DSH_MEMORY_TURNS` | `10` | dsh backend: recent user+assistant turns injected as context |
155
+ | `WECHATBRIDGE_DSH_MEMORY_CHARS` | `6000` | dsh backend: max chars of injected memory context |
156
+ | `WECHATBRIDGE_DSH_RESUME` | `false` | dsh backend: true persistent-session mode — one dsh session per user resumed on every message (codex-style); requires the dsh-bridge-runner plugin in the headless profile; skips windowed memory when enabled |
157
+ | `WECHATBRIDGE_DSH_HOME` | _empty_ | explicit `DSH_HOME` passed to the dsh child. Explicitly set = dedicated home + auto session cleanup; unset = fallback to `WECHATBRIDGE_HOST_HOME/.dsh` or `~/.dsh` without auto cleanup (precedence: `WECHATBRIDGE_DSH_HOME` > `WECHATBRIDGE_HOST_HOME/.dsh` > `~/.dsh`) |
158
+ | `WECHATBRIDGE_HOST_HOME` | _empty_ | host home directory override for service user deployments; used as fallback base for grok (`~/.grok`), codex (`~/.codex`), and dsh (`~/.dsh`) |
159
+ | `WECHATBRIDGE_DSH_STATE_DIR` | _derived_ | bridge-private dsh state directory (memory JSONL and persistent session IDs; defaults to `~/.local/share/wechatbridge/<instance>/dsh_state`) |
150
160
  | `WECHATBRIDGE_BACKEND` | `agy` | global default backend (`agy` / `grok` / `codex` / `dsh`; overridable per user via `/backend`) |
151
161
  | `WECHATBRIDGE_INSTANCE` | `default` | instance name; state / session / QR paths derive from it |
152
162
  | `WECHATBRIDGE_ALLOWED_SENDERS` | _empty_ | comma-separated WeChat IDs (empty = allow all) |
@@ -236,8 +246,8 @@ See [`deploy/wechatbridge-windows.md`](deploy/wechatbridge-windows.md).
236
246
  | Command | Action |
237
247
  |---|---|
238
248
  | `/help` | list supported commands for the active backend |
239
- | `/backend <agy\|grok\|codex\|dsh>` | switch CLI backend for this WeChat user (on real change: clears that backend's continuation state — agy/grok flag and codex `thread_id`/resume — so the next turn starts a fresh session; history files may remain until retention cleanup) |
240
- | `/clear` or `/new` | drop continue flag so the next CLI turn is a new conversation (does not instantly delete history files) |
249
+ | `/backend <agy\|grok\|codex\|dsh>` | switch CLI backend for this WeChat user (on real change: clears that backend's continuation state — agy/grok flag, codex `thread_id`/resume, or dsh memory/session ID — so the next turn starts a fresh session; history files may remain until retention cleanup) |
250
+ | `/clear` or `/new` | start a new conversation (agy/grok: drops continue flag; codex: clears thread ID; dsh: clears window memory or persistent session ID) |
241
251
  | `/model <name>` | set model (all backends validate against a live list: agy/grok via CLI `models`; codex via `codex debug models` [then `--bundled`]; unknown name or list-fetch failure refuse and do not write prefs; see `/models`) |
242
252
  | `/models` | list models — agy/grok/codex all query the live CLI (codex: `debug models`; falls back to a built-in reference note only if the live list cannot be fetched) |
243
253
  | `/fast` | set low reasoning effort (**on only** — not a toggle; no “off” command) |
@@ -259,7 +269,7 @@ Other `/…` commands are either rejected (e.g. `/exit`), reported as unsupporte
259
269
  - **Auto-approve CLIs.** agy runs with `--dangerously-skip-permissions`; grok with `--always-approve` (unless planning mode); dsh tools run without host path restrictions. Treat this as trusted-user tooling, not a multi-tenant sandbox.
260
270
  - **Danger gate is keyword-based**, not full intent understanding. Defaults target concrete patterns (`rm -rf /`, pipe-to-shell, `mkfs`, `format c:`, a few heavy Chinese phrases, …). Everyday wording like bare “delete” is **not** gated. Override list via `WECHATBRIDGE_CONFIRM_KEYWORDS`; approve with `WECHATBRIDGE_CONFIRM_TOKEN` (default `y`), TTL `WECHATBRIDGE_PENDING_TTL`.
261
271
  - **Inbound media** is size-capped (default 20 MB), streamed, and CDN hosts are allowlisted. Missing `aes_key` returns a clear error.
262
- - **Outbound artifacts** only leave the allowed per-user tree (agy: session scratch; grok: under session dir), after `realpath` checks, and only if under `WECHATBRIDGE_MAX_OUTBOUND_BYTES`.
272
+ - **Outbound artifacts** only leave the allowed per-user tree (agy: session scratch; grok: under session dir; dsh: under session dir), after `realpath` checks, and only if under `WECHATBRIDGE_MAX_OUTBOUND_BYTES`.
263
273
  - **Concurrency:** global process-slot cap (`WECHATBRIDGE_MAX_CONCURRENT`, default 4). Same user is serialized and does **not** hold a global slot while waiting on their previous message; different users can run in parallel up to the cap.
264
274
  - **Long replies** are split into chunks (`WECHATBRIDGE_MESSAGE_CHUNK`, default 2000 characters).
265
275
  - **Data layout:** instance data under `~/.local/share/wechatbridge/<instance>/` (override with env). Runtime dirs prefer `0700`; token/QR files prefer `0600` (Unix; Windows relies on NTFS ACLs).
@@ -270,7 +280,7 @@ Other `/…` commands are either rejected (e.g. `/exit`), reported as unsupporte
270
280
 
271
281
  - Not a standalone agent — requires agy and/or grok and/or codex and/or dsh.
272
282
  - The **codex** backend is not yet verified against a real Codex subscription/CLI; it is validated by source research, a JSONL fixture, and a fake CLI in tests. Treat it as community-tested until a real user confirms.
273
- - The **dsh** backend is single-turn only (the `headless` profile always starts a fresh session) and has not yet been verified against a real `dsh login` + headless run; it is validated against the published headless bundle contract and a fake CLI in tests.
283
+ - The **dsh** backend supports both windowed memory (default, last `WECHATBRIDGE_DSH_MEMORY_TURNS` turns) and true persistent sessions (`WECHATBRIDGE_DSH_RESUME=true`). Covered by the test suite; persistent resume mode requires users to configure and mount their own `dsh-bridge-runner` plugin (this repository does not bundle plugin files).
274
284
  - dsh model/effort/mode/persona slash commands are not wired yet; `/model`, `/fast`, `/planning`, `/persona`, `/add-dir` return a "not supported" notice on the dsh backend.
275
285
  - Voice is WeChat speech-to-text only; no local ASR; empty transcript → “type instead”.
276
286
  - No video send/receive; no native WeChat voice-bubble replies (no silk encode).
@@ -28,6 +28,7 @@ dependencies = [
28
28
  "qrcode",
29
29
  "Pillow",
30
30
  "cryptography",
31
+ "PyYAML",
31
32
  ]
32
33
 
33
34
  [project.scripts]