dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
Codex 风格 Worktree:会话默认在独立 git worktree 中工作,输入框上可选择本地或工作树,清理有保护、删除前留可恢复快照。
English | 简体中文
A "work like Codex" placement choice for DSH sessions: on the new-session hero row, pick Local or New local worktree (the default) next to the workspace chip; after you send the first message the plugin creates a detached checkout and switches that session's cwd into it. No new Workspace, no naming, no branch from the plugin (Git owns branch naming at commit time).
0.5.1 (2026-10-20; package / plugin id / loader row name: dsh-codex-style-worktree; profile-patch insert row id: codex-worktree)/api/dsh-codex-style-worktree, RPC method prefix dsh-codex-style-worktree/; sidecar: $DSH_HOME/dsh-codex-style-worktree/state.json; disable marker: .dsh-codex-style-worktree-off0.2.0-rc.2 / 0.2.1-rc.2, Node.js >= 22.19.0, git on PATHagent/created the moment the New Session appears — long before the first prompt — so the old wiring decided the work location at Session-open time; a choice made in that blank Session therefore applied only to the next Session (switching to Local still created a checkout, switching back to New local worktree created none).agent/pre-step with turn === 1) — i.e. after you send the first message — and the per-workspace placement is read at that moment, so the choice made in the blank Session applies to that Session.dsh plugin --profile desktop update dsh-codex-style-worktree and reopen DSH once.dsh-codex-worktree to dsh-codex-style-worktree; the display title "Codex-style Worktree" is unchanged.codex-worktree (the profile-patch insert row id is untouched), so saved settings survive; every editable field is unchanged.~/.dsh/dsh-codex-worktree to ~/.dsh/dsh-codex-style-worktree (rename, no data loss; if the rename fails the old directory keeps being read)..dsh-codex-style-worktree-off; the old .dsh-codex-worktree-off is still honored.dsh plugin --profile desktop remove dsh-codex-worktree, then dsh plugin --profile desktop add github:wplct/dsh-codex-style-worktree), then fully quit and reopen DSH once.| Capability | 0.3.0 | 0.4.0 |
|---|---|---|
| Cleanup guard | Cleanup deletes directly | Refused when the checkout is another active session's current cwd (worktree-in-use); cleaning your own session's checkout first moves its cwd back to the workspace; force overrides after confirmation |
| Snapshot before delete | Only autoPrune kept a ref for detached commits | Every delete path: when the checkout is detached and HEAD ≠ baseCommit, write refs/dsh-codex-style-worktree/<timestamp+session suffix> and record it in the sidecar |
| Snapshot restore | Manual git branch only | /wt-snapshots, /wt-restore <ref>, and a "recoverable snapshots" list in the settings page |
| Archive cleanup | Checkouts always kept after a session ends | Optional cleanupOnArchive (default off): a lazy sweep removes clean, unused checkouts after archive |
| RPC | 6 endpoints | 7 (adds restore); the list response gains per-repository snapshots |
| Sidecar | v2 | v3 (adds a snapshots map; v1/v2 are migrated in memory on read, no data loss) |
| Capability | 0.2.x | 0.3.0 |
|---|---|---|
| When the worktree is created | Automatically for every new session (no choice); a "New Worktree" button could also create one on demand | You choose a placement on the hero row; the worktree is created after the first message (first-turn agent/pre-step) |
| Selector location | A dock button above the composer | The hero workspace row: a pill between 📁 workspace ▼ and standard mode ▼ |
| Remembered choice | None | Per workspace, persisted in the sidecar (placements), restart-safe |
| Workspace | The old button called workspaces.create, cluttering the sidebar | Never creates a Workspace; only the session cwd changes |
| Progress feedback | None | Inline three-step progress card above the composer (projection-driven, minimum 1 s) |
| Sidebar | No marker | Managed sessions show a read-only worktree icon (tooltip = checkout path) |
| Settings page | Read-only config | Editable form; save writes the active profile patch and applies live (volatile, no restart) |
| State sync | Some RPC reads | Everything is event/projection-driven, zero polling |
| RPC | Included create, etc. | status/list/clean/prune/placement/settings, no create/cancel |
agent/pre-step (before the model's first step) → the host decides by that workspace's placement whether to create a checkout;<repo>/.agents/worktrees/wt-<timestamp> and this session's working directory is switched to it; the session stays attached to its original Workspace;codexWorktree session projection and rendered as an inline progress card above the composer (Prepare workspace → Check out files → Set up environment); when done the card fades and the header chip plus the sidebar marker appear;git switch -c when you want a branch. mode: branch makes the plugin create a <branchPrefix><name> branch up front (not the default).Position: the official conversation.hero.options seat — the compact options area of the New Session row, right-aligned after the workspace and preset controls, rendered only once the blank new Session exists. It sits on the same row as 📁 workspace ▼ and standard mode ▼:
📁 shian360 ▼ standard mode ▼ ⑂ New local worktree ▼
┌────────────────────────────────────────────────────┐
│ Describe what you want to build, / for commands… │
└────────────────────────────────────────────────────┘
Local / 本地 — run directly in the current workspace directory;New local worktree / 新建本地工作树 — run in a new checkout (default);Creating a local worktree does not add a Workspace; it only switches this Session’s working directory.isGit=false from the host) and for existing sessions.conversation.hero.workspace (that seat is kind: "single" and owned by the workspace picker; registering there would shadow the shipped workspace switcher). The plugin registers under its own id in conversation.hero.options and never shadows shipped UI.placement RPC and stored per workspace path in the sidecar. The control updates optimistically and rolls back with a message if the write fails; if reading fails it degrades to a read-only display of the default and never blocks sending.defaultPlacement only decides the value for a workspace that has no stored placement yet. First open shows placements[path] ?? defaultPlacement.agent/pre-step listener runs before the model's first step (a blank Session on its own creates nothing);local → do nothing; worktree → run Prepare workspace (create the pool directory + self-ignoring .gitignore) → Check out files (git worktree add --detach) → Set up environment (canonicalize the path, workingDirectory.set, write the sidecar record);┌────────────────────────────────────────────────────┐
│ ⑂ Creating worktree │
│ ✓ Prepare workspace │
│ ◐ Check out files │
│ ○ Set up environment │
│ Cancel │
└────────────────────────────────────────────────────┘
Worktree created fades out after ~400 ms.| Error code | Meaning |
|---|---|
not-git-repository | The workspace is not a Git repository |
no-commit | The repository has no commit yet |
disabled | enabled: false, or .dsh-codex-style-worktree-off exists at the repository root |
root-unreachable | The configured root is not writable, or the session sandbox forbids that path |
limit-reached | The repository reached maxPerRepo (clean up old checkouts first) |
| other message | git error / timeout etc., shown verbatim |
workingDirectory.set for the current session.workspaces.create / workspaces.rename / sessions.create and never adds a Workspace; acceptance: the number of workspaces in workspace.json is unchanged before and after.mode: detached: the plugin creates no branch, the checkout stays on a detached HEAD; Git decides the branch name at commit/push time (use git switch -c when you want a named branch). mode: branch switches to creating <branchPrefix><name> up front.sidebar.session.row.leading (the official ambient-marker seat: a persistent leading icon area of the session row; the component reads sessionId). This is a deliberate design decision: the row's action strip is a hover-only button strip and is unsuitable for a permanent marker; the seat does not belong to the shipped workspace picker and never shadows shipped UI.⑂-style SVG, currentColor, muted), with the tooltip Codex-style Worktree · <checkout path>.codexWorktree session projection; the client reads sessions.list.getSnapshot().byId[sessionId].projectionValues?.codexWorktree and renders the icon only when phase === 'ready', hiding it for null/missing. Zero RPC, zero polling.Known limitation: when you clean a non-live session's checkout from the settings page, the host cannot append a projection event for that session, so its sidebar marker disappears only on that session's next consistency repair (reopening the session or its first step).
Codex-style Worktree plus status (managed / not managed / error).status call for details such as the dirty count (on demand, not polling).One Codex-style Worktree section with two blocks:
cordis.patch.yml, and takes effect immediately (volatile config: no restart, no re-mount);configure({ auto: true }); both share one revision and conflicts are surfaced;Prerequisites: DSH 0.2.0-rc.2 / 0.2.1-rc.2, Node.js >= 22.19.0, git; finish or stop running tasks/sessions first.
dsh plugin --profile desktop add github:wplct/dsh-codex-style-worktree
Then quit and reopen DSH once (the bundle layer takes effect; this is not a page refresh). Afterwards configuration changes need no restart (volatile, live).
0.5.0 → 0.5.1 (current):
dsh plugin --profile desktop update dsh-codex-style-worktree, then quit and reopen DSH once.0.4.x → 0.5.0 (rename-only):
cleanupOnArchive all stay);refs/dsh-codex-style-worktree/; refs written by the old version can still be pointed at with git update-ref).0.3.x → 0.4.0 (history):
state.json is migrated in memory on read from v2 (or older v1) to v3 (adds snapshots; older versions also get placements) with no data loss, and the next write persists v3;cleanupOnArchive (default false); leaving it alone keeps the 0.3.0 "do not delete on session end" behavior;worktree-in-use (cleaning your own session's checkout first moves its cwd back to the workspace; override with force after confirming); new /wt-snapshots, /wt-restore <ref> and the settings-page "recoverable snapshots" list;0.2.x → 0.3.0 (history):
create / cancel endpoints and the pending/adoption logic were removed;state.json migrates automatically from v1 to v2 (keeps records, adds an empty placements map, bumps the version); old v1 records keep showing in the chip and settings page;.agents/worktrees/wt-* checkouts and leftover pending records;auto-worktree rows in its dependencies / bundles / cordis.patch.yml (0.2.2 already cleaned those) and that the market's disabled list has no old package name;The repository also ships a direct-install script that bypasses the package manager, for local development or offline use:
node install.mjs --dry-run # print the plan only, write nothing
node install.mjs # install into the default profile (desktop)
node install.mjs --profile web
DSH_HOME=/path/to/.dsh node install.mjs --profile desktop
What the script does:
package.json, cordis.patch.yml, lib/**, client/** must all be there; a missing half aborts with an absolute path in the error);package.json, cordis.patch.yml, README*.md, lib/** and client/** (recursively) into <profile>/node_modules/dsh-codex-style-worktree/ — staged first in a temporary directory on the same volume, never overwriting the live directory in place;node --check on every .js file in the staged copy and publishes only if all of them pass; a failure rolls back;<profile>/cordis.patch.yml to .bak-<ISO timestamp> and appends the marked patch block (the package's cordis.patch.yml minus # comment lines; that file currently holds only the config-less insert row, defaults come from lib/config.js). If the file already contains # >>> dsh-codex-style-worktree >>>, the append is skipped;The direct installer explicitly does not: modify the profile's package.json, run pnpm/npm, or restart DSH.
Uninstall with the symmetric DSH plugin command, then restart DSH:
dsh plugin --profile desktop remove dsh-codex-style-worktree
If the package was installed with the direct-install script from section 3.3, use uninstall.mjs instead:
node uninstall.mjs --dry-run # print the plan only
node uninstall.mjs # default profile
node uninstall.mjs --profile web
uninstall.mjs removes the marked block (markers and everything between them, other lines preserved, file backed up to .bak-<ISO timestamp> first) from <profile>/cordis.patch.yml, deletes <profile>/node_modules/dsh-codex-style-worktree/ recursively, and prints every action and backup path; it is idempotent and --dry-run writes nothing. It also reports (without deleting) install snapshots .dsh-codex-style-worktree.previous-* and the sidecar ledger.
| Goal | Action |
|---|---|
| Full uninstall | dsh plugin --profile desktop remove dsh-codex-style-worktree (direct install: node uninstall.mjs), then restart DSH |
| Revert the profile patch only | cp <profile>/cordis.patch.yml.bak-<timestamp> <profile>/cordis.patch.yml, delete node_modules/dsh-codex-style-worktree/ manually, restart |
| Roll back to the previous package | rename the printed .dsh-codex-style-worktree.previous-<timestamp> directory back to node_modules/dsh-codex-style-worktree, restart |
Uninstall never deletes your worktrees or branches: check /wt-status, clean up with /wt-clean, or run git worktree remove <path> / git branch -d <branch> yourself. Deleting the sidecar ledger ($DSH_HOME/dsh-codex-style-worktree/) also clears the remembered placements.
lib/config.js (resolveConfig is the single source of truth; EDITABLE_FIELDS defines the settings form fields and their order);cordis.patch.yml: a plain insert row (id: codex-worktree, name: dsh-codex-style-worktree) with no config, so the market can hot-mount it;- id: codex-worktree config row in the active profile's cordis.patch.yml (the settings page maintains it automatically);| Field | Type | Default | Control | Constraints / notes |
|---|---|---|---|---|
enabled | boolean | true | switch | Master switch; when off, new sessions get no worktree (existing checkouts are untouched) |
defaultPlacement | worktree|local | worktree | select | Placement for a workspace with no stored record yet |
directory | string | .agents/worktrees | text | Pool directory: relative, no ./.. segments |
root | string | "" | text | Empty = inside the repository; may be absolute or repository-relative, no .. |
mode | detached|branch | detached | select | detached creates no branch (default); branch creates <branchPrefix><name> up front |
namePrefix | string | wt- | text | Checkout name prefix; no / \, must not start with - |
branchPrefix | string | wt/ | text | Branch prefix for mode: branch; must not start with - or / |
baseRef | string | HEAD | text | Local base revision; must not start with -; resolved locally (network only via fetchBeforeCreate) |
fetchBeforeCreate | boolean | false | switch | Run git fetch --prune origin before creation; failures only warn and never block; skipped without origin |
autoPrune | boolean | true | switch | After each successful creation, prune the repository's oldest checkouts in the background (keep the newest autoPruneKeep) |
autoPruneKeep | number | 15 | number | Newest checkouts kept per repository; non-negative integer |
pruneGraceMs | number | 60000 | number | Checkouts younger than this are never auto-pruned (milliseconds); non-negative integer |
cleanupOnArchive | boolean | false | switch | After a session is archived, a lazy sweep deletes its clean (no uncommitted changes) and unused worktree; off by default to keep the Codex-style "do not delete on session end" behavior |
poolGitignore | boolean | true | switch | Write a self-ignoring .gitignore when the pool is created, keeping the main checkout's git status clean |
gitTimeoutMs | number | 20000 | number | Hard timeout for one git command (milliseconds); positive integer |
skipChildAgents | boolean | true | switch | Never create a worktree for subagents/child sessions |