@exagone313/dsh-podman 0.2.0-rc.2

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 (165) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +41 -0
  3. package/README.zh.md +41 -0
  4. package/cordis.patch.yml +17 -0
  5. package/dist/approval-reasons.d.ts +129 -0
  6. package/dist/approval-reasons.js +253 -0
  7. package/dist/approval-reasons.test.d.ts +1 -0
  8. package/dist/approval-reasons.test.js +115 -0
  9. package/dist/approval.d.ts +29 -0
  10. package/dist/approval.js +412 -0
  11. package/dist/approval.test.d.ts +1 -0
  12. package/dist/approval.test.js +462 -0
  13. package/dist/card-route.d.ts +15 -0
  14. package/dist/card-route.js +478 -0
  15. package/dist/card-route.test.d.ts +1 -0
  16. package/dist/card-route.test.js +181 -0
  17. package/dist/card-test-support.d.ts +15 -0
  18. package/dist/card-test-support.js +160 -0
  19. package/dist/client/ContainerCard.d.ts +6 -0
  20. package/dist/client/ContainerCard.js +79 -0
  21. package/dist/client/card-client.d.ts +6 -0
  22. package/dist/client/card-client.js +47 -0
  23. package/dist/client/card-protocol.d.ts +101 -0
  24. package/dist/client/card-protocol.js +10 -0
  25. package/dist/client/container-card-caches.d.ts +10 -0
  26. package/dist/client/container-card-caches.js +31 -0
  27. package/dist/client/container-card-controller.d.ts +107 -0
  28. package/dist/client/container-card-controller.js +207 -0
  29. package/dist/client/container-card-create-modal.d.ts +24 -0
  30. package/dist/client/container-card-create-modal.js +109 -0
  31. package/dist/client/container-card-directory-styles.d.ts +5 -0
  32. package/dist/client/container-card-directory-styles.js +246 -0
  33. package/dist/client/container-card-directory.d.ts +12 -0
  34. package/dist/client/container-card-directory.js +144 -0
  35. package/dist/client/container-card-editors.d.ts +30 -0
  36. package/dist/client/container-card-editors.js +116 -0
  37. package/dist/client/container-card-images.d.ts +37 -0
  38. package/dist/client/container-card-images.js +35 -0
  39. package/dist/client/container-card-paths.d.ts +20 -0
  40. package/dist/client/container-card-paths.js +88 -0
  41. package/dist/client/container-card-row.d.ts +30 -0
  42. package/dist/client/container-card-row.js +54 -0
  43. package/dist/client/container-card-secrets.d.ts +21 -0
  44. package/dist/client/container-card-secrets.js +71 -0
  45. package/dist/client/container-card-shared.d.ts +55 -0
  46. package/dist/client/container-card-shared.js +152 -0
  47. package/dist/client/container-card-styles.d.ts +21 -0
  48. package/dist/client/container-card-styles.js +143 -0
  49. package/dist/client/container-card-volumes.d.ts +12 -0
  50. package/dist/client/container-card-volumes.js +37 -0
  51. package/dist/client/container-card-workspace.d.ts +31 -0
  52. package/dist/client/container-card-workspace.js +24 -0
  53. package/dist/client/directory-picker.d.ts +12 -0
  54. package/dist/client/directory-picker.js +30 -0
  55. package/dist/client/index.d.ts +5 -0
  56. package/dist/client/index.js +4419 -0
  57. package/dist/client/locales.d.ts +9 -0
  58. package/dist/client/locales.js +370 -0
  59. package/dist/client/read-only-approval.d.ts +8 -0
  60. package/dist/client/read-only-approval.js +63 -0
  61. package/dist/client/slot-contract.d.ts +12 -0
  62. package/dist/client/slot-contract.js +4 -0
  63. package/dist/client/terminal-styles.d.ts +5 -0
  64. package/dist/client/terminal-styles.js +31 -0
  65. package/dist/client/tool-views.d.ts +8 -0
  66. package/dist/client/tool-views.js +241 -0
  67. package/dist/containers.test.d.ts +1 -0
  68. package/dist/containers.test.js +443 -0
  69. package/dist/daemons.test.d.ts +1 -0
  70. package/dist/daemons.test.js +153 -0
  71. package/dist/fs-provider.d.ts +23 -0
  72. package/dist/fs-provider.js +184 -0
  73. package/dist/fs-provider.test.d.ts +1 -0
  74. package/dist/fs-provider.test.js +260 -0
  75. package/dist/generated/version.d.ts +2 -0
  76. package/dist/generated/version.js +6 -0
  77. package/dist/grpc/proto/dshctl/v1/control.proto +133 -0
  78. package/dist/grpc/proto/dshguest/v1/guest.proto +79 -0
  79. package/dist/grpc/runtime-client.d.ts +5 -0
  80. package/dist/grpc/runtime-client.js +43 -0
  81. package/dist/guest-rpc.d.ts +62 -0
  82. package/dist/guest-rpc.js +391 -0
  83. package/dist/images.test.d.ts +1 -0
  84. package/dist/images.test.js +79 -0
  85. package/dist/index.d.ts +22 -0
  86. package/dist/index.js +140 -0
  87. package/dist/misc.test.d.ts +1 -0
  88. package/dist/misc.test.js +141 -0
  89. package/dist/mount-enums.d.ts +22 -0
  90. package/dist/mount-enums.js +81 -0
  91. package/dist/mount-input.d.ts +14 -0
  92. package/dist/mount-input.js +64 -0
  93. package/dist/mounts.test.d.ts +1 -0
  94. package/dist/mounts.test.js +408 -0
  95. package/dist/output-reader.d.ts +34 -0
  96. package/dist/output-reader.js +87 -0
  97. package/dist/paths.test.d.ts +1 -0
  98. package/dist/paths.test.js +74 -0
  99. package/dist/preferences.d.ts +6 -0
  100. package/dist/preferences.js +27 -0
  101. package/dist/project-path.d.ts +6 -0
  102. package/dist/project-path.js +73 -0
  103. package/dist/project-path.test.d.ts +1 -0
  104. package/dist/project-path.test.js +52 -0
  105. package/dist/prompts.d.ts +13 -0
  106. package/dist/prompts.js +149 -0
  107. package/dist/public.d.ts +4 -0
  108. package/dist/public.js +63 -0
  109. package/dist/read-only-shell.d.ts +14 -0
  110. package/dist/read-only-shell.js +150 -0
  111. package/dist/read-only-shell.test.d.ts +1 -0
  112. package/dist/read-only-shell.test.js +127 -0
  113. package/dist/secrets.test.d.ts +1 -0
  114. package/dist/secrets.test.js +181 -0
  115. package/dist/settings-commands.test.d.ts +1 -0
  116. package/dist/settings-commands.test.js +549 -0
  117. package/dist/settings-create.test.d.ts +1 -0
  118. package/dist/settings-create.test.js +394 -0
  119. package/dist/settings-mounts.test.d.ts +1 -0
  120. package/dist/settings-mounts.test.js +467 -0
  121. package/dist/settings-paths.test.d.ts +1 -0
  122. package/dist/settings-paths.test.js +65 -0
  123. package/dist/settings-schema.d.ts +14 -0
  124. package/dist/settings-schema.js +14 -0
  125. package/dist/settings-workspaces.test.d.ts +1 -0
  126. package/dist/settings-workspaces.test.js +265 -0
  127. package/dist/spill-store.d.ts +17 -0
  128. package/dist/spill-store.js +54 -0
  129. package/dist/spill-store.test.d.ts +1 -0
  130. package/dist/spill-store.test.js +59 -0
  131. package/dist/subprocess.d.ts +9 -0
  132. package/dist/subprocess.js +369 -0
  133. package/dist/subprocess.test.d.ts +1 -0
  134. package/dist/subprocess.test.js +234 -0
  135. package/dist/test-support.d.ts +328 -0
  136. package/dist/test-support.js +437 -0
  137. package/dist/tool-defs.d.ts +26 -0
  138. package/dist/tool-defs.js +188 -0
  139. package/dist/tool-handlers.d.ts +2 -0
  140. package/dist/tool-handlers.js +546 -0
  141. package/dist/tool-params.d.ts +803 -0
  142. package/dist/tool-params.js +494 -0
  143. package/dist/tool-schemas.d.ts +2 -0
  144. package/dist/tool-schemas.js +5 -0
  145. package/dist/tool-views.d.ts +2 -0
  146. package/dist/tool-views.js +56 -0
  147. package/dist/views.test.d.ts +1 -0
  148. package/dist/views.test.js +23 -0
  149. package/dist/volumes.test.d.ts +1 -0
  150. package/dist/volumes.test.js +40 -0
  151. package/dist/workspace-binding.d.ts +40 -0
  152. package/dist/workspace-binding.js +275 -0
  153. package/dist/workspace-binding.test.d.ts +1 -0
  154. package/dist/workspace-binding.test.js +337 -0
  155. package/docs/architecture.md +173 -0
  156. package/docs/architecture.zh.md +129 -0
  157. package/docs/configuration.md +160 -0
  158. package/docs/configuration.zh.md +147 -0
  159. package/docs/development.md +142 -0
  160. package/docs/development.zh.md +135 -0
  161. package/docs/install-dsh-and-dsh-podman.md +165 -0
  162. package/docs/install-dsh-and-dsh-podman.zh.md +145 -0
  163. package/docs/usage.md +393 -0
  164. package/docs/usage.zh.md +315 -0
  165. package/package.json +119 -0
package/docs/usage.md ADDED
@@ -0,0 +1,393 @@
1
+ <!--
2
+ SPDX-FileCopyrightText: 2026 Elouan Martinet <exa@elou.world>
3
+
4
+ SPDX-License-Identifier: MIT
5
+ -->
6
+
7
+ # Usage
8
+
9
+ This page documents the model-facing tools, the settings card, and the image
10
+ model. See [Architecture](architecture.md) for how the pieces fit together.
11
+
12
+ ## Model context
13
+
14
+ The harness adds a prompt section naming its own on-disk checkout so the model
15
+ can inspect or extend DSH itself. That checkout lives on the host and is not
16
+ reachable from the workspace container, so dsh-podman removes the section from
17
+ the assembled system prompt; the model is never told a misleading path.
18
+
19
+ dsh-podman also adds its own prompt section clarifying that the built-in shell
20
+ and filesystem tools (`bash`, `read`, `write`, `edit`, `glob`, `grep`) run
21
+ inside the workspace's default container rather than on the host, and how they
22
+ relate to the `container_*` tools.
23
+
24
+ ## Image model
25
+
26
+ dsh-podman organizes the images its containers run into three tiers:
27
+
28
+ - **Primitive images** are public upstream images pulled from the internet, e.g.
29
+ `docker.io/library/ubuntu:latest`. The base registry pins their full
30
+ references; they serve only as the `FROM` when building a base image and are
31
+ never referenced directly.
32
+ - **Base images** are the fixed, built-in set provided by dsh-podman:
33
+ `archlinux` (pacman), `ubuntu` (apt), and `alpine` (apk). Each is defined by
34
+ its primitive, a dsh-podman-owned default package list, and its package
35
+ manager. By default they are **built locally** from the primitive (installing
36
+ the default packages); when `DSH_PODMAN_BASE_IMAGE_PREFIX` points at a
37
+ registry (anything not starting with `localhost/`), they are **pulled**
38
+ instead. Base images are listed in the settings UI even when not yet
39
+ built/pulled, are rebuilt or pulled from the card, and their short names are
40
+ reserved — they cannot be built over, rebuilt, or removed as custom images.
41
+ - **Custom images** are user-built images created from a **parent** — a base
42
+ image or another custom image — inheriting its package manager and adding
43
+ extra packages on top. They are referenced by their short name (e.g.
44
+ `valkey`).
45
+
46
+ Image references (`imageId`, `parent`, `image`) are **short names only** (no
47
+ registry prefix, no `:tag`).
48
+
49
+ ## Tools
50
+
51
+ The plugin registers the following model-facing tools. Tools marked `✱` require
52
+ approval (some only under certain parameters — noted in their row). Container
53
+ tools operate on a **logical container name** of the current workspace
54
+ (`"default"` selects the workspace's default container).
55
+
56
+ ### Approval
57
+
58
+ Approval is enforced by the plugin itself through a `tools/pre-execute` policy
59
+ that reads the session's effective permission knobs (sandbox mode + approval
60
+ policy):
61
+
62
+ - **Read Only** — only the get/list tools run (`image_list`, `image_get`,
63
+ `container_list`, `container_read`, `container_glob`, `container_grep`,
64
+ `container_mount_list`, `volume_list`, `secret_list`, `daemon_list`,
65
+ `daemon_logs`); every other plugin tool is denied. The built-in file and shell
66
+ tools (`write`, `edit`, `bash`, and `pwsh` on Windows) and their container
67
+ counterparts (`container_bash`, `container_exec`, `container_write`,
68
+ `container_edit`, `daemon_start`) run when every mount of the target container
69
+ that carries a mode (project and volume; tmpfs and secrets do not) is already
70
+ `read_only` — the harness sandbox that would confine them is bypassed inside
71
+ the container. When a read-write mount would block one of them, the plugin
72
+ asks through DSH's approval service with a prompt listing the mounts it would
73
+ remount `read_only` and those it keeps, then recreates the container with the
74
+ read-only list before running the tool; a rejected prompt denies the call.
75
+ `read`, `glob`, and `grep` always run.
76
+ - **Workspace Write** — the `✱` tools ask through DSH's approval service (the
77
+ call shows the standard approval prompt and is denied when no approval channel
78
+ is available); `container_start` asks only when `mounts` is passed.
79
+ - **Full access** — tools run without approval prompts.
80
+
81
+ The harness's `sandbox_permissions` argument (a one-shot sandbox widening, e.g.
82
+ on `bash`) is accepted but has no effect here: the sandbox is bypassed inside
83
+ the container, so the plugin grants the escalation without prompting.
84
+
85
+ The prompt's reason is a full sentence naming the action and the objects it
86
+ touches, quoting every identifier — for example
87
+ `Add mount to container "web": volume "data" (read-only)`. It covers the
88
+ container image, each mount's kind and destination, the environment variable
89
+ keys, and the resolved file path. It follows the UI language: the browser client
90
+ records the active locale in the plugin settings, with the durable locale
91
+ preference as the fallback (English when neither is set). A policy denial — a
92
+ read-only sandbox, or a destination on a project mount — is reported in the same
93
+ language. The settings-card actions are direct control calls and are not gated.
94
+
95
+ `container_start`, `container_recreate`, and `container_bash` accept an `env`
96
+ map applied to the container (or the bash process); `container_exec` and
97
+ `daemon_start` already accept `env`, and `daemon_restart` reuses a daemon's
98
+ stored environment. `container_start` and `container_recreate` also accept a
99
+ `secretEnv` map (env var name → secret short name) that attaches existing
100
+ secrets to the container's environment — see [Secrets](#secrets). On
101
+ `container_start` and `container_recreate`, an omitted `env` (or `secretEnv`)
102
+ keeps the container's stored map, while a provided map replaces it entirely.
103
+ Environment variables are not treated as secrets, so the approval reason and
104
+ `container_list` show the variable **keys**. Keys starting with `DSH_PODMAN` are
105
+ reserved and rejected, since the orchestrator uses that namespace for
106
+ guest-agent wiring.
107
+
108
+ `container_bash` and `container_exec` run with the same command-visible
109
+ environment as the built-in `bash`: the harness's managed `DSH_*` facts
110
+ (`DSH_HOME`, `DSH_SHELL`, `DSH_SESSION_ID`, `DSH_WEB_URL`) and its
111
+ non-interactive terminal overrides (`NO_COLOR`, `TERM=dumb`, `PAGER=cat`,
112
+ `GIT_PAGER=cat`). A caller's `env` entry beats an override but cannot displace a
113
+ managed `DSH_*` fact.
114
+
115
+ ### Images
116
+
117
+ | Tool | Params | Description |
118
+ | --------------------- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
119
+ | `image_build` ✱ | `imageId`, `parent`, `packages` | Build a new custom image from a base or custom parent and package list |
120
+ | `image_get` | `imageId` | Details for one image |
121
+ | `image_list` | — | List the built workspace images, base images first |
122
+ | `image_rebuild_all` ✱ | — | Ensure every base, then rebuild every custom image in dependency order, skipping any image whose rebuild fails and its dependents |
123
+ | `image_rebuild` ✱ | `imageId` | Rebuild an existing custom image in place |
124
+ | `image_remove` ✱ | `imageId` | Remove a built image; refused while a workspace or container still references it |
125
+
126
+ ### Containers
127
+
128
+ | Tool | Params | Description |
129
+ | ---------------------- | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
130
+ | `container_bash` | `container`, `command`, `description`, optional `workdir`, `timeoutMs`, `env`, `uid`, `gid`, `groups` | Run a shell command |
131
+ | `container_edit` | `container`, `file_path`, `old_string`, `new_string`, optional `replace_all` | Edit a file |
132
+ | `container_exec` | `container`, `argv`, `description`, optional `workdir`, `timeoutMs`, `env`, `uid`, `gid`, `groups` | Run a program |
133
+ | `container_glob` | `container`, `pattern`, optional `path` | List files matching a pattern |
134
+ | `container_grep` | `container`, `pattern`, optional `path`, `include` | Search files for a regex |
135
+ | `container_list` | — | List the containers of the current workspace |
136
+ | `container_read` | `container`, `file_path`, optional `offset`, `limit` | Read a file |
137
+ | `container_recreate` ✱ | `container`, optional `image`, `mounts`, `env`, `secretEnv`, `paths` | Recreate a container, keeping its current image when `image` is omitted, optionally with new project mounts, environment, or PATH additions |
138
+ | `container_remove` ✱ | `container` | Remove a container (stops its daemons gracefully first) |
139
+ | `container_start` ✱ | `container`, optional `image`, `mounts`, `env`, `secretEnv`, `paths` | Start a container (default image when `image` is omitted); approval required only when `mounts` is passed |
140
+ | `container_write` | `container`, `file_path`, `content` | Write a file |
141
+
142
+ The `container_bash`, `container_exec`, `container_read`, `container_write`,
143
+ `container_edit`, `container_glob` and `container_grep` arguments mirror the
144
+ harness's built-in `bash`/`read`/`write`/`edit`/`glob`/`grep` tools (plus the
145
+ `container` target), so the same vocabulary works against a chosen container.
146
+ `container_read`, `container_write` and `container_edit` also resolve their
147
+ target through the same filesystem provider as the built-in file tools, so they
148
+ share their behavior: binary files are refused with the same error, and the
149
+ read-before-write guard (refusing to overwrite a file that was not read in the
150
+ session) applies to them exactly as it does to the built-in `write` and `edit` —
151
+ a `container_read` satisfies that guard for the path it read, just like the
152
+ built-in `read`. A path that was read and has since been removed is a new file:
153
+ writing it creates it again instead of reporting a stale version. The plugin
154
+ also registers a dedicated UI row for every one of its tools (icon, title,
155
+ summary, and result body), so they render like the built-in tools rather than as
156
+ a generic `Tool call` row.
157
+
158
+ Paths and working directories may be absolute or relative. A relative value is
159
+ resolved against the session's working directory, which is also where the
160
+ project is mounted inside the container. A `file_path` on `container_read`,
161
+ `container_write` and `container_edit` may not contain a `..` segment; working
162
+ directories may, since commands are not confined to the projects root.
163
+
164
+ When no working directory is given, `container_bash`, `container_exec` and
165
+ `daemon_start` run in the session's working directory (like the harness's `bash`
166
+ tool), and `container_glob`/`container_grep` search it by default. That
167
+ directory must be mounted in the container — otherwise the guest agent's own
168
+ working directory is used. An explicit `workdir` (or `path`) takes precedence.
169
+
170
+ `container_bash` and `container_exec` accept an optional `uid`, `gid` and
171
+ `groups` to run the command as another user. The values are numeric only: the
172
+ container's `/etc/passwd` and `/etc/group` live on the read-only rootfs, so
173
+ names never resolve. `groups` replaces the process's whole supplementary set,
174
+ and applying any identity requires the guest agent to run as root, which it
175
+ does. `HOME` is not managed, so a command run as another uid inherits the
176
+ container's `HOME` (the read-only `/root`); pass `env` to point it at a writable
177
+ directory.
178
+
179
+ The file tools (`container_read`, `container_write`, `container_edit`) can reach
180
+ the workspace's mounts: the project directory under the projects root, as well
181
+ as any `volume` and `tmpfs` mounts at their absolute destinations. `secret`
182
+ mounts are not exposed through the file API — the secret value is only readable
183
+ by processes running inside the container.
184
+
185
+ ### Mounts and volumes
186
+
187
+ | Tool | Params | Description |
188
+ | -------------------------- | ---------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
189
+ | `container_mount_add` ✱ | `container`, optional `kind`, `project`, `destination`, `mode`, `volume`, `secret` | Add a mount; `kind` is `project` (default), `tmpfs`, `volume`, or `secret` |
190
+ | `container_mount_list` | `container` | List the container's mounts |
191
+ | `container_mount_remove` ✱ | `container`, optional `kind`, `project`, `volume`, `destination`, `secret` | Remove a mount; identify it by `kind` plus its handle (see below) |
192
+ | `container_mount_update` ✱ | `container`, `mode`, optional `kind`, `project`, `volume`, `destination` | Change a project or volume mount's mode; identify it like removal (below) |
193
+ | `volume_create` | `name` | Create a managed named volume |
194
+ | `volume_list` | — | List the managed named volumes (short names) |
195
+ | `volume_remove` ✱ | `name` | Remove a managed named volume; refused while a container still mounts it |
196
+
197
+ A `project` mount binds a path under the projects root (`team`, or `team/src`
198
+ for a directory inside it); `tmpfs` mounts a writable in-memory filesystem and
199
+ `volume` mounts a podman named volume (auto-created on first use) — both at an
200
+ arbitrary absolute container path, never under the projects root, `/tmp`, or
201
+ another reserved path. A `secret` mount exposes a managed secret as a read-only
202
+ file at an absolute container path (see [Secrets](#secrets)).
203
+
204
+ Project mounts do not take a `destination`: a project directory is always
205
+ mounted at its mirrored path under the projects root. `destination` applies only
206
+ to `tmpfs`, `volume` and `secret` mounts.
207
+
208
+ In the settings card, the project path field of the add-mount and
209
+ create-container dialogs has a **Browse…** button. It opens a directory picker
210
+ that reads the projects root through dsh's own host-side directory listing — the
211
+ same service behind dsh's workspace directory selection — not through the
212
+ orchestrator or a container. Its layout follows dsh's own directory browser: two
213
+ columns, chevron breadcrumbs, folder icons, and a hidden-entry toggle. The
214
+ dialog is confined to the projects root, and the path it produces is stored
215
+ relative to that root.
216
+
217
+ Removing a mount names it by `kind` plus that mount's own handle: `project` for
218
+ a project mount, `volume` for a named volume, `secret` for a secret, and
219
+ `destination` for a `tmpfs` mount. A handle that matches more than one mount is
220
+ rejected, so pass `destination` as well when a volume or secret is mounted more
221
+ than once. `container_mount_list` reports the exact values. `kind` is optional —
222
+ inferred from `secret` or `volume`, otherwise `project` — except for `tmpfs`,
223
+ which has no name to infer from and must be named explicitly.
224
+
225
+ `mode` is `read_only` or `read_write`, and defaults to `read_only` so that
226
+ adding a mount never grants write access that was not asked for. `tmpfs` mounts
227
+ are always `read_write`, and `secret` mounts take no mode. The workspace's own
228
+ project mount is created `read_write`; remount it `read_only` with
229
+ `container_mount_update` when a session should not modify the project.
230
+
231
+ Changing a mount's mode is `container_mount_update`, not `container_mount_add`:
232
+ re-adding a mount that already exists is rejected rather than silently changing
233
+ it. It identifies the mount exactly like `container_mount_remove` (a handle that
234
+ matches more than one mount is rejected), requires `mode`, and applies only to
235
+ `project` and `volume` mounts — `tmpfs` is always `read_write` and `secret`
236
+ mounts carry no mode. A mode change to the mode the mount already has is
237
+ rejected.
238
+
239
+ A **named container** carries exactly the mounts it was created with: the
240
+ workspace project directory is not mounted automatically. The **default
241
+ container** always keeps its workspace project mount, which cannot be removed —
242
+ but it can be remounted `read_only` with `container_mount_update`.
243
+
244
+ Mutating a container's mounts
245
+ (`container_mount_add`/`container_mount_remove`/`container_mount_update`) or its
246
+ secret environment variables (`container_secret_add`/`container_secret_remove`)
247
+ **recreates** the container: its running processes, including daemons, are
248
+ terminated. Data in bind-mounted volumes persists; `tmpfs` contents do not.
249
+
250
+ ### PATH additions
251
+
252
+ | Tool | Params | Description |
253
+ | ------------------------- | -------------------- | ------------------------------------------------------------------------------- |
254
+ | `container_path_set` ✱ | `container`, `paths` | Replace the whole ordered list, first entry highest priority; empty clears it |
255
+ | `container_path_add` ✱ | `container`, `path` | Prepend one directory; an existing entry moves to the front |
256
+ | `container_path_remove` ✱ | `container`, `path` | Remove one added directory; a directory of the container's default PATH is kept |
257
+
258
+ Every entry must be an absolute, lexically clean directory with no `:` or
259
+ newline. The list is prepended to the container's own `PATH` (the image's, as
260
+ the running guest agent sees it) for every command the agent starts: the
261
+ built-in `bash`/`read`/`write`/`edit` tools, `container_bash`, `container_exec`,
262
+ the terminal, and daemons started afterwards. Nothing is recreated — the running
263
+ guest agent receives the new list immediately, so daemons already running keep
264
+ their old `PATH`. A recreated container restores the persisted list. The list
265
+ can also be set when a container is created, started, or recreated, through the
266
+ settings modal or the `paths` argument of `container_start` and
267
+ `container_recreate`.
268
+
269
+ ### Secrets
270
+
271
+ | Tool | Params | Description |
272
+ | --------------------------- | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
273
+ | `container_secret_add` ✱ | `container`, `env`, `secret` | Attach a secret to a container as an environment variable |
274
+ | `container_secret_remove` ✱ | `container`, `env` | Detach a secret environment variable from a container |
275
+ | `secret_create` | `name`, optional `length`, `charset` | Create a secret with an **orchestrator-generated random** value (`length` default 32; `charset` `alphanumeric` \| `hex` \| `base64url`) |
276
+ | `secret_list` | — | List the managed secrets (short names) |
277
+ | `secret_remove` ✱ | `name` | Remove a managed secret; refused while a container mounts it or attaches it as an environment variable |
278
+
279
+ Secrets are stored in podman under `DSH_PODMAN_SECRET_PREFIX` (default
280
+ `dsh-podman-`); the tools and UI use short names. `secret_create` values are
281
+ generated server-side with `crypto/rand` and **never exposed** — there is no
282
+ read tool. A secret can be attached to a container either as a **mount**
283
+ (`container_mount_add kind="secret"` + `secret` + `destination`, read-only, at
284
+ an absolute path never under the projects root) or as an **environment
285
+ variable** (`container_secret_add`; the env var name must not start with
286
+ `DSH_PODMAN`). The settings card can **overwrite** a secret with user-typed
287
+ content (write-only) but never reads it.
288
+
289
+ A secret mounted at a path is created as a root-owned **file** (not a directory)
290
+ with permissions that deny everyone but root, so only the container's default
291
+ (root) user can read it; a daemon started with a different `uid` cannot read a
292
+ mounted secret. A secret attached as an environment variable is inherited by
293
+ every process the agent starts unless the daemon is started with
294
+ `inheritEnv=false` (see [Daemons](#daemons)).
295
+
296
+ ### Daemons
297
+
298
+ | Tool | Params | Description |
299
+ | ---------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
300
+ | `daemon_list` | `container` | List the daemons (including their effective `uid`/`gid` and `groups`) |
301
+ | `daemon_logs` | `container`, `name`, optional `tailBytes` | Tail a daemon's stdout/stderr |
302
+ | `daemon_restart` | `container`, `name` | Restart a daemon with the same command, environment, and user |
303
+ | `daemon_start` | `container`, `name`, `argv`, optional `cwd`, `env`, `inheritEnv`, `uid`, `gid`, `groups` | Start a background daemon; optional `uid`/`gid`/`groups` run it as another user |
304
+ | `daemon_stop` | `container`, `name`, optional `signal` | Stop a daemon |
305
+
306
+ Daemons run as the container user by default. When only `uid` is set, `gid`
307
+ defaults to the same value; when neither is set, the daemon runs without any
308
+ uid/gid override. `groups` replaces the process's whole supplementary set.
309
+ `daemon_list` reports the effective `uid`/`gid` and supplementary `groups` of
310
+ each daemon. Starting a daemon with a name that already exists stops that daemon
311
+ first (when it is still running) and replaces it; stopped daemons stay listed so
312
+ their logs remain readable. Daemons live in the container's guest agent and do
313
+ not survive a container recreate (see
314
+ [Mounts and volumes](#mounts-and-volumes)).
315
+
316
+ A daemon inherits the container's environment by default — every variable the
317
+ guest agent has, including environment secrets attached with
318
+ `container_secret_add` — minus the reserved `DSH_PODMAN` namespace. Pass
319
+ `inheritEnv=false` to start it isolated: it then receives only `PATH` and `HOME`
320
+ (from the container) plus its own `env`, so container and secret environment
321
+ variables are not visible. `daemon_restart` replays the mode the daemon was
322
+ started with.
323
+
324
+ ## Container management UI
325
+
326
+ The plugin ships a browser half that registers a card in the dsh **Settings →
327
+ Plugins** page. The card lists the orchestrator-created guest containers and the
328
+ built images. A workspace with no container gets a **Create container** button
329
+ that opens a configuration modal — image, environment, mounts (project, tmpfs,
330
+ volume, and secret), PATH additions, and secret environment variables — and
331
+ workspaces that already have containers offer an **Add container** button for
332
+ additional, named containers through the same modal, which is titled after the
333
+ button that opened it. In that modal each mount's mode is a dropdown
334
+ (read-only/read-write), so the workspace project mount can be created read-only
335
+ in one step; tmpfs and secret mounts are fixed (read-write and read-only
336
+ respectively) and show a disabled dropdown. Container rows show their
337
+ environment and secret-environment variables and their mounts, let you edit
338
+ environment variables and add/remove mounts (each removal is confirmed), and
339
+ attach/detach named secrets to a container's environment variables; each row
340
+ also offers **Remove**, **Recreate** (same image), and **Recreate with image**.
341
+ Every workspace row also offers **Remove pod**, which removes the workspace's
342
+ pod, all of its containers, and the orchestrator's record for it (volumes,
343
+ secrets, and project data are kept); removing a workspace's last container
344
+ removes its pod as well, so an empty pod is never left behind. The card re-reads
345
+ the live state whenever the Plugins page is opened, and its header has a
346
+ **Reload this view** button. The images section can rebuild a single image or
347
+ **rebuild all** in dependency order; **Build image** opens a popup with an
348
+ image-id/base-image form and a chip input for the package list (type a name and
349
+ press space/comma, or paste a list, to add removable chips). Volumes and secrets
350
+ are listed as individual expandable rows, each with its own actions, and
351
+ **Create volume** / **Create secret** open popup forms (the secret form takes an
352
+ optional length; a secret's value can be overwritten, never read). Card actions
353
+ are direct control calls and are not approval-gated.
354
+
355
+ A **Package caches** section reports the size of every configured build cache
356
+ (`DSH_PODMAN_HOST_PACMAN_CACHE`, `DSH_PODMAN_HOST_APT_CACHE`,
357
+ `DSH_PODMAN_HOST_APK_CACHE`) and offers two cleanup actions: **Keep latest
358
+ versions** removes every cached package file except the newest of each package
359
+ (and the signature it carried), and **Remove all** empties the caches. Both are
360
+ safe — a cached package is only ever re-downloaded — and neither runs while an
361
+ image build is in progress.
362
+
363
+ ## Podman operator mode
364
+
365
+ The plugin ships an **agent preset** named _Podman operator mode_ (id
366
+ `podman-ops`). On every load it (re)writes the preset into the harness's
367
+ user-presets root (`~/.dsh/.agent-presets/podman-ops/`), overwriting any local
368
+ copy so the shipped content stays authoritative. It appears in the session's
369
+ agent-preset picker next to the shipped presets.
370
+
371
+ The preset composes a Podman-focused persona with the built-in task tools
372
+ (`ask_user_question`, `todo_write`) and `web_search` (web fetch disabled). It
373
+ does not mount the host shell, host filesystem, or the coding-agent rows
374
+ (subagents, workflows, skills, goal, plan mode, jobs). The plugin's own tools
375
+ are global and all remain available, split as:
376
+
377
+ - **Direct:** `image_list`, `image_get`, `container_list`, `container_read`,
378
+ `container_glob`, `container_grep`, `container_mount_list`, `volume_list`,
379
+ `secret_list`, `secret_create`, `daemon_list`, `daemon_logs`, `daemon_stop`,
380
+ `daemon_restart`, `container_start` (asks only when `mounts` is passed).
381
+ - **Approval-gated** (the usual `✱` tools): `image_build`, `image_rebuild`,
382
+ `image_rebuild_all`, `image_remove`, `container_recreate`, `container_remove`,
383
+ `container_mount_add`, `container_mount_remove`, `container_mount_update`,
384
+ `container_path_set`, `container_path_add`, `container_path_remove`,
385
+ `volume_remove`, `secret_remove`, `container_secret_add`,
386
+ `container_secret_remove`.
387
+ - **Approval-gated only in this preset:** `container_bash`, `container_exec`,
388
+ `container_write`, `container_edit`, `daemon_start` — so the agent can run
389
+ commands, edit container files, or start daemons once the user approves,
390
+ without those tools asking in other presets.
391
+
392
+ The permission knobs in the approval policy above still apply (Read Only allows
393
+ only the direct read/list tools; Full access skips every prompt).