@sagmans/dsh-tui 0.5.2 → 0.7.0
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.
- package/.agents/skills/dsh-tui-dogfood/SKILL.md +83 -0
- package/.agents/skills/dsh-tui-dogfood/references/home-state.md +78 -0
- package/.agents/skills/dsh-tui-dogfood/scripts/clone-links.mjs +133 -0
- package/.agents/skills/dsh-tui-dogfood/scripts/run-plugin-from-worktree.sh +417 -0
- package/.agents/skills/dsh-tui-update-models/SKILL.md +121 -0
- package/.agents/skills/dsh-tui-update-models/references/model-wiring.md +162 -0
- package/.agents/skills/dsh-tui-update-models/scripts/dump-model-catalog.mjs +178 -0
- package/README.md +131 -17
- package/lib/agent/host.d.ts +11 -0
- package/lib/agent/host.d.ts.map +1 -1
- package/lib/agent/host.js +16 -2
- package/lib/agent/host.js.map +1 -1
- package/lib/agent/present.d.ts.map +1 -1
- package/lib/agent/present.js +2 -1
- package/lib/agent/present.js.map +1 -1
- package/lib/agent/undo.d.ts +52 -0
- package/lib/agent/undo.d.ts.map +1 -0
- package/lib/agent/undo.js +106 -0
- package/lib/agent/undo.js.map +1 -0
- package/lib/cards/composition.d.ts +68 -0
- package/lib/cards/composition.d.ts.map +1 -0
- package/lib/cards/composition.js +117 -0
- package/lib/cards/composition.js.map +1 -0
- package/lib/cards/presenter.d.ts +23 -0
- package/lib/cards/presenter.d.ts.map +1 -0
- package/lib/cards/presenter.js +394 -0
- package/lib/cards/presenter.js.map +1 -0
- package/lib/cards/preview.d.ts +42 -0
- package/lib/cards/preview.d.ts.map +1 -0
- package/lib/cards/preview.js +51 -0
- package/lib/cards/preview.js.map +1 -0
- package/lib/cards.d.ts +42 -95
- package/lib/cards.d.ts.map +1 -1
- package/lib/cards.js +4 -526
- package/lib/cards.js.map +1 -1
- package/lib/compat/section.d.ts +29 -0
- package/lib/compat/section.d.ts.map +1 -0
- package/lib/compat/section.js +82 -0
- package/lib/compat/section.js.map +1 -0
- package/lib/config.d.ts +27 -2
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +61 -1
- package/lib/config.js.map +1 -1
- package/lib/contracts.d.ts +28 -0
- package/lib/contracts.d.ts.map +1 -1
- package/lib/fold-cursor.d.ts +8 -0
- package/lib/fold-cursor.d.ts.map +1 -1
- package/lib/fold-cursor.js +18 -0
- package/lib/fold-cursor.js.map +1 -1
- package/lib/gates/question-card.d.ts +77 -0
- package/lib/gates/question-card.d.ts.map +1 -0
- package/lib/gates/question-card.js +113 -0
- package/lib/gates/question-card.js.map +1 -0
- package/lib/gates/questions.d.ts +127 -0
- package/lib/gates/questions.d.ts.map +1 -0
- package/lib/gates/questions.js +418 -0
- package/lib/gates/questions.js.map +1 -0
- package/lib/gates.d.ts +1 -113
- package/lib/gates.d.ts.map +1 -1
- package/lib/gates.js +1 -484
- package/lib/gates.js.map +1 -1
- package/lib/herdr/reporter.d.ts +9 -4
- package/lib/herdr/reporter.d.ts.map +1 -1
- package/lib/herdr/reporter.js +5 -9
- package/lib/herdr/reporter.js.map +1 -1
- package/lib/herdr/state.d.ts +35 -4
- package/lib/herdr/state.d.ts.map +1 -1
- package/lib/herdr/state.js +37 -4
- package/lib/herdr/state.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +343 -2267
- package/lib/index.js.map +1 -1
- package/lib/input/action-catalog.d.ts +74 -0
- package/lib/input/action-catalog.d.ts.map +1 -0
- package/lib/input/action-catalog.js +137 -0
- package/lib/input/action-catalog.js.map +1 -0
- package/lib/input/actions.d.ts +5 -81
- package/lib/input/actions.d.ts.map +1 -1
- package/lib/input/actions.js +4 -621
- package/lib/input/actions.js.map +1 -1
- package/lib/input/completion.d.ts +11 -1
- package/lib/input/completion.d.ts.map +1 -1
- package/lib/input/completion.js +17 -1
- package/lib/input/completion.js.map +1 -1
- package/lib/input/file-index.d.ts +40 -0
- package/lib/input/file-index.d.ts.map +1 -0
- package/lib/input/file-index.js +141 -0
- package/lib/input/file-index.js.map +1 -0
- package/lib/input/file-search.d.ts +1 -38
- package/lib/input/file-search.d.ts.map +1 -1
- package/lib/input/file-search.js +1 -487
- package/lib/input/file-search.js.map +1 -1
- package/lib/input/key-press.d.ts +57 -0
- package/lib/input/key-press.d.ts.map +1 -0
- package/lib/input/key-press.js +218 -0
- package/lib/input/key-press.js.map +1 -0
- package/lib/input/keymap-conflicts.d.ts +59 -0
- package/lib/input/keymap-conflicts.d.ts.map +1 -0
- package/lib/input/keymap-conflicts.js +292 -0
- package/lib/input/keymap-conflicts.js.map +1 -0
- package/lib/input/keymap-settings.js +1 -1
- package/lib/input/keymap-settings.js.map +1 -1
- package/lib/input/keymap.d.ts.map +1 -1
- package/lib/input/keymap.js +5 -1
- package/lib/input/keymap.js.map +1 -1
- package/lib/input/submission.d.ts +11 -1
- package/lib/input/submission.d.ts.map +1 -1
- package/lib/input/submission.js +10 -1
- package/lib/input/submission.js.map +1 -1
- package/lib/input/workspace-files.d.ts +10 -0
- package/lib/input/workspace-files.d.ts.map +1 -0
- package/lib/input/workspace-files.js +359 -0
- package/lib/input/workspace-files.js.map +1 -0
- package/lib/install-skills.d.ts +20 -0
- package/lib/install-skills.d.ts.map +1 -0
- package/lib/install-skills.js +198 -0
- package/lib/install-skills.js.map +1 -0
- package/lib/keys-command.d.ts +2 -1
- package/lib/keys-command.d.ts.map +1 -1
- package/lib/keys-command.js +2 -1
- package/lib/keys-command.js.map +1 -1
- package/lib/model-list.d.ts +37 -0
- package/lib/model-list.d.ts.map +1 -0
- package/lib/model-list.js +60 -0
- package/lib/model-list.js.map +1 -0
- package/lib/startup.d.ts +8 -0
- package/lib/startup.d.ts.map +1 -1
- package/lib/startup.js +120 -24
- package/lib/startup.js.map +1 -1
- package/lib/subagents.d.ts +7 -3
- package/lib/subagents.d.ts.map +1 -1
- package/lib/subagents.js +28 -4
- package/lib/subagents.js.map +1 -1
- package/lib/surface/appearance-preferences.d.ts +26 -0
- package/lib/surface/appearance-preferences.d.ts.map +1 -0
- package/lib/surface/appearance-preferences.js +139 -0
- package/lib/surface/appearance-preferences.js.map +1 -0
- package/lib/surface/appearance.d.ts +87 -0
- package/lib/surface/appearance.d.ts.map +1 -0
- package/lib/surface/appearance.js +353 -0
- package/lib/surface/appearance.js.map +1 -0
- package/lib/surface/background-work.d.ts +49 -0
- package/lib/surface/background-work.d.ts.map +1 -0
- package/lib/surface/background-work.js +146 -0
- package/lib/surface/background-work.js.map +1 -0
- package/lib/surface/commands.d.ts +80 -0
- package/lib/surface/commands.d.ts.map +1 -0
- package/lib/surface/commands.js +364 -0
- package/lib/surface/commands.js.map +1 -0
- package/lib/surface/modal-input.d.ts +58 -0
- package/lib/surface/modal-input.d.ts.map +1 -0
- package/lib/surface/modal-input.js +237 -0
- package/lib/surface/modal-input.js.map +1 -0
- package/lib/surface/model-choice.d.ts +54 -0
- package/lib/surface/model-choice.d.ts.map +1 -0
- package/lib/surface/model-choice.js +325 -0
- package/lib/surface/model-choice.js.map +1 -0
- package/lib/surface/preset-choice.d.ts +78 -0
- package/lib/surface/preset-choice.d.ts.map +1 -0
- package/lib/surface/preset-choice.js +163 -0
- package/lib/surface/preset-choice.js.map +1 -0
- package/lib/surface/prompt-input.d.ts +97 -0
- package/lib/surface/prompt-input.d.ts.map +1 -0
- package/lib/surface/prompt-input.js +215 -0
- package/lib/surface/prompt-input.js.map +1 -0
- package/lib/surface/prompt-memory.d.ts +76 -0
- package/lib/surface/prompt-memory.d.ts.map +1 -0
- package/lib/surface/prompt-memory.js +167 -0
- package/lib/surface/prompt-memory.js.map +1 -0
- package/lib/surface/session-lifecycle.d.ts +120 -0
- package/lib/surface/session-lifecycle.d.ts.map +1 -0
- package/lib/surface/session-lifecycle.js +321 -0
- package/lib/surface/session-lifecycle.js.map +1 -0
- package/lib/surface/session-picker.d.ts +39 -0
- package/lib/surface/session-picker.d.ts.map +1 -0
- package/lib/surface/session-picker.js +107 -0
- package/lib/surface/session-picker.js.map +1 -0
- package/lib/surface/session-view.d.ts +86 -0
- package/lib/surface/session-view.d.ts.map +1 -0
- package/lib/surface/session-view.js +215 -0
- package/lib/surface/session-view.js.map +1 -0
- package/lib/surface/staged-turns.d.ts +81 -0
- package/lib/surface/staged-turns.d.ts.map +1 -0
- package/lib/surface/staged-turns.js +206 -0
- package/lib/surface/staged-turns.js.map +1 -0
- package/lib/surface/terminal-lifecycle.d.ts +76 -0
- package/lib/surface/terminal-lifecycle.d.ts.map +1 -0
- package/lib/surface/terminal-lifecycle.js +222 -0
- package/lib/surface/terminal-lifecycle.js.map +1 -0
- package/lib/terminal/warning-screen.d.ts +8 -1
- package/lib/terminal/warning-screen.d.ts.map +1 -1
- package/lib/terminal/warning-screen.js +9 -0
- package/lib/terminal/warning-screen.js.map +1 -1
- package/lib/terminal-text/scan.d.ts +44 -0
- package/lib/terminal-text/scan.d.ts.map +1 -0
- package/lib/terminal-text/scan.js +195 -0
- package/lib/terminal-text/scan.js.map +1 -0
- package/lib/terminal-text/sgr.d.ts +32 -0
- package/lib/terminal-text/sgr.d.ts.map +1 -0
- package/lib/terminal-text/sgr.js +203 -0
- package/lib/terminal-text/sgr.js.map +1 -0
- package/lib/terminal-text.d.ts +1 -1
- package/lib/terminal-text.d.ts.map +1 -1
- package/lib/terminal-text.js +2 -379
- package/lib/terminal-text.js.map +1 -1
- package/lib/theme-command.js +2 -1
- package/lib/theme-command.js.map +1 -1
- package/lib/theme-defaults.d.ts +64 -0
- package/lib/theme-defaults.d.ts.map +1 -0
- package/lib/theme-defaults.js +248 -0
- package/lib/theme-defaults.js.map +1 -0
- package/lib/theme-resolver.d.ts +36 -0
- package/lib/theme-resolver.d.ts.map +1 -0
- package/lib/theme-resolver.js +112 -0
- package/lib/theme-resolver.js.map +1 -0
- package/lib/theme-schema.d.ts +6 -6
- package/lib/theme-schema.d.ts.map +1 -1
- package/lib/theme-schema.js +3 -2
- package/lib/theme-schema.js.map +1 -1
- package/lib/theme-settings.d.ts +13 -15
- package/lib/theme-settings.d.ts.map +1 -1
- package/lib/theme-settings.js +27 -19
- package/lib/theme-settings.js.map +1 -1
- package/lib/theme-tokens.d.ts +1 -90
- package/lib/theme-tokens.d.ts.map +1 -1
- package/lib/theme-tokens.js +11 -341
- package/lib/theme-tokens.js.map +1 -1
- package/lib/theme.d.ts +1 -1
- package/lib/theme.d.ts.map +1 -1
- package/lib/theme.js +2 -1
- package/lib/theme.js.map +1 -1
- package/lib/tool-display.d.ts.map +1 -1
- package/lib/tool-display.js +2 -1
- package/lib/tool-display.js.map +1 -1
- package/lib/transcript/message-content.d.ts +21 -0
- package/lib/transcript/message-content.d.ts.map +1 -0
- package/lib/transcript/message-content.js +61 -0
- package/lib/transcript/message-content.js.map +1 -0
- package/lib/transcript/tool-calls.d.ts +106 -0
- package/lib/transcript/tool-calls.d.ts.map +1 -0
- package/lib/transcript/tool-calls.js +254 -0
- package/lib/transcript/tool-calls.js.map +1 -0
- package/lib/transcript.d.ts +38 -43
- package/lib/transcript.d.ts.map +1 -1
- package/lib/transcript.js +58 -245
- package/lib/transcript.js.map +1 -1
- package/lib/ui/copy.d.ts +17 -0
- package/lib/ui/copy.d.ts.map +1 -0
- package/lib/ui/copy.js +104 -0
- package/lib/ui/copy.js.map +1 -0
- package/lib/ui/dock.d.ts +35 -3
- package/lib/ui/dock.d.ts.map +1 -1
- package/lib/ui/dock.js +124 -23
- package/lib/ui/dock.js.map +1 -1
- package/lib/ui/editor.d.ts.map +1 -1
- package/lib/ui/editor.js +2 -1
- package/lib/ui/editor.js.map +1 -1
- package/lib/ui/frame.d.ts +31 -0
- package/lib/ui/frame.d.ts.map +1 -1
- package/lib/ui/frame.js +27 -4
- package/lib/ui/frame.js.map +1 -1
- package/lib/ui/keymap-picker.d.ts +2 -1
- package/lib/ui/keymap-picker.d.ts.map +1 -1
- package/lib/ui/keymap-picker.js +1 -0
- package/lib/ui/keymap-picker.js.map +1 -1
- package/lib/ui/view/gate-card.d.ts +21 -0
- package/lib/ui/view/gate-card.d.ts.map +1 -0
- package/lib/ui/view/gate-card.js +104 -0
- package/lib/ui/view/gate-card.js.map +1 -0
- package/lib/ui/view/tool-card.d.ts +138 -0
- package/lib/ui/view/tool-card.d.ts.map +1 -0
- package/lib/ui/view/tool-card.js +413 -0
- package/lib/ui/view/tool-card.js.map +1 -0
- package/lib/ui/view/transcript-message.d.ts +62 -0
- package/lib/ui/view/transcript-message.d.ts.map +1 -0
- package/lib/ui/view/transcript-message.js +158 -0
- package/lib/ui/view/transcript-message.js.map +1 -0
- package/lib/ui/view.d.ts +87 -156
- package/lib/ui/view.d.ts.map +1 -1
- package/lib/ui/view.js +122 -556
- package/lib/ui/view.js.map +1 -1
- package/package.json +3 -1
- package/themes/deepseek-blue.yaml +9 -0
- package/themes/violet-orbit.yaml +7 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dsh-tui-dogfood
|
|
3
|
+
description: "Use when a dsh plugin checkout must be tested against a real profile without writing to the developer's live home: clone the home, relink only that plugin, and run or hand off the profile."
|
|
4
|
+
---
|
|
5
|
+
# Dogfood a dsh plugin checkout
|
|
6
|
+
|
|
7
|
+
A fresh `DSH_HOME` misses the installed bundles, profile patch, settings, and themes.
|
|
8
|
+
A run against the real home writes real sessions and state. Clone the source
|
|
9
|
+
home and relink only the plugin under test inside the clone.
|
|
10
|
+
|
|
11
|
+
From **any dsh plugin checkout root**, run the packaged helper:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
~/.agents/skills/dsh-tui-dogfood/scripts/run-plugin-from-worktree.sh --no-launch
|
|
15
|
+
~/.agents/skills/dsh-tui-dogfood/scripts/run-plugin-from-worktree.sh feat/plugin-fix -- --resume
|
|
16
|
+
~/.agents/skills/dsh-tui-dogfood/scripts/run-plugin-from-worktree.sh ../plugin-fix --dry-run
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
No target means the current checkout. A name matches its repository's worktree
|
|
20
|
+
directory or branch tail. A path selects that checkout directly. The helper reads
|
|
21
|
+
`package.json` for the plugin name; it never assumes `@sagmans/dsh-tui`. It
|
|
22
|
+
runs `pnpm run build` only when the checkout declares a build script. It adds
|
|
23
|
+
the bundle once if absent from the cloned profile, updates only its dependency,
|
|
24
|
+
restores local links from `link:` specifications, and launches
|
|
25
|
+
`dsh --profile tui` by default. The legacy
|
|
26
|
+
`scripts/dogfood/run-tui-from-worktree.sh` remains available in dsh-tui and
|
|
27
|
+
defaults to that script's checkout even when called elsewhere.
|
|
28
|
+
|
|
29
|
+
| Need | Option |
|
|
30
|
+
| --- | --- |
|
|
31
|
+
| Inspect clone and linked bundle | `--status` |
|
|
32
|
+
| Copy saved sessions for `--resume` | `--with-sessions` |
|
|
33
|
+
| Copy credentials and settings only | `--fresh` |
|
|
34
|
+
| Select profile, source home, or scratch home | `--profile NAME`, `--source-home DIR`, `--home DIR` |
|
|
35
|
+
| Print plan without mutation | `--dry-run` |
|
|
36
|
+
| Set up without starting dsh | `--no-launch` |
|
|
37
|
+
| Discard and clone again | `--reseed` |
|
|
38
|
+
| Remove matching scratch clone | `--clean` |
|
|
39
|
+
| List checkout worktrees | `--list` |
|
|
40
|
+
|
|
41
|
+
The helper ignores `$DSH_HOME` as source. Pass `--source-home "$DSH_HOME"`
|
|
42
|
+
when you intend to clone it. The default scratch path includes the checkout
|
|
43
|
+
name and path hash to avoid reuse across repositories.
|
|
44
|
+
|
|
45
|
+
The clone contains copied credentials. Store it only in private scratch storage
|
|
46
|
+
and remove it with `--clean`. Cleanup and reseed refuse nonmatching markers,
|
|
47
|
+
source-home overlap, and home-root paths.
|
|
48
|
+
|
|
49
|
+
External links are allowed only under `profiles/node_modules`,
|
|
50
|
+
`profiles/*/node_modules`, and `profiles/*/.dsh-module-fallback/node_modules`.
|
|
51
|
+
These package links can point to global packages or external checkouts, but
|
|
52
|
+
never into the source home. Every other cloned link must stay inside the clone.
|
|
53
|
+
An instruction file linked into a shared prompt tree (`AGENTS.md`, `CLAUDE.md`)
|
|
54
|
+
is read only, so seeding copies it in as a regular file; a link to state the run
|
|
55
|
+
may write — credentials, settings, storages, sessions, profiles — still stops the
|
|
56
|
+
run. Mutable cloned files with multiple hardlinks are rejected. Never write test
|
|
57
|
+
state through a package link.
|
|
58
|
+
|
|
59
|
+
The old dsh-tui entry point still requires a prelisted bundle. Do not use
|
|
60
|
+
`dsh plugin add` on a cloned profile with other bundles; it can drop them.
|
|
61
|
+
|
|
62
|
+
Use `--status` to verify the existing bundles and the checkout `link:`
|
|
63
|
+
dependency. In dsh-tui, drive a cloned profile with:
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
node tools/pty-drive.mjs --launcher "$(command -v dsh)" --home <clone> --prompt 'Reply with exactly: pong'
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Dsh-tui source edits are invisible until `pnpm run build` updates `lib/`.
|
|
70
|
+
Omit `--no-build` for real runs.
|
|
71
|
+
|
|
72
|
+
If the checkout or source home was removed, use
|
|
73
|
+
`--clean --home <clone> --source-home <original-source-path>`. Run from the
|
|
74
|
+
checkout while it exists, or pass its path as the target from another
|
|
75
|
+
directory. If the checkout was removed, run from any directory. Supply the
|
|
76
|
+
original `--profile NAME` when it was not `tui`.
|
|
77
|
+
|
|
78
|
+
Cleanup canonicalizes a missing source path only for `--clean --home` and
|
|
79
|
+
still requires its marker to match. Orphan cleanup requires a private marker
|
|
80
|
+
owned by the current user. It validates home, source, profile, and absent
|
|
81
|
+
target, but cannot recheck a removed checkout.
|
|
82
|
+
|
|
83
|
+
For home contents, storage, and cleanup details, read [references/home-state.md](references/home-state.md).
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# What a ~/.dsh home holds, and what a dogfood run touches
|
|
2
|
+
|
|
3
|
+
Read this when a dogfood run behaves differently from the developer's own
|
|
4
|
+
profile. It gives the operational detail behind [../SKILL.md](../SKILL.md).
|
|
5
|
+
|
|
6
|
+
## The entries
|
|
7
|
+
|
|
8
|
+
| Entry | Kind | What reads or writes it |
|
|
9
|
+
| --- | --- | --- |
|
|
10
|
+
| `.credentials.yaml` | secret | the provider layer, at start-up; read-only |
|
|
11
|
+
| `settings.yaml`, `settings.yaml.imported` | config | the `dsh-tui:` section: theme, tokens, dock, stash, prompt history |
|
|
12
|
+
| `themes/` | data | created and watched at start-up; saving a file changes the live surface |
|
|
13
|
+
| `prompt-history.json` | data | the editor's history recall; appended as prompts are sent |
|
|
14
|
+
| `tui-stash/` | data | the parked-draft bank, one file per session, behind a lock |
|
|
15
|
+
| `storages/` | data | durable plugin state |
|
|
16
|
+
| `attachments/` | data | images pasted into prompts |
|
|
17
|
+
| `profiles/<name>/` | config | `package.json` (bundle list and deps), `cordis.yml`, `cordis.patch.yml`, a pnpm lockfile, `node_modules` |
|
|
18
|
+
| `sessions/<bucket>/<id>/` | data | the transcript (`session.v3.jsonl.zstd`) and a lock; the bucket name encodes the launch directory |
|
|
19
|
+
| `AGENTS.md` | config | injected into the agent's instructions |
|
|
20
|
+
|
|
21
|
+
## Home state versus profile state
|
|
22
|
+
|
|
23
|
+
Credentials, settings, themes, history, the stash, storages, and sessions belong
|
|
24
|
+
to the **home**: every profile in it shares them. The bundle list and the patch
|
|
25
|
+
overlay belong to the **profile**: a new profile name starts from the base plus
|
|
26
|
+
whatever is added to it, and inherits none of another profile's bundles or
|
|
27
|
+
patches. That is the trap behind "test it in a profile named after the feature" —
|
|
28
|
+
the run is compositionally thinner than the developer's own while still writing
|
|
29
|
+
their real state.
|
|
30
|
+
|
|
31
|
+
## What the dogfood clone changes
|
|
32
|
+
|
|
33
|
+
The checkout package name selects its bundle in `profiles/<name>/package.json`.
|
|
34
|
+
The generic helper adds it once if absent, repoints only its dependency, and
|
|
35
|
+
rebuilds cloned local symlinks. It uses `dsh plugin add` only if the profile is
|
|
36
|
+
missing after `dsh --help`, and does not modify the lockfile of an existing
|
|
37
|
+
profile. Other bundles and the profile patch remain copied from the source home.
|
|
38
|
+
One module, `scripts/clone-links.mjs`, holds the clone's link policy, so the
|
|
39
|
+
seeding step and the validator read the same rules.
|
|
40
|
+
|
|
41
|
+
`sessions/` is the only entry left out by default: it is the bulk of a home
|
|
42
|
+
(190M of 200M in a busy one) and a test drive rarely needs it. `--with-sessions`
|
|
43
|
+
copies it when present, which makes `--resume` reach the developer's history.
|
|
44
|
+
|
|
45
|
+
## Failure modes worth knowing
|
|
46
|
+
|
|
47
|
+
- **A missing row is silent.** `dsh plugin install` drops a bundle whose path
|
|
48
|
+
does not resolve and still exits 0; the profile then composes the base alone and
|
|
49
|
+
the surface waits with no output. Check
|
|
50
|
+
`node -p "require('<home>/profiles/<name>/package.json').dsh.profile.bundles"`.
|
|
51
|
+
- **Mutable home state must remain inside the clone.** External symlinks in
|
|
52
|
+
settings, storages, or sessions and multiply linked mutable files stop the run.
|
|
53
|
+
Package links in `profiles/node_modules`, `profiles/*/node_modules`, and
|
|
54
|
+
`profiles/*/.dsh-module-fallback/node_modules` can point to global installs or
|
|
55
|
+
external checkouts, but not into the source home. An instruction file linked
|
|
56
|
+
into a shared prompt tree (`AGENTS.md`, `CLAUDE.md`) is read only and is copied
|
|
57
|
+
in while seeding, so the clone stays self-contained; a link to state the run may
|
|
58
|
+
write still stops the run. Writes through permitted package links are not
|
|
59
|
+
isolated. Re-seed with `--reseed` when the source changes.
|
|
60
|
+
- **The clone keeps a copy of the credentials.** Create it in a scratch
|
|
61
|
+
directory, keep it `700`, and `--clean` it when done.
|
|
62
|
+
- **The launcher decides the harness, not the profile.** `pnpm dsh` runs the
|
|
63
|
+
workspace-linked harness checkout (a dev build, which may fail to compose);
|
|
64
|
+
`dsh` on `PATH` is the released one. A profile cannot tell them apart.
|
|
65
|
+
- **A turn can fail for reasons outside the surface** — no provider credit, a
|
|
66
|
+
sandbox policy, a missing credential. Read the error line in the transcript
|
|
67
|
+
before treating it as a rendering bug.
|
|
68
|
+
|
|
69
|
+
## Cleanup
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
~/.agents/skills/dsh-tui-dogfood/scripts/run-plugin-from-worktree.sh --status # which clone, which target
|
|
73
|
+
~/.agents/skills/dsh-tui-dogfood/scripts/run-plugin-from-worktree.sh --clean # removes it (marker-guarded)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The default clone lives at `${TMPDIR:-/tmp}/dsh-dogfood/<worktree>-<checkout-id>`
|
|
77
|
+
and survives until `--clean` removes it. Cleanup validates the scratch-home
|
|
78
|
+
marker against its canonical home, source, checkout, and profile.
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Link policy for the dogfood clone.
|
|
4
|
+
*
|
|
5
|
+
* A cloned home is a sandbox: a dogfood run may write only inside it. Two kinds
|
|
6
|
+
* of link are legitimate there — a package entry that resolves to an installed
|
|
7
|
+
* package or another checkout, and a link that stays inside the clone. A link
|
|
8
|
+
* that escapes is either copied in or refused, and which one depends on who
|
|
9
|
+
* writes the entry: the instruction files below are only read, so the bytes come
|
|
10
|
+
* along; everything else (credentials, settings, storages, sessions, profiles)
|
|
11
|
+
* is state the run may write, and a link out of the clone would let that write
|
|
12
|
+
* reach the developer's own home. Refusing is the safe answer there.
|
|
13
|
+
*
|
|
14
|
+
* Usage: clone-links.mjs check|materialize <home> <source>
|
|
15
|
+
*/
|
|
16
|
+
import fs from 'node:fs'
|
|
17
|
+
import path from 'node:path'
|
|
18
|
+
import { pathToFileURL } from 'node:url'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Entries the surface only reads. A developer commonly links AGENTS.md into a
|
|
22
|
+
* shared tree, and refusing that would stop an otherwise safe run, so the copy
|
|
23
|
+
* is made instead. Nothing here may be added that any component writes.
|
|
24
|
+
*/
|
|
25
|
+
export const INSTRUCTION_ENTRIES = new Set(['AGENTS.md', 'CLAUDE.md'])
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Paths whose whole purpose is to point at an installed package, which may live
|
|
29
|
+
* outside the clone. A link here is expected; a link anywhere else is not.
|
|
30
|
+
*/
|
|
31
|
+
export const packageEntry = (parts) => parts[0] === 'profiles' && (
|
|
32
|
+
parts[1] === 'node_modules' && parts.length > 2 ||
|
|
33
|
+
parts[2] === 'node_modules' && parts.length > 3 ||
|
|
34
|
+
parts[2] === '.dsh-module-fallback' && parts[3] === 'node_modules' && parts.length > 4
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
export const inside = (root, file) => file === root || file.startsWith(root + path.sep)
|
|
38
|
+
|
|
39
|
+
const instruction = (home, file) => {
|
|
40
|
+
const parts = path.relative(home, file).split(path.sep)
|
|
41
|
+
return parts.length === 1 && INSTRUCTION_ENTRIES.has(parts[0])
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Absolute path a link target will occupy once it exists. A clone may hold a
|
|
46
|
+
* link to a file an install has not created yet, and the missing leaf can still
|
|
47
|
+
* sit under a link that crosses the clone boundary.
|
|
48
|
+
*/
|
|
49
|
+
export function destination(file) {
|
|
50
|
+
let ancestor = file
|
|
51
|
+
while (!fs.existsSync(ancestor)) {
|
|
52
|
+
const parent = path.dirname(ancestor)
|
|
53
|
+
if (parent === ancestor) throw Error('cannot resolve cloned link: ' + file)
|
|
54
|
+
ancestor = parent
|
|
55
|
+
}
|
|
56
|
+
return path.resolve(fs.realpathSync(ancestor), path.relative(ancestor, file))
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const entries = (root) => fs.readdirSync(root, { withFileTypes: true })
|
|
60
|
+
const partsOf = (home, file) => path.relative(home, file).split(path.sep)
|
|
61
|
+
|
|
62
|
+
/** Every reason the clone cannot be run as a sandbox, in the order they are met. */
|
|
63
|
+
export function problems(home, source) {
|
|
64
|
+
const found = []
|
|
65
|
+
const visit = (dir) => {
|
|
66
|
+
for (const entry of entries(dir)) {
|
|
67
|
+
const file = path.join(dir, entry.name)
|
|
68
|
+
const parts = partsOf(home, file)
|
|
69
|
+
if (entry.isSymbolicLink()) {
|
|
70
|
+
const target = destination(path.resolve(dir, fs.readlinkSync(file)))
|
|
71
|
+
const allowed = packageEntry(parts) ? !inside(source, target) : inside(home, target)
|
|
72
|
+
if (!allowed) found.push('unsafe cloned symlink escaping scratch or entering source home: ' + file)
|
|
73
|
+
} else if (entry.isDirectory()) {
|
|
74
|
+
visit(file)
|
|
75
|
+
} else if (entry.isFile() && !packageEntry(parts) && fs.lstatSync(file).nlink > 1) {
|
|
76
|
+
found.push('unsafe cloned hardlink outside package modules: ' + file)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
visit(home)
|
|
81
|
+
return found
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Copy every escaping instruction link in, so the clone can read it as its own. */
|
|
85
|
+
export function materialize(home) {
|
|
86
|
+
const report = { copied: 0, dropped: 0 }
|
|
87
|
+
const visit = (dir) => {
|
|
88
|
+
for (const entry of entries(dir)) {
|
|
89
|
+
const file = path.join(dir, entry.name)
|
|
90
|
+
if (entry.isSymbolicLink()) {
|
|
91
|
+
const parts = partsOf(home, file)
|
|
92
|
+
if (packageEntry(parts) || !instruction(home, file)) continue
|
|
93
|
+
const target = destination(path.resolve(dir, fs.readlinkSync(file)))
|
|
94
|
+
if (inside(home, target)) continue
|
|
95
|
+
if (!fs.existsSync(target)) {
|
|
96
|
+
fs.rmSync(file)
|
|
97
|
+
console.log('materialize: dropped dangling link ' + path.relative(home, file))
|
|
98
|
+
report.dropped += 1
|
|
99
|
+
continue
|
|
100
|
+
}
|
|
101
|
+
const stat = fs.statSync(target)
|
|
102
|
+
fs.rmSync(file, { recursive: true, force: true })
|
|
103
|
+
if (stat.isDirectory()) {
|
|
104
|
+
fs.cpSync(target, file, { recursive: true, dereference: true })
|
|
105
|
+
} else {
|
|
106
|
+
fs.copyFileSync(target, file)
|
|
107
|
+
fs.chmodSync(file, stat.mode & 0o777)
|
|
108
|
+
}
|
|
109
|
+
console.log('materialize: copied ' + path.relative(home, file))
|
|
110
|
+
report.copied += 1
|
|
111
|
+
continue
|
|
112
|
+
}
|
|
113
|
+
if (entry.isDirectory()) visit(file)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
visit(home)
|
|
117
|
+
return report
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
121
|
+
const [mode, home, source] = process.argv.slice(2)
|
|
122
|
+
if (mode === 'check') {
|
|
123
|
+
const found = problems(home, source)
|
|
124
|
+
for (const problem of found) console.error(problem)
|
|
125
|
+
process.exitCode = found.length ? 1 : 0
|
|
126
|
+
} else if (mode === 'materialize') {
|
|
127
|
+
const report = materialize(home)
|
|
128
|
+
console.log('materialize: ' + report.copied + ' instruction link(s) copied, ' + report.dropped + ' dangling link(s) dropped')
|
|
129
|
+
} else {
|
|
130
|
+
console.error('usage: clone-links.mjs check|materialize <home> <source>')
|
|
131
|
+
process.exitCode = 2
|
|
132
|
+
}
|
|
133
|
+
}
|